/** @brief function for the @b init_comm method in a link_device instance @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance */ static int mem_init_comm(struct link_device *ld, struct io_device *iod) { struct mem_link_device *mld = to_mem_link_device(ld); struct modem_ctl *mc = ld->mc; struct io_device *check_iod; int id = iod->id; int fmt2rfs = (SIPC5_CH_ID_RFS_0 - SIPC5_CH_ID_FMT_0); int rfs2fmt = (SIPC5_CH_ID_FMT_0 - SIPC5_CH_ID_RFS_0); if (atomic_read(&mld->cp_boot_done)) return 0; #ifdef CONFIG_LINK_CONTROL_MSG_IOSM if (mld->iosm) { struct sbd_link_device *sl = &mld->sbd_link_dev; struct sbd_ipc_device *sid = sbd_ch2dev(sl, iod->id); if (atomic_read(&sid->config_done)) { tx_iosm_message(mld, IOSM_A2C_OPEN_CH, (u32 *)&id); return 0; } else { mif_err("%s isn't configured channel\n", iod->name); return -ENODEV; } } #endif switch (id) { case SIPC5_CH_ID_FMT_0 ... SIPC5_CH_ID_FMT_9: check_iod = link_get_iod_with_channel(ld, (id + fmt2rfs)); if (check_iod ? atomic_read(&check_iod->opened) : true) { mif_err("%s: %s->INIT_END->%s\n", ld->name, iod->name, mc->name); send_ipc_irq(mld, cmd2int(CMD_INIT_END)); atomic_set(&mld->cp_boot_done, 1); } else { mif_err("%s is not opened yet\n", check_iod->name); } break; case SIPC5_CH_ID_RFS_0 ... SIPC5_CH_ID_RFS_9: check_iod = link_get_iod_with_channel(ld, (id + rfs2fmt)); if (check_iod) { if (atomic_read(&check_iod->opened)) { mif_err("%s: %s->INIT_END->%s\n", ld->name, iod->name, mc->name); send_ipc_irq(mld, cmd2int(CMD_INIT_END)); atomic_set(&mld->cp_boot_done, 1); } else { mif_err("%s not opened yet\n", check_iod->name); } } break; default: break; } return 0; }
/** @brief function for the @b dump_start method in a link_device instance @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance */ int mem_start_upload(struct link_device *ld, struct io_device *iod) { struct mem_link_device *mld = to_mem_link_device(ld); if (mld->attrs & LINK_ATTR(LINK_ATTR_MEM_DUMP)) sbd_deactivate(&mld->sbd_link_dev); reset_ipc_map(mld); if (mld->attrs & LINK_ATTR(LINK_ATTR_DUMP_ALIGNED)) ld->aligned = true; else ld->aligned = false; if (mld->dpram_magic) { unsigned int magic; set_magic(mld, MEM_DUMP_MAGIC); magic = get_magic(mld); if (magic != MEM_DUMP_MAGIC) { mif_err("%s: ERR! magic 0x%08X != DUMP_MAGIC 0x%08X\n", ld->name, magic, MEM_DUMP_MAGIC); return -EFAULT; } mif_info("%s: magic == 0x%08X\n", ld->name, magic); } return 0; }
/** @brief function for the @b send method in a link_device instance @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance @param skb the pointer to an skb that will be transmitted @retval "> 0" the length of data transmitted if there is NO ERROR @retval "< 0" an error code */ static int mem_send(struct link_device *ld, struct io_device *iod, struct sk_buff *skb) { struct mem_link_device *mld = to_mem_link_device(ld); struct modem_ctl *mc = ld->mc; enum dev_format id = iod->format; u8 ch = iod->id; switch (id) { case IPC_FMT: case IPC_RAW: if (likely(sipc5_ipc_ch(ch))) return xmit_ipc(mld, ch, skb); else return xmit_udl(mld, ch, skb); case IPC_BOOT: case IPC_RAMDUMP: if (sipc5_udl_ch(ch)) return xmit_udl(mld, ch, skb); break; default: break; } mif_err("%s:%s->%s: ERR! Invalid IO device (format:%s id:%d)\n", ld->name, iod->name, mc->name, dev_str(id), ch); return -ENODEV; }
static int mem_start_download(struct link_device *ld, struct io_device *iod) { struct mem_link_device *mld = to_mem_link_device(ld); reset_ipc_map(mld); #ifdef CONFIG_LINK_DEVICE_WITH_SBD_ARCH if (mld->attrs & LINK_ATTR(LINK_ATTR_MEM_BOOT)) sbd_deactivate(&mld->sbd_link_dev); #endif if (mld->attrs & LINK_ATTR(LINK_ATTR_BOOT_ALIGNED)) ld->aligned = true; else ld->aligned = false; if (mld->dpram_magic) { unsigned int magic; set_magic(mld, MEM_BOOT_MAGIC); magic = get_magic(mld); if (magic != MEM_BOOT_MAGIC) { mif_err("%s: ERR! magic 0x%08X != BOOT_MAGIC 0x%08X\n", ld->name, magic, MEM_BOOT_MAGIC); return -EFAULT; } mif_err("%s: magic == 0x%08X\n", ld->name, magic); } return 0; }
static void mem_boot_on(struct link_device *ld, struct io_device *iod) { struct mem_link_device *mld = to_mem_link_device(ld); unsigned long flags; atomic_set(&mld->cp_boot_done, 0); spin_lock_irqsave(&ld->lock, flags); ld->state = LINK_STATE_OFFLINE; spin_unlock_irqrestore(&ld->lock, flags); cancel_tx_timer(mld, &mld->tx_timer); #ifdef CONFIG_LINK_DEVICE_WITH_SBD_ARCH #ifdef CONFIG_LTE_MODEM_XMM7260 sbd_deactivate(&mld->sbd_link_dev); #endif cancel_tx_timer(mld, &mld->sbd_tx_timer); if (mld->iosm) { memset(mld->base + CMD_RGN_OFFSET, 0, CMD_RGN_SIZE); mif_info("Control message region has been initialized\n"); } #endif purge_txq(mld); }
static void mem_terminate_comm(struct link_device *ld, struct io_device *iod) { struct mem_link_device *mld = to_mem_link_device(ld); if (mld->iosm) tx_iosm_message(mld, IOSM_A2C_CLOSE_CH, (u32 *)&iod->id); }
/** @brief function for the @b force_dump method in a link_device instance @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance */ static int mem_force_dump(struct link_device *ld, struct io_device *iod) { struct mem_link_device *mld = to_mem_link_device(ld); mif_err("+++\n"); mem_forced_cp_crash(mld); mif_err("---\n"); return 0; }
/** @brief function for the @b terminate_comm method in a link_device instance @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance */ static void mem_terminate_comm(struct link_device *ld, struct io_device *iod) { #ifdef CONFIG_LINK_CONTROL_MSG_IOSM struct mem_link_device *mld = to_mem_link_device(ld); if (mld->iosm) tx_iosm_message(mld, IOSM_A2C_CLOSE_CH, (u32 *)&iod->id); #endif }
static void link_to_demux_work(struct work_struct *ws) { struct link_device *ld; struct mem_link_device *mld; ld = container_of(ws, struct link_device, rx_delayed_work.work); mld = to_mem_link_device(ld); link_to_demux(mld); }
/** @brief function for the @b stop method in a link_device instance @param ld the pointer to a link_device instance @remark It must be invoked after mc->state has already been changed to not STATE_ONLINE. */ static void mem_close_tx(struct link_device *ld) { struct mem_link_device *mld = to_mem_link_device(ld); unsigned long flags; spin_lock_irqsave(&ld->lock, flags); ld->state = LINK_STATE_OFFLINE; spin_unlock_irqrestore(&ld->lock, flags); stop_tx(mld); }
/** @brief function for the @b dump_start method in a link_device instance @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance */ static int mem_start_upload(struct link_device *ld, struct io_device *iod) { struct mem_link_device *mld = to_mem_link_device(ld); reset_ipc_map(mld); reset_ipc_devices(mld); mif_err("%s: magic = 0x%08X\n", ld->name, SHM_DUMP_MAGIC); set_magic(mld, SHM_DUMP_MAGIC); return 0; }
/** @brief function for the @b xmit_boot method in a link_device instance Copies a CP bootloader binary in a user space to the BOOT region for CP @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance @param arg the pointer to a modem_firmware instance @retval "= 0" if NO error @retval "< 0" an error code */ static int mem_xmit_boot(struct link_device *ld, struct io_device *iod, unsigned long arg) { struct mem_link_device *mld = to_mem_link_device(ld); struct modem_ctl *mc = iod->mc; void __iomem *dst; void __user *src; int err; struct modem_firmware mf; atomic_set(&mld->cp_boot_done, 0); /** * CP must be stopped before AP writes CP bootloader. */ if (cp_online(mc) || cp_booting(mc)) mc->pmu->stop(); /** * Get the information about the boot image */ memset(&mf, 0, sizeof(struct modem_firmware)); err = copy_from_user(&mf, (const void __user *)arg, sizeof(mf)); if (err) { mif_err("%s: ERR! INFO copy_from_user fail\n", ld->name); return -EFAULT; } /** * Check the size of the boot image */ if (mf.size > mld->boot_size) { mif_err("%s: ERR! Invalid BOOT size %d\n", ld->name, mf.size); return -EINVAL; } mif_err("%s: BOOT size = %d bytes\n", ld->name, mf.size); /** * Copy the boot image to the BOOT region */ memset(mld->boot_base, 0, mld->boot_size); dst = (void __iomem *)mld->boot_base; src = (void __user *)mf.binary; err = copy_from_user(dst, src, mf.size); if (err) { mif_err("%s: ERR! BOOT copy_from_user fail\n", ld->name); return err; } return 0; }
static int mem_xmit_boot(struct link_device *ld, struct io_device *iod, unsigned long arg) { struct mem_link_device *mld = to_mem_link_device(ld); void __iomem *dst; void __user *src; int err; struct modem_firmware mf; void __iomem *v_base; unsigned valid_space; /** * Get the information about the boot image */ memset(&mf, 0, sizeof(struct modem_firmware)); err = copy_from_user(&mf, (const void __user *)arg, sizeof(mf)); if (err) { mif_err("%s: ERR! INFO copy_from_user fail\n", ld->name); return -EFAULT; } /* Calculate size of valid space which BL will download */ valid_space = (mf.mode) ? (mld->shm_size - mld->boot_size - mld->size) : mld->boot_size; /* Calculate base address (0: BOOT_MODE, 1: DUMP_MODE) */ v_base = (mf.mode) ? (mld->base + mld->size) : mld->boot_base; /** * Check the size of the boot image * fix the integer overflow of "mf.m_offset + mf.len" from Jose Duart */ if (mf.size > valid_space || mf.len > valid_space || mf.m_offset > valid_space - mf.len) { mif_err("%s: ERR! Invalid args: size %x, offset %x, len %x\n", ld->name, mf.size, mf.m_offset, mf.len); return -EINVAL; } dst = (void __iomem *)(v_base + mf.m_offset); src = (void __user *)mf.binary; err = copy_from_user(dst, src, mf.len); if (err) { mif_err("%s: ERR! BOOT copy_from_user fail\n", ld->name); return err; } return 0; }
/** @brief function for the @b firm_update method in a link_device instance Updates download information for each CP binary image by copying download information for a CP binary image from a user space to a local buffer in a mem_link_device instance. @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance @param arg the pointer to a std_dload_info instance */ static int mem_update_firm_info(struct link_device *ld, struct io_device *iod, unsigned long arg) { struct mem_link_device *mld = to_mem_link_device(ld); int ret; ret = copy_from_user(&mld->img_info, (void __user *)arg, sizeof(struct std_dload_info)); if (ret) { mif_err("ERR! copy_from_user fail!\n"); return -EFAULT; } return 0; }
static void mem_close_tx(struct link_device *ld) { struct mem_link_device *mld = to_mem_link_device(ld); struct modem_ctl *mc = ld->mc; unsigned long flags; spin_lock_irqsave(&ld->lock, flags); ld->state = LINK_STATE_OFFLINE; spin_unlock_irqrestore(&ld->lock, flags); if (timer_pending(&mc->crash_ack_timer)) del_timer(&mc->crash_ack_timer); stop_tx(mld); purge_txq(mld); }
/** @brief function for the @b init_comm method in a link_device instance @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance */ static int mem_init_comm(struct link_device *ld, struct io_device *iod) { struct mem_link_device *mld = to_mem_link_device(ld); struct modem_ctl *mc = ld->mc; struct io_device *check_iod; int id = iod->id; int fmt2rfs = (SIPC5_CH_ID_RFS_0 - SIPC5_CH_ID_FMT_0); int rfs2fmt = (SIPC5_CH_ID_FMT_0 - SIPC5_CH_ID_RFS_0); if (atomic_read(&mld->cp_boot_done)) return 0; switch (id) { case SIPC5_CH_ID_FMT_0 ... SIPC5_CH_ID_FMT_9: check_iod = link_get_iod_with_channel(ld, (id + fmt2rfs)); if (check_iod ? atomic_read(&check_iod->opened) : true) { mif_err("%s: %s->%s: Send 0xC2 (INIT_END)\n", ld->name, iod->name, mc->name); send_ipc_irq(mld, cmd2int(CMD_INIT_END)); atomic_set(&mld->cp_boot_done, 1); } else { mif_err("%s is not opened yet\n", check_iod->name); } break; case SIPC5_CH_ID_RFS_0 ... SIPC5_CH_ID_RFS_9: check_iod = link_get_iod_with_channel(ld, (id + rfs2fmt)); if (check_iod) { if (atomic_read(&check_iod->opened)) { mif_err("%s: %s->%s: Send 0xC2 (INIT_END)\n", ld->name, iod->name, mc->name); send_ipc_irq(mld, cmd2int(CMD_INIT_END)); atomic_set(&mld->cp_boot_done, 1); } else { mif_err("%s not opened yet\n", check_iod->name); } } break; default: break; } return 0; }
/** @brief function for the @b xmit_boot method in a link_device instance Copies a CP bootloader binary in a user space to the BOOT region for CP @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance @param arg the pointer to a modem_firmware instance @retval "= 0" if NO error @retval "< 0" an error code */ int mem_xmit_boot(struct link_device *ld, struct io_device *iod, unsigned long arg) { struct mem_link_device *mld = to_mem_link_device(ld); void __iomem *dst; void __user *src; int err; struct modem_firmware mf; /** * Get the information about the boot image */ memset(&mf, 0, sizeof(struct modem_firmware)); err = copy_from_user(&mf, (const void __user *)arg, sizeof(mf)); if (err) { mif_err("%s: ERR! INFO copy_from_user fail\n", ld->name); return -EFAULT; } /** * Check the size of the boot image */ if (mf.size > mld->boot_size) { mif_err("%s: ERR! Invalid BOOT size %d\n", ld->name, mf.size); return -EINVAL; } mif_info("%s: BOOT size = %d bytes\n", ld->name, mf.size); /** * Copy the boot image to the BOOT region */ memset(mld->boot_base, 0, mld->boot_size); dst = (void __iomem *)mld->boot_base; src = (void __user *)mf.binary; err = copy_from_user(dst, src, mf.size); if (err) { mif_err("%s: ERR! BOOT copy_from_user fail\n", ld->name); return err; } return 0; }
/** @brief function for the @b send method in a link_device instance @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance @param skb the pointer to an skb that will be transmitted @retval "> 0" the length of data transmitted if there is NO ERROR @retval "< 0" an error code */ static int mem_send(struct link_device *ld, struct io_device *iod, struct sk_buff *skb) { struct mem_link_device *mld = to_mem_link_device(ld); struct modem_ctl *mc = ld->mc; enum dev_format id = iod->format; u8 ch = iod->id; switch (id) { case IPC_FMT: case IPC_RAW: case IPC_RFS: if (likely(sipc5_ipc_ch(ch))) { if (unlikely(!ipc_active(mld))) return -EIO; if (iod->sbd_ipc) { if (likely(sbd_active(&mld->sbd_link_dev))) return xmit_ipc_to_rb(mld, ch, skb); else return -ENODEV; } else { BUG_ON(1); } } else { return xmit_udl(mld, iod, ch, skb); } break; case IPC_BOOT: case IPC_DUMP: if (sipc5_udl_ch(ch)) return xmit_udl(mld, iod, ch, skb); break; default: break; } mif_err("%s:%s->%s: ERR! Invalid IO device (format:%s id:%d)\n", ld->name, iod->name, mc->name, dev_str(id), ch); return -ENODEV; }
/** @brief function for the @b dload_start method in a link_device instance Set all flags and environments for CP binary download @param ld the pointer to a link_device instance @param iod the pointer to an io_device instance */ static int mem_start_download(struct link_device *ld, struct io_device *iod) { struct mem_link_device *mld = to_mem_link_device(ld); unsigned int magic; atomic_set(&mld->cp_boot_done, 0); reset_ipc_map(mld); reset_ipc_devices(mld); set_magic(mld, SHM_BOOT_MAGIC); magic = get_magic(mld); if (magic != SHM_BOOT_MAGIC) { mif_err("%s: ERR! magic 0x%08X != SHM_BOOT_MAGIC 0x%08X\n", ld->name, magic, SHM_BOOT_MAGIC); return -EFAULT; } return 0; }
static int mem_rx_setup(struct link_device *ld) { struct mem_link_device *mld = to_mem_link_device(ld); if (!zalloc_cpumask_var(&mld->dmask, GFP_KERNEL)) return -ENOMEM; if (!zalloc_cpumask_var(&mld->imask, GFP_KERNEL)) return -ENOMEM; if (!zalloc_cpumask_var(&mld->tmask, GFP_KERNEL)) return -ENOMEM; #ifdef CONFIG_ARGOS /* Below hard-coded mask values should be removed later on. * Like net-sysfs, argos module also should support sysfs knob, * so that user layer must be able to control these cpu mask. */ #ifdef CONFIG_SCHED_HMP cpumask_copy(mld->dmask, &hmp_slow_cpu_mask); #endif cpumask_or(mld->imask, mld->imask, cpumask_of(3)); argos_irq_affinity_setup_label(217, "IPC", mld->imask, mld->dmask); #endif ld->tx_wq = create_singlethread_workqueue("mem_tx_work"); if (!ld->tx_wq) { mif_err("%s: ERR! fail to create tx_wq\n", ld->name); return -ENOMEM; } ld->rx_wq = alloc_workqueue( "mem_rx_work", WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1); if (!ld->rx_wq) { mif_err("%s: ERR! fail to create rx_wq\n", ld->name); return -ENOMEM; } INIT_DELAYED_WORK(&ld->rx_delayed_work, link_to_demux_work); return 0; }
/** @brief pass socket buffers in every skb_rxq to the DEMUX layer @param ws the pointer to a work_struct instance @see schedule_link_to_demux() @see rx_frames_from_dev() @see mem_create_link_device() */ static void link_to_demux_work(struct work_struct *ws) { struct link_device *ld; struct mem_link_device *mld; int i; ld = container_of(ws, struct link_device, rx_delayed_work.work); mld = to_mem_link_device(ld); for (i = IPC_FMT; i < MAX_SIPC5_DEV; i++) { struct mem_ipc_device *dev = mld->dev[i]; struct sk_buff_head *skb_rxq = dev->skb_rxq; while (1) { struct sk_buff *skb; skb = skb_dequeue(skb_rxq); if (!skb) break; pass_skb_to_demux(mld, skb); } } }