// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2019 Samsung Electronics. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "modem_prj.h" #include "modem_utils.h" #include "modem_dump.h" static int ipc_open(struct inode *inode, struct file *filp) { struct io_device *iod = to_io_device(inode->i_cdev); struct modem_shared *msd = iod->msd; struct link_device *ld; int ret; filp->private_data = (void *)iod; atomic_inc(&iod->opened); list_for_each_entry(ld, &msd->link_dev_list, list) { if (IS_CONNECTED(iod, ld) && ld->init_comm) { ret = ld->init_comm(ld, iod); if (ret < 0) { mif_err("%s<->%s: ERR! init_comm fail(%d)\n", iod->name, ld->name, ret); atomic_dec(&iod->opened); return ret; } } } mif_info("%s (opened %d) by %s\n", iod->name, atomic_read(&iod->opened), current->comm); return 0; } static int ipc_release(struct inode *inode, struct file *filp) { struct io_device *iod = (struct io_device *)filp->private_data; struct modem_shared *msd = iod->msd; struct link_device *ld; int i; if (atomic_dec_and_test(&iod->opened)) { skb_queue_purge(&iod->sk_rx_q); /* purge multi_frame queue */ for (i = 0; i < NUM_SIPC_MULTI_FRAME_IDS; i++) skb_queue_purge(&iod->sk_multi_q[i]); } list_for_each_entry(ld, &msd->link_dev_list, list) { if (IS_CONNECTED(iod, ld) && ld->terminate_comm) ld->terminate_comm(ld, iod); } mif_info("%s (opened %d) by %s\n", iod->name, atomic_read(&iod->opened), current->comm); return 0; } static unsigned int ipc_poll(struct file *filp, struct poll_table_struct *wait) { struct io_device *iod = (struct io_device *)filp->private_data; struct modem_ctl *mc; struct sk_buff_head *rxq; struct link_device *ld; if (!iod) return POLLERR; mc = iod->mc; rxq = &iod->sk_rx_q; ld = get_current_link(iod); if (skb_queue_empty(rxq)) poll_wait(filp, &iod->wq, wait); switch (mc->phone_state) { case STATE_BOOTING: case STATE_ONLINE: if (!mc->sim_state.changed) { if (!skb_queue_empty(rxq)) return POLLIN | POLLRDNORM; else /* wq is waken up without rx, return for wait */ return 0; } /* fall through, if sim_state has been changed */ case STATE_CRASH_EXIT: case STATE_CRASH_RESET: case STATE_NV_REBUILDING: case STATE_CRASH_WATCHDOG: mif_err_limited("%s: %s.state == %s\n", iod->name, mc->name, mc_state(mc)); if (iod->format == IPC_FMT) return POLLHUP; /* give delay to prevent infinite sys_poll call from * select() in APP layer without 'sleep' user call takes * almost 100% cpu usage when it is looked up by 'top' * command. */ msleep(20); break; case STATE_OFFLINE: if ((iod->ch == EXYNOS_CH_ID_CPLOG && ld->protocol == PROTOCOL_SIT) || iod->ch == SIPC_CH_ID_CASS) return POLLHUP; default: break; } return 0; } static long ipc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct io_device *iod = (struct io_device *)filp->private_data; struct link_device *ld = get_current_link(iod); struct modem_ctl *mc = iod->mc; enum modem_state p_state; switch (cmd) { case IOCTL_GET_CP_STATUS: mif_debug("%s: IOCTL_GET_CP_STATUS\n", iod->name); p_state = mc->phone_state; if (p_state != STATE_ONLINE) { mif_debug("%s: IOCTL_GET_CP_STATUS (state %s)\n", iod->name, cp_state_str(p_state)); } if (mc->sim_state.changed) { enum modem_state s_state = mc->sim_state.online ? STATE_SIM_ATTACH : STATE_SIM_DETACH; mc->sim_state.changed = false; return s_state; } if (p_state == STATE_NV_REBUILDING) mc->phone_state = STATE_ONLINE; return p_state; case IOCTL_TRIGGER_CP_CRASH: { char *buff = ld->crash_reason.string; void __user *user_buff = (void __user *)arg; switch (ld->protocol) { case PROTOCOL_SIPC: if (arg) ld->crash_reason.type = (u32)arg; mif_err("%s: IOCTL_TRIGGER_CP_CRASH (%lu)\n", iod->name, arg); break; case PROTOCOL_SIT: ld->crash_reason.type = CRASH_REASON_RIL_TRIGGER_CP_CRASH; if (arg) { if (copy_from_user(buff, user_buff, CP_CRASH_INFO_SIZE)) mif_info("No argument from USER\n"); } else mif_info("No argument from USER\n"); mif_info("Crash Reason:%s\n", buff); break; default: mif_err("ERR - unknown protocol\n"); break; } if (!mc->ops.trigger_cp_crash) { mif_err("%s: trigger_cp_crash is null\n", iod->name); return -EINVAL; } return mc->ops.trigger_cp_crash(mc); } #if IS_ENABLED(CONFIG_CPIF_MBIM) case IOCTL_SET_INTERNET_PDN_CID: { if (arg) ld->internet_pdn_cid = arg; mif_info("Internet CID: %lu\n", arg); break; } case IOCTL_SET_MULTIPLE_PDN_INFO: { struct pdn_info pdn_arg; u8 rmnet_type; mif_info("IOCTL_SET_MULTIPLE_PDN_INFO\n"); if (copy_from_user(&pdn_arg, (struct pdn_info __user *)arg, sizeof(struct pdn_info))) { mif_err("copy_from_user fail - IOCTL_SET_MULTIPLE_PDN_INFO\n"); return -EFAULT; } if (pdn_arg.is_internet) ld->internet_pdn_cid = pdn_arg.cid; rmnet_type = pdn_arg.cid - 1; if (rmnet_type < RMNET_COUNT) { if (pdn_arg.is_activated == true) { mif_info("cid: %d, ipv4: %pI4, ipv6: %pI6c\n", pdn_arg.cid, &pdn_arg.ipv4_src_addr, &pdn_arg.ipv6_src_addr); memcpy(&ld->pdn_table.pdn[rmnet_type], &pdn_arg, sizeof(struct pdn_info)); } else { mif_info("Deactivate pdn table (cid: %d)\n", pdn_arg.cid); memset(&ld->pdn_table.pdn[rmnet_type], 0, sizeof(struct pdn_info)); } } else { mif_err("Unknow CID: %d\n", pdn_arg.cid); return -EFAULT; } break; } case IOCTL_SET_IP_PACKET_FILTERS: { struct packet_filter *filter_arg; u8 rmnet_type; mif_info("IOCTL_SET_IP_PACKET_FILTERS\n"); filter_arg = kvzalloc(sizeof(struct packet_filter), GFP_KERNEL); if (!filter_arg) { mif_err("packet_filter kvzalloc fail\n"); return -EFAULT; } if (copy_from_user(filter_arg, (struct packet_filter __user *)arg, sizeof(struct packet_filter))) { mif_err("copy_from_user fail - IOCTL_SET_IP_PACKET_FILTERS\n"); kvfree(filter_arg); return -EFAULT; } rmnet_type = filter_arg->cid - 1; if (rmnet_type < RMNET_COUNT) { if (filter_arg->filters_count > NUMBER_FILTERS) { mif_err("Filters count out of bounds %d", filter_arg->filters_count); kvfree(filter_arg); return -EINVAL; } if (filter_arg->filters_count != 0) { memcpy(&ld->packet_filter_table.rmnet[rmnet_type], filter_arg, sizeof(struct packet_filter)); ld->is_modern_standby = true; } else { memset(&ld->packet_filter_table.rmnet[rmnet_type], 0, sizeof(struct packet_filter)); ld->is_modern_standby = false; } } else { mif_err("Unknow CID: %d\n", filter_arg->cid); kvfree(filter_arg); return -EFAULT; } kvfree(filter_arg); break; } case IOCTL_GET_IP_PACKET_FILTERS: { struct packet_filter *filter_arg; u8 rmnet_type; mif_info("IOCTL_GET_IP_PACKET_FILTERS\n"); filter_arg = kvzalloc(sizeof(struct packet_filter), GFP_KERNEL); if (!filter_arg) { mif_err("packet_filter kvzalloc fail\n"); return -EFAULT; } if (copy_from_user(filter_arg, (struct packet_filter __user *)arg, sizeof(struct packet_filter))) { mif_err("copy_from_user fail - IOCTL_GET_IP_PACKET_FILTERS\n"); kvfree(filter_arg); return -EFAULT; } rmnet_type = filter_arg->cid - 1; if (rmnet_type < RMNET_COUNT) { if (copy_to_user((void __user *)arg, &ld->packet_filter_table.rmnet[rmnet_type], sizeof(struct packet_filter))) { mif_err("copy_to_user fail - IOCTL_GET_IP_PACKET_FILTERS\n"); kvfree(filter_arg); return -EFAULT; } } else { mif_err("Unknow CID: %d\n", filter_arg->cid); kvfree(filter_arg); return -EFAULT; } kvfree(filter_arg); break; } #endif default: /* If you need to handle the ioctl for specific link device, * then assign the link ioctl handler to ld->ioctl * It will be call for specific link ioctl */ if (ld->ioctl) return ld->ioctl(ld, iod, cmd, arg); mif_info("%s: ERR! undefined cmd 0x%X\n", iod->name, cmd); return -EINVAL; } return 0; } #define INIT_END_WAIT_MS 150 static ssize_t ipc_write(struct file *filp, const char __user *data, size_t count, loff_t *fpos) { struct io_device *iod = (struct io_device *)filp->private_data; struct link_device *ld = get_current_link(iod); struct mem_link_device *mld = to_mem_link_device(ld); struct modem_ctl *mc = iod->mc; u8 cfg = 0; u16 cfg_sit = 0; unsigned int headroom = 0; unsigned int copied = 0, tot_frame = 0, copied_frm = 0; /* 64bit prevent */ unsigned int cnt = (unsigned int)count; struct timespec64 ts; int curr_init_end_cnt; int retry = 0; /* Record the timestamp */ ktime_get_ts64(&ts); if (iod->format <= IPC_RFS && iod->ch == 0) return -EINVAL; if (unlikely(!cp_online(mc)) && ld->is_ipc_ch(iod->ch)) { mif_debug("%s: ERR! %s->state == %s\n", iod->name, mc->name, mc_state(mc)); return -EPERM; } if (iod->link_header) { switch (ld->protocol) { case PROTOCOL_SIPC: cfg = sipc5_build_config(iod, ld, cnt); headroom = sipc5_get_hdr_len(&cfg); break; case PROTOCOL_SIT: cfg_sit = exynos_build_fr_config(iod, ld, cnt); headroom = EXYNOS_HEADER_SIZE; break; default: mif_err("protocol error %d\n", ld->protocol); return -EINVAL; } } /* Wait for a while if a new CMD_INIT_END is sent */ while ((curr_init_end_cnt = atomic_read(&mld->init_end_cnt)) != mld->last_init_end_cnt && retry++ < 3) { mif_info_limited("%s: wait for INIT_END done (%dms) cnt:%d last:%d cmd:0x%02X\n", iod->name, INIT_END_WAIT_MS, curr_init_end_cnt, mld->last_init_end_cnt, mld->read_ap2cp_irq(mld)); if (atomic_inc_return(&mld->init_end_busy) > 1) curr_init_end_cnt = -1; msleep(INIT_END_WAIT_MS); if (curr_init_end_cnt >= 0) mld->last_init_end_cnt = curr_init_end_cnt; atomic_dec(&mld->init_end_busy); } if (unlikely(!mld->last_init_end_cnt)) { mif_err_limited("%s: INIT_END is not done\n", iod->name); return -EAGAIN; } while (copied < cnt) { struct sk_buff *skb; char *buff; unsigned int remains = cnt - copied; unsigned int tailroom = 0; unsigned int tx_bytes; unsigned int alloc_size; int ret; switch (ld->protocol) { case PROTOCOL_SIPC: alloc_size = min_t(unsigned int, remains + headroom, iod->max_tx_size ?: remains + headroom); break; case PROTOCOL_SIT: alloc_size = min_t(unsigned int, remains + headroom, SZ_2K); break; default: mif_err("protocol error %d\n", ld->protocol); return -EINVAL; } /* Calculate tailroom for padding size */ if (iod->link_header && ld->aligned) tailroom = ld->calc_padding_size(alloc_size); alloc_size += tailroom; skb = alloc_skb(alloc_size, GFP_KERNEL); if (!skb) { mif_info("%s: ERR! alloc_skb fail (alloc_size:%d)\n", iod->name, alloc_size); return -ENOMEM; } tx_bytes = alloc_size - headroom - tailroom; /* Reserve the space for a link header */ skb_reserve(skb, headroom); /* Copy an IPC message from the user space to the skb */ buff = skb_put(skb, tx_bytes); if (copy_from_user(buff, data + copied, tx_bytes)) { mif_err("%s->%s: ERR! copy_from_user fail(count %lu)\n", iod->name, ld->name, (unsigned long)count); dev_kfree_skb_any(skb); return -EFAULT; } /* Update size of copied payload */ copied += tx_bytes; /* Update size of total frame included hdr, pad size */ tot_frame += alloc_size; /* Store the IO device, the link device, etc. */ skbpriv(skb)->iod = iod; skbpriv(skb)->ld = ld; skbpriv(skb)->lnk_hdr = iod->link_header; skbpriv(skb)->sipc_ch = iod->ch; /* Copy the timestamp to the skb */ skbpriv(skb)->ts = ts; #ifdef DEBUG_MODEM_IF_IODEV_TX mif_pkt(iod->ch, "IOD-TX", skb); #endif /* Build SIPC5 link header*/ if (cfg || cfg_sit) { buff = skb_push(skb, headroom); switch (ld->protocol) { case PROTOCOL_SIPC: sipc5_build_header(iod, buff, cfg, tx_bytes, cnt - copied); break; case PROTOCOL_SIT: exynos_build_header(iod, ld, buff, cfg_sit, 0, tx_bytes); /* modify next link header for multiframe */ if (((cfg_sit >> 8) & EXYNOS_SINGLE_MASK) != EXYNOS_SINGLE_MASK) cfg_sit = modify_next_frame(cfg_sit); break; default: mif_err("protocol error %d\n", ld->protocol); return -EINVAL; } } /* Apply padding */ if (tailroom) skb_put(skb, tailroom); /** * Send the skb with a link device */ ret = ld->send(ld, iod, skb); if (ret < 0) { mif_err("%s->%s: %s->send fail(%d, tx:%d len:%lu)\n", iod->name, mc->name, ld->name, ret, tx_bytes, (unsigned long)count); dev_kfree_skb_any(skb); return ret; } copied_frm += ret; } if (copied_frm != tot_frame) { mif_info("%s->%s: WARN! %s->send ret:%d (len:%lu)\n", iod->name, mc->name, ld->name, copied_frm, (unsigned long)count); } return count; } static ssize_t ipc_read(struct file *filp, char *buf, size_t count, loff_t *fpos) { struct io_device *iod = (struct io_device *)filp->private_data; struct sk_buff_head *rxq = &iod->sk_rx_q; struct sk_buff *skb; int copied; if (skb_queue_empty(rxq)) { long tmo = msecs_to_jiffies(100); wait_event_timeout(iod->wq, !skb_queue_empty(rxq), tmo); } skb = skb_dequeue(rxq); if (unlikely(!skb)) { mif_info("%s: NO data in RXQ\n", iod->name); return 0; } copied = skb->len > count ? count : skb->len; if (copy_to_user(buf, skb->data, copied)) { mif_err("%s: ERR! copy_to_user fail\n", iod->name); dev_kfree_skb_any(skb); return -EFAULT; } if (iod->ch == SIPC_CH_ID_CPLOG1) { struct net_device *ndev = iod->ndev; if (!ndev) { mif_err("%s: ERR! no iod->ndev\n", iod->name); } else { ndev->stats.rx_packets++; ndev->stats.rx_bytes += copied; } } #ifdef DEBUG_MODEM_IF_IODEV_RX mif_pkt(iod->ch, "IOD-RX", skb); #endif mif_debug("%s: data:%d copied:%d qlen:%d\n", iod->name, skb->len, copied, rxq->qlen); if (skb->len > copied) { skb_pull(skb, copied); skb_queue_head(rxq, skb); } else { dev_consume_skb_any(skb); } return copied; } const struct file_operations ipc_io_fops = { .owner = THIS_MODULE, .open = ipc_open, .release = ipc_release, .poll = ipc_poll, .unlocked_ioctl = ipc_ioctl, .compat_ioctl = ipc_ioctl, .write = ipc_write, .read = ipc_read, }; const struct file_operations *get_ipc_io_fops(void) { return &ipc_io_fops; }