Add 11 KUnit cases for the RX allocation, submission, completion and retry paths. Cover each retryable completion followed by ENOMEM/EAGAIN, batch sizes 1, 8, 9 and 32, skb allocation failure, startup failure positions, cancellation and shutdown. Run the actual RX helpers and worker with task-scoped stubs for allocation and USB submission. Observer references check that the driver releases its URB and skb references. A delayed-work case checks that one retry request schedules the submission worker. Assisted-by: GPT-6 Astra Signed-off-by: kimwooseok <5mghybrid@khu.ac.kr> --- .../wireless/realtek/rtl8xxxu/.kunitconfig | 16 + drivers/net/wireless/realtek/rtl8xxxu/Kconfig | 11 + .../net/wireless/realtek/rtl8xxxu/Makefile | 3 + drivers/net/wireless/realtek/rtl8xxxu/core.c | 66 ++- .../net/wireless/realtek/rtl8xxxu/rx-test.c | 461 ++++++++++++++++++ .../net/wireless/realtek/rtl8xxxu/rx-test.h | 27 + 6 files changed, 569 insertions(+), 15 deletions(-) create mode 100644 drivers/net/wireless/realtek/rtl8xxxu/.kunitconfig create mode 100644 drivers/net/wireless/realtek/rtl8xxxu/rx-test.c create mode 100644 drivers/net/wireless/realtek/rtl8xxxu/rx-test.h diff --git a/drivers/net/wireless/realtek/rtl8xxxu/.kunitconfig b/drivers/net/wireless/realtek/rtl8xxxu/.kunitconfig new file mode 100644 index 0000000000000..decf383c3d51e --- /dev/null +++ b/drivers/net/wireless/realtek/rtl8xxxu/.kunitconfig @@ -0,0 +1,16 @@ +CONFIG_MODULES=y +CONFIG_KUNIT=y +CONFIG_KUNIT_ALL_TESTS=n +CONFIG_NET=y +CONFIG_NETDEVICES=y +CONFIG_WIRELESS=y +CONFIG_CFG80211=y +CONFIG_MAC80211=y +CONFIG_WLAN=y +CONFIG_WLAN_VENDOR_REALTEK=y +CONFIG_USB_SUPPORT=y +CONFIG_USB=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_RTL8XXXU=y +CONFIG_RTL8XXXU_KUNIT_TEST=y diff --git a/drivers/net/wireless/realtek/rtl8xxxu/Kconfig b/drivers/net/wireless/realtek/rtl8xxxu/Kconfig index 14d0343368ac0..e2c16d62293a4 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/Kconfig +++ b/drivers/net/wireless/realtek/rtl8xxxu/Kconfig @@ -35,3 +35,14 @@ config RTL8XXXU_UNTESTED author or reported to be working by third parties. Please report your results! + +config RTL8XXXU_KUNIT_TEST + tristate "KUnit tests for rtl8xxxu RX recovery" if !KUNIT_ALL_TESTS + depends on KUNIT && RTL8XXXU + default KUNIT_ALL_TESTS + help + Exercise RX buffer ownership, startup and recovery with the real + driver functions and controlled allocation and USB submission results. + These tests do not require a USB adapter. + + If unsure, say N. diff --git a/drivers/net/wireless/realtek/rtl8xxxu/Makefile b/drivers/net/wireless/realtek/rtl8xxxu/Makefile index 580a2fa675ee2..a592a81197857 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/Makefile +++ b/drivers/net/wireless/realtek/rtl8xxxu/Makefile @@ -4,3 +4,6 @@ obj-$(CONFIG_RTL8XXXU) += rtl8xxxu.o rtl8xxxu-y := core.o 8192e.o 8723b.o \ 8723a.o 8192c.o 8188f.o \ 8188e.o 8710b.o 8192f.o + +obj-$(CONFIG_RTL8XXXU_KUNIT_TEST) += rtl8xxxu-rx-test.o +rtl8xxxu-rx-test-y := rx-test.o diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 883c9a56f52a4..323411e7f5e3b 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -17,6 +17,8 @@ #include #include "regs.h" #include "rtl8xxxu.h" +#include "rx-test.h" +#include #define DRIVER_NAME "rtl8xxxu" @@ -60,8 +62,7 @@ MODULE_PARM_DESC(dma_agg_pages, "Set DMA aggregation pages (range 1-127, 0 to di #define RTL8XXXU_TX_URB_HIGH_WATER 32 static void rtl8xxxu_stop(struct ieee80211_hw *hw, bool suspend); -static int rtl8xxxu_submit_rx_urb(struct rtl8xxxu_priv *priv, - struct rtl8xxxu_rx_urb *rx_urb); + static struct ieee80211_rate rtl8xxxu_rates[] = { { .bitrate = 10, .hw_value = DESC_RATE_1M, .flags = 0 }, @@ -5817,7 +5818,36 @@ void jaguar2_rx_parse_phystats(struct rtl8xxxu_priv *priv, } } -static void rtl8xxxu_free_rx_resources(struct rtl8xxxu_priv *priv) +VISIBLE_IF_KUNIT struct rtl8xxxu_rx_urb *rtl8xxxu_alloc_rx_urb(void) +{ + KUNIT_STATIC_STUB_REDIRECT(rtl8xxxu_alloc_rx_urb); + return kmalloc_obj(struct rtl8xxxu_rx_urb); +} +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_alloc_rx_urb); + +VISIBLE_IF_KUNIT struct sk_buff *rtl8xxxu_alloc_rx_skb(unsigned int size) +{ + KUNIT_STATIC_STUB_REDIRECT(rtl8xxxu_alloc_rx_skb, size); + return __netdev_alloc_skb(NULL, size, GFP_KERNEL); +} +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_alloc_rx_skb); + +VISIBLE_IF_KUNIT int rtl8xxxu_rx_usb_submit(struct urb *urb, gfp_t flags) +{ + KUNIT_STATIC_STUB_REDIRECT(rtl8xxxu_rx_usb_submit, urb, flags); + return usb_submit_urb(urb, flags); +} +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_rx_usb_submit); + +VISIBLE_IF_KUNIT void rtl8xxxu_schedule_rx_retry(struct rtl8xxxu_priv *priv) +{ + KUNIT_STATIC_STUB_REDIRECT(rtl8xxxu_schedule_rx_retry, priv); + queue_delayed_work(system_wq, &priv->rx_urb_retry_wq, + msecs_to_jiffies(RTL8XXXU_RX_URB_RETRY_DELAY_MS)); +} +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_schedule_rx_retry); + +VISIBLE_IF_KUNIT void rtl8xxxu_free_rx_resources(struct rtl8xxxu_priv *priv) { struct rtl8xxxu_rx_urb *rx_urb, *tmp; unsigned long flags; @@ -5838,15 +5868,16 @@ static void rtl8xxxu_free_rx_resources(struct rtl8xxxu_priv *priv) spin_unlock_irqrestore(&priv->rx_urb_lock, flags); } +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_free_rx_resources); -static int rtl8xxxu_alloc_rx_urbs(struct rtl8xxxu_priv *priv) +VISIBLE_IF_KUNIT int rtl8xxxu_alloc_rx_urbs(struct rtl8xxxu_priv *priv) { struct rtl8xxxu_rx_urb *rx_urb; int i; /* No RX work is active until the complete pool has been allocated. */ for (i = 0; i < RTL8XXXU_RX_URBS; i++) { - rx_urb = kmalloc_obj(struct rtl8xxxu_rx_urb); + rx_urb = rtl8xxxu_alloc_rx_urb(); if (!rx_urb) return -ENOMEM; @@ -5859,6 +5890,7 @@ static int rtl8xxxu_alloc_rx_urbs(struct rtl8xxxu_priv *priv) return 0; } +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_alloc_rx_urbs); static void rtl8xxxu_queue_rx_urb(struct rtl8xxxu_priv *priv, struct rtl8xxxu_rx_urb *rx_urb) @@ -5887,7 +5919,7 @@ static void rtl8xxxu_queue_rx_urb(struct rtl8xxxu_priv *priv, spin_unlock_irqrestore(&priv->rx_urb_lock, flags); } -static void rtl8xxxu_rx_urb_retry_work(struct work_struct *work) +VISIBLE_IF_KUNIT void rtl8xxxu_rx_urb_retry_work(struct work_struct *work) { struct rtl8xxxu_priv *priv = container_of(to_delayed_work(work), struct rtl8xxxu_priv, @@ -5907,6 +5939,7 @@ static void rtl8xxxu_rx_urb_retry_work(struct work_struct *work) spin_unlock_irqrestore(&priv->rx_urb_lock, flags); } +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_rx_urb_retry_work); static void rtl8xxxu_queue_rx_urb_retry(struct rtl8xxxu_priv *priv, struct rtl8xxxu_rx_urb *rx_urb) @@ -5919,8 +5952,7 @@ static void rtl8xxxu_queue_rx_urb_retry(struct rtl8xxxu_priv *priv, list_add_tail(&rx_urb->list, &priv->rx_urb_retry_list); priv->rx_urb_retry_count++; /* Keep normal completions from bypassing the error backoff. */ - queue_delayed_work(system_wq, &priv->rx_urb_retry_wq, - msecs_to_jiffies(RTL8XXXU_RX_URB_RETRY_DELAY_MS)); + rtl8xxxu_schedule_rx_retry(priv); } else { usb_free_urb(&rx_urb->urb); } @@ -5928,7 +5960,7 @@ static void rtl8xxxu_queue_rx_urb_retry(struct rtl8xxxu_priv *priv, spin_unlock_irqrestore(&priv->rx_urb_lock, flags); } -static void rtl8xxxu_rx_urb_work(struct work_struct *work) +VISIBLE_IF_KUNIT void rtl8xxxu_rx_urb_work(struct work_struct *work) { struct rtl8xxxu_priv *priv; struct rtl8xxxu_rx_urb *rx_urb, *tmp; @@ -5968,8 +6000,9 @@ static void rtl8xxxu_rx_urb_work(struct work_struct *work) } } } +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_rx_urb_work); -static int rtl8xxxu_start_rx(struct rtl8xxxu_priv *priv) +VISIBLE_IF_KUNIT int rtl8xxxu_start_rx(struct rtl8xxxu_priv *priv) { struct rtl8xxxu_rx_urb *rx_urb, *tmp; unsigned long flags; @@ -6006,6 +6039,7 @@ free_remaining: } return ret; } +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_start_rx); /* * The RTL8723BU/RTL8192EU vendor driver use coexistence table type @@ -6646,7 +6680,7 @@ int rtl8xxxu_parse_rxdesc24(struct rtl8xxxu_priv *priv, struct sk_buff *skb) return RX_TYPE_DATA_PKT; } -static void rtl8xxxu_rx_complete(struct urb *urb) +VISIBLE_IF_KUNIT void rtl8xxxu_rx_complete(struct urb *urb) { struct rtl8xxxu_rx_urb *rx_urb = container_of(urb, struct rtl8xxxu_rx_urb, urb); @@ -6686,9 +6720,10 @@ cleanup: usb_free_urb(urb); dev_kfree_skb(skb); } +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_rx_complete); -static int rtl8xxxu_submit_rx_urb(struct rtl8xxxu_priv *priv, - struct rtl8xxxu_rx_urb *rx_urb) +VISIBLE_IF_KUNIT int rtl8xxxu_submit_rx_urb(struct rtl8xxxu_priv *priv, + struct rtl8xxxu_rx_urb *rx_urb) { struct rtl8xxxu_fileops *fops = priv->fops; struct sk_buff *skb; @@ -6704,7 +6739,7 @@ static int rtl8xxxu_submit_rx_urb(struct rtl8xxxu_priv *priv, skb_size = IEEE80211_MAX_FRAME_LEN + rx_desc_sz; } - skb = __netdev_alloc_skb(NULL, skb_size, GFP_KERNEL); + skb = rtl8xxxu_alloc_rx_skb(skb_size); if (!skb) return -ENOMEM; @@ -6712,7 +6747,7 @@ static int rtl8xxxu_submit_rx_urb(struct rtl8xxxu_priv *priv, usb_fill_bulk_urb(&rx_urb->urb, priv->udev, priv->pipe_in, skb->data, skb_size, rtl8xxxu_rx_complete, skb); usb_anchor_urb(&rx_urb->urb, &priv->rx_anchor); - ret = usb_submit_urb(&rx_urb->urb, GFP_ATOMIC); + ret = rtl8xxxu_rx_usb_submit(&rx_urb->urb, GFP_ATOMIC); if (ret) { usb_unanchor_urb(&rx_urb->urb); dev_kfree_skb(skb); @@ -6720,6 +6755,7 @@ static int rtl8xxxu_submit_rx_urb(struct rtl8xxxu_priv *priv, } return ret; } +EXPORT_SYMBOL_IF_KUNIT(rtl8xxxu_submit_rx_urb); static void rtl8xxxu_int_complete(struct urb *urb) { diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rx-test.c b/drivers/net/wireless/realtek/rtl8xxxu/rx-test.c new file mode 100644 index 0000000000000..aedddcb049cdf --- /dev/null +++ b/drivers/net/wireless/realtek/rtl8xxxu/rx-test.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include + +#include "rtl8xxxu.h" +#include "rx-test.h" + +#define RX_TEST_URBS 32 +#define RX_TEST_SKBS 512 + +struct rx_test { + struct rtl8xxxu_priv priv; + struct ieee80211_hw hw; + struct rtl8xxxu_fileops fops; + struct usb_device udev; + struct rtl8xxxu_rx_urb *urbs[RX_TEST_URBS]; + struct sk_buff *skbs[RX_TEST_SKBS]; + unsigned int allocated; + unsigned int buffers; + unsigned int alloc_calls; + unsigned int alloc_fail_at; + unsigned int submit_calls; + unsigned int submit_fail_at; + int submit_error; + bool fail_skb; + bool retry_pending; + unsigned int retry_arms; + atomic_t normal_runs; +}; + +static struct rx_test *rx_current(void) +{ + return kunit_get_current_test()->priv; +} + +static struct rtl8xxxu_rx_urb *rx_alloc_object(void) +{ + struct rx_test *ctx = rx_current(); + struct rtl8xxxu_rx_urb *rx; + + ctx->alloc_calls++; + if (ctx->alloc_calls == ctx->alloc_fail_at) + return NULL; + rx = kmalloc_obj(struct rtl8xxxu_rx_urb); + if (rx) + ctx->urbs[ctx->allocated++] = rx; + return rx; +} + +static struct sk_buff *rx_alloc_buffer(unsigned int size) +{ + struct rx_test *ctx = rx_current(); + struct sk_buff *skb; + + if (ctx->fail_skb) + return NULL; + skb = alloc_skb(size, GFP_KERNEL); + if (skb) { + /* Retain an observer reference to detect a lost driver reference. */ + skb_get(skb); + ctx->skbs[ctx->buffers++] = skb; + } + return skb; +} + +static int rx_submit(struct urb *urb, gfp_t flags) +{ + struct rx_test *ctx = rx_current(); + + ctx->submit_calls++; + if (!ctx->submit_fail_at || + ctx->submit_calls == ctx->submit_fail_at) + return ctx->submit_error; + return 0; +} + +static void rx_schedule_retry(struct rtl8xxxu_priv *priv) +{ + struct rx_test *ctx = rx_current(); + + /* Model the single pending delayed-work ticket, not a submission. */ + if (!ctx->retry_pending) { + ctx->retry_pending = true; + ctx->retry_arms++; + } +} + +static void rx_observe_work(struct work_struct *work) +{ + struct rtl8xxxu_priv *priv; + struct rx_test *ctx; + + priv = container_of(work, struct rtl8xxxu_priv, rx_urb_wq); + ctx = container_of(priv, struct rx_test, priv); + atomic_inc(&ctx->normal_runs); +} + +static struct rx_test *rx_init(struct kunit *test, bool fake_timer) +{ + struct rx_test *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + test->priv = ctx; + ctx->hw.priv = &ctx->priv; + ctx->priv.hw = &ctx->hw; + ctx->priv.udev = &ctx->udev; + ctx->priv.fops = &ctx->fops; + ctx->fops.rx_desc_size = sizeof(struct rtl8xxxu_rxdesc16); + spin_lock_init(&ctx->priv.rx_urb_lock); + INIT_LIST_HEAD(&ctx->priv.rx_urb_pending_list); + INIT_LIST_HEAD(&ctx->priv.rx_urb_retry_list); + init_usb_anchor(&ctx->priv.rx_anchor); + INIT_WORK(&ctx->priv.rx_urb_wq, rx_observe_work); + INIT_DELAYED_WORK(&ctx->priv.rx_urb_retry_wq, + rtl8xxxu_rx_urb_retry_work); + atomic_set(&ctx->normal_runs, 0); + + kunit_activate_static_stub(test, rtl8xxxu_alloc_rx_urb, rx_alloc_object); + kunit_activate_static_stub(test, rtl8xxxu_alloc_rx_skb, rx_alloc_buffer); + kunit_activate_static_stub(test, rtl8xxxu_rx_usb_submit, rx_submit); + if (fake_timer) + kunit_activate_static_stub(test, rtl8xxxu_schedule_rx_retry, + rx_schedule_retry); + return ctx; +} + +static void rx_get_observers(struct rx_test *ctx) +{ + unsigned int i; + + for (i = 0; i < ctx->allocated; i++) + usb_get_urb(&ctx->urbs[i]->urb); +} + +static int rx_pool(struct rx_test *ctx, unsigned int count) +{ + struct rtl8xxxu_rx_urb *rx; + unsigned int i; + int ret; + + ret = rtl8xxxu_alloc_rx_urbs(&ctx->priv); + rx_get_observers(ctx); + if (ret) + return ret; + + /* A reduced pool also tests recovery without the normal watermark. */ + for (i = count; i < ctx->allocated; i++) { + rx = ctx->urbs[i]; + list_del_init(&rx->list); + ctx->priv.rx_urb_pending_count--; + usb_free_urb(&rx->urb); + } + return 0; +} + +static void rx_giveback(struct rx_test *ctx, unsigned int i, int status) +{ + struct urb *urb = &ctx->urbs[i]->urb; + + /* The fake USB boundary does not emulate HCD giveback. */ + usb_unanchor_urb(urb); + urb->status = status; + urb->actual_length = 0; + rtl8xxxu_rx_complete(urb); +} + +static void rx_finish(struct kunit *test, struct rx_test *ctx) +{ + struct urb *urb; + unsigned int i; + + ctx->priv.shutdown = true; + cancel_delayed_work_sync(&ctx->priv.rx_urb_retry_wq); + cancel_work_sync(&ctx->priv.rx_urb_wq); + for (i = 0; i < ctx->allocated; i++) { + urb = &ctx->urbs[i]->urb; + if (urb->anchor) + rx_giveback(ctx, i, -ENOENT); + } + rtl8xxxu_free_rx_resources(&ctx->priv); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_pending_count, 0); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 0); + KUNIT_EXPECT_TRUE(test, usb_anchor_empty(&ctx->priv.rx_anchor)); + for (i = 0; i < ctx->allocated; i++) { + urb = &ctx->urbs[i]->urb; + KUNIT_EXPECT_EQ(test, refcount_read(&urb->kref.refcount), 1); + usb_free_urb(urb); + } + for (i = 0; i < ctx->buffers; i++) { + KUNIT_EXPECT_EQ(test, refcount_read(&ctx->skbs[i]->users), 1); + kfree_skb(ctx->skbs[i]); + } +} + +static void rx_retry_step(struct kunit *test, struct rx_test *ctx) +{ + KUNIT_ASSERT_TRUE(test, ctx->retry_pending); + ctx->retry_pending = false; + rtl8xxxu_rx_urb_retry_work(&ctx->priv.rx_urb_retry_wq.work); + flush_work(&ctx->priv.rx_urb_wq); + /* Execute the actual worker in the test task so boundary stubs apply. */ + rtl8xxxu_rx_urb_work(&ctx->priv.rx_urb_wq); +} + +static void rx_completion_submit_chain(struct kunit *test) +{ + static const int errors[] = { + -EPROTO, -EILSEQ, -ETIME, -EOVERFLOW, -ECOMM, -ENOSR, + }; + static const int submissions[] = { -ENOMEM, -EAGAIN }; + struct rx_test *ctx; + unsigned int e, f, i; + int runs; + + for (e = 0; e < ARRAY_SIZE(errors); e++) { + for (f = 0; f < ARRAY_SIZE(submissions); f++) { + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, RX_TEST_URBS), 0); + KUNIT_ASSERT_EQ(test, rtl8xxxu_start_rx(&ctx->priv), 0); + for (i = 0; i < RX_TEST_URBS; i++) + rx_giveback(ctx, i, errors[e]); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 32); + ctx->submit_error = submissions[f]; + rx_retry_step(test, ctx); + runs = atomic_read(&ctx->normal_runs); + flush_work(&ctx->priv.rx_urb_wq); + KUNIT_EXPECT_EQ(test, atomic_read(&ctx->normal_runs), runs); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_pending_count, 0); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 32); + KUNIT_EXPECT_TRUE(test, ctx->retry_pending); + for (i = 0; i < ctx->buffers; i++) + KUNIT_EXPECT_EQ(test, + refcount_read(&ctx->skbs[i]->users), 1); + ctx->submit_error = 0; + rx_retry_step(test, ctx); + KUNIT_EXPECT_FALSE(test, ctx->retry_pending); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 0); + KUNIT_EXPECT_FALSE(test, usb_anchor_empty(&ctx->priv.rx_anchor)); + rx_finish(test, ctx); + } + } +} + +static void rx_worker_batch_sizes(struct kunit *test) +{ + static const unsigned int sizes[] = { 1, 8, 9, 32 }; + struct rx_test *ctx; + unsigned int n, pass; + int runs; + + for (n = 0; n < ARRAY_SIZE(sizes); n++) { + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, sizes[n]), 0); + ctx->submit_error = -EAGAIN; + rtl8xxxu_rx_urb_work(&ctx->priv.rx_urb_wq); + for (pass = 0; pass < 3; pass++) { + runs = atomic_read(&ctx->normal_runs); + flush_work(&ctx->priv.rx_urb_wq); + KUNIT_EXPECT_EQ(test, atomic_read(&ctx->normal_runs), runs); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, sizes[n]); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_pending_count, 0); + rx_retry_step(test, ctx); + } + ctx->submit_error = 0; + rx_retry_step(test, ctx); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 0); + rx_finish(test, ctx); + } +} + +static void rx_skb_allocation_failure(struct kunit *test) +{ + struct rx_test *ctx; + + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, 8), 0); + ctx->fail_skb = true; + rtl8xxxu_rx_urb_work(&ctx->priv.rx_urb_wq); + KUNIT_EXPECT_EQ(test, ctx->submit_calls, 0); + KUNIT_EXPECT_EQ(test, ctx->buffers, 0); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 8); + ctx->fail_skb = false; + rx_retry_step(test, ctx); + KUNIT_EXPECT_EQ(test, ctx->submit_calls, 8); + rx_finish(test, ctx); +} + +static void rx_start_allocation_failure(struct kunit *test) +{ + static const unsigned int positions[] = { 1, 8, 32 }; + struct rx_test *ctx; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(positions); i++) { + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->alloc_fail_at = positions[i]; + KUNIT_EXPECT_EQ(test, rtl8xxxu_alloc_rx_urbs(&ctx->priv), -ENOMEM); + rx_get_observers(ctx); + KUNIT_EXPECT_EQ(test, ctx->allocated, positions[i] - 1); + KUNIT_EXPECT_EQ(test, ctx->submit_calls, 0); + KUNIT_EXPECT_FALSE(test, ctx->retry_pending); + rx_finish(test, ctx); + } +} + +static void rx_start_temporary_failure(struct kunit *test) +{ + static const int errors[] = { -ENOMEM, -EAGAIN }; + struct rx_test *ctx; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(errors); i++) { + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, 32), 0); + ctx->submit_error = errors[i]; + KUNIT_EXPECT_EQ(test, rtl8xxxu_start_rx(&ctx->priv), 0); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 32); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_pending_count, 0); + KUNIT_EXPECT_TRUE(test, ctx->retry_pending); + ctx->submit_error = 0; + rx_retry_step(test, ctx); + rx_finish(test, ctx); + } +} + +static void rx_start_fatal_failure(struct kunit *test) +{ + static const unsigned int positions[] = { 1, 8, 32 }; + struct rx_test *ctx; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(positions); i++) { + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, 32), 0); + ctx->submit_fail_at = positions[i]; + ctx->submit_error = -ENODEV; + KUNIT_EXPECT_EQ(test, rtl8xxxu_start_rx(&ctx->priv), -ENODEV); + KUNIT_EXPECT_EQ(test, ctx->submit_calls, positions[i]); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_pending_count, 0); + KUNIT_EXPECT_FALSE(test, ctx->retry_pending); + rx_finish(test, ctx); + } +} + +static void rx_terminal_completion(struct kunit *test) +{ + static const int errors[] = { + -ENOENT, -ECONNRESET, -ENODEV, -ESHUTDOWN, -EPIPE, + }; + struct rx_test *ctx; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(errors); i++) { + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, 1), 0); + KUNIT_ASSERT_EQ(test, rtl8xxxu_start_rx(&ctx->priv), 0); + rx_giveback(ctx, 0, errors[i]); + KUNIT_EXPECT_FALSE(test, ctx->retry_pending); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 0); + rx_finish(test, ctx); + } +} + +static void rx_shutdown_completion(struct kunit *test) +{ + struct rx_test *ctx; + + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, 1), 0); + KUNIT_ASSERT_EQ(test, rtl8xxxu_start_rx(&ctx->priv), 0); + ctx->priv.shutdown = true; + rx_giveback(ctx, 0, -EPROTO); + KUNIT_EXPECT_FALSE(test, ctx->retry_pending); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 0); + rx_finish(test, ctx); +} + +static void rx_shutdown_pending_retry(struct kunit *test) +{ + struct rx_test *ctx; + + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, 8), 0); + ctx->submit_error = -ENOMEM; + rtl8xxxu_rx_urb_work(&ctx->priv.rx_urb_wq); + ctx->priv.shutdown = true; + rtl8xxxu_rx_urb_retry_work(&ctx->priv.rx_urb_retry_wq.work); + flush_work(&ctx->priv.rx_urb_wq); + KUNIT_EXPECT_EQ(test, atomic_read(&ctx->normal_runs), 0); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 8); + rx_finish(test, ctx); +} + +static void rx_shutdown_submission_failure(struct kunit *test) +{ + struct rx_test *ctx; + + ctx = rx_init(test, true); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, 8), 0); + ctx->priv.shutdown = true; + ctx->submit_error = -ENOMEM; + rtl8xxxu_rx_urb_work(&ctx->priv.rx_urb_wq); + KUNIT_EXPECT_FALSE(test, ctx->retry_pending); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 0); + rx_finish(test, ctx); +} + +static void rx_real_delayed_bridge(struct kunit *test) +{ + struct rx_test *ctx; + + ctx = rx_init(test, false); + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_EQ(test, rx_pool(ctx, 1), 0); + KUNIT_ASSERT_EQ(test, rtl8xxxu_start_rx(&ctx->priv), 0); + rx_giveback(ctx, 0, -EPROTO); + flush_delayed_work(&ctx->priv.rx_urb_retry_wq); + flush_work(&ctx->priv.rx_urb_wq); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_retry_count, 0); + KUNIT_EXPECT_EQ(test, ctx->priv.rx_urb_pending_count, 1); + KUNIT_EXPECT_EQ(test, atomic_read(&ctx->normal_runs), 1); + rx_finish(test, ctx); +} + +static struct kunit_case rx_cases[] = { + KUNIT_CASE(rx_completion_submit_chain), + KUNIT_CASE(rx_worker_batch_sizes), + KUNIT_CASE(rx_skb_allocation_failure), + KUNIT_CASE(rx_start_allocation_failure), + KUNIT_CASE(rx_start_temporary_failure), + KUNIT_CASE(rx_start_fatal_failure), + KUNIT_CASE(rx_terminal_completion), + KUNIT_CASE(rx_shutdown_completion), + KUNIT_CASE(rx_shutdown_pending_retry), + KUNIT_CASE(rx_shutdown_submission_failure), + KUNIT_CASE(rx_real_delayed_bridge), + {} +}; + +static struct kunit_suite rx_suite = { + .name = "rtl8xxxu-rx", + .test_cases = rx_cases, +}; + +kunit_test_suite(rx_suite); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); +MODULE_DESCRIPTION("rtl8xxxu RX recovery KUnit tests"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rx-test.h b/drivers/net/wireless/realtek/rtl8xxxu/rx-test.h new file mode 100644 index 0000000000000..295befdfe8144 --- /dev/null +++ b/drivers/net/wireless/realtek/rtl8xxxu/rx-test.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef RTL8XXXU_RX_TEST_H +#define RTL8XXXU_RX_TEST_H + +#include +#include + +struct rtl8xxxu_priv; +struct rtl8xxxu_rx_urb; +struct sk_buff; +struct urb; +struct work_struct; + +VISIBLE_IF_KUNIT struct rtl8xxxu_rx_urb *rtl8xxxu_alloc_rx_urb(void); +VISIBLE_IF_KUNIT struct sk_buff *rtl8xxxu_alloc_rx_skb(unsigned int size); +VISIBLE_IF_KUNIT int rtl8xxxu_rx_usb_submit(struct urb *urb, gfp_t flags); +VISIBLE_IF_KUNIT void rtl8xxxu_schedule_rx_retry(struct rtl8xxxu_priv *priv); +VISIBLE_IF_KUNIT int rtl8xxxu_alloc_rx_urbs(struct rtl8xxxu_priv *priv); +VISIBLE_IF_KUNIT int rtl8xxxu_start_rx(struct rtl8xxxu_priv *priv); +VISIBLE_IF_KUNIT int rtl8xxxu_submit_rx_urb(struct rtl8xxxu_priv *priv, + struct rtl8xxxu_rx_urb *rx_urb); +VISIBLE_IF_KUNIT void rtl8xxxu_free_rx_resources(struct rtl8xxxu_priv *priv); +VISIBLE_IF_KUNIT void rtl8xxxu_rx_complete(struct urb *urb); +VISIBLE_IF_KUNIT void rtl8xxxu_rx_urb_work(struct work_struct *work); +VISIBLE_IF_KUNIT void rtl8xxxu_rx_urb_retry_work(struct work_struct *work); + +#endif -- 2.48.1