net: wwan: iosm: transport layer support for fw flashing/cd
authorM Chetan Kumar <m.chetan.kumar@linux.intel.com>
Sun, 19 Sep 2021 17:27:56 +0000 (22:57 +0530)
committerDavid S. Miller <davem@davemloft.net>
Mon, 20 Sep 2021 09:03:37 +0000 (10:03 +0100)
Implements transport layer protocol for fw flashing/coredump
collection.

Signed-off-by: M Chetan Kumar <m.chetan.kumar@linux.intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.c
drivers/net/wwan/iosm/iosm_ipc_chnl_cfg.h
drivers/net/wwan/iosm/iosm_ipc_imem.c
drivers/net/wwan/iosm/iosm_ipc_imem.h
drivers/net/wwan/iosm/iosm_ipc_imem_ops.c
drivers/net/wwan/iosm/iosm_ipc_imem_ops.h

index 519361e..128c999 100644 (file)
@@ -8,7 +8,7 @@
 #include "iosm_ipc_chnl_cfg.h"
 
 /* Max. sizes of a downlink buffers */
-#define IPC_MEM_MAX_DL_FLASH_BUF_SIZE (16 * 1024)
+#define IPC_MEM_MAX_DL_FLASH_BUF_SIZE (64 * 1024)
 #define IPC_MEM_MAX_DL_LOOPBACK_SIZE (1 * 1024 * 1024)
 #define IPC_MEM_MAX_DL_AT_BUF_SIZE 2048
 #define IPC_MEM_MAX_DL_RPC_BUF_SIZE (32 * 1024)
@@ -60,6 +60,10 @@ static struct ipc_chnl_cfg modem_cfg[] = {
        { IPC_MEM_CTRL_CHL_ID_6, IPC_MEM_PIPE_12, IPC_MEM_PIPE_13,
          IPC_MEM_MAX_TDS_MBIM, IPC_MEM_MAX_TDS_MBIM,
          IPC_MEM_MAX_DL_MBIM_BUF_SIZE, WWAN_PORT_MBIM },
+       /* Flash Channel/Coredump Channel */
+       { IPC_MEM_CTRL_CHL_ID_7, IPC_MEM_PIPE_0, IPC_MEM_PIPE_1,
+         IPC_MEM_MAX_TDS_FLASH_UL, IPC_MEM_MAX_TDS_FLASH_DL,
+         IPC_MEM_MAX_DL_FLASH_BUF_SIZE, WWAN_PORT_UNKNOWN },
 };
 
 int ipc_chnl_cfg_get(struct ipc_chnl_cfg *chnl_cfg, int index)
index 4224713..e77084e 100644 (file)
@@ -23,6 +23,7 @@ enum ipc_channel_id {
        IPC_MEM_CTRL_CHL_ID_4,
        IPC_MEM_CTRL_CHL_ID_5,
        IPC_MEM_CTRL_CHL_ID_6,
+       IPC_MEM_CTRL_CHL_ID_7,
 };
 
 /**
index 9f00e36..1cf49e9 100644 (file)
@@ -6,6 +6,8 @@
 #include <linux/delay.h>
 
 #include "iosm_ipc_chnl_cfg.h"
+#include "iosm_ipc_devlink.h"
+#include "iosm_ipc_flash.h"
 #include "iosm_ipc_imem.h"
 #include "iosm_ipc_port.h"
 
@@ -263,9 +265,12 @@ static void ipc_imem_dl_skb_process(struct iosm_imem *ipc_imem,
        switch (pipe->channel->ctype) {
        case IPC_CTYPE_CTRL:
                port_id = pipe->channel->channel_id;
-
-               /* Pass the packet to the wwan layer. */
-               wwan_port_rx(ipc_imem->ipc_port[port_id]->iosm_port, skb);
+               if (port_id == IPC_MEM_CTRL_CHL_ID_7)
+                       ipc_imem_sys_devlink_notify_rx(ipc_imem->ipc_devlink,
+                                                      skb);
+               else
+                       wwan_port_rx(ipc_imem->ipc_port[port_id]->iosm_port,
+                                    skb);
                break;
 
        case IPC_CTYPE_WWAN:
@@ -399,19 +404,8 @@ static void ipc_imem_rom_irq_exec(struct iosm_imem *ipc_imem)
 {
        struct ipc_mem_channel *channel;
 
-       if (ipc_imem->flash_channel_id < 0) {
-               ipc_imem->rom_exit_code = IMEM_ROM_EXIT_FAIL;
-               dev_err(ipc_imem->dev, "Missing flash app:%d",
-                       ipc_imem->flash_channel_id);
-               return;
-       }
-
+       channel = ipc_imem->ipc_devlink->devlink_sio.channel;
        ipc_imem->rom_exit_code = ipc_mmio_get_rom_exit_code(ipc_imem->mmio);
-
-       /* Wake up the flash app to continue or to terminate depending
-        * on the CP ROM exit code.
-        */
-       channel = &ipc_imem->channels[ipc_imem->flash_channel_id];
        complete(&channel->ul_sem);
 }
 
@@ -572,7 +566,7 @@ static void ipc_imem_handle_irq(struct iosm_imem *ipc_imem, int irq)
        enum ipc_phase old_phase, phase;
        bool retry_allocation = false;
        bool ul_pending = false;
-       int ch_id, i;
+       int i;
 
        if (irq != IMEM_IRQ_DONT_CARE)
                ipc_imem->ev_irq_pending[irq] = false;
@@ -696,11 +690,8 @@ static void ipc_imem_handle_irq(struct iosm_imem *ipc_imem, int irq)
        if ((phase == IPC_P_PSI || phase == IPC_P_EBL) &&
            ipc_imem->ipc_requested_state == IPC_MEM_DEVICE_IPC_RUNNING &&
            ipc_mmio_get_ipc_state(ipc_imem->mmio) ==
-                   IPC_MEM_DEVICE_IPC_RUNNING &&
-           ipc_imem->flash_channel_id >= 0) {
-               /* Wake up the flash app to open the pipes. */
-               ch_id = ipc_imem->flash_channel_id;
-               complete(&ipc_imem->channels[ch_id].ul_sem);
+                                               IPC_MEM_DEVICE_IPC_RUNNING) {
+               complete(&ipc_imem->ipc_devlink->devlink_sio.channel->ul_sem);
        }
 
        /* Reset the expected CP state. */
@@ -1176,6 +1167,9 @@ void ipc_imem_cleanup(struct iosm_imem *ipc_imem)
                ipc_port_deinit(ipc_imem->ipc_port);
        }
 
+       if (ipc_imem->ipc_devlink)
+               ipc_devlink_deinit(ipc_imem->ipc_devlink);
+
        ipc_imem_device_ipc_uninit(ipc_imem);
        ipc_imem_channel_reset(ipc_imem);
 
@@ -1258,6 +1252,7 @@ struct iosm_imem *ipc_imem_init(struct iosm_pcie *pcie, unsigned int device_id,
                                void __iomem *mmio, struct device *dev)
 {
        struct iosm_imem *ipc_imem = kzalloc(sizeof(*pcie->imem), GFP_KERNEL);
+       enum ipc_mem_exec_stage stage;
 
        if (!ipc_imem)
                return NULL;
@@ -1272,9 +1267,6 @@ struct iosm_imem *ipc_imem_init(struct iosm_pcie *pcie, unsigned int device_id,
        ipc_imem->cp_version = 0;
        ipc_imem->device_sleep = IPC_HOST_SLEEP_ENTER_SLEEP;
 
-       /* Reset the flash channel id. */
-       ipc_imem->flash_channel_id = -1;
-
        /* Reset the max number of configured channels */
        ipc_imem->nr_of_channels = 0;
 
@@ -1328,8 +1320,21 @@ struct iosm_imem *ipc_imem_init(struct iosm_pcie *pcie, unsigned int device_id,
                goto imem_config_fail;
        }
 
-       return ipc_imem;
+       stage = ipc_mmio_get_exec_stage(ipc_imem->mmio);
+       if (stage == IPC_MEM_EXEC_STAGE_BOOT) {
+               /* Alloc and Register devlink */
+               ipc_imem->ipc_devlink = ipc_devlink_init(ipc_imem);
+               if (!ipc_imem->ipc_devlink) {
+                       dev_err(ipc_imem->dev, "Devlink register failed");
+                       goto imem_config_fail;
+               }
 
+               if (ipc_flash_link_establish(ipc_imem))
+                       goto devlink_channel_fail;
+       }
+       return ipc_imem;
+devlink_channel_fail:
+       ipc_devlink_deinit(ipc_imem->ipc_devlink);
 imem_config_fail:
        hrtimer_cancel(&ipc_imem->td_alloc_timer);
        hrtimer_cancel(&ipc_imem->fast_update_timer);
@@ -1361,3 +1366,51 @@ void ipc_imem_td_update_timer_suspend(struct iosm_imem *ipc_imem, bool suspend)
 {
        ipc_imem->td_update_timer_suspended = suspend;
 }
+
+/* Verify the CP execution state, copy the chip info,
+ * change the execution phase to ROM
+ */
+static int ipc_imem_devlink_trigger_chip_info_cb(struct iosm_imem *ipc_imem,
+                                                int arg, void *msg,
+                                                size_t msgsize)
+{
+       enum ipc_mem_exec_stage stage;
+       struct sk_buff *skb;
+       int rc = -EINVAL;
+       size_t size;
+
+       /* Test the CP execution state. */
+       stage = ipc_mmio_get_exec_stage(ipc_imem->mmio);
+       if (stage != IPC_MEM_EXEC_STAGE_BOOT) {
+               dev_err(ipc_imem->dev,
+                       "Execution_stage: expected BOOT, received = %X", stage);
+               goto trigger_chip_info_fail;
+       }
+       /* Allocate a new sk buf for the chip info. */
+       size = ipc_imem->mmio->chip_info_size;
+       if (size > IOSM_CHIP_INFO_SIZE_MAX)
+               goto trigger_chip_info_fail;
+
+       skb = ipc_pcie_alloc_local_skb(ipc_imem->pcie, GFP_ATOMIC, size);
+       if (!skb) {
+               dev_err(ipc_imem->dev, "exhausted skbuf kernel DL memory");
+               rc = -ENOMEM;
+               goto trigger_chip_info_fail;
+       }
+       /* Copy the chip info characters into the ipc_skb. */
+       ipc_mmio_copy_chip_info(ipc_imem->mmio, skb_put(skb, size), size);
+       /* First change to the ROM boot phase. */
+       dev_dbg(ipc_imem->dev, "execution_stage[%X] eq. BOOT", stage);
+       ipc_imem->phase = ipc_imem_phase_update(ipc_imem);
+       ipc_imem_sys_devlink_notify_rx(ipc_imem->ipc_devlink, skb);
+       rc = 0;
+trigger_chip_info_fail:
+       return rc;
+}
+
+int ipc_imem_devlink_trigger_chip_info(struct iosm_imem *ipc_imem)
+{
+       return ipc_task_queue_send_task(ipc_imem,
+                                       ipc_imem_devlink_trigger_chip_info_cb,
+                                       0, NULL, 0, true);
+}
index dc65b07..6be6708 100644 (file)
@@ -69,7 +69,7 @@ struct ipc_chnl_cfg;
 
 #define IMEM_IRQ_DONT_CARE (-1)
 
-#define IPC_MEM_MAX_CHANNELS 7
+#define IPC_MEM_MAX_CHANNELS 8
 
 #define IPC_MEM_MUX_IP_SESSION_ENTRIES 8
 
@@ -98,6 +98,7 @@ struct ipc_chnl_cfg;
 #define IPC_MEM_DL_ETH_OFFSET 16
 
 #define IPC_CB(skb) ((struct ipc_skb_cb *)((skb)->cb))
+#define IOSM_CHIP_INFO_SIZE_MAX 100
 
 #define FULLY_FUNCTIONAL 0
 
@@ -304,9 +305,9 @@ enum ipc_phase {
  * @ipc_port:                  IPC PORT data structure pointer
  * @pcie:                      IPC PCIe
  * @dev:                       Pointer to device structure
- * @flash_channel_id:          Reserved channel id for flashing to RAM.
  * @ipc_requested_state:       Expected IPC state on CP.
  * @channels:                  Channel list with UL/DL pipe pairs.
+ * @ipc_devlink:               IPC Devlink data structure pointer
  * @ipc_status:                        local ipc_status
  * @nr_of_channels:            number of configured channels
  * @startup_timer:             startup timer for NAND support.
@@ -349,9 +350,9 @@ struct iosm_imem {
        struct iosm_cdev *ipc_port[IPC_MEM_MAX_CHANNELS];
        struct iosm_pcie *pcie;
        struct device *dev;
-       int flash_channel_id;
        enum ipc_mem_device_ipc_state ipc_requested_state;
        struct ipc_mem_channel channels[IPC_MEM_MAX_CHANNELS];
+       struct iosm_devlink *ipc_devlink;
        u32 ipc_status;
        u32 nr_of_channels;
        struct hrtimer startup_timer;
@@ -575,4 +576,15 @@ void ipc_imem_ipc_init_check(struct iosm_imem *ipc_imem);
  */
 void ipc_imem_channel_init(struct iosm_imem *ipc_imem, enum ipc_ctype ctype,
                           struct ipc_chnl_cfg chnl_cfg, u32 irq_moderation);
+
+/**
+ * ipc_imem_devlink_trigger_chip_info - Inform devlink that the chip
+ *                                     information are available if the
+ *                                     flashing to RAM interworking shall be
+ *                                     executed.
+ * @ipc_imem:  Pointer to imem structure
+ *
+ * Returns: 0 on success, -1 on failure
+ */
+int ipc_imem_devlink_trigger_chip_info(struct iosm_imem *ipc_imem);
 #endif
index 0a472ce..b885a65 100644 (file)
@@ -6,6 +6,7 @@
 #include <linux/delay.h>
 
 #include "iosm_ipc_chnl_cfg.h"
+#include "iosm_ipc_devlink.h"
 #include "iosm_ipc_imem.h"
 #include "iosm_ipc_imem_ops.h"
 #include "iosm_ipc_port.h"
@@ -331,3 +332,319 @@ int ipc_imem_sys_cdev_write(struct iosm_cdev *ipc_cdev, struct sk_buff *skb)
 out:
        return ret;
 }
+
+/* Open a SIO link to CP and return the channel instance */
+struct ipc_mem_channel *ipc_imem_sys_devlink_open(struct iosm_imem *ipc_imem)
+{
+       struct ipc_mem_channel *channel;
+       enum ipc_phase phase;
+       int channel_id;
+
+       phase = ipc_imem_phase_update(ipc_imem);
+       switch (phase) {
+       case IPC_P_OFF:
+       case IPC_P_ROM:
+               /* Get a channel id as flash id and reserve it. */
+               channel_id = ipc_imem_channel_alloc(ipc_imem,
+                                                   IPC_MEM_CTRL_CHL_ID_7,
+                                                   IPC_CTYPE_CTRL);
+
+               if (channel_id < 0) {
+                       dev_err(ipc_imem->dev,
+                               "reservation of a flash channel id failed");
+                       goto error;
+               }
+
+               ipc_imem->ipc_devlink->devlink_sio.channel_id = channel_id;
+               channel = &ipc_imem->channels[channel_id];
+
+               /* Enqueue chip info data to be read */
+               if (ipc_imem_devlink_trigger_chip_info(ipc_imem)) {
+                       dev_err(ipc_imem->dev, "Enqueue of chip info failed");
+                       channel->state = IMEM_CHANNEL_FREE;
+                       goto error;
+               }
+
+               return channel;
+
+       case IPC_P_PSI:
+       case IPC_P_EBL:
+               ipc_imem->cp_version = ipc_mmio_get_cp_version(ipc_imem->mmio);
+               if (ipc_imem->cp_version == -1) {
+                       dev_err(ipc_imem->dev, "invalid CP version");
+                       goto error;
+               }
+
+               channel_id = ipc_imem->ipc_devlink->devlink_sio.channel_id;
+               return ipc_imem_channel_open(ipc_imem, channel_id,
+                                            IPC_HP_CDEV_OPEN);
+
+       default:
+               /* CP is in the wrong state (e.g. CRASH or CD_READY) */
+               dev_err(ipc_imem->dev, "SIO open refused, phase %d", phase);
+       }
+error:
+       return NULL;
+}
+
+/* Release a SIO channel link to CP. */
+void ipc_imem_sys_devlink_close(struct iosm_devlink *ipc_devlink)
+{
+       struct iosm_imem *ipc_imem = ipc_devlink->pcie->imem;
+       int boot_check_timeout = BOOT_CHECK_DEFAULT_TIMEOUT;
+       enum ipc_mem_exec_stage exec_stage;
+       struct ipc_mem_channel *channel;
+       enum ipc_phase curr_phase;
+       int status = 0;
+       u32 tail = 0;
+
+       channel = ipc_imem->ipc_devlink->devlink_sio.channel;
+       curr_phase = ipc_imem->phase;
+       /* Increase the total wait time to boot_check_timeout */
+       do {
+               exec_stage = ipc_mmio_get_exec_stage(ipc_imem->mmio);
+               if (exec_stage == IPC_MEM_EXEC_STAGE_RUN ||
+                   exec_stage == IPC_MEM_EXEC_STAGE_PSI)
+                       break;
+               msleep(20);
+               boot_check_timeout -= 20;
+       } while (boot_check_timeout > 0);
+
+       /* If there are any pending TDs then wait for Timeout/Completion before
+        * closing pipe.
+        */
+       if (channel->ul_pipe.old_tail != channel->ul_pipe.old_head) {
+               status = wait_for_completion_interruptible_timeout
+                       (&ipc_imem->ul_pend_sem,
+                        msecs_to_jiffies(IPC_PEND_DATA_TIMEOUT));
+               if (status == 0) {
+                       dev_dbg(ipc_imem->dev,
+                               "Data Timeout on UL-Pipe:%d Head:%d Tail:%d",
+                               channel->ul_pipe.pipe_nr,
+                               channel->ul_pipe.old_head,
+                               channel->ul_pipe.old_tail);
+               }
+       }
+
+       ipc_protocol_get_head_tail_index(ipc_imem->ipc_protocol,
+                                        &channel->dl_pipe, NULL, &tail);
+
+       if (tail != channel->dl_pipe.old_tail) {
+               status = wait_for_completion_interruptible_timeout
+                       (&ipc_imem->dl_pend_sem,
+                        msecs_to_jiffies(IPC_PEND_DATA_TIMEOUT));
+               if (status == 0) {
+                       dev_dbg(ipc_imem->dev,
+                               "Data Timeout on DL-Pipe:%d Head:%d Tail:%d",
+                               channel->dl_pipe.pipe_nr,
+                               channel->dl_pipe.old_head,
+                               channel->dl_pipe.old_tail);
+               }
+       }
+
+       /* Due to wait for completion in messages, there is a small window
+        * between closing the pipe and updating the channel is closed. In this
+        * small window there could be HP update from Host Driver. Hence update
+        * the channel state as CLOSING to aviod unnecessary interrupt
+        * towards CP.
+        */
+       channel->state = IMEM_CHANNEL_CLOSING;
+       /* Release the pipe resources */
+       ipc_imem_pipe_cleanup(ipc_imem, &channel->ul_pipe);
+       ipc_imem_pipe_cleanup(ipc_imem, &channel->dl_pipe);
+}
+
+void ipc_imem_sys_devlink_notify_rx(struct iosm_devlink *ipc_devlink,
+                                   struct sk_buff *skb)
+{
+       skb_queue_tail(&ipc_devlink->devlink_sio.rx_list, skb);
+       complete(&ipc_devlink->devlink_sio.read_sem);
+}
+
+/* PSI transfer */
+static int ipc_imem_sys_psi_transfer(struct iosm_imem *ipc_imem,
+                                    struct ipc_mem_channel *channel,
+                                    unsigned char *buf, int count)
+{
+       int psi_start_timeout = PSI_START_DEFAULT_TIMEOUT;
+       enum ipc_mem_exec_stage exec_stage;
+
+       dma_addr_t mapping = 0;
+       int ret;
+
+       ret = ipc_pcie_addr_map(ipc_imem->pcie, buf, count, &mapping,
+                               DMA_TO_DEVICE);
+       if (ret)
+               goto pcie_addr_map_fail;
+
+       /* Save the PSI information for the CP ROM driver on the doorbell
+        * scratchpad.
+        */
+       ipc_mmio_set_psi_addr_and_size(ipc_imem->mmio, mapping, count);
+       ipc_doorbell_fire(ipc_imem->pcie, 0, IPC_MEM_EXEC_STAGE_BOOT);
+
+       ret = wait_for_completion_interruptible_timeout
+               (&channel->ul_sem,
+                msecs_to_jiffies(IPC_PSI_TRANSFER_TIMEOUT));
+
+       if (ret <= 0) {
+               dev_err(ipc_imem->dev, "Failed PSI transfer to CP, Error-%d",
+                       ret);
+               goto psi_transfer_fail;
+       }
+       /* If the PSI download fails, return the CP boot ROM exit code */
+       if (ipc_imem->rom_exit_code != IMEM_ROM_EXIT_OPEN_EXT &&
+           ipc_imem->rom_exit_code != IMEM_ROM_EXIT_CERT_EXT) {
+               ret = (-1) * ((int)ipc_imem->rom_exit_code);
+               goto psi_transfer_fail;
+       }
+
+       dev_dbg(ipc_imem->dev, "PSI image successfully downloaded");
+
+       /* Wait psi_start_timeout milliseconds until the CP PSI image is
+        * running and updates the execution_stage field with
+        * IPC_MEM_EXEC_STAGE_PSI. Verify the execution stage.
+        */
+       do {
+               exec_stage = ipc_mmio_get_exec_stage(ipc_imem->mmio);
+
+               if (exec_stage == IPC_MEM_EXEC_STAGE_PSI)
+                       break;
+
+               msleep(20);
+               psi_start_timeout -= 20;
+       } while (psi_start_timeout > 0);
+
+       if (exec_stage != IPC_MEM_EXEC_STAGE_PSI)
+               goto psi_transfer_fail; /* Unknown status of CP PSI process. */
+
+       ipc_imem->phase = IPC_P_PSI;
+
+       /* Enter the PSI phase. */
+       dev_dbg(ipc_imem->dev, "execution_stage[%X] eq. PSI", exec_stage);
+
+       /* Request the RUNNING state from CP and wait until it was reached
+        * or timeout.
+        */
+       ipc_imem_ipc_init_check(ipc_imem);
+
+       ret = wait_for_completion_interruptible_timeout
+               (&channel->ul_sem, msecs_to_jiffies(IPC_PSI_TRANSFER_TIMEOUT));
+       if (ret <= 0) {
+               dev_err(ipc_imem->dev,
+                       "Failed PSI RUNNING state on CP, Error-%d", ret);
+               goto psi_transfer_fail;
+       }
+
+       if (ipc_mmio_get_ipc_state(ipc_imem->mmio) !=
+                       IPC_MEM_DEVICE_IPC_RUNNING) {
+               dev_err(ipc_imem->dev,
+                       "ch[%d] %s: unexpected CP IPC state %d, not RUNNING",
+                       channel->channel_id,
+                       ipc_imem_phase_get_string(ipc_imem->phase),
+                       ipc_mmio_get_ipc_state(ipc_imem->mmio));
+
+               goto psi_transfer_fail;
+       }
+
+       /* Create the flash channel for the transfer of the images. */
+       if (!ipc_imem_sys_devlink_open(ipc_imem)) {
+               dev_err(ipc_imem->dev, "can't open flash_channel");
+               goto psi_transfer_fail;
+       }
+
+       ret = 0;
+psi_transfer_fail:
+       ipc_pcie_addr_unmap(ipc_imem->pcie, count, mapping, DMA_TO_DEVICE);
+pcie_addr_map_fail:
+       return ret;
+}
+
+int ipc_imem_sys_devlink_write(struct iosm_devlink *ipc_devlink,
+                              unsigned char *buf, int count)
+{
+       struct iosm_imem *ipc_imem = ipc_devlink->pcie->imem;
+       struct ipc_mem_channel *channel;
+       struct sk_buff *skb;
+       dma_addr_t mapping;
+       int ret;
+
+       channel = ipc_imem->ipc_devlink->devlink_sio.channel;
+
+       /* In the ROM phase the PSI image is passed to CP about a specific
+        *  shared memory area and doorbell scratchpad directly.
+        */
+       if (ipc_imem->phase == IPC_P_ROM) {
+               ret = ipc_imem_sys_psi_transfer(ipc_imem, channel, buf, count);
+               /* If the PSI transfer fails then send crash
+                * Signature.
+                */
+               if (ret > 0)
+                       ipc_imem_msg_send_feature_set(ipc_imem,
+                                                     IPC_MEM_INBAND_CRASH_SIG,
+                                                     false);
+               goto out;
+       }
+
+       /* Allocate skb memory for the uplink buffer. */
+       skb = ipc_pcie_alloc_skb(ipc_devlink->pcie, count, GFP_KERNEL, &mapping,
+                                DMA_TO_DEVICE, 0);
+       if (!skb) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       memcpy(skb_put(skb, count), buf, count);
+
+       IPC_CB(skb)->op_type = UL_USR_OP_BLOCKED;
+
+       /* Add skb to the uplink skbuf accumulator. */
+       skb_queue_tail(&channel->ul_list, skb);
+
+       /* Inform the IPC tasklet to pass uplink IP packets to CP. */
+       if (!ipc_imem_call_cdev_write(ipc_imem)) {
+               ret = wait_for_completion_interruptible(&channel->ul_sem);
+
+               if (ret < 0) {
+                       dev_err(ipc_imem->dev,
+                               "ch[%d] no CP confirmation, status = %d",
+                               channel->channel_id, ret);
+                       ipc_pcie_kfree_skb(ipc_devlink->pcie, skb);
+                       goto out;
+               }
+       }
+       ret = 0;
+out:
+       return ret;
+}
+
+int ipc_imem_sys_devlink_read(struct iosm_devlink *devlink, u8 *data,
+                             u32 bytes_to_read, u32 *bytes_read)
+{
+       struct sk_buff *skb = NULL;
+       int rc = 0;
+
+       /* check skb is available in rx_list or wait for skb */
+       devlink->devlink_sio.devlink_read_pend = 1;
+       while (!skb && !(skb = skb_dequeue(&devlink->devlink_sio.rx_list))) {
+               if (!wait_for_completion_interruptible_timeout
+                               (&devlink->devlink_sio.read_sem,
+                                msecs_to_jiffies(IPC_READ_TIMEOUT))) {
+                       dev_err(devlink->dev, "Read timedout");
+                       rc =  -ETIMEDOUT;
+                       goto devlink_read_fail;
+               }
+       }
+       devlink->devlink_sio.devlink_read_pend = 0;
+       if (bytes_to_read < skb->len) {
+               dev_err(devlink->dev, "Invalid size,expected len %d", skb->len);
+               rc = -EINVAL;
+               goto devlink_read_fail;
+       }
+       *bytes_read = skb->len;
+       memcpy(data, skb->data, skb->len);
+
+devlink_read_fail:
+       ipc_pcie_kfree_skb(devlink->pcie, skb);
+       return rc;
+}
index 2007fe2..f0c88ac 100644 (file)
@@ -9,7 +9,7 @@
 #include "iosm_ipc_mux_codec.h"
 
 /* Maximum wait time for blocking read */
-#define IPC_READ_TIMEOUT 500
+#define IPC_READ_TIMEOUT 3000
 
 /* The delay in ms for defering the unregister */
 #define SIO_UNREGISTER_DEFER_DELAY_MS 1
@@ -98,4 +98,51 @@ int ipc_imem_sys_wwan_transmit(struct iosm_imem *ipc_imem, int if_id,
  */
 void ipc_imem_wwan_channel_init(struct iosm_imem *ipc_imem,
                                enum ipc_mux_protocol mux_type);
+
+/**
+ * ipc_imem_sys_devlink_open - Open a Flash/CD Channel link to CP
+ * @ipc_imem:   iosm_imem instance
+ *
+ * Return:     channel instance on success, NULL for failure
+ */
+struct ipc_mem_channel *ipc_imem_sys_devlink_open(struct iosm_imem *ipc_imem);
+
+/**
+ * ipc_imem_sys_devlink_close - Release a Flash/CD channel link to CP
+ * @ipc_devlink:       Pointer to ipc_devlink data-struct
+ *
+ */
+void ipc_imem_sys_devlink_close(struct iosm_devlink *ipc_devlink);
+
+/**
+ * ipc_imem_sys_devlink_notify_rx - Receive downlink characters from CP,
+ *                             the downlink skbuf is added at the end of the
+ *                             downlink or rx list
+ * @ipc_devlink:       Pointer to ipc_devlink data-struct
+ * @skb:               Pointer to sk buffer
+ */
+void ipc_imem_sys_devlink_notify_rx(struct iosm_devlink *ipc_devlink,
+                                   struct sk_buff *skb);
+
+/**
+ * ipc_imem_sys_devlink_read - Copy the rx data and free the skbuf
+ * @ipc_devlink:       Devlink instance
+ * @data:              Buffer to read the data from modem
+ * @bytes_to_read:     Size of destination buffer
+ * @bytes_read:                Number of bytes read
+ *
+ * Return: 0 on success and failure value on error
+ */
+int ipc_imem_sys_devlink_read(struct iosm_devlink *ipc_devlink, u8 *data,
+                             u32 bytes_to_read, u32 *bytes_read);
+
+/**
+ * ipc_imem_sys_devlink_write - Route the uplink buffer to CP
+ * @ipc_devlink:       Devlink_sio instance
+ * @buf:               Pointer to buffer
+ * @count:             Number of data bytes to write
+ * Return:             0 on success and failure value on error
+ */
+int ipc_imem_sys_devlink_write(struct iosm_devlink *ipc_devlink,
+                              unsigned char *buf, int count);
 #endif