Sending patch for B0 medfield firmware update, also incorported review comments 
from Alan Cox.

>From 43daaec4a036b1ae6fa9424a329ce014baf5feaa Mon Sep 17 00:00:00 2001
From: Sudha Krishnakumar <[email protected]>
Date: Thu, 16 Dec 2010 13:14:56 -0800
Subject: [PATCH] Changes for B0 medfield firmware update. Involves updating 
security firmware.
 Also code has been optimized. Incorporated review comments from Alan Cox.
 Tested on IA side, needs updated SCU firmware for more testing.


Signed-off-by: Sudha Krishnakumar <[email protected]>
---
 intel_scu_ipc.c |  419 ++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 288 insertions(+), 131 deletions(-)

diff --git a/intel_scu_ipc.c b/intel_scu_ipc.c
index 1e775c2..4a618e9 100644
--- a/intel_scu_ipc.c
+++ b/intel_scu_ipc.c
@@ -678,6 +678,12 @@ EXPORT_SYMBOL(intel_scu_ipc_fw_update);
 #define LOWER_128K       "LOFW"
 #define UPPER_128K       "HIFW"
 #define UPDATE_DONE      "DONE"
+#define PSFW1           "PSFW1"
+#define PSFW2           "PSFW2"
+#define SSFW            "SSFW"
+
+#define MAX_LEN_PSFW     7
+#define MAX_LEN_SSFW     6

 /* Modified IA-SCU mailbox for medfield firmware update. */
 struct ia_scu_mailbox {
@@ -703,6 +709,34 @@ struct mfld_fw_update {
        char mb_status[8];
 };

+/* Structure to hold firmware update header */
+struct fuph_hdr {
+       u32 sig;
+       u32 mip_size;
+       u32 ifwi_size;
+       u32 psfw1_size;
+       u32 psfw2_size;
+       u32 ssfw_size;
+       u32 checksum;
+};
+
+enum mailbox_status {
+       MB_DONE,
+       MB_CONTINUE,
+       MB_ERROR
+};
+
+struct sec_fw {
+       const char *fw_type;
+       u8 str_len;
+};
+
+static struct sec_fw sec_fw_table[] = {
+       { .fw_type = "PSFW1", .str_len  = MAX_LEN_PSFW },
+       { .fw_type = "SSFW", .str_len  = MAX_LEN_SSFW  },
+       { .fw_type = "PSFW2", .str_len  = MAX_LEN_PSFW  }
+};
+
 /*
  * IA will wait in busy-state, and poll mailbox, to check
  * if SCU is done processing.
@@ -714,20 +748,18 @@ static int busy_wait(struct mfld_fw_update *mfld_fw_upd)
        u32 count = 0;
        u32 flag;

-       if (!mfld_fw_upd)
-               return -EINVAL;
-
        flag = mfld_fw_upd->wscu;

-       while ((ioread32(mfld_fw_upd->mailbox + SCU_FLAG_OFFSET) != flag)
-               && (count < 120)) {
+       while (ioread32(mfld_fw_upd->mailbox + SCU_FLAG_OFFSET) != flag
+               && count < 120) {
                /* There are synchronization issues between IA and SCU */
-               smp_mb();
+               mb();
+               /* FIXME: we must use mdelay currently */
                mdelay(10);
                count++;
        }

-       if ((ioread32(mfld_fw_upd->mailbox + SCU_FLAG_OFFSET) != flag)) {
+       if (ioread32(mfld_fw_upd->mailbox + SCU_FLAG_OFFSET) != flag) {
                dev_err(&ipcdev.pdev->dev, "IA-wait >2 secs,quitting\n");
                return -ETIMEDOUT;
        }
@@ -745,66 +777,199 @@ static int busy_wait(struct mfld_fw_update *mfld_fw_upd)
 static int process_fw_chunk(u8 *fws, u8 __user *userptr, u32 chunklen,
                                        struct mfld_fw_update *mfld_fw_upd)
 {
-       int ret_val = 0;
-
-       if (fws == NULL || userptr == NULL || mfld_fw_upd == NULL)
-               return -EINVAL;
-
-       ret_val = copy_from_user(fws, userptr, chunklen);
-
-       if (ret_val < 0)
+       if (copy_from_user(fws, userptr, chunklen))
                return -EFAULT;

-       /*IA copy to sram */
+       /* IA copy to sram */
        memcpy_toio(mfld_fw_upd->sram, fws, chunklen);

        /* There are synchronization issues between IA and SCU */
-       smp_mb();
+       mb();
        mfld_fw_upd->wia = !(mfld_fw_upd->wia);
        iowrite32(mfld_fw_upd->wia, mfld_fw_upd->mailbox + IA_FLAG_OFFSET);

-       smp_mb();
+       mb();
        dev_dbg(&ipcdev.pdev->dev, "wrote ia_flag=%d\n",
                 ioread32(mfld_fw_upd->mailbox + IA_FLAG_OFFSET));

-       mfld_fw_upd->wscu = !(mfld_fw_upd->wscu);
+       mfld_fw_upd->wscu = !mfld_fw_upd->wscu;
        return busy_wait(mfld_fw_upd);
 }

 /*
- * This function will check if mailbox status flag, indicates any errors.
+ * This function will check mailbox status flag, and return state of mailbox.
  */
-static int check_mb_status(char *mb_status, struct mfld_fw_update *mfld_fw_upd)
+static enum mailbox_status check_mb_status(struct mfld_fw_update *mfld_fw_upd)
 {

-       if (mb_status == NULL || mfld_fw_upd == NULL)
-               return -EINVAL;
+       enum mailbox_status mb_state;

        /* There are synchronization issues between IA and SCU */
-       smp_mb();
+       mb();

        memcpy_fromio(mfld_fw_upd->mb_status, mfld_fw_upd->mailbox, 8);

-       if ((strncmp(mfld_fw_upd->mb_status, "ER", 2) == 0) ||
-               (strncmp(mfld_fw_upd->mb_status, "HLT0", 4) == 0)) {
+       if (!strncmp(mfld_fw_upd->mb_status, "ER", 2) ||
+               !strncmp(mfld_fw_upd->mb_status, "HLT0", 4)) {
                dev_dbg(&ipcdev.pdev->dev,
                        "mailbox error=%s\n", mfld_fw_upd->mb_status);
-               return -EINVAL;
+               return MB_ERROR;
        } else {
-               if (strncmp(mfld_fw_upd->mb_status, mb_status,
-                               strlen(mfld_fw_upd->mb_status)) != 0) {
-                       dev_dbg(&ipcdev.pdev->dev,
-                               "unexpected state=%s", mfld_fw_upd->mb_status);
-                       return -EINVAL;
-               } else {
-                       dev_dbg(&ipcdev.pdev->dev,
-                               "mailbox pass=%s\n", mfld_fw_upd->mb_status);
-               }
+               mb_state = (!strncmp(mfld_fw_upd->mb_status, UPDATE_DONE,
+                               sizeof(UPDATE_DONE))) ? MB_DONE : MB_CONTINUE;
+               dev_dbg(&ipcdev.pdev->dev,
+                       "mailbox pass=%s, mb_state=%d\n",
+                       mfld_fw_upd->mb_status, mb_state);
        }

+       return mb_state;
+
+}
+
+/* Helper function used to calculate length and offset.  */
+int helper_for_calc_offset_length(struct fw_ud *fw_ud_ptr, char *scu_req,
+                               void **offset, u32 *len, struct fuph_hdr *fuph,
+                               char *fw_type)
+{
+
+       unsigned long chunk_no;
+       u32 chunk_rem;
+       u32 max_chunk_cnt;
+       u32 fw_size;
+       u32 fw_offset;
+
+       if (!strncmp(fw_type, PSFW1, strlen(PSFW1))) {
+
+               if (strict_strtoul(scu_req + strlen(PSFW1), 10,
+                                               &chunk_no) < 0)
+                       return -EINVAL;
+
+               fw_size = fuph->psfw1_size;
+               fw_offset = fuph->mip_size + fuph->ifwi_size;
+       } else if (!strncmp(fw_type, PSFW2, strlen(PSFW2))) {
+
+               if (strict_strtoul(scu_req + strlen(PSFW2), 10,
+                                               &chunk_no) < 0)
+                       return -EINVAL;
+
+               fw_size = fuph->psfw2_size;
+               fw_offset = fuph->mip_size + fuph->ifwi_size +
+                               fuph->psfw1_size + fuph->ssfw_size;
+       } else if (!strncmp(fw_type, SSFW, strlen(SSFW))) {
+
+               if (strict_strtoul(scu_req + strlen(SSFW), 10,
+                                               &chunk_no) < 0)
+                       return -EINVAL;
+
+               fw_size = fuph->ssfw_size;
+               fw_offset = fuph->mip_size + fuph->ifwi_size +
+                               fuph->psfw1_size;
+       } else
+               return -EINVAL;
+
+       chunk_rem = fw_size % MAX_FW_CHUNK;
+       max_chunk_cnt = (fw_size/MAX_FW_CHUNK) + (chunk_rem ? 1 : 0);
+
+       dev_dbg(&ipcdev.pdev->dev,
+               "str=%s,chunk_no=%d, chunk_rem=%d,max_chunk_cnt=%d\n",
+               fw_type, chunk_no, chunk_rem, max_chunk_cnt);
+
+       if ((chunk_no + 1) > max_chunk_cnt)
+               return -EINVAL;
+
+       /* Note::Logic below will make sure, that we get right length if input
+        is 128K or multiple. */
+       *len = (chunk_no == (max_chunk_cnt - 1)) ?
+               (chunk_rem ? chunk_rem : MAX_FW_CHUNK) : MAX_FW_CHUNK;
+
+       *offset = fw_ud_ptr->fw_file_data + fw_offset +
+               (fw_size/((max_chunk_cnt - chunk_no)
+               * MAX_FW_CHUNK))*MAX_FW_CHUNK;
+
        return 0;
+
 }

+/*
+ * This api calculates offset and length depending on type of firmware chunk
+ * requested by SCU. Note: Intent is to follow the architecture such that,
+ * SCU controls the flow, and IA simply hands out, what is requested by SCU.
+ * IA will simply follow SCU's commands, unless SCU requests for something
+ * IA cannot give. TODO:That will be a special error case, need to figure out
+ * how to handle that.
+ */
+int calc_offset_and_length(struct fw_ud *fw_ud_ptr, char *scu_req,
+                       void **offset, u32 *len, struct fuph_hdr *fuph)
+{
+
+       u8 cnt;
+
+       if (!strncmp(DNX_IMAGE, scu_req, strlen(scu_req))) {
+               *offset = fw_ud_ptr->dnx_file_data;
+               *len = fw_ud_ptr->dnx_size;
+               return 0;
+       } else if (!strncmp(FUPH, scu_req, strlen(scu_req))) {
+               *offset = fw_ud_ptr->fw_file_data + fw_ud_ptr->fsize
+                               - FUPH_HDR_LEN - 2;
+               *len = FUPH_HDR_LEN;
+               return 0;
+       } else if (!strncmp(MIP, scu_req, strlen(scu_req))) {
+               *offset = fw_ud_ptr->fw_file_data + MIP_HEADER_OFFSET;
+               *len = fuph->mip_size;
+               return 0;
+       } else if (!strncmp(LOWER_128K, scu_req, strlen(scu_req))) {
+               *offset = fw_ud_ptr->fw_file_data + fuph->mip_size;
+               *len = MAX_FW_CHUNK;
+               return 0;
+       } else if (!strncmp(UPPER_128K, scu_req, strlen(scu_req))) {
+               *offset = fw_ud_ptr->fw_file_data
+                               + fuph->mip_size + MAX_FW_CHUNK;
+               *len = MAX_FW_CHUNK;
+               return 0;
+       } else {
+               for (cnt = 0; cnt < ARRAY_SIZE(sec_fw_table) ; cnt++) {
+
+                       if (!strncmp(sec_fw_table[cnt].fw_type, scu_req,
+                                       strlen(sec_fw_table[cnt].fw_type))) {
+
+                               if (strlen(scu_req) ==
+                                               sec_fw_table[cnt].str_len) {
+
+                                       if (helper_for_calc_offset_length
+                                               (fw_ud_ptr, scu_req,
+                                               offset, len, fuph, PSFW1) < 0)
+                                               goto error_case;
+
+                                       dev_dbg(&ipcdev.pdev->dev,
+                                       "\npsfw1, len=%d,offset=%d",
+                                       *len, *offset);
+
+                                       return 0;
+
+                               } else
+                                       goto error_case;
+                       }
+               }
+
+       }
+
+       dev_dbg(&ipcdev.pdev->dev,
+                       "Unexpected mailbox request from scu\n");
+
+error_case:
+       /* TODO::Need to test this error case..and see how SCU reacts
+       * and how IA handles
+       * subsequent error response and whether exit is graceful...
+       */
+
+       dev_dbg(&ipcdev.pdev->dev, "error case,respond back to SCU..\n");
+       dev_dbg(&ipcdev.pdev->dev, "scu_req=%s\n", scu_req);
+       *len = 0;
+       *offset = 0;
+
+       return -EINVAL;
+
+}

 /**
  *     intel_scu_ipc_medfw_upgrade     - Medfield Firmware update utility
@@ -822,9 +987,16 @@ int intel_scu_ipc_medfw_upgrade(void __user *arg)
        struct mfld_fw_update   mfld_fw_upd;
        u8 *fw_file_data = NULL;
        u8 *fws = NULL;
-       u8 *fuph_ptr = NULL;
        int ret_val = 0;

+       struct fuph_hdr fuph;
+       u32 length = 0;
+       void *offset;
+       enum mailbox_status mb_state;
+
+       if (platform != MRST_CPU_CHIP_PENWELL)
+               return -EINVAL;
+
        mutex_lock(&ipclock);
        if (ipcdev.pdev == NULL) {
                ret_val = -ENODEV;
@@ -839,9 +1011,9 @@ int intel_scu_ipc_medfw_upgrade(void __user *arg)
                return -EFAULT;

        fw_file_data = fw_ud_param.fw_file_data;
-       if ((fw_file_data == NULL) || ((fw_ud_param.fsize == 0) ||
-           (fw_ud_param.dnx_size == 0)) || (fw_ud_param.dnx_hdr == NULL) ||
-           (fw_ud_param.dnx_file_data == NULL)) {
+       if (fw_file_data == NULL || fw_ud_param.fsize == 0 ||
+           fw_ud_param.dnx_size == 0 || fw_ud_param.dnx_hdr == NULL ||
+           fw_ud_param.dnx_file_data == NULL) {
                dev_err(&ipcdev.pdev->dev,
                        "ipc_medfw_upgrade, invalid args!!\n");
                ret_val = -EINVAL;
@@ -859,7 +1031,7 @@ int intel_scu_ipc_medfw_upgrade(void __user *arg)
                                        sizeof(struct ia_scu_mailbox));

        if (mfld_fw_upd.mailbox == NULL) {
-               dev_err(&ipcdev.pdev->dev, "unable to map the MailBox\n");
+               dev_err(&ipcdev.pdev->dev, "unable to map the mailbox\n");
                ret_val = -ENOMEM;
                goto unmap_sram;
        }
@@ -873,6 +1045,27 @@ int intel_scu_ipc_medfw_upgrade(void __user *arg)
                ret_val = -ENOMEM;
                goto unmap_mb;
        }
+       /* Copy fuph header to kernel space */
+       if (copy_from_user(&fuph,
+                       (fw_ud_param.fw_file_data + (fw_ud_param.fsize - 1)
+                       - (FUPH_HDR_LEN - 1)),
+                       FUPH_HDR_LEN)) {
+               dev_err(&ipcdev.pdev->dev,  "copy from user failed\n");
+               ret_val = -EFAULT;
+               goto term;
+       }
+
+       /* Convert sizes in DWORDS to number of bytes. */
+       fuph.mip_size = fuph.mip_size * 4;
+       fuph.ifwi_size = fuph.ifwi_size * 4;
+       fuph.psfw1_size = fuph.psfw1_size * 4;
+       fuph.psfw2_size = fuph.psfw2_size * 4;
+       fuph.ssfw_size = fuph.ssfw_size * 4;
+
+       dev_dbg(&ipcdev.pdev->dev, "\nmip=%d, ifwi=%d,ps1=%d, ps2=%d, sfw=%d",
+                       fuph.mip_size,
+                       fuph.ifwi_size, fuph.psfw1_size, fuph.psfw2_size,
+                       fuph.ssfw_size);

        /* TODO_SK::There is just
         *  1 write required from IA side for DFU.
@@ -889,117 +1082,81 @@ int intel_scu_ipc_medfw_upgrade(void __user *arg)
        memcpy_toio(mfld_fw_upd.sram, fws, DNX_HDR_LEN);

        /* There are synchronization issues between IA and SCU */
-       smp_mb();
+       mb();

        /* Write cmd to trigger an interrupt to SCU for firmware update*/
        ipc_command(IPC_CMD_FW_UPDATE_GO);

        mfld_fw_upd.wscu = !mfld_fw_upd.wscu;

-       if ((busy_wait(&mfld_fw_upd)) < 0) {
+       if (busy_wait(&mfld_fw_upd) < 0) {
                ret_val = -1;
                goto term;
        }

-       if (check_mb_status(DNX_IMAGE, &mfld_fw_upd) < 0) {
-               ret_val = -1;
-               goto term;
-       }
-
-       /*2. DNX FILE   */
-       if ((process_fw_chunk(fws, fw_ud_param.dnx_file_data,
-                             fw_ud_param.dnx_size, &mfld_fw_upd)) != 0) {
-               dev_err(&ipcdev.pdev->dev, "Error fw chunk=%s\n", DNX_IMAGE);
-               ret_val = -1;
-               goto term;
-       }
-
-       if (check_mb_status(FUPH_HDR_SIZE, &mfld_fw_upd) < 0) {
-               ret_val = -1;
-               goto term;
-       }
-
-       iowrite32(FUPH_HDR_LEN, mfld_fw_upd.sram);
-
-       /* There are synchronization issues between IA and SCU */
-       smp_mb();
-       dev_dbg(&ipcdev.pdev->dev,
-               "copied fuph hdr size=%d\n", ioread32(mfld_fw_upd.sram));
-       mfld_fw_upd.wia = !(mfld_fw_upd.wia);
-       iowrite32(mfld_fw_upd.wia, mfld_fw_upd.mailbox + IA_FLAG_OFFSET);
-       dev_dbg(&ipcdev.pdev->dev, "ia_flag=%d\n",
-               ioread32(mfld_fw_upd.mailbox + IA_FLAG_OFFSET));
-
-       smp_mb();
-
-       mfld_fw_upd.wscu = !mfld_fw_upd.wscu;
-       if ((busy_wait(&mfld_fw_upd)) < 0) {
-               ret_val = -1;
-               goto term;
-       }
-       if (check_mb_status(FUPH, &mfld_fw_upd) < 0) {
-               dev_err(&ipcdev.pdev->dev,
-                       "error after FUPH hdrsize, IA quitting\n");
-               ret_val = -1;
-               goto term;
-       }
-
-       /*3. FUPH HEADER   */
-       fuph_ptr = fw_file_data + (fw_ud_param.fsize - 1) - (FUPH_HDR_LEN - 1);
-       if ((process_fw_chunk(fws, fuph_ptr, FUPH_HDR_LEN,
-                               &mfld_fw_upd)) != 0) {
-               dev_err(&ipcdev.pdev->dev, "Error fw chunk=%s\n", FUPH);
-               ret_val = -1;
-               goto term;
-       }
+       /* TODO:Add a count for iteration, based on sizes of security firmware,
+       * so that we determine finite number of iterations to loop thro.
+       * That way at the very least, we can atleast control the number
+       * of iterations, and prevent infinite looping if there are any bugs.
+       * The only catch being for B0, SCU will request twice for each firmware
+       * chunk, since its writing to 2 partitions.
+       * TODO::Investigate if we need to increase timeout for busy_wait,
+       * since SCU is now writing to 2 partitions.
+       */

-       /* TODO: Fix this function, to make it handle more than 1 string
-        * At this point, if MIP size is 0 in FUPH, then SCU can jump to LOFW.
-        */
-       if ((check_mb_status(MIP, &mfld_fw_upd) < 0) &&
-               (check_mb_status(LOWER_128K, &mfld_fw_upd) < 0)) {
-               ret_val = -1;
-               goto term;
-       }
+       while ((mb_state = check_mb_status(&mfld_fw_upd)) != MB_DONE) {

-       if ((strncmp(mfld_fw_upd.mb_status, MIP,
-                                       strlen(mfld_fw_upd.mb_status)) == 0)) {
-               /*4. MIP HEADER   */
-               if ((process_fw_chunk(fws, (fw_file_data + MIP_HEADER_OFFSET),
-                                     MIP_HEADER_LEN, &mfld_fw_upd)) != 0) {
-                       dev_err(&ipcdev.pdev->dev, "Error fw chunk=%s\n", MIP);
+               if (mb_state == MB_ERROR) {
+                       dev_dbg(&ipcdev.pdev->dev, "check_mb_status,error\n");
                        ret_val = -1;
                        goto term;
                }

-               if (check_mb_status(LOWER_128K, &mfld_fw_upd) < 0) {
+               if (!strncmp(mfld_fw_upd.mb_status, FUPH_HDR_SIZE,
+                               strlen(FUPH_HDR_SIZE))) {
+                       iowrite32(FUPH_HDR_LEN, mfld_fw_upd.sram);
+                       /* There are synchronization issues between IA-SCU */
+                       mb();
+                       dev_dbg(&ipcdev.pdev->dev,
+                               "copied fuph hdr size=%d\n",
+                               ioread32(mfld_fw_upd.sram));
+                       mfld_fw_upd.wia = !mfld_fw_upd.wia;
+                       iowrite32(mfld_fw_upd.wia, mfld_fw_upd.mailbox +
+                       IA_FLAG_OFFSET);
+                       dev_dbg(&ipcdev.pdev->dev, "ia_flag=%d\n",
+                       ioread32(mfld_fw_upd.mailbox + IA_FLAG_OFFSET));
+                       mb();
+                       mfld_fw_upd.wscu = !mfld_fw_upd.wscu;
+
+                       if (busy_wait(&mfld_fw_upd) < 0) {
+                               ret_val = -1;
+                               goto term;
+                       }
+
+                       continue;
+               }
+
+               if (calc_offset_and_length(&fw_ud_param, mfld_fw_upd.mb_status,
+                                       &offset, &length, &fuph) < 0) {
+                       dev_dbg(&ipcdev.pdev->dev,
+                       "calc_offset_and_length_error,error\n");
                        ret_val = -1;
                        goto term;
                }
-       }

-       /*5. LOWER 128K */
-       if ((process_fw_chunk(fws, (fw_file_data + LOWER_128K_OFFSET),
-                             MAX_FW_CHUNK, &mfld_fw_upd)) != 0) {
-               dev_err(&ipcdev.pdev->dev, "Error fw chunk=%s\n", LOWER_128K);
-               ret_val = -1;
-               goto term;
-       }
-
-       if (check_mb_status(UPPER_128K, &mfld_fw_upd) < 0) {
-               ret_val = -1;
-               goto term;
-       }
-
-       /*6. UPPER 128K */
-       if ((process_fw_chunk(fws, (fw_file_data + UPPER_128K_OFFSET),
-                             MAX_FW_CHUNK, &mfld_fw_upd)) != 0) {
-               dev_err(&ipcdev.pdev->dev, "Error fw chunk=%s\n", UPPER_128K);
-               ret_val = -1;
-               goto term;
+               if ((process_fw_chunk(fws, offset, length,
+                       &mfld_fw_upd)) != 0) {
+                       dev_dbg(&ipcdev.pdev->dev,
+                       "Error processing fw chunk=%s\n",
+                       mfld_fw_upd.mb_status);
+                       ret_val = -1;
+                       goto term;
+               } else
+                       dev_dbg(&ipcdev.pdev->dev,
+                       "PASS processing fw chunk=%s\n",
+                       mfld_fw_upd.mb_status);
        }

-       ret_val = check_mb_status(UPDATE_DONE, &mfld_fw_upd);
 term:
        kfree(fws);
 unmap_mb:
@@ -1010,7 +1167,7 @@ out_unlock:
        mutex_unlock(&ipclock);
        return ret_val;
 }
-EXPORT_SYMBOL(intel_scu_ipc_medfw_upgrade);
+EXPORT_SYMBOL_GPL(intel_scu_ipc_medfw_upgrade);

 /*
  * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
--
1.6.0.6

Attachment: 0001-Changes-for-B0-medfield-firmware-update.-Involves-up.patch
Description: 0001-Changes-for-B0-medfield-firmware-update.-Involves-up.patch

_______________________________________________
MeeGo-kernel mailing list
[email protected]
http://lists.meego.com/listinfo/meego-kernel

Reply via email to