Search Linux Wireless

[RFC v1 065/256] cl8k: add dsp.c

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

 



From: Viktor Barna <viktor.barna@xxxxxxxxxx>

(Part of the split. Please, take a look at the cover letter for more
details).

Signed-off-by: Viktor Barna <viktor.barna@xxxxxxxxxx>
---
 drivers/net/wireless/celeno/cl8k/dsp.c | 611 +++++++++++++++++++++++++
 1 file changed, 611 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/dsp.c

diff --git a/drivers/net/wireless/celeno/cl8k/dsp.c b/drivers/net/wireless/celeno/cl8k/dsp.c
new file mode 100644
index 000000000000..cf9646cd1ed4
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/dsp.c
@@ -0,0 +1,611 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "reg/reg_modem_gcu.h"
+#include "reg/reg_macdsp_api.h"
+#include "reg/reg_cmu.h"
+#include "reg/reg_access.h"
+#include "reg/ceva.h"
+#include "dsp.h"
+#include "hw.h"
+#include <linux/firmware.h>
+
+#define BUSY_WAIT_LIMIT 10000
+
+static int dsp_busy_wait(struct cl_hw *cl_hw, u32 control_reg)
+{
+       int i;
+
+       for (i = 0; i < BUSY_WAIT_LIMIT; i++) {
+               /* Poll Bit29 to verify DMA transfer has ended. */
+               if ((cl_reg_read(cl_hw, control_reg) & 0x20000000) == 0)
+                       return 0;
+
+               cpu_relax();
+       }
+
+       return -EIO;
+}
+
+static void cl_dsp_boot(struct cl_hw *cl_hw)
+{
+       struct cl_chip *chip = cl_hw->chip;
+
+       if (cl_hw_is_tcv0(cl_hw)) {
+               /* Disable ceva_free_clk */
+               cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 0);
+               /* Assert Ceva reset */
+               cmu_phy_0_rst_ceva_0_global_rst_n_setf(chip, 0);
+       } else {
+               /* Disable ceva_free_clk */
+               cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 0);
+               /* Assert Ceva reset */
+               cmu_phy_1_rst_ceva_1_global_rst_n_setf(chip, 0);
+       }
+
+       /* Set Ceva boot=1 */
+       modem_gcu_ceva_ctrl_boot_setf(cl_hw, 1);
+       /* Set Ceva vector */
+       modem_gcu_ceva_vec_set(cl_hw, 0);
+
+       if (cl_hw_is_tcv0(cl_hw)) {
+               /* Enable ceva_clk */
+               cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 1);
+               /* Disabel ceva_clk */
+               cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 0);
+               /* De-Assert Ceva reset - Reset Release */
+               cmu_phy_0_rst_ceva_0_global_rst_n_setf(chip, 1);
+               /* Enable ceva_clk */
+               cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 1);
+       } else {
+               /* Enable ceva_clk */
+               cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 1);
+               /* Disabel ceva_clk */
+               cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 0);
+               /* De-Assert Ceva reset - Reset Release */
+               cmu_phy_1_rst_ceva_1_global_rst_n_setf(chip, 1);
+               /* Enable ceva_clk */
+               cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 1);
+       }
+
+       /* Release Ceva external_wait */
+       modem_gcu_ceva_ctrl_external_wait_setf(cl_hw, 0);
+       /* Set Ceva boot=0 */
+       modem_gcu_ceva_ctrl_boot_setf(cl_hw, 0);
+}
+
+static void config_dma_for_code_copy(struct cl_hw *cl_hw, u32 page)
+{
+       /* Configure Program DMA to copy FW code from Shared PMEM to internal PMEM. */
+
+       /* External address to read from. */
+       cl_reg_write(cl_hw, CEVA_CPM_PDEA_REG, CEVA_SHARED_PMEM_BASE_ADDR_INTERNAL);
+       /* Internal address to write to. */
+       cl_reg_write(cl_hw, CEVA_CPM_PDIA_REG, CEVA_SHARED_PMEM_SIZE * page);
+       /* Page size */
+       cl_reg_write(cl_hw, CEVA_CPM_PDTC_REG, CEVA_SHARED_PMEM_SIZE);
+}
+
+static void config_dma_for_external_data_copy(struct cl_hw *cl_hw)
+{
+       /* Configure Program DMA to copy FW code from Shared XMEM to internal XMEM. */
+
+       /* External address to read from. */
+       cl_reg_write(cl_hw, CEVA_CPM_DDEA_REG, CEVA_SHARED_XMEM_BASE_ADDR_INTERNAL);
+       /* Internal address to write to. */
+       cl_reg_write(cl_hw, CEVA_CPM_DDIA_REG, 0);
+       /* Page size + DMA direction is write */
+       cl_reg_write(cl_hw, CEVA_CPM_DDTC_REG,
+                    CEVA_SHARED_XMEM_SIZE | CEVA_CPM_DDTC_WRITE_COMMAND);
+}
+
+static int cl_dsp_hex_load(struct cl_hw *cl_hw, const u8 *buf,
+                          off_t offset, size_t size, size_t buf_size)
+{
+       u8 single_buf[4] = {0};
+       u32 bin_data = 0;
+       u8 next_byte;
+       u8 byte_num = 0;
+       int ret = 0;
+       ssize_t oft = 0;
+       size_t real_size = min(size * 3, buf_size);
+       /*
+        * CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST is global and we don't
+        * want to add TCV reg offset.
+        */
+       bool chip_reg = (offset == CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST);
+
+       if (buf_size % 3) {
+               cl_dbg_err(cl_hw, "DSP size %zu must be divided by 3 !!!\n",
+                          buf_size);
+               return -EINVAL;
+       }
+
+       while (oft < real_size) {
+               memcpy(single_buf, buf + oft, 3);
+               /* Each line contains 2 hex digits + a line feed, i.e. 3 bytes */
+               ret = kstrtou8(single_buf, 16, &next_byte);
+               if (ret < 0) {
+                       cl_dbg_err(cl_hw,
+                                  "ret = %d, oft = %zu,"
+                                  "single_buf = 0x%x 0x%x 0x%x 0x%x\n",
+                                  ret, oft, single_buf[0], single_buf[1],
+                                  single_buf[2], single_buf[3]);
+                       return ret;
+               }
+
+               /* Little-endian order. */
+               bin_data += next_byte << (8 * byte_num);
+               byte_num = (byte_num + 1) % 4;
+
+               /* Read 4 lines from the file, and then write. */
+               if (byte_num == 0) {
+                       if (chip_reg)
+                               cl_reg_write_chip(cl_hw->chip, offset, bin_data);
+                       else
+                               cl_reg_write_direct(cl_hw, offset, bin_data);
+                       offset += 4;
+                       bin_data = 0;
+               }
+
+               memset(&single_buf, 0, sizeof(single_buf));
+               oft += 3;
+       }
+
+       return 0;
+}
+
+static int load_dsp_code(struct cl_hw *cl_hw)
+{
+       struct cl_chip *chip = cl_hw->chip;
+       u32 real_size;
+       u32 page;
+       const struct firmware *fw;
+       size_t size = 0;
+       u8 *buf = NULL;
+       char path_name[CL_PATH_MAX] = {0};
+       int ret;
+
+       snprintf(path_name, sizeof(path_name), "cl8k/%s",
+                cl_hw->conf->ce_dsp_code);
+
+       cl_dbg_verbose(cl_hw, "from %s\n", cl_hw->conf->ce_dsp_code);
+
+       ret = request_firmware(&fw, path_name, chip->dev);
+
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to get %s, with error: %x!\n",
+                          path_name, ret);
+               goto out;
+       }
+
+       size = fw->size;
+       buf = (u8 *)fw->data;
+
+       for (page = 0; page < CEVA_MAX_PAGES; page++) {
+               /* Copy DSP code (one page each time) */
+               ret =  cl_dsp_hex_load(cl_hw, buf,
+                                      CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST,
+                                      CEVA_SHARED_PMEM_SIZE, size);
+               if (ret != 0) {
+                       cl_dbg_err(cl_hw, "Failed to load pmem page 0x%x!\n", page);
+                       break;
+               }
+
+               config_dma_for_code_copy(cl_hw, page);
+               ret = dsp_busy_wait(cl_hw, CEVA_CPM_PDTC_REG);
+
+               if (ret) {
+                       cl_dbg_err(cl_hw, "dsp_busy_wait failed!\n");
+                       goto out;
+               }
+
+               real_size = min_t(u32, CEVA_SHARED_PMEM_SIZE * 3, size);
+               buf += real_size;
+               size -= real_size;
+       }
+
+out:
+       release_firmware(fw);
+
+       return ret;
+}
+
+static int load_dsp_data(struct cl_hw *cl_hw)
+{
+       struct cl_chip *chip = cl_hw->chip;
+       const struct firmware *fw;
+       size_t size = 0;
+       char path_name[CL_PATH_MAX] = {0};
+       int ret;
+
+       snprintf(path_name, sizeof(path_name), "cl8k/%s",
+                cl_hw->conf->ce_dsp_data);
+
+       cl_dbg_verbose(cl_hw, "from %s\n", cl_hw->conf->ce_dsp_data);
+
+       ret = request_firmware(&fw, path_name, chip->dev);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to get %s, with error: %x!\n",
+                          path_name, ret);
+               goto out;
+       }
+
+       size = fw->size;
+
+       ret = cl_dsp_hex_load(cl_hw, fw->data, REG_MACDSP_API_BASE_ADDR,
+                             CEVA_DSP_DATA_SIZE, size);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to load HEX file\n");
+               goto out;
+       }
+out:
+       release_firmware(fw);
+
+       return ret;
+}
+
+static int load_dsp_external_data(struct cl_hw *cl_hw)
+{
+       /*
+        * Shared XMEM is not accessible by host.
+        * Copy the XMEM section to DRAM first and then use CEVA internal DMA to copy to
+        * SHARED XMEM.
+        */
+       struct cl_chip *chip = cl_hw->chip;
+       const struct firmware *fw;
+       size_t size = 0;
+       char path_name[CL_PATH_MAX] = {0};
+       int ret;
+
+       snprintf(path_name, sizeof(path_name), "cl8k/%s",
+                cl_hw->conf->ce_dsp_external_data);
+
+       cl_dbg_verbose(cl_hw, "from %s\n", cl_hw->conf->ce_dsp_external_data);
+
+       ret = request_firmware(&fw, path_name, chip->dev);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to get %s, with error: %x!\n",
+                          path_name, ret);
+               goto out;
+       }
+
+       size = fw->size;
+
+       ret = cl_dsp_hex_load(cl_hw, fw->data, REG_MACDSP_API_BASE_ADDR,
+                             CEVA_DSP_EXT_DATA_SIZE, size);
+
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to load HEX file\n");
+               goto out;
+       }
+
+       config_dma_for_external_data_copy(cl_hw);
+       ret = dsp_busy_wait(cl_hw, CEVA_CPM_DDTC_REG);
+
+       if (ret) {
+               cl_dbg_err(cl_hw, "dsp_busy_wait failed!\n");
+               goto out;
+       }
+
+out:
+       release_firmware(fw);
+
+       return ret;
+}
+
+static bool cl_dsp_is_universal_file(struct cl_chip *chip)
+{
+       return (cl_chip_is_tcv0_enabled(chip) &&
+               cl_chip_is_tcv1_enabled(chip) &&
+               !strcmp(chip->cl_hw_tcv0->conf->ce_dsp_code,
+                       chip->cl_hw_tcv1->conf->ce_dsp_code));
+}
+
+static int load_dsp_code_dual(struct cl_chip *chip, const char *filename)
+{
+       struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+       struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+       u32 real_size;
+       u32 page;
+       const struct firmware *fw;
+       size_t size = 0;
+       u8 *buf = NULL;
+       char path_name[CL_PATH_MAX] = {0};
+       int ret;
+
+       snprintf(path_name, sizeof(path_name), "cl8k/%s", filename);
+       cl_dbg_chip_verbose(chip, "from %s\n", filename);
+       ret = request_firmware(&fw, path_name, chip->dev);
+
+       if (ret) {
+               cl_dbg_chip_err(chip, "Failed to get %s, with error: %x!\n",
+                               path_name, ret);
+               goto out;
+       }
+
+       size = fw->size;
+       buf = (u8 *)fw->data;
+
+       for (page = 0; page < CEVA_MAX_PAGES; page++) {
+               /* Copy DSP code (one page each time) */
+               ret = cl_dsp_hex_load(chip->cl_hw_tcv0, buf,
+                                     CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST,
+                                     CEVA_SHARED_PMEM_SIZE, size);
+               if (ret) {
+                       cl_dbg_chip_err(chip, "Failed to load pmem page 0x%x!\n", page);
+                       break;
+               }
+
+               config_dma_for_code_copy(cl_hw_tcv0, page);
+               ret = dsp_busy_wait(cl_hw_tcv0, CEVA_CPM_PDTC_REG);
+
+               if (ret) {
+                       cl_dbg_err(cl_hw_tcv0, "dsp_busy_wait failed\n");
+                       goto out;
+               }
+
+               config_dma_for_code_copy(cl_hw_tcv1, page);
+               ret = dsp_busy_wait(cl_hw_tcv1, CEVA_CPM_PDTC_REG);
+
+               if (ret) {
+                       cl_dbg_err(cl_hw_tcv1, "dsp_busy_wait failed\n");
+                       goto out;
+               }
+
+               real_size = min_t(u32, CEVA_SHARED_PMEM_SIZE * 3, size);
+               buf += real_size;
+               size -= real_size;
+       }
+
+out:
+       release_firmware(fw);
+
+       return ret;
+}
+
+static int load_dsp_external_data_dual(struct cl_chip *chip, const char *filename)
+{
+       /*
+        * Shared XMEM is not accessible by host.
+        * Copy the XMEM section to DRAM first and then use CEVA internal DMA to copy to
+        * SHARED XMEM.
+        */
+       struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+       struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+       const struct firmware *fw;
+       size_t size = 0;
+       char path_name[CL_PATH_MAX] = {0};
+       int ret;
+
+       snprintf(path_name, sizeof(path_name), "cl8k/%s", filename);
+       cl_dbg_chip_verbose(chip, "from %s\n", filename);
+       ret = request_firmware(&fw, path_name, chip->dev);
+
+       if (ret) {
+               cl_dbg_chip_err(chip, "Failed to get %s, with error: %x!\n",
+                               path_name, ret);
+               goto out;
+       }
+
+       size = fw->size;
+
+       /* TCV0 */
+       ret = cl_dsp_hex_load(cl_hw_tcv0, fw->data, REG_MACDSP_API_BASE_ADDR,
+                             CEVA_DSP_EXT_DATA_SIZE, size);
+
+       if (ret) {
+               cl_dbg_err(cl_hw_tcv0, "Failed to load HEX file\n");
+               goto out;
+       }
+
+       config_dma_for_external_data_copy(cl_hw_tcv0);
+       ret = dsp_busy_wait(cl_hw_tcv0, CEVA_CPM_DDTC_REG);
+
+       if (ret) {
+               cl_dbg_err(cl_hw_tcv0, "dsp_busy_wait failed!\n");
+               goto out;
+       }
+
+       /* TCV1 */
+       ret = cl_dsp_hex_load(cl_hw_tcv1, fw->data, REG_MACDSP_API_BASE_ADDR,
+                             CEVA_DSP_EXT_DATA_SIZE, size);
+
+       if (ret) {
+               cl_dbg_err(cl_hw_tcv1, "Failed to load HEX file\n");
+               goto out;
+       }
+
+       config_dma_for_external_data_copy(cl_hw_tcv1);
+       ret = dsp_busy_wait(cl_hw_tcv1, CEVA_CPM_DDTC_REG);
+
+       if (ret) {
+               cl_dbg_err(cl_hw_tcv1, "dsp_busy_wait failed!\n");
+               goto out;
+       }
+
+out:
+       release_firmware(fw);
+
+       return ret;
+}
+
+static int load_dsp_data_dual(struct cl_chip *chip, const char *filename)
+{
+       struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+       struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+       const struct firmware *fw;
+       size_t size = 0;
+       char path_name[CL_PATH_MAX] = {0};
+       int ret;
+
+       snprintf(path_name, sizeof(path_name), "cl8k/%s", filename);
+       cl_dbg_chip_verbose(chip, "from %s\n", filename);
+       ret = request_firmware(&fw, path_name, chip->dev);
+
+       if (ret) {
+               cl_dbg_chip_err(chip, "Failed to get %s, with error: %x!\n",
+                               path_name, ret);
+               goto out;
+       }
+
+       size = fw->size;
+
+       ret = cl_dsp_hex_load(cl_hw_tcv0, fw->data,
+                             REG_MACDSP_API_BASE_ADDR,
+                             CEVA_DSP_DATA_SIZE, size);
+
+       if (ret != 0) {
+               cl_dbg_err(cl_hw_tcv0, "Failed to load HEX file\n");
+               goto out;
+       }
+
+       ret = cl_dsp_hex_load(cl_hw_tcv1, fw->data,
+                             REG_MACDSP_API_BASE_ADDR,
+                             CEVA_DSP_DATA_SIZE, size);
+
+       if (ret != 0) {
+               cl_dbg_err(cl_hw_tcv1, "Failed to load HEX file\n");
+               goto out;
+       }
+
+out:
+       release_firmware(fw);
+
+       return ret;
+}
+
+static void print_ceva_core_info(struct cl_hw *cl_hw)
+{
+       cl_dbg_trace(cl_hw, "CEVA_CORE_VERSION_ADDR=0x%X.\n",
+                    cl_reg_read(cl_hw, CEVA_CORE_VERSION_ADDR));
+       cl_dbg_trace(cl_hw, "CEVA_CORE_ID_ADDR=0x%X.\n",
+                    cl_reg_read(cl_hw, CEVA_CORE_ID_ADDR));
+}
+
+static int cl_dsp_load_dual(struct cl_chip *chip)
+{
+       int ret = 0;
+       struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+       struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+       struct cl_tcv_conf *tcv0_conf = cl_hw_tcv0->conf;
+
+       modem_gcu_ceva_ctrl_external_wait_setf(cl_hw_tcv0, 0x1);
+       modem_gcu_ceva_ctrl_external_wait_setf(cl_hw_tcv1, 0x1);
+
+       print_ceva_core_info(cl_hw_tcv0);
+       print_ceva_core_info(cl_hw_tcv1);
+
+       ret = load_dsp_code_dual(chip, tcv0_conf->ce_dsp_code);
+       if (ret != 0) {
+               cl_dbg_chip_err(chip,
+                               "Failed to load DSP code. Error code %d.\n",
+                               ret);
+               return ret;
+       }
+
+       ret = load_dsp_external_data_dual(chip, tcv0_conf->ce_dsp_external_data);
+       if (ret != 0) {
+               cl_dbg_chip_err(chip,
+                               "Failed to load DSP external data. Error code %d.\n",
+                               ret);
+               return ret;
+       }
+
+       ret = load_dsp_data_dual(chip, tcv0_conf->ce_dsp_data);
+       if (ret != 0) {
+               cl_dbg_chip_err(chip,
+                               "Failed to load DSP data. Error code %d.\n",
+                               ret);
+               return ret;
+       }
+
+       macdsp_api_config_space_set(cl_hw_tcv0, 0);
+       /* Release DSP wait. */
+       cl_dsp_boot(cl_hw_tcv0);
+
+       macdsp_api_config_space_set(cl_hw_tcv1, 0);
+       /* Release DSP wait. */
+       cl_dsp_boot(cl_hw_tcv1);
+
+       return ret;
+}
+
+static int _cl_dsp_load(struct cl_hw *cl_hw)
+{
+       int ret = 0;
+
+       modem_gcu_ceva_ctrl_external_wait_setf(cl_hw, 0x1);
+       print_ceva_core_info(cl_hw);
+
+       ret = load_dsp_code(cl_hw);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to load DSP code %d\n", ret);
+               return ret;
+       }
+
+       ret = load_dsp_external_data(cl_hw);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to load DSP external data %d\n", ret);
+               return ret;
+       }
+
+       ret = load_dsp_data(cl_hw);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to load DSP data %d\n", ret);
+               return ret;
+       }
+
+       macdsp_api_config_space_set(cl_hw, 0);
+       /* Release DSP wait */
+       cl_dsp_boot(cl_hw);
+
+       return ret;
+}
+
+int cl_dsp_load_regular(struct cl_chip *chip)
+{
+       int ret = 0;
+
+       if (cl_dsp_is_universal_file(chip))
+               return cl_dsp_load_dual(chip);
+
+       if (cl_chip_is_tcv0_enabled(chip)) {
+               ret = _cl_dsp_load(chip->cl_hw_tcv0);
+               if (ret)
+                       return ret;
+       }
+
+       if (cl_chip_is_tcv1_enabled(chip)) {
+               ret = _cl_dsp_load(chip->cl_hw_tcv1);
+               if (ret)
+                       return ret;
+       }
+
+       return ret;
+}
+
+int cl_dsp_load_recovery(struct cl_hw *cl_hw)
+{
+       int ret = 0;
+
+       modem_gcu_ceva_ctrl_external_wait_setf(cl_hw, 0x1);
+
+       ret = load_dsp_external_data(cl_hw);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to load DSP external data %d\n", ret);
+               return ret;
+       }
+
+       ret = load_dsp_data(cl_hw);
+       if (ret) {
+               cl_dbg_err(cl_hw, "Failed to load DSP data %d\n", ret);
+               return ret;
+       }
+
+       macdsp_api_config_space_set(cl_hw, 0);
+       /* Release DSP wait. */
+       cl_dsp_boot(cl_hw);
+
+       return ret;
+}
--
2.30.0

________________________________
The information transmitted is intended only for the person or entity to which it is addressed and may contain confidential and/or privileged material. Any retransmission, dissemination, copying or other use of, or taking of any action in reliance upon this information is prohibited. If you received this in error, please contact the sender and delete the material from any computer. Nothing contained herein shall be deemed as a representation, warranty or a commitment by Celeno. No warranties are expressed or implied, including, but not limited to, any implied warranties of non-infringement, merchantability and fitness for a particular purpose.
________________________________





[Index of Archives]     [Linux Host AP]     [ATH6KL]     [Linux Wireless Personal Area Network]     [Linux Bluetooth]     [Wireless Regulations]     [Linux Netdev]     [Kernel Newbies]     [Linux Kernel]     [IDE]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite Hiking]     [MIPS Linux]     [ARM Linux]     [Linux RAID]

  Powered by Linux