[PATCH net-next v3 10/12] net: mctp: usblib: Add initial kunit tests
From: Jeremy Kerr <jk@codeconstruct.com.au>
Date: 2026-07-08 09:59:51
Also in:
linux-usb
Subsystem:
management component transport protocol (mctp), networking drivers, the rest · Maintainers:
Jeremy Kerr, Matt Johnston, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Add some initial tests for the usblib receive path, where we're extracting MCTP packets from incoming USB transfer data. Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au> --- v2: - account for KUNIT_ASSERT-based exits; do cleanup through kunit_action facilities. - fix off-by-one in packet count duing skb length checks, in case we ended up with more skbs than expected - perform route updates under rntl lock --- drivers/net/mctp/Kconfig | 5 + drivers/net/mctp/mctp-usblib-test.c | 410 ++++++++++++++++++++++++++++++++++++ drivers/net/mctp/mctp-usblib.c | 4 + 3 files changed, 419 insertions(+)
diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig
index a564a792801d..c40ac9c665b7 100644
--- a/drivers/net/mctp/Kconfig
+++ b/drivers/net/mctp/Kconfig@@ -57,6 +57,11 @@ config MCTP_TRANSPORT_USBLIB This will be automatically enabled by the transport driver. +config MCTP_TRANSPORT_USBLIB_TEST + bool "MCTP usblib tests" if !KUNIT_ALL_TESTS + depends on MCTP_TRANSPORT_USBLIB=y && KUNIT=y + default KUNIT_ALL_TESTS + config MCTP_TRANSPORT_USB tristate "MCTP USB transport" depends on USB
diff --git a/drivers/net/mctp/mctp-usblib-test.c b/drivers/net/mctp/mctp-usblib-test.c
new file mode 100644
index 000000000000..2a22be999fa0
--- /dev/null
+++ b/drivers/net/mctp/mctp-usblib-test.c@@ -0,0 +1,410 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mctp-usblib-test.c - MCTP-over-USB (DMTF DSP0283) transport helper library, + * unit test definitions. + * + * Copyright (C) 2026 Code Construct Pty Ltd + */ + +#include <uapi/linux/netdevice.h> +#include <linux/netdevice.h> +#include <kunit/test.h> +#include <linux/if_arp.h> +#include <net/mctp.h> +#include <net/mctpdevice.h> +#include <linux/usb/mctp-usb.h> + +struct mctp_usblib_test_dev { + struct net_device *ndev; + struct mctp_dev *mdev; + struct sk_buff_head rx_pkts; +}; + +struct mctp_usblib_test_ctx { + struct mctp_usblib_test_dev *dev; + struct mctp_route rt; +}; + +static netdev_tx_t mctp_usblib_dev_tx(struct sk_buff *skb, + struct net_device *ndev) +{ + /* we don't track any TXed packets at present */ + kfree_skb(skb); + return NETDEV_TX_OK; +} + +static const struct net_device_ops mctp_test_netdev_ops = { + .ndo_start_xmit = mctp_usblib_dev_tx, +}; + +static const u16 ep_maxpacket = 512; +static const mctp_eid_t local_eid = 8; + +static void mctp_usblib_dev_setup(struct net_device *ndev) +{ + ndev->type = ARPHRD_MCTP; + ndev->mtu = 8192; + ndev->flags = IFF_NOARP; + ndev->netdev_ops = &mctp_test_netdev_ops; + ndev->needs_free_netdev = true; + ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS; +} + +static void mctp_usblib_test_dev_action(void *data) +{ + struct mctp_usblib_test_dev *dev = data; + + skb_queue_purge(&dev->rx_pkts); + if (dev->mdev) + mctp_dev_put(dev->mdev); + unregister_netdev(dev->ndev); +} + +static struct mctp_usblib_test_dev * +mctp_usblib_test_create_dev(struct kunit *test) +{ + struct mctp_usblib_test_dev *dev; + struct net_device *ndev; + int rc; + + ndev = alloc_netdev(sizeof(*dev), "mctptest%d", NET_NAME_ENUM, + mctp_usblib_dev_setup); + if (!ndev) + return NULL; + + dev = netdev_priv(ndev); + dev->ndev = ndev; + skb_queue_head_init(&dev->rx_pkts); + + rc = register_netdev(ndev); + if (rc) { + free_netdev(ndev); + return NULL; + } + + rc = kunit_add_action_or_reset(test, mctp_usblib_test_dev_action, dev); + if (rc) + return NULL; + + rcu_read_lock(); + dev->mdev = __mctp_dev_get(ndev); + if (dev->mdev) + dev->mdev->net = mctp_default_net(dev_net(ndev)); + rcu_read_unlock(); + + if (!dev->mdev) + return NULL; + + rtnl_lock(); + rc = dev_open(ndev, NULL); + rtnl_unlock(); + if (rc) + return NULL; + + return dev; +} + +static int mctp_usblib_test_dst_output(struct mctp_dst *dst, + struct sk_buff *skb) +{ + struct mctp_usblib_test_dev *dev = netdev_priv(skb->dev); + + skb_queue_tail(&dev->rx_pkts, skb); + + return 0; +} + +static void mctp_usblib_test_fini_action(void *data) +{ + struct mctp_usblib_test_ctx *ctx = data; + + /* The device will have been destroyed, so ->rt will be unlinked. + * Just ensure that the refcount is as expected. + */ + KUNIT_ASSERT_TRUE(current->kunit_test, + refcount_dec_and_test(&ctx->rt.refs)); + + kfree(ctx); +} + +static struct mctp_usblib_test_ctx *mctp_usblib_test_init(struct kunit *test) +{ + struct mctp_usblib_test_ctx *ctx; + struct mctp_route *rt; + int rc; + + ctx = kzalloc_obj(*ctx); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + INIT_LIST_HEAD(&ctx->rt.list); + rt = &ctx->rt; + refcount_set(&rt->refs, 1); + + rc = kunit_add_action_or_reset(test, mctp_usblib_test_fini_action, ctx); + KUNIT_ASSERT_EQ(test, rc, 0); + + ctx->dev = mctp_usblib_test_create_dev(test); + KUNIT_ASSERT_NOT_NULL(test, ctx->dev); + + rt->min = local_eid; + rt->max = local_eid; + rt->dst_type = MCTP_ROUTE_DIRECT; + rt->type = RTN_LOCAL; + rt->dev = ctx->dev->mdev; + rt->output = mctp_usblib_test_dst_output; + + rtnl_lock(); + list_add_rcu(&ctx->rt.list, &init_net.mctp.routes); + refcount_inc(&rt->refs); + rtnl_unlock(); + + return ctx; +} + +/* Init a MCTP-over-USB packet within a buffer. @len is the length of the + * buffer to write, @payload_len is the reported size of the MCTP-over-USB + * packet. + */ +static void mctp_usblib_test_init_pkt(void *data, size_t len, + size_t payload_len) +{ + struct { + struct mctp_usb_hdr usb; + struct mctp_hdr mctp; + } hdr; + + hdr.usb.id = cpu_to_be16(MCTP_USB_DMTF_ID); + hdr.usb.len = cpu_to_be16(payload_len); + hdr.mctp.ver = 1; + hdr.mctp.dest = local_eid; + hdr.mctp.src = 0; + hdr.mctp.flags_seq_tag = 0; + + memcpy(data, &hdr, min(len, sizeof(hdr))); + if (len > sizeof(hdr)) + memset(data + sizeof(hdr), 0, len - sizeof(hdr)); +} + +static void action_rx_fini(void *data) +{ + struct mctp_usblib_rx *rx = data; + + mctp_usblib_rx_fini(rx); + kfree(rx); +} + +static struct mctp_usblib_rx * +mctp_usblib_test_rx_init(struct kunit *test, bool span) +{ + struct mctp_usblib_rx *rx; + int rc; + + rx = kzalloc_obj(*rx); + if (rx) { + rc = kunit_add_action_or_reset(test, action_rx_fini, rx); + KUNIT_ASSERT_EQ(test, rc, 0); + } + KUNIT_ASSERT_NOT_NULL(test, rx); + mctp_usblib_rx_init(rx, ep_maxpacket, span); + + return rx; +} + +/* Wrappers for usblib's rx_complete callback, which is intended to be called + * from atomic context + */ +static int mctp_usblib_test_rx_complete(struct net_device *netdev, + struct mctp_usblib_rx *rx, size_t len) +{ + int rc; + + local_bh_disable(); + rc = mctp_usblib_rx_complete(netdev, rx, len); + local_bh_enable(); + + return rc; +} + +/* Single packet, starting on a transfer boundary, contained entirely within + * the transfer + */ +static void mctp_usblib_test_rx_single(struct kunit *test) +{ + struct mctp_usblib_test_dev *dev; + struct mctp_usblib_test_ctx *ctx; + struct mctp_usblib_rx *rx; + struct sk_buff *skb; + size_t len; + void *buf; + int rc; + + ctx = mctp_usblib_test_init(test); + dev = ctx->dev; + + rx = mctp_usblib_test_rx_init(test, true); + + rc = mctp_usblib_rx_prepare(dev->ndev, rx, + &buf, &len, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, rc, 0); + + /* we should always have a maxpacket of transfer available */ + KUNIT_ASSERT_GE(test, len, ep_maxpacket); + + mctp_usblib_test_init_pkt(buf, 8, 8); + + rc = mctp_usblib_test_rx_complete(dev->ndev, rx, 8); + KUNIT_ASSERT_EQ(test, rc, 0); + + skb = __skb_dequeue(&dev->rx_pkts); + KUNIT_EXPECT_NOT_NULL(test, skb); + if (skb) + KUNIT_EXPECT_EQ(test, skb->len, 4); + kfree_skb(skb); +} + +struct mctp_usblib_test_pkt_span { + const char *name; + size_t n_pkts; + size_t pkts[6]; + size_t n_xfers; + size_t xfers[6]; +}; + +static void +mctp_usblib_test_pkt_span_to_desc(const struct mctp_usblib_test_pkt_span *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +static void +mctp_usblib_test_pkt_span_validate(struct kunit *test, + const struct mctp_usblib_test_pkt_span *span, + size_t *len) +{ + size_t pkt_len = 0, xfer_len = 0; + unsigned int i; + + for (i = 0; i < span->n_pkts; i++) { + KUNIT_ASSERT_GE_MSG(test, span->pkts[i], 8, + "pkt[%d] len too small (%zd) for %s", + i, span->pkts[i], span->name); + pkt_len += span->pkts[i]; + } + + for (i = 0; i < span->n_xfers; i++) + xfer_len += span->xfers[i]; + + KUNIT_ASSERT_EQ_MSG(test, pkt_len, xfer_len, + "invalid pkt_len (%zd) != xfer_len (%zd) for %s", + pkt_len, xfer_len, span->name); + + *len = pkt_len; +} + +static void mctp_usblib_test_rx_pkt_span(struct kunit *test) +{ + const struct mctp_usblib_test_pkt_span *pkt_span = test->param_value; + size_t len, xfer_len, off, xfer_off; + struct mctp_usblib_test_dev *dev; + struct mctp_usblib_test_ctx *ctx; + struct mctp_usblib_rx *rx; + unsigned int i; + u8 *pktbuf; + void *buf; + int rc; + + mctp_usblib_test_pkt_span_validate(test, pkt_span, &len); + pktbuf = kunit_kmalloc_array(test, 1, len, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pktbuf); + + /* lay out packets */ + for (off = 0, i = 0; i < pkt_span->n_pkts; i++) { + len = pkt_span->pkts[i]; + mctp_usblib_test_init_pkt(pktbuf + off, len, len); + off += len; + } + + ctx = mctp_usblib_test_init(test); + dev = ctx->dev; + + rx = mctp_usblib_test_rx_init(test, true); + + /* feed transfers */ + for (off = 0, xfer_off = 0, i = 0; i < pkt_span->n_xfers;) { + xfer_len = pkt_span->xfers[i] - xfer_off; + rc = mctp_usblib_rx_prepare(dev->ndev, rx, + &buf, &len, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, rc, 0); + + KUNIT_ASSERT_GE(test, len, ep_maxpacket); + + len = min(len, xfer_len); + memcpy(buf, pktbuf + off, len); + + if (len == xfer_len) { + /* whole/end xfer, proceed to next */ + xfer_off = 0; + i++; + } else { + /* partial */ + xfer_off += len; + } + + rc = mctp_usblib_test_rx_complete(dev->ndev, rx, len); + KUNIT_ASSERT_EQ(test, rc, 0); + off += len; + } + + /* check received packets */ + KUNIT_EXPECT_EQ(test, dev->rx_pkts.qlen, pkt_span->n_pkts); + for (i = 0; ; i++) { + struct sk_buff *skb = __skb_dequeue(&dev->rx_pkts); + + if (!skb) + break; + + if (i < pkt_span->n_pkts) + KUNIT_EXPECT_EQ(test, skb->len, pkt_span->pkts[i] - 4); + + kfree_skb(skb); + } +} + +static const struct mctp_usblib_test_pkt_span mctp_usblib_test_pkt_spans[] = { + /* One packet completely within a transfer */ + { "1p1x-complete", 1, { 8 }, 1, { 8 } }, + /* Two small packets combined within one transfer */ + { "2p1x-combined", 2, { 8, 8 }, 1, { 16 } }, + /* A packet split over two transfers, at the MCTP payload */ + { "1p2x-split-payload", 1, { 16 }, 2, { 8, 8 } }, + /* A packet split over two transfers, at the USB transport header */ + { "1p2x-split-usbhdr", 1, { 16 }, 2, { 2, 14 } }, + /* A packet split over two transfers, at the MCTP header */ + { "1p2x-split-mctphdr", 1, { 16 }, 2, { 6, 10 } }, + /* Single packet split over 3 transfers, middle entirely continuation */ + { "1p3x-split", 1, { 12 }, 3, { 4, 4, 4 } }, + /* Max-sized single transfer */ + { "1p1x-large", 1, { 8191 }, 1, { 8191 } }, + /* Two large packets, split at the worst-case for allocation, with a + * single byte continuing the span + */ + { "2p2x-large-split", 2, { 8190, 8190 }, 2, { 8191, 8189 } }, +}; + +KUNIT_ARRAY_PARAM(mctp_usblib_test_rx_pkt_span, mctp_usblib_test_pkt_spans, + mctp_usblib_test_pkt_span_to_desc); + +static struct kunit_case mctp_usblib_test_cases[] = { + KUNIT_CASE(mctp_usblib_test_rx_single), + KUNIT_CASE_PARAM(mctp_usblib_test_rx_pkt_span, + mctp_usblib_test_rx_pkt_span_gen_params), + {} +}; + +static struct kunit_suite mctp_usblib_test_suite = { + .name = "mctp-usblib", + .test_cases = mctp_usblib_test_cases, +}; + +kunit_test_suite(mctp_usblib_test_suite);
diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c
index 4131be31db1a..fc0282152e6e 100644
--- a/drivers/net/mctp/mctp-usblib.c
+++ b/drivers/net/mctp/mctp-usblib.c@@ -610,3 +610,7 @@ EXPORT_SYMBOL_GPL(mctp_usblib_tx_cancel); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>"); MODULE_DESCRIPTION("MCTP USB transport library"); + +#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_USBLIB_TEST) +#include "mctp-usblib-test.c" +#endif
--
2.47.3