blob: 59bee4b366699eae430cd29c210e786d46227f82 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0+
/*
* f_mctp.c - USB peripheral MCTP driver
*
* Copyright (C) 2024 Code Construct Pty Ltd
*/
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/usb/composite.h>
#include <linux/skbuff.h>
#include <linux/err.h>
#include <linux/netdevice.h>
#include <linux/list.h>
#include <net/mctp.h>
#include <net/pkt_sched.h>
#include <linux/usb/func_utils.h>
#include <linux/usb/mctp-usb.h>
#include <uapi/linux/if_arp.h>
#define MCTP_USB_PREALLOC 4
struct f_mctp {
struct usb_function function;
struct usb_ep *in_ep;
struct usb_ep *out_ep;
struct net_device *dev;
/* Updates to skb_free_list and the req lists are performed under
* ->lock
*/
spinlock_t lock;
struct sk_buff_head skb_free_list;
struct list_head rx_reqs;
struct list_head tx_reqs;
struct work_struct prealloc_work;
};
struct f_mctp_opts {
struct usb_function_instance function_instance;
};
static inline struct f_mctp *func_to_mctp(struct usb_function *f)
{
return container_of(f, struct f_mctp, function);
}
static struct usb_interface_descriptor mctp_usbg_intf = {
.bLength = sizeof(mctp_usbg_intf),
.bDescriptorType = USB_DT_INTERFACE,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_MCTP,
.bInterfaceSubClass = 0x0, /* todo: allow host-interface mode? */
.bInterfaceProtocol = 0x1, /* MCTP version 1 */
/* .iInterface = DYNAMIC */
};
/* descriptors, full speed only */
static struct usb_endpoint_descriptor hs_mctp_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE),
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
static struct usb_endpoint_descriptor hs_mctp_sink_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE),
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
static struct usb_descriptor_header *hs_mctp_descs[] = {
(struct usb_descriptor_header *) &mctp_usbg_intf,
(struct usb_descriptor_header *) &hs_mctp_sink_desc,
(struct usb_descriptor_header *) &hs_mctp_source_desc,
NULL,
};
/* strings */
static struct usb_string mctp_usbg_strings[] = {
{ .s = "MCTP over USB" },
{ 0 }
};
static struct usb_gadget_strings mctp_usbg_stringtab = {
.language = 0x0409, /* en-us */
.strings = mctp_usbg_strings,
};
static struct usb_gadget_strings *mctp_usbg_gadget_strings[] = {
&mctp_usbg_stringtab,
NULL,
};
static int mctp_usbg_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct f_mctp *mctp = func_to_mctp(f);
int id, rc;
id = usb_interface_id(c, f);
if (id < 0)
return id;
mctp_usbg_intf.bInterfaceNumber = id;
id = usb_string_id(cdev);
if (id < 0)
return id;
mctp_usbg_strings[0].id = id;
mctp->in_ep = usb_ep_autoconfig(cdev->gadget, &hs_mctp_source_desc);
if (!mctp->in_ep) {
ERROR(cdev, "%s in_ep autoconfig failed\n", f->name);
return -ENODEV;
}
mctp->out_ep = usb_ep_autoconfig(cdev->gadget, &hs_mctp_sink_desc);
if (!mctp->out_ep) {
ERROR(cdev, "%s out_ep autoconfig failed\n", f->name);
return -ENODEV;
}
rc = usb_assign_descriptors(f, NULL, hs_mctp_descs, NULL, NULL);
if (rc) {
ERROR(cdev, "assign_descriptors failed %d\n", rc);
return rc;
}
DBG(cdev, "%s: in %s, out %s\n", f->name, mctp->in_ep->name,
mctp->out_ep->name);
return 0;
}
static void mctp_usbg_prealloc(struct f_mctp *mctp)
{
struct sk_buff_head skbs;
struct usb_request *req;
struct list_head reqs;
struct sk_buff *skb;
unsigned long flags;
unsigned int n_skb;
/* allocate SKBs for requests that have been consumed and don't
* have an associated skb, then requeue.
*/
spin_lock_irqsave(&mctp->lock, flags);
list_replace_init(&mctp->rx_reqs, &reqs);
spin_unlock_irqrestore(&mctp->lock, flags);
list_for_each_entry(req, &reqs, list) {
skb = __netdev_alloc_skb(mctp->dev, MCTP_USB_XFER_SIZE,
GFP_KERNEL);
if (!skb)
break;
req->buf = skb->data;
req->context = skb;
usb_ep_queue(mctp->out_ep, req, GFP_KERNEL);
}
/* next, allocate our pool of spare skbs */
n_skb = skb_queue_len_lockless(&mctp->skb_free_list);
__skb_queue_head_init(&skbs);
for (; n_skb < MCTP_USB_PREALLOC; n_skb++) {
skb = __netdev_alloc_skb(mctp->dev, MCTP_USB_XFER_SIZE,
GFP_KERNEL);
if (!skb)
break;
__skb_queue_tail(&skbs, skb);
}
spin_lock_irqsave(&mctp->lock, flags);
skb_queue_splice_tail(&skbs, &mctp->skb_free_list);
spin_unlock_irqrestore(&mctp->lock, flags);
}
static void mctp_usbg_prealloc_work(struct work_struct *work)
{
struct f_mctp *mctp = container_of(work, struct f_mctp, prealloc_work);
mctp_usbg_prealloc(mctp);
}
static int mctp_usbg_requeue(struct f_mctp *mctp, struct usb_ep *ep,
struct usb_request *req)
{
unsigned long flags;
struct sk_buff *skb;
int rc = 0;
req->buf = NULL;
spin_lock_irqsave(&mctp->lock, flags);
/* Do we have a preallocated skb available? if so, we can requeue
* immediately; otherwise wait for the workqueue to populate.
*/
skb = __skb_dequeue(&mctp->skb_free_list);
if (skb) {
req->buf = skb->data;
req->context = skb;
rc = usb_ep_queue(ep, req, GFP_ATOMIC);
} else {
/* keep for later allocation */
list_add_tail(&req->list, &mctp->rx_reqs);
}
spin_unlock_irqrestore(&mctp->lock, flags);
schedule_work(&mctp->prealloc_work);
return rc;
}
static void mctp_usbg_handle_rx_urb(struct f_mctp *mctp,
struct usb_request *req)
{
struct device *dev = &mctp->function.config->cdev->gadget->dev;
struct sk_buff *skb = req->context;
struct mctp_usb_hdr *hdr;
struct mctp_skb_cb *cb;
unsigned int len;
u16 id;
len = req->actual;
__skb_put(skb, len);
hdr = skb_pull_data(skb, sizeof(*hdr));
if (!hdr)
goto err;
id = be16_to_cpu(hdr->id);
if (id != MCTP_USB_DMTF_ID) {
dev_dbg(dev, "%s: invalid id %04x\n", __func__, id);
goto err;
}
if (hdr->len < sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) {
dev_dbg(dev, "%s: short packet (hdr) %d\n",
__func__, hdr->len);
goto err;
}
/* todo: multi-packet transfers */
if (hdr->len - sizeof(struct mctp_usb_hdr) < skb->len) {
dev_dbg(dev, "%s: short packet (xfer) %d, actual %d\n",
__func__, hdr->len, skb->len);
goto err;
}
skb->protocol = htons(ETH_P_MCTP);
skb_reset_network_header(skb);
cb = __mctp_cb(skb);
cb->halen = 0;
netif_rx(skb);
return;
err:
/* todo: return to free list */
kfree_skb(skb);
}
static void mctp_usbg_out_ep_complete(struct usb_ep *ep,
struct usb_request *req)
{
struct f_mctp *mctp = ep->driver_data;
struct usb_composite_dev *cdev = mctp->function.config->cdev;
int rc;
switch (req->status) {
case 0:
mctp_usbg_handle_rx_urb(mctp, req);
/* re-queue out request */
rc = mctp_usbg_requeue(mctp, ep, req);
if (rc) {
WARNING(cdev, "%s: unable to re-queue out req\n",
__func__);
usb_ep_free_request(ep, req);
}
break;
case -ECONNABORTED:
case -ECONNRESET:
case -ESHUTDOWN:
kfree_skb(req->context);
usb_ep_free_request(ep, req);
break;
default:
WARNING(cdev, "%s: invalid status %d?", __func__, req->status);
usb_ep_free_request(ep, req);
}
}
static void mctp_usbg_in_ep_complete(struct usb_ep *ep,
struct usb_request *req)
{
struct f_mctp *mctp = ep->driver_data;
struct usb_composite_dev *cdev = mctp->function.config->cdev;
struct sk_buff *skb = req->context;
unsigned long flags;
kfree_skb(skb);
req->context = NULL;
req->buf = NULL;
/* todo: tx stats */
switch (req->status) {
case 0:
spin_lock_irqsave(&mctp->lock, flags);
if (list_empty(&mctp->tx_reqs))
netif_wake_queue(mctp->dev);
list_add(&req->list, &mctp->tx_reqs);
spin_unlock_irqrestore(&mctp->lock, flags);
break;
default:
WARNING(cdev, "%s: invalid status %d?", __func__, req->status);
fallthrough;
case -ECONNABORTED:
case -ECONNRESET:
case -ESHUTDOWN:
usb_ep_free_request(ep, req);
break;
}
}
static int mctp_usbg_enable_ep(struct usb_gadget *gadget, struct f_mctp *mctp,
struct usb_ep *ep)
{
int rc;
rc = config_ep_by_speed(gadget, &mctp->function, ep);
if (rc)
return rc;
rc = usb_ep_enable(ep);
if (rc)
return rc;
ep->driver_data = mctp;
return 0;
}
static int mctp_usbg_enable(struct usb_composite_dev *cdev, struct f_mctp *mctp)
{
struct usb_request *out_req, *in_req;
unsigned long flags;
struct sk_buff *skb;
int rc;
rc = mctp_usbg_enable_ep(cdev->gadget, mctp, mctp->out_ep);
if (rc) {
ERROR(cdev, "%s: out ep enable failed %d\n", __func__, rc);
return rc;
}
rc = mctp_usbg_enable_ep(cdev->gadget, mctp, mctp->in_ep);
if (rc) {
ERROR(cdev, "%s: in ep enable failed %d\n", __func__, rc);
goto err_disable_out;
}
/* todo: just one out queued req for now */
out_req = alloc_ep_req(mctp->out_ep, MCTP_USB_XFER_SIZE);
if (!out_req) {
ERROR(cdev, "%s: out req alloc failed\n", __func__);
goto err_disable_in;
}
spin_lock_irqsave(&mctp->lock, flags);
skb = __skb_dequeue(&mctp->skb_free_list);
spin_unlock_irqrestore(&mctp->lock, flags);
if (!skb)
skb = netdev_alloc_skb(mctp->dev, MCTP_USB_XFER_SIZE);
if (!skb)
goto err_free_req;
out_req->context = skb;
out_req->buf = skb->data;
out_req->complete = mctp_usbg_out_ep_complete;
rc = usb_ep_queue(mctp->out_ep, out_req, GFP_ATOMIC);
if (rc) {
ERROR(cdev, "%s: out req queue failed %d\b", __func__, rc);
goto err_free_skb;
}
/* todo: and just one in the in queue too */
in_req = usb_ep_alloc_request(mctp->in_ep, GFP_ATOMIC);
if (!in_req) {
ERROR(cdev, "%s: out req alloc failed\n", __func__);
goto err_disable_in;
}
in_req->complete = mctp_usbg_in_ep_complete;
spin_lock_irqsave(&mctp->lock, flags);
list_add(&in_req->list, &mctp->tx_reqs);
spin_unlock_irqrestore(&mctp->lock, flags);
return 0;
err_free_skb:
kfree_skb(skb);
err_free_req:
free_ep_req(mctp->out_ep, out_req);
err_disable_in:
usb_ep_disable(mctp->in_ep);
err_disable_out:
usb_ep_disable(mctp->out_ep);
return rc;
}
static netdev_tx_t mctp_usbg_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct f_mctp *mctp = netdev_priv(dev);
struct mctp_usb_hdr *hdr;
struct usb_request *req;
unsigned long flags;
unsigned int plen;
if (skb->len + sizeof(*hdr) > MCTP_USB_XFER_SIZE)
goto drop;
spin_lock_irqsave(&mctp->lock, flags);
req = list_first_entry_or_null(&mctp->tx_reqs, struct usb_request, list);
if (req) {
list_del(&req->list);
if (list_empty(&mctp->tx_reqs))
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&mctp->lock, flags);
if (!req) {
netdev_err(dev, "no tx reqs available!\n");
goto drop;
}
plen = skb->len;
hdr = skb_push(skb, sizeof(*hdr));
hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID);
hdr->rsvd = 0;
hdr->len = plen + sizeof(*hdr);
/* todo: just one skb per transfer.. */
req->context = skb;
req->buf = skb->data;
req->length = skb->len;
usb_ep_queue(mctp->in_ep, req, GFP_ATOMIC);
return NETDEV_TX_OK;
drop:
kfree_skb(skb);
return NETDEV_TX_OK;
}
static int mctp_usbg_open(struct net_device *net)
{
return 0;
}
static int mctp_usbg_stop(struct net_device *net)
{
return 0;
}
static const struct net_device_ops mctp_usbg_netdev_ops = {
.ndo_open = mctp_usbg_open,
.ndo_stop = mctp_usbg_stop,
.ndo_start_xmit = mctp_usbg_start_xmit,
};
static void __mctp_usbg_disable(struct f_mctp *mctp)
{
usb_ep_disable(mctp->in_ep);
usb_ep_disable(mctp->out_ep);
}
static void mctp_usbg_disable(struct usb_function *f)
{
struct f_mctp *mctp = func_to_mctp(f);
__mctp_usbg_disable(mctp);
}
static int mctp_usbg_set_alt(struct usb_function *f,
unsigned intf, unsigned alt)
{
struct usb_composite_dev *cdev = f->config->cdev;
struct f_mctp *mctp = func_to_mctp(f);
__mctp_usbg_disable(mctp);
return mctp_usbg_enable(cdev, mctp);
}
static void mctp_usbg_free_func(struct usb_function *f)
{
struct f_mctp *mctp = func_to_mctp(f);
kfree(mctp);
}
static void mctp_usbg_netdev_setup(struct net_device *dev)
{
dev->type = ARPHRD_MCTP;
dev->mtu = MCTP_USB_MTU_MIN;
dev->min_mtu = MCTP_USB_MTU_MIN;
dev->max_mtu = MCTP_USB_MTU_MAX;
dev->hard_header_len = 0;
dev->addr_len = 0;
dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
dev->flags = IFF_NOARP;
dev->netdev_ops = &mctp_usbg_netdev_ops;
dev->needs_free_netdev = true;
dev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
}
static struct usb_function
*mctp_usbg_alloc_func(struct usb_function_instance *fi)
{
struct f_mctp_opts *opts;
struct net_device *dev;
struct f_mctp *mctp;
int rc;
opts = container_of(fi, struct f_mctp_opts, function_instance);
dev = alloc_netdev(sizeof(*mctp), "mctpusbg%d", NET_NAME_ENUM,
mctp_usbg_netdev_setup);
if (!dev)
return ERR_PTR(-ENOMEM);
mctp = netdev_priv(dev);
mctp->dev = dev;
spin_lock_init(&mctp->lock);
INIT_LIST_HEAD(&mctp->rx_reqs);
INIT_LIST_HEAD(&mctp->tx_reqs);
__skb_queue_head_init(&mctp->skb_free_list);
INIT_WORK(&mctp->prealloc_work, mctp_usbg_prealloc_work);
mctp->function.name = "mctp";
mctp->function.bind = mctp_usbg_bind;
mctp->function.set_alt = mctp_usbg_set_alt;
mctp->function.disable = mctp_usbg_disable;
mctp->function.strings = mctp_usbg_gadget_strings;
mctp->function.free_func = mctp_usbg_free_func;
/* this will allocate our first pool of out (rx) skbs */
mctp_usbg_prealloc(mctp);
rc = register_netdev(dev);
if (rc) {
free_netdev(dev);
return ERR_PTR(rc);
}
return &mctp->function;
}
static struct f_mctp_opts *to_f_mctp_opts(struct config_item *item)
{
return container_of(to_config_group(item), struct f_mctp_opts,
function_instance.group);
}
static void mctp_usbg_attr_release(struct config_item *item)
{
struct f_mctp_opts *opts = to_f_mctp_opts(item);
usb_put_function_instance(&opts->function_instance);
}
static struct configfs_item_operations mctp_usbg_item_ops = {
.release = mctp_usbg_attr_release,
};
static struct configfs_attribute *mctp_usbg_attrs[] = {
NULL,
};
static const struct config_item_type mctp_usbg_func_type = {
.ct_item_ops = &mctp_usbg_item_ops,
.ct_attrs = mctp_usbg_attrs,
.ct_owner = THIS_MODULE,
};
static void mctp_usbg_free_instance(struct usb_function_instance *fi)
{
struct f_mctp_opts *opts;
opts = container_of(fi, struct f_mctp_opts, function_instance);
kfree(opts);
}
static struct usb_function_instance *mctp_usbg_alloc_instance(void)
{
struct f_mctp_opts *opts;
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
if (!opts)
return ERR_PTR(-ENOMEM);
opts->function_instance.free_func_inst = mctp_usbg_free_instance;
config_group_init_type_name(&opts->function_instance.group, "",
&mctp_usbg_func_type);
return &opts->function_instance;
}
DECLARE_USB_FUNCTION_INIT(mctp, mctp_usbg_alloc_instance, mctp_usbg_alloc_func);
MODULE_LICENSE("GPL");