[PATCH v4] wwan: core: Support slicing in port TX flow of WWAN subsystem

Loic Poulain loic.poulain at linaro.org
Wed Nov 23 13:36:20 PST 2022


On Tue, 22 Nov 2022 at 09:25, <haozhe.chang at mediatek.com> wrote:
>
> From: haozhe chang <haozhe.chang at mediatek.com>
>
> 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 at mediatek.com>
>
> ---
> Changes in v2
>   -send fragments to device driver by skb frag_list.
>
> Changes in v3
>   -move frag_len and headroom_len setting to wwan_create_port.
>
> Changes in v4
>   -change unreadable parameters to macro definition.
> ---
>  drivers/net/wwan/iosm/iosm_ipc_port.c  |  3 +-
>  drivers/net/wwan/mhi_wwan_ctrl.c       |  3 +-
>  drivers/net/wwan/rpmsg_wwan_ctrl.c     |  3 +-
>  drivers/net/wwan/t7xx/t7xx_port_wwan.c | 34 +++++++--------
>  drivers/net/wwan/wwan_core.c           | 59 ++++++++++++++++++++------
>  drivers/net/wwan/wwan_hwsim.c          |  1 +
>  drivers/usb/class/cdc-wdm.c            |  3 +-
>  include/linux/wwan.h                   | 15 +++++++
>  8 files changed, 86 insertions(+), 35 deletions(-)
>
> diff --git a/drivers/net/wwan/iosm/iosm_ipc_port.c b/drivers/net/wwan/iosm/iosm_ipc_port.c
> index b6d81c627277..7798348f61d0 100644
> --- a/drivers/net/wwan/iosm/iosm_ipc_port.c
> +++ b/drivers/net/wwan/iosm/iosm_ipc_port.c
> @@ -63,7 +63,8 @@ struct iosm_cdev *ipc_port_init(struct iosm_imem *ipc_imem,
>         ipc_port->ipc_imem = ipc_imem;
>
>         ipc_port->iosm_port = wwan_create_port(ipc_port->dev, port_type,
> -                                              &ipc_wwan_ctrl_ops, ipc_port);
> +                                              &ipc_wwan_ctrl_ops, WWAN_NO_FRAGMENT,
> +                                              WWAN_NO_HEADROOM, ipc_port);
>
>         return ipc_port;
>  }
> diff --git a/drivers/net/wwan/mhi_wwan_ctrl.c b/drivers/net/wwan/mhi_wwan_ctrl.c
> index f7ca52353f40..c397aa53db5d 100644
> --- a/drivers/net/wwan/mhi_wwan_ctrl.c
> +++ b/drivers/net/wwan/mhi_wwan_ctrl.c
> @@ -237,7 +237,8 @@ static int mhi_wwan_ctrl_probe(struct mhi_device *mhi_dev,
>
>         /* Register as a wwan port, id->driver_data contains wwan port type */
>         port = wwan_create_port(&cntrl->mhi_dev->dev, id->driver_data,
> -                               &wwan_pops, mhiwwan);
> +                               &wwan_pops, WWAN_NO_FRAGMENT, WWAN_NO_HEADROOM,
> +                               mhiwwan);
>         if (IS_ERR(port)) {
>                 kfree(mhiwwan);
>                 return PTR_ERR(port);
> diff --git a/drivers/net/wwan/rpmsg_wwan_ctrl.c b/drivers/net/wwan/rpmsg_wwan_ctrl.c
> index 31c24420ab2e..fc6c228b7e1c 100644
> --- a/drivers/net/wwan/rpmsg_wwan_ctrl.c
> +++ b/drivers/net/wwan/rpmsg_wwan_ctrl.c
> @@ -129,7 +129,8 @@ static int rpmsg_wwan_ctrl_probe(struct rpmsg_device *rpdev)
>
>         /* Register as a wwan port, id.driver_data contains wwan port type */
>         port = wwan_create_port(parent, rpdev->id.driver_data,
> -                               &rpmsg_wwan_pops, rpwwan);
> +                               &rpmsg_wwan_pops, WWAN_NO_FRAGMENT,
> +                               WWAN_NO_HEADROOM, rpwwan);
>         if (IS_ERR(port))
>                 return PTR_ERR(port);
>
> diff --git a/drivers/net/wwan/t7xx/t7xx_port_wwan.c b/drivers/net/wwan/t7xx/t7xx_port_wwan.c
> index 33931bfd78fd..b75bb272f861 100644
> --- a/drivers/net/wwan/t7xx/t7xx_port_wwan.c
> +++ b/drivers/net/wwan/t7xx/t7xx_port_wwan.c
> @@ -54,13 +54,13 @@ static void t7xx_port_ctrl_stop(struct wwan_port *port)
>  static int t7xx_port_ctrl_tx(struct wwan_port *port, struct sk_buff *skb)
>  {
>         struct t7xx_port *port_private = wwan_port_get_drvdata(port);
> -       size_t len, offset, chunk_len = 0, txq_mtu = CLDMA_MTU;
>         const struct t7xx_port_conf *port_conf;
> +       struct sk_buff *cur = skb, *cloned;
>         struct t7xx_fsm_ctl *ctl;
>         enum md_state md_state;
> +       int cnt = 0, ret;
>
> -       len = skb->len;
> -       if (!len || !port_private->chan_enable)
> +       if (!port_private->chan_enable)
>                 return -EINVAL;
>
>         port_conf = port_private->port_conf;
> @@ -72,23 +72,21 @@ static int t7xx_port_ctrl_tx(struct wwan_port *port, struct sk_buff *skb)
>                 return -ENODEV;
>         }
>
> -       for (offset = 0; offset < len; offset += chunk_len) {
> -               struct sk_buff *skb_ccci;
> -               int ret;
> -
> -               chunk_len = min(len - offset, txq_mtu - sizeof(struct ccci_header));
> -               skb_ccci = t7xx_port_alloc_skb(chunk_len);
> -               if (!skb_ccci)
> -                       return -ENOMEM;
> -
> -               skb_put_data(skb_ccci, skb->data + offset, chunk_len);
> -               ret = t7xx_port_send_skb(port_private, skb_ccci, 0, 0);
> +       while (cur) {
> +               cloned = skb_clone(cur, GFP_KERNEL);
> +               cloned->len = skb_headlen(cur);
> +               ret = t7xx_port_send_skb(port_private, cloned, 0, 0);
>                 if (ret) {
> -                       dev_kfree_skb_any(skb_ccci);
> +                       dev_kfree_skb(cloned);
>                         dev_err(port_private->dev, "Write error on %s port, %d\n",
>                                 port_conf->name, ret);
> -                       return ret;
> +                       return cnt ? cnt + ret : ret;
>                 }
> +               cnt += cur->len;
> +               if (cur == skb)
> +                       cur = skb_shinfo(skb)->frag_list;
> +               else
> +                       cur = cur->next;
>         }
>
>         dev_kfree_skb(skb);
> @@ -154,13 +152,15 @@ static int t7xx_port_wwan_disable_chl(struct t7xx_port *port)
>  static void t7xx_port_wwan_md_state_notify(struct t7xx_port *port, unsigned int state)
>  {
>         const struct t7xx_port_conf *port_conf = port->port_conf;
> +       unsigned int header_len = sizeof(struct ccci_header);
>
>         if (state != MD_STATE_READY)
>                 return;
>
>         if (!port->wwan_port) {
>                 port->wwan_port = wwan_create_port(port->dev, port_conf->port_type,
> -                                                  &wwan_ops, port);
> +                                                  &wwan_ops, CLDMA_MTU - header_len,
> +                                                  header_len, port);
>                 if (IS_ERR(port->wwan_port))
>                         dev_err(port->dev, "Unable to create WWWAN port %s", port_conf->name);
>         }
> diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c
> index 62e9f7d6c9fe..8d35513bcd4c 100644
> --- a/drivers/net/wwan/wwan_core.c
> +++ b/drivers/net/wwan/wwan_core.c
> @@ -67,6 +67,8 @@ struct wwan_device {
>   * @rxq: Buffer inbound queue
>   * @waitqueue: The waitqueue for port fops (read/write/poll)
>   * @data_lock: Port specific data access serialization
> + * @headroom_len: SKB reserved headroom size
> + * @frag_len: Length to fragment packet
>   * @at_data: AT port specific data
>   */
>  struct wwan_port {
> @@ -79,6 +81,8 @@ struct wwan_port {
>         struct sk_buff_head rxq;
>         wait_queue_head_t waitqueue;
>         struct mutex data_lock; /* Port specific data access serialization */
> +       size_t headroom_len;
> +       size_t frag_len;
>         union {
>                 struct {
>                         struct ktermios termios;
> @@ -422,6 +426,8 @@ static int __wwan_port_dev_assign_name(struct wwan_port *port, const char *fmt)
>  struct wwan_port *wwan_create_port(struct device *parent,
>                                    enum wwan_port_type type,
>                                    const struct wwan_port_ops *ops,
> +                                  size_t frag_len,
> +                                  unsigned int headroom_len,
>                                    void *drvdata)
>  {
>         struct wwan_device *wwandev;
> @@ -455,6 +461,8 @@ struct wwan_port *wwan_create_port(struct device *parent,
>
>         port->type = type;
>         port->ops = ops;
> +       port->frag_len = frag_len ? frag_len : SIZE_MAX;
> +       port->headroom_len = headroom_len;
>         mutex_init(&port->ops_lock);
>         skb_queue_head_init(&port->rxq);
>         init_waitqueue_head(&port->waitqueue);
> @@ -698,30 +706,53 @@ static ssize_t wwan_port_fops_read(struct file *filp, char __user *buf,
>  static ssize_t wwan_port_fops_write(struct file *filp, const char __user *buf,
>                                     size_t count, loff_t *offp)
>  {
> +       struct sk_buff *skb, *head = NULL, *tail = NULL;
>         struct wwan_port *port = filp->private_data;
> -       struct sk_buff *skb;
> +       size_t frag_len, remain = count;
>         int ret;
>
>         ret = wwan_wait_tx(port, !!(filp->f_flags & O_NONBLOCK));
>         if (ret)
>                 return ret;
>
> -       skb = alloc_skb(count, GFP_KERNEL);
> -       if (!skb)
> -               return -ENOMEM;
> +       do {
> +               frag_len = min(remain, port->frag_len);
> +               skb = alloc_skb(frag_len + port->headroom_len, GFP_KERNEL);
> +               if (!skb) {
> +                       ret = -ENOMEM;
> +                       goto freeskb;
> +               }
> +               skb_reserve(skb, port->headroom_len);
> +
> +               if (!head) {
> +                       head = skb;
> +               } else if (!tail) {
> +                       skb_shinfo(head)->frag_list = skb;
> +                       tail = skb;
> +               } else {
> +                       tail->next = skb;
> +                       tail = skb;
> +               }
>
> -       if (copy_from_user(skb_put(skb, count), buf, count)) {
> -               kfree_skb(skb);
> -               return -EFAULT;
> -       }
> +               if (copy_from_user(skb_put(skb, frag_len), buf + count - remain, frag_len)) {
> +                       ret = -EFAULT;
> +                       goto freeskb;
> +               }
>
> -       ret = wwan_port_op_tx(port, skb, !!(filp->f_flags & O_NONBLOCK));
> -       if (ret) {
> -               kfree_skb(skb);
> -               return ret;
> -       }
> +               if (skb != head) {
> +                       head->data_len += skb->len;
> +                       head->len += skb->len;
> +                       head->truesize += skb->truesize;
> +               }
> +       } while (remain -= frag_len);
> +
> +       ret = wwan_port_op_tx(port, head, !!(filp->f_flags & O_NONBLOCK));
> +       if (!ret)
> +               return count;
>
> -       return count;
> +freeskb:
> +       kfree_skb(head);
> +       return ret;
>  }
>
>  static __poll_t wwan_port_fops_poll(struct file *filp, poll_table *wait)
> diff --git a/drivers/net/wwan/wwan_hwsim.c b/drivers/net/wwan/wwan_hwsim.c
> index ff09a8cedf93..7fb54cb51628 100644
> --- a/drivers/net/wwan/wwan_hwsim.c
> +++ b/drivers/net/wwan/wwan_hwsim.c
> @@ -205,6 +205,7 @@ static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev)
>
>         port->wwan = wwan_create_port(&dev->dev, WWAN_PORT_AT,
>                                       &wwan_hwsim_port_ops,
> +                                     WWAN_NO_FRAGMENT, WWAN_NO_HEADROOM,
>                                       port);
>         if (IS_ERR(port->wwan)) {
>                 err = PTR_ERR(port->wwan);
> diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
> index 1f0951be15ab..e0f0bc878bbd 100644
> --- a/drivers/usb/class/cdc-wdm.c
> +++ b/drivers/usb/class/cdc-wdm.c
> @@ -929,7 +929,8 @@ static void wdm_wwan_init(struct wdm_device *desc)
>                 return;
>         }
>
> -       port = wwan_create_port(&intf->dev, desc->wwanp_type, &wdm_wwan_port_ops, desc);
> +       port = wwan_create_port(&intf->dev, desc->wwanp_type, &wdm_wwan_port_ops,
> +                               WWAN_NO_FRAGMENT, WWAN_NO_HEADROOM, desc);
>         if (IS_ERR(port)) {
>                 dev_err(&intf->dev, "%s: Unable to create WWAN port\n",
>                         dev_name(intf->usb_dev));
> diff --git a/include/linux/wwan.h b/include/linux/wwan.h
> index 5ce2acf444fb..37f25ebb9733 100644
> --- a/include/linux/wwan.h
> +++ b/include/linux/wwan.h
> @@ -62,11 +62,24 @@ struct wwan_port_ops {
>                             poll_table *wait);
>  };
>
> +/*
> + * Used to indicate that the WWAN core should not fragment tx packages.
> + */
> +#define WWAN_NO_FRAGMENT       0
> +
> +/*
> + * Used to indicate that the WWAN core should not reserve headroom in SKB.
> + */
> +#define WWAN_NO_HEADROOM       0

It could be a bit misleading here; these values are only used as
'control ports' parameters, and not for the 'regular' WWAN network
payload. Make this more clear in the above comments/def-names.

Regards,
Loic





> +
>  /**
>   * wwan_create_port - Add a new WWAN port
>   * @parent: Device to use as parent and shared by all WWAN ports
>   * @type: WWAN port type
>   * @ops: WWAN port operations
> + * @frag_len: TX fragments length, if 0 is set,
> + *            the WWAN core don't fragment the user package.
> + * @headroom_len: TX fragments reserved headroom length
>   * @drvdata: Pointer to caller driver data
>   *
>   * Allocate and register a new WWAN port. The port will be automatically exposed
> @@ -84,6 +97,8 @@ struct wwan_port_ops {
>  struct wwan_port *wwan_create_port(struct device *parent,
>                                    enum wwan_port_type type,
>                                    const struct wwan_port_ops *ops,
> +                                  size_t frag_len,
> +                                  unsigned int headroom_len,
>                                    void *drvdata);
>
>  /**
> --
> 2.17.0
>



More information about the Linux-mediatek mailing list