Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

wwan: core: Support slicing in port TX flow of WWAN subsystem

wwan_port_fops_write inputs the SKB parameter to the TX callback of
the WWAN device driver. However, the WWAN device (e.g., t7xx) may
have an MTU less than the size of SKB, causing the TX buffer to be
sliced and copied once more in the WWAN device driver.

This patch implements the slicing in the WWAN subsystem and gives
the WWAN devices driver the option to slice(by frag_len) or not. By
doing so, the additional memory copy is reduced.

Meanwhile, this patch gives WWAN devices driver the option to reserve
headroom in fragments for the device-specific metadata.

Signed-off-by: haozhe chang <haozhe.chang@mediatek.com>
Reviewed-by: Loic Poulain <loic.poulain@linaro.org>
Link: https://lore.kernel.org/r/20230316095826.181904-1-haozhe.chang@mediatek.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

haozhe chang and committed by
Jakub Kicinski
36bd28c1 ed0578a4

+81 -36
+2 -1
drivers/net/wwan/iosm/iosm_ipc_port.c
··· 63 63 ipc_port->ipc_imem = ipc_imem; 64 64 65 65 ipc_port->iosm_port = wwan_create_port(ipc_port->dev, port_type, 66 - &ipc_wwan_ctrl_ops, ipc_port); 66 + &ipc_wwan_ctrl_ops, NULL, 67 + ipc_port); 67 68 68 69 return ipc_port; 69 70 }
+1 -1
drivers/net/wwan/mhi_wwan_ctrl.c
··· 237 237 238 238 /* Register as a wwan port, id->driver_data contains wwan port type */ 239 239 port = wwan_create_port(&cntrl->mhi_dev->dev, id->driver_data, 240 - &wwan_pops, mhiwwan); 240 + &wwan_pops, NULL, mhiwwan); 241 241 if (IS_ERR(port)) { 242 242 kfree(mhiwwan); 243 243 return PTR_ERR(port);
+1 -1
drivers/net/wwan/rpmsg_wwan_ctrl.c
··· 129 129 130 130 /* Register as a wwan port, id.driver_data contains wwan port type */ 131 131 port = wwan_create_port(parent, rpdev->id.driver_data, 132 - &rpmsg_wwan_pops, rpwwan); 132 + &rpmsg_wwan_pops, NULL, rpwwan); 133 133 if (IS_ERR(port)) 134 134 return PTR_ERR(port); 135 135
+19 -17
drivers/net/wwan/t7xx/t7xx_port_wwan.c
··· 54 54 static int t7xx_port_ctrl_tx(struct wwan_port *port, struct sk_buff *skb) 55 55 { 56 56 struct t7xx_port *port_private = wwan_port_get_drvdata(port); 57 - size_t len, offset, chunk_len = 0, txq_mtu = CLDMA_MTU; 58 57 const struct t7xx_port_conf *port_conf; 58 + struct sk_buff *cur = skb, *cloned; 59 59 struct t7xx_fsm_ctl *ctl; 60 60 enum md_state md_state; 61 + int cnt = 0, ret; 61 62 62 - len = skb->len; 63 - if (!len || !port_private->chan_enable) 63 + if (!port_private->chan_enable) 64 64 return -EINVAL; 65 65 66 66 port_conf = port_private->port_conf; ··· 72 72 return -ENODEV; 73 73 } 74 74 75 - for (offset = 0; offset < len; offset += chunk_len) { 76 - struct sk_buff *skb_ccci; 77 - int ret; 78 - 79 - chunk_len = min(len - offset, txq_mtu - sizeof(struct ccci_header)); 80 - skb_ccci = t7xx_port_alloc_skb(chunk_len); 81 - if (!skb_ccci) 82 - return -ENOMEM; 83 - 84 - skb_put_data(skb_ccci, skb->data + offset, chunk_len); 85 - ret = t7xx_port_send_skb(port_private, skb_ccci, 0, 0); 75 + while (cur) { 76 + cloned = skb_clone(cur, GFP_KERNEL); 77 + cloned->len = skb_headlen(cur); 78 + ret = t7xx_port_send_skb(port_private, cloned, 0, 0); 86 79 if (ret) { 87 - dev_kfree_skb_any(skb_ccci); 80 + dev_kfree_skb(cloned); 88 81 dev_err(port_private->dev, "Write error on %s port, %d\n", 89 82 port_conf->name, ret); 90 - return ret; 83 + return cnt ? cnt + ret : ret; 91 84 } 85 + cnt += cur->len; 86 + if (cur == skb) 87 + cur = skb_shinfo(skb)->frag_list; 88 + else 89 + cur = cur->next; 92 90 } 93 91 94 92 dev_kfree_skb(skb); ··· 152 154 static void t7xx_port_wwan_md_state_notify(struct t7xx_port *port, unsigned int state) 153 155 { 154 156 const struct t7xx_port_conf *port_conf = port->port_conf; 157 + unsigned int header_len = sizeof(struct ccci_header); 158 + struct wwan_port_caps caps; 155 159 156 160 if (state != MD_STATE_READY) 157 161 return; 158 162 159 163 if (!port->wwan.wwan_port) { 164 + caps.frag_len = CLDMA_MTU - header_len; 165 + caps.headroom_len = header_len; 160 166 port->wwan.wwan_port = wwan_create_port(port->dev, port_conf->port_type, 161 - &wwan_ops, port); 167 + &wwan_ops, &caps, port); 162 168 if (IS_ERR(port->wwan.wwan_port)) 163 169 dev_err(port->dev, "Unable to create WWWAN port %s", port_conf->name); 164 170 }
+44 -14
drivers/net/wwan/wwan_core.c
··· 67 67 * @rxq: Buffer inbound queue 68 68 * @waitqueue: The waitqueue for port fops (read/write/poll) 69 69 * @data_lock: Port specific data access serialization 70 + * @headroom_len: SKB reserved headroom size 71 + * @frag_len: Length to fragment packet 70 72 * @at_data: AT port specific data 71 73 */ 72 74 struct wwan_port { ··· 81 79 struct sk_buff_head rxq; 82 80 wait_queue_head_t waitqueue; 83 81 struct mutex data_lock; /* Port specific data access serialization */ 82 + size_t headroom_len; 83 + size_t frag_len; 84 84 union { 85 85 struct { 86 86 struct ktermios termios; ··· 430 426 struct wwan_port *wwan_create_port(struct device *parent, 431 427 enum wwan_port_type type, 432 428 const struct wwan_port_ops *ops, 429 + struct wwan_port_caps *caps, 433 430 void *drvdata) 434 431 { 435 432 struct wwan_device *wwandev; ··· 464 459 465 460 port->type = type; 466 461 port->ops = ops; 462 + port->frag_len = caps ? caps->frag_len : SIZE_MAX; 463 + port->headroom_len = caps ? caps->headroom_len : 0; 467 464 mutex_init(&port->ops_lock); 468 465 skb_queue_head_init(&port->rxq); 469 466 init_waitqueue_head(&port->waitqueue); ··· 709 702 static ssize_t wwan_port_fops_write(struct file *filp, const char __user *buf, 710 703 size_t count, loff_t *offp) 711 704 { 705 + struct sk_buff *skb, *head = NULL, *tail = NULL; 712 706 struct wwan_port *port = filp->private_data; 713 - struct sk_buff *skb; 707 + size_t frag_len, remain = count; 714 708 int ret; 715 709 716 710 ret = wwan_wait_tx(port, !!(filp->f_flags & O_NONBLOCK)); 717 711 if (ret) 718 712 return ret; 719 713 720 - skb = alloc_skb(count, GFP_KERNEL); 721 - if (!skb) 722 - return -ENOMEM; 714 + do { 715 + frag_len = min(remain, port->frag_len); 716 + skb = alloc_skb(frag_len + port->headroom_len, GFP_KERNEL); 717 + if (!skb) { 718 + ret = -ENOMEM; 719 + goto freeskb; 720 + } 721 + skb_reserve(skb, port->headroom_len); 723 722 724 - if (copy_from_user(skb_put(skb, count), buf, count)) { 725 - kfree_skb(skb); 726 - return -EFAULT; 727 - } 723 + if (!head) { 724 + head = skb; 725 + } else if (!tail) { 726 + skb_shinfo(head)->frag_list = skb; 727 + tail = skb; 728 + } else { 729 + tail->next = skb; 730 + tail = skb; 731 + } 728 732 729 - ret = wwan_port_op_tx(port, skb, !!(filp->f_flags & O_NONBLOCK)); 730 - if (ret) { 731 - kfree_skb(skb); 732 - return ret; 733 - } 733 + if (copy_from_user(skb_put(skb, frag_len), buf + count - remain, frag_len)) { 734 + ret = -EFAULT; 735 + goto freeskb; 736 + } 734 737 735 - return count; 738 + if (skb != head) { 739 + head->data_len += skb->len; 740 + head->len += skb->len; 741 + head->truesize += skb->truesize; 742 + } 743 + } while (remain -= frag_len); 744 + 745 + ret = wwan_port_op_tx(port, head, !!(filp->f_flags & O_NONBLOCK)); 746 + if (!ret) 747 + return count; 748 + 749 + freeskb: 750 + kfree_skb(head); 751 + return ret; 736 752 } 737 753 738 754 static __poll_t wwan_port_fops_poll(struct file *filp, poll_table *wait)
+1 -1
drivers/net/wwan/wwan_hwsim.c
··· 205 205 206 206 port->wwan = wwan_create_port(&dev->dev, WWAN_PORT_AT, 207 207 &wwan_hwsim_port_ops, 208 - port); 208 + NULL, port); 209 209 if (IS_ERR(port->wwan)) { 210 210 err = PTR_ERR(port->wwan); 211 211 goto err_free_port;
+2 -1
drivers/usb/class/cdc-wdm.c
··· 929 929 return; 930 930 } 931 931 932 - port = wwan_create_port(&intf->dev, desc->wwanp_type, &wdm_wwan_port_ops, desc); 932 + port = wwan_create_port(&intf->dev, desc->wwanp_type, &wdm_wwan_port_ops, 933 + NULL, desc); 933 934 if (IS_ERR(port)) { 934 935 dev_err(&intf->dev, "%s: Unable to create WWAN port\n", 935 936 dev_name(intf->usb_dev));
+11
include/linux/wwan.h
··· 64 64 poll_table *wait); 65 65 }; 66 66 67 + /** struct wwan_port_caps - The WWAN port capbilities 68 + * @frag_len: WWAN port TX fragments length 69 + * @headroom_len: WWAN port TX fragments reserved headroom length 70 + */ 71 + struct wwan_port_caps { 72 + size_t frag_len; 73 + unsigned int headroom_len; 74 + }; 75 + 67 76 /** 68 77 * wwan_create_port - Add a new WWAN port 69 78 * @parent: Device to use as parent and shared by all WWAN ports 70 79 * @type: WWAN port type 71 80 * @ops: WWAN port operations 81 + * @caps: WWAN port capabilities 72 82 * @drvdata: Pointer to caller driver data 73 83 * 74 84 * Allocate and register a new WWAN port. The port will be automatically exposed ··· 96 86 struct wwan_port *wwan_create_port(struct device *parent, 97 87 enum wwan_port_type type, 98 88 const struct wwan_port_ops *ops, 89 + struct wwan_port_caps *caps, 99 90 void *drvdata); 100 91 101 92 /**