[PATCH 1/2] drm/amdgpu: refactor RLCG access path part 1

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



what changed:
1)provide new implementation interface for the rlcg access path
2)put SQ_CMD/SQ_IND_INDEX/SQ_IND_DATA to GFX9 RLCG path to align with
SRIOV RLCG logic

background:
we what to clear the code path for WREG32_RLC, to make it only covered
and handled by amdgpu_mm_wreg() routine, this way we can let RLCG
to serve the register access even through UMR (via debugfs interface)
the current implementation cannot achieve that goal because it can only
hardcode everywhere, but UMR only pass "offset" as varable to driver

tested-by: Monk Liu <monk.liu@xxxxxxx>
tested-by: Zhou pengju <pengju.zhou@xxxxxxx>
Signed-off-by: Zhou pengju <pengju.zhou@xxxxxxx>
Signed-off-by: Monk Liu <Monk.Liu@xxxxxxx>
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h |   2 +
 drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c  |  80 ++++++++++++++-
 drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c   | 177 +++++++++++++++++++++++++++++++-
 drivers/gpu/drm/amd/amdgpu/soc15.h      |   7 ++
 4 files changed, 264 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h
index 52509c2..60bb3e8 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h
@@ -127,6 +127,8 @@ struct amdgpu_rlc_funcs {
 	void (*reset)(struct amdgpu_device *adev);
 	void (*start)(struct amdgpu_device *adev);
 	void (*update_spm_vmid)(struct amdgpu_device *adev, unsigned vmid);
+	void (*rlcg_wreg)(struct amdgpu_device *adev, u32 offset, u32 v);
+	bool (*is_rlcg_access_range)(struct amdgpu_device *adev, uint32_t reg);
 };
 
 struct amdgpu_rlc {
diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c
index 82ef08d..3222cd3 100644
--- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c
+++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c
@@ -224,6 +224,56 @@ static const struct soc15_reg_golden golden_settings_gc_10_1_2[] =
 	SOC15_REG_GOLDEN_VALUE(GC, 0, mmUTCL1_CTRL, 0xffffffff, 0x00800000)
 };
 
+static const struct soc15_reg_rlcg rlcg_access_gc_10_0[] = {
+	{SOC15_REG_ENTRY(GC, 0, mmRLC_CSIB_ADDR_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmRLC_CSIB_ADDR_LO)},
+	{SOC15_REG_ENTRY(GC, 0, mmRLC_CSIB_LENGTH)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_ME_CNTL)},
+};
+
+static void gfx_v10_rlcg_wreg(struct amdgpu_device *adev, u32 offset, u32 v)
+{
+	static void *scratch_reg0;
+	static void *scratch_reg1;
+	static void *scratch_reg2;
+	static void *scratch_reg3;
+	static void *spare_int;
+	static uint32_t grbm_cntl;
+	static uint32_t grbm_idx;
+	uint32_t i = 0;
+	uint32_t retries = 50000;
+
+	scratch_reg0 = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmSCRATCH_REG0_BASE_IDX] + mmSCRATCH_REG0)*4;
+	scratch_reg1 = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmSCRATCH_REG1_BASE_IDX] + mmSCRATCH_REG1)*4;
+	scratch_reg2 = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmSCRATCH_REG1_BASE_IDX] + mmSCRATCH_REG2)*4;
+	scratch_reg3 = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmSCRATCH_REG1_BASE_IDX] + mmSCRATCH_REG3)*4;
+	spare_int = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmRLC_SPARE_INT_BASE_IDX] + mmRLC_SPARE_INT)*4;
+
+	grbm_cntl = adev->reg_offset[GC_HWIP][0][mmGRBM_GFX_CNTL_BASE_IDX] + mmGRBM_GFX_CNTL;
+	grbm_idx = adev->reg_offset[GC_HWIP][0][mmGRBM_GFX_INDEX_BASE_IDX] + mmGRBM_GFX_INDEX;
+
+	if (amdgpu_sriov_runtime(adev)) {
+		pr_err("shoudn't call rlcg write register during runtime\n");
+		return;
+	}
+
+	writel(v, scratch_reg0);
+	writel(offset | 0x80000000, scratch_reg1);
+	writel(1, spare_int);
+	for (i = 0; i < retries; i++) {
+		u32 tmp;
+
+		tmp = readl(scratch_reg1);
+		if (!(tmp & 0x80000000))
+			break;
+
+		udelay(10);
+	}
+
+	if (i >= retries)
+		pr_err("timeout: rlcg program reg:0x%05x failed !\n", offset);
+}
+
 static const struct soc15_reg_golden golden_settings_gc_10_1_nv14[] =
 {
 	/* Pending on emulation bring up */
@@ -4247,6 +4297,32 @@ static void gfx_v10_0_update_spm_vmid(struct amdgpu_device *adev, unsigned vmid)
 	WREG32_SOC15(GC, 0, mmRLC_SPM_MC_CNTL, data);
 }
 
+static bool gfx_v10_0_check_rlcg_range(struct amdgpu_device *adev,
+					uint32_t offset,
+					struct soc15_reg_rlcg *entries, int arr_size)
+{
+	int i;
+	uint32_t reg;
+
+	for (i = 0; i < arr_size; i++) {
+		const struct soc15_reg_rlcg *entry;
+
+		entry = &entries[i];
+		reg = adev->reg_offset[entry->hwip][entry->instance][entry->segment] + entry->reg;
+		if (offset == reg)
+			return true;
+	}
+
+	return false;
+}
+
+static bool gfx_v10_0_is_rlcg_access_range(struct amdgpu_device *adev, u32 offset)
+{
+	return gfx_v10_0_check_rlcg_range(adev, offset,
+					(void *)rlcg_access_gc_10_0,
+					ARRAY_SIZE(rlcg_access_gc_10_0));
+}
+
 static const struct amdgpu_rlc_funcs gfx_v10_0_rlc_funcs = {
 	.is_rlc_enabled = gfx_v10_0_is_rlc_enabled,
 	.set_safe_mode = gfx_v10_0_set_safe_mode,
@@ -4258,7 +4334,9 @@ static const struct amdgpu_rlc_funcs gfx_v10_0_rlc_funcs = {
 	.stop = gfx_v10_0_rlc_stop,
 	.reset = gfx_v10_0_rlc_reset,
 	.start = gfx_v10_0_rlc_start,
-	.update_spm_vmid = gfx_v10_0_update_spm_vmid
+	.update_spm_vmid = gfx_v10_0_update_spm_vmid,
+	.rlcg_wreg = gfx_v10_rlcg_wreg,
+	.is_rlcg_access_range = gfx_v10_0_is_rlcg_access_range,
 };
 
 static int gfx_v10_0_set_powergating_state(void *handle,
diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c
index 87747b3..1fc430d 100644
--- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c
+++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c
@@ -697,6 +697,96 @@ static const struct soc15_reg_golden golden_settings_gc_9_4_1_arct[] =
 	SOC15_REG_GOLDEN_VALUE(GC, 0, mmSQ_FIFO_SIZES, 0xffffffff, 0x00000f00),
 };
 
+static const struct soc15_reg_rlcg rlcg_access_gc_9_0[] = {
+	{SOC15_REG_ENTRY(GC, 0, mmGRBM_GFX_INDEX)},
+	{SOC15_REG_ENTRY(GC, 0, mmGRBM_GFX_CNTL)},
+	{SOC15_REG_ENTRY(GC, 0, mmSQ_IND_INDEX)},
+	{SOC15_REG_ENTRY(GC, 0, mmSQ_IND_DATA)},
+	{SOC15_REG_ENTRY(GC, 0, mmSQ_CMD)},
+	{SOC15_REG_ENTRY(GC, 0, mmGRBM_CNTL)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_MEC_CNTL)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_ME_CNTL)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_MQD_BASE_ADDR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_MQD_BASE_ADDR_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_MQD_CONTROL)},
+	{SOC15_REG_ENTRY(GC, 0, mmMC_VM_AGP_BASE)},
+	{SOC15_REG_ENTRY(GC, 0, mmMC_VM_AGP_BOT)},
+	{SOC15_REG_ENTRY(GC, 0, mmMC_VM_AGP_TOP)},
+	{SOC15_REG_ENTRY(GC, 0, mmMC_VM_FB_LOCATION_BASE)},
+	{SOC15_REG_ENTRY(GC, 0, mmMC_VM_FB_LOCATION_TOP)},
+	{SOC15_REG_ENTRY(GC, 0, mmMC_VM_MX_L1_TLB_CNTL)},
+	{SOC15_REG_ENTRY(GC, 0, mmMC_VM_SYSTEM_APERTURE_HIGH_ADDR)},
+	{SOC15_REG_ENTRY(GC, 0, mmMC_VM_SYSTEM_APERTURE_LOW_ADDR)},
+	{SOC15_REG_ENTRY(GC, 0, mmPA_SC_BINNER_EVENT_CNTL_3)},
+	{SOC15_REG_ENTRY(GC, 0, mmPA_SC_ENHANCE)},
+	{SOC15_REG_ENTRY(GC, 0, mmPA_SC_ENHANCE_1)},
+	{SOC15_REG_ENTRY(GC, 0, mmRLC_CP_SCHEDULERS)},
+	{SOC15_REG_ENTRY(GC, 0, mmRLC_CSIB_ADDR_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmRLC_CSIB_ADDR_LO)},
+	{SOC15_REG_ENTRY(GC, 0, mmRLC_CSIB_LENGTH)},
+	{SOC15_REG_ENTRY(GC, 0, mmSH_MEM_BASES)},
+	{SOC15_REG_ENTRY(GC, 0, mmSH_MEM_CONFIG)},
+	{SOC15_REG_ENTRY(GC, 0, mmVM_L2_CNTL)},
+	{SOC15_REG_ENTRY(GC, 0, mmVM_L2_CNTL2)},
+	{SOC15_REG_ENTRY(GC, 0, mmVM_L2_CNTL3)},
+	{SOC15_REG_ENTRY(GC, 0, mmVM_L2_CNTL4)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_DEQUEUE_REQUEST)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_EOP_BASE_ADDR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_EOP_BASE_ADDR_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_EOP_CONTROL)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_EOP_RPTR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_IB_CONTROL)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_IQ_TIMER)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PERSISTENT_STATE)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PIPE_PRIORITY)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_BASE)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_BASE_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_CONTROL)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_DOORBELL_CONTROL)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_RPTR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_RPTR_REPORT_ADDR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_RPTR_REPORT_ADDR_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_WPTR_LO)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_WPTR_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_WPTR_POLL_ADDR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_PQ_WPTR_POLL_ADDR_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_QUEUE_PRIORITY)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_VMID)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_ACTIVE)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_QUANTUM)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_IB_BASE_ADDR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_IB_BASE_ADDR_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_IB_RPTR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_IQ_RPTR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_DMA_OFFLOAD)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_OFFLOAD)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_SEMA_CMD)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_MSG_TYPE)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_ATOMIC0_PREOP_LO)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_ATOMIC0_PREOP_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_ATOMIC1_PREOP_LO)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_ATOMIC1_PREOP_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_HQ_SCHEDULER0)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_HQ_STATUS0)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_HQ_CONTROL0)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_HQ_SCHEDULER1)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_HQ_STATUS1)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_HQ_CONTROL1)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_EOP_WPTR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_EOP_EVENTS)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_CTX_SAVE_BASE_ADDR_LO)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_CTX_SAVE_BASE_ADDR_HI)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_CTX_SAVE_CONTROL)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_CNTL_STACK_OFFSET)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_CNTL_STACK_SIZE)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_WG_STATE_OFFSET)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_CTX_SAVE_SIZE)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_GDS_RESOURCE_STATE)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_ERROR)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_EOP_WPTR_MEM)},
+	{SOC15_REG_ENTRY(GC, 0, mmCP_HQD_AQL_CONTROL)},
+};
+
 static const u32 GFX_RLC_SRM_INDEX_CNTL_ADDR_OFFSETS[] =
 {
 	mmRLC_SRM_INDEX_CNTL_ADDR_0 - mmRLC_SRM_INDEX_CNTL_ADDR_0,
@@ -721,6 +811,63 @@ static const u32 GFX_RLC_SRM_INDEX_CNTL_DATA_OFFSETS[] =
 	mmRLC_SRM_INDEX_CNTL_DATA_7 - mmRLC_SRM_INDEX_CNTL_DATA_0,
 };
 
+void gfx_v9_0_rlcg_wreg(struct amdgpu_device *adev, u32 offset, u32 v)
+{
+	static void *scratch_reg0;
+	static void *scratch_reg1;
+	static void *scratch_reg2;
+	static void *scratch_reg3;
+	static void *spare_int;
+	static uint32_t grbm_cntl;
+	static uint32_t grbm_idx;
+	bool shadow;
+
+	scratch_reg0 = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmSCRATCH_REG0_BASE_IDX] + mmSCRATCH_REG0)*4;
+	scratch_reg1 = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmSCRATCH_REG1_BASE_IDX] + mmSCRATCH_REG1)*4;
+	scratch_reg2 = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmSCRATCH_REG1_BASE_IDX] + mmSCRATCH_REG2)*4;
+	scratch_reg3 = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmSCRATCH_REG1_BASE_IDX] + mmSCRATCH_REG3)*4;
+	spare_int = adev->rmmio + (adev->reg_offset[GC_HWIP][0][mmRLC_SPARE_INT_BASE_IDX] + mmRLC_SPARE_INT)*4;
+
+	grbm_cntl = adev->reg_offset[GC_HWIP][0][mmGRBM_GFX_CNTL_BASE_IDX] + mmGRBM_GFX_CNTL;
+	grbm_idx = adev->reg_offset[GC_HWIP][0][mmGRBM_GFX_INDEX_BASE_IDX] + mmGRBM_GFX_INDEX;
+
+	if (amdgpu_sriov_runtime(adev)) {
+		pr_err("shoudn't call rlcg write register during runtime\n");
+		return;
+	}
+
+	if (offset == grbm_cntl || offset == grbm_idx)
+		shadow = true;
+
+	if (shadow) {
+		if (offset  == grbm_cntl)
+			writel(v, scratch_reg2);
+		else if (offset == grbm_idx)
+			writel(v, scratch_reg3);
+
+		writel(v, ((void __iomem *)adev->rmmio) + (offset * 4));
+	} else {
+		uint32_t i = 0;
+		uint32_t retries = 50000;
+
+		writel(v, scratch_reg0);
+		writel(offset | 0x80000000, scratch_reg1);
+		writel(1, spare_int);
+		for (i = 0; i < retries; i++) {
+			u32 tmp;
+
+			tmp = readl(scratch_reg1);
+			if (!(tmp & 0x80000000))
+				break;
+
+			udelay(10);
+		}
+		if (i >= retries)
+			pr_err("timeout: rlcg program reg:0x%05x failed !\n", offset);
+	}
+
+}
+
 #define VEGA10_GB_ADDR_CONFIG_GOLDEN 0x2a114042
 #define VEGA12_GB_ADDR_CONFIG_GOLDEN 0x24104041
 #define RAVEN_GB_ADDR_CONFIG_GOLDEN 0x24000042
@@ -4783,6 +4930,32 @@ static void gfx_v9_0_update_spm_vmid(struct amdgpu_device *adev, unsigned vmid)
 	WREG32_SOC15(GC, 0, mmRLC_SPM_MC_CNTL, data);
 }
 
+static bool gfx_v9_0_check_rlcg_range(struct amdgpu_device *adev,
+					uint32_t offset,
+					struct soc15_reg_rlcg *entries, int arr_size)
+{
+	int i;
+	uint32_t reg;
+
+	for (i = 0; i < arr_size; i++) {
+		const struct soc15_reg_rlcg *entry;
+
+		entry = &entries[i];
+		reg = adev->reg_offset[entry->hwip][entry->instance][entry->segment] + entry->reg;
+		if (offset == reg)
+			return true;
+	}
+
+	return false;
+}
+
+static bool gfx_v9_0_is_rlcg_access_range(struct amdgpu_device *adev, u32 offset)
+{
+	return gfx_v9_0_check_rlcg_range(adev, offset,
+					(void *)rlcg_access_gc_9_0,
+					ARRAY_SIZE(rlcg_access_gc_9_0));
+}
+
 static const struct amdgpu_rlc_funcs gfx_v9_0_rlc_funcs = {
 	.is_rlc_enabled = gfx_v9_0_is_rlc_enabled,
 	.set_safe_mode = gfx_v9_0_set_safe_mode,
@@ -4795,7 +4968,9 @@ static const struct amdgpu_rlc_funcs gfx_v9_0_rlc_funcs = {
 	.stop = gfx_v9_0_rlc_stop,
 	.reset = gfx_v9_0_rlc_reset,
 	.start = gfx_v9_0_rlc_start,
-	.update_spm_vmid = gfx_v9_0_update_spm_vmid
+	.update_spm_vmid = gfx_v9_0_update_spm_vmid,
+	.rlcg_wreg = gfx_v9_0_rlcg_wreg,
+	.is_rlcg_access_range = gfx_v9_0_is_rlcg_access_range,
 };
 
 static int gfx_v9_0_set_powergating_state(void *handle,
diff --git a/drivers/gpu/drm/amd/amdgpu/soc15.h b/drivers/gpu/drm/amd/amdgpu/soc15.h
index d0fb7a6..b03f950 100644
--- a/drivers/gpu/drm/amd/amdgpu/soc15.h
+++ b/drivers/gpu/drm/amd/amdgpu/soc15.h
@@ -42,6 +42,13 @@ struct soc15_reg_golden {
 	u32	or_mask;
 };
 
+struct soc15_reg_rlcg {
+	u32	hwip;
+	u32	instance;
+	u32	segment;
+	u32	reg;
+};
+
 struct soc15_reg_entry {
 	uint32_t hwip;
 	uint32_t inst;
-- 
2.7.4

_______________________________________________
amd-gfx mailing list
amd-gfx@xxxxxxxxxxxxxxxxxxxxx
https://lists.freedesktop.org/mailman/listinfo/amd-gfx



[Index of Archives]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux