mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 23:59:33 -04:00
net: mctp: usblib: Add initial kunit tests
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> Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-10-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
081ac93ca9
commit
e84c0edbc4
@@ -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
|
||||
|
||||
412
drivers/net/mctp/mctp-usblib-test.c
Normal file
412
drivers/net/mctp/mctp-usblib-test.c
Normal file
@@ -0,0 +1,412 @@
|
||||
// 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_EXPECT_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);
|
||||
|
||||
rc = mctp_usblib_rx_init(rx, ep_maxpacket, span);
|
||||
KUNIT_ASSERT_EQ(test, rc, 0);
|
||||
|
||||
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[%u] len too small (%zu) 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 (%zu) != xfer_len (%zu) 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);
|
||||
@@ -619,3 +619,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
|
||||
|
||||
Reference in New Issue
Block a user