diff options
| author | Danilo Krummrich <dakr@kernel.org> | 2026-03-17 22:11:03 +0300 |
|---|---|---|
| committer | Danilo Krummrich <dakr@kernel.org> | 2026-03-17 22:12:36 +0300 |
| commit | de25dc008ea74bc6f33b8d6e773e51a920813fdc (patch) | |
| tree | e1ca61335676a2f5fd4bb603ef40f47d14398190 /samples | |
| parent | dc33ae50d32b509af5ae61030912fa20c79ef112 (diff) | |
| parent | 79cf41692aadc3d0ac9b1d8e2c2f620ce2103918 (diff) | |
| download | linux-de25dc008ea74bc6f33b8d6e773e51a920813fdc.tar.xz | |
Merge tag 'rust_io-7.1-rc1' into driver-core-next
Register abstraction and I/O infrastructure improvements
Introduce the register!() macro to define type-safe I/O register
accesses. Refactor the IoCapable trait into a functional trait, which
simplifies I/O backends and removes the need for overloaded Io methods.
This is a stable tag for other trees to merge.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
Diffstat (limited to 'samples')
| -rw-r--r-- | samples/rust/rust_dma.rs | 30 | ||||
| -rw-r--r-- | samples/rust/rust_driver_pci.rs | 90 | ||||
| -rw-r--r-- | samples/workqueue/stall_detector/Makefile | 1 | ||||
| -rw-r--r-- | samples/workqueue/stall_detector/wq_stall.c | 98 |
4 files changed, 183 insertions, 36 deletions
diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs index 9c45851c876e..ce39b5545097 100644 --- a/samples/rust/rust_dma.rs +++ b/samples/rust/rust_dma.rs @@ -68,7 +68,7 @@ impl pci::Driver for DmaSampleDriver { CoherentAllocation::alloc_coherent(pdev.as_ref(), TEST_VALUES.len(), GFP_KERNEL)?; for (i, value) in TEST_VALUES.into_iter().enumerate() { - kernel::dma_write!(ca[i] = MyStruct::new(value.0, value.1))?; + kernel::dma_write!(ca, [i]?, MyStruct::new(value.0, value.1)); } let size = 4 * page::PAGE_SIZE; @@ -85,24 +85,26 @@ impl pci::Driver for DmaSampleDriver { } } +impl DmaSampleDriver { + fn check_dma(&self) -> Result { + for (i, value) in TEST_VALUES.into_iter().enumerate() { + let val0 = kernel::dma_read!(self.ca, [i]?.h); + let val1 = kernel::dma_read!(self.ca, [i]?.b); + + assert_eq!(val0, value.0); + assert_eq!(val1, value.1); + } + + Ok(()) + } +} + #[pinned_drop] impl PinnedDrop for DmaSampleDriver { fn drop(self: Pin<&mut Self>) { dev_info!(self.pdev, "Unload DMA test driver.\n"); - for (i, value) in TEST_VALUES.into_iter().enumerate() { - let val0 = kernel::dma_read!(self.ca[i].h); - let val1 = kernel::dma_read!(self.ca[i].b); - assert!(val0.is_ok()); - assert!(val1.is_ok()); - - if let Ok(val0) = val0 { - assert_eq!(val0, value.0); - } - if let Ok(val1) = val1 { - assert_eq!(val1, value.1); - } - } + assert!(self.check_dma().is_ok()); for (i, entry) in self.sgt.iter().enumerate() { dev_info!( diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index d3d4a7931deb..47d3e84fab63 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -5,30 +5,63 @@ //! To make this driver probe, QEMU must be run with `-device pci-testdev`. use kernel::{ - device::Bound, - device::Core, + device::{ + Bound, + Core, // + }, devres::Devres, - io::Io, + io::{ + register, + register::Array, + Io, // + }, + num::Bounded, pci, prelude::*, sync::aref::ARef, // }; -struct Regs; +mod regs { + use super::*; -impl Regs { - const TEST: usize = 0x0; - const OFFSET: usize = 0x4; - const DATA: usize = 0x8; - const COUNT: usize = 0xC; - const END: usize = 0x10; + register! { + pub(super) TEST(u8) @ 0x0 { + 7:0 index => TestIndex; + } + + pub(super) OFFSET(u32) @ 0x4 { + 31:0 offset; + } + + pub(super) DATA(u8) @ 0x8 { + 7:0 data; + } + + pub(super) COUNT(u32) @ 0xC { + 31:0 count; + } + } + + pub(super) const END: usize = 0x10; } -type Bar0 = pci::Bar<{ Regs::END }>; +type Bar0 = pci::Bar<{ regs::END }>; #[derive(Copy, Clone, Debug)] struct TestIndex(u8); +impl From<Bounded<u8, 8>> for TestIndex { + fn from(value: Bounded<u8, 8>) -> Self { + Self(value.into()) + } +} + +impl From<TestIndex> for Bounded<u8, 8> { + fn from(value: TestIndex) -> Self { + value.0.into() + } +} + impl TestIndex { const NO_EVENTFD: Self = Self(0); } @@ -54,40 +87,53 @@ kernel::pci_device_table!( impl SampleDriver { fn testdev(index: &TestIndex, bar: &Bar0) -> Result<u32> { // Select the test. - bar.write8(index.0, Regs::TEST); + bar.write_reg(regs::TEST::zeroed().with_index(*index)); - let offset = bar.read32(Regs::OFFSET) as usize; - let data = bar.read8(Regs::DATA); + let offset = bar.read(regs::OFFSET).into_raw() as usize; + let data = bar.read(regs::DATA).into(); // Write `data` to `offset` to increase `count` by one. // // Note that we need `try_write8`, since `offset` can't be checked at compile-time. bar.try_write8(data, offset)?; - Ok(bar.read32(Regs::COUNT)) + Ok(bar.read(regs::COUNT).into()) } fn config_space(pdev: &pci::Device<Bound>) { let config = pdev.config_space(); - // TODO: use the register!() macro for defining PCI configuration space registers once it - // has been move out of nova-core. + // Some PCI configuration space registers. + register! { + VENDOR_ID(u16) @ 0x0 { + 15:0 vendor_id; + } + + REVISION_ID(u8) @ 0x8 { + 7:0 revision_id; + } + + BAR(u32)[6] @ 0x10 { + 31:0 value; + } + } + dev_info!( pdev, "pci-testdev config space read8 rev ID: {:x}\n", - config.read8(0x8) + config.read(REVISION_ID).revision_id() ); dev_info!( pdev, "pci-testdev config space read16 vendor ID: {:x}\n", - config.read16(0) + config.read(VENDOR_ID).vendor_id() ); dev_info!( pdev, "pci-testdev config space read32 BAR 0: {:x}\n", - config.read32(0x10) + config.read(BAR::at(0)).value() ); } } @@ -111,7 +157,7 @@ impl pci::Driver for SampleDriver { pdev.set_master(); Ok(try_pin_init!(Self { - bar <- pdev.iomap_region_sized::<{ Regs::END }>(0, c"rust_driver_pci"), + bar <- pdev.iomap_region_sized::<{ regs::END }>(0, c"rust_driver_pci"), index: *info, _: { let bar = bar.access(pdev.as_ref())?; @@ -131,7 +177,7 @@ impl pci::Driver for SampleDriver { fn unbind(pdev: &pci::Device<Core>, this: Pin<&Self>) { if let Ok(bar) = this.bar.access(pdev.as_ref()) { // Reset pci-testdev by writing a new test index. - bar.write8(this.index.0, Regs::TEST); + bar.write_reg(regs::TEST::zeroed().with_index(this.index)); } } } diff --git a/samples/workqueue/stall_detector/Makefile b/samples/workqueue/stall_detector/Makefile new file mode 100644 index 000000000000..8849e85e95bb --- /dev/null +++ b/samples/workqueue/stall_detector/Makefile @@ -0,0 +1 @@ +obj-m += wq_stall.o diff --git a/samples/workqueue/stall_detector/wq_stall.c b/samples/workqueue/stall_detector/wq_stall.c new file mode 100644 index 000000000000..6f4a497b1881 --- /dev/null +++ b/samples/workqueue/stall_detector/wq_stall.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * wq_stall - Test module for the workqueue stall detector. + * + * Deliberately creates a workqueue stall so the watchdog fires and + * prints diagnostic output. Useful for verifying that the stall + * detector correctly identifies stuck workers and produces useful + * backtraces. + * + * The stall is triggered by clearing PF_WQ_WORKER before sleeping, + * which hides the worker from the concurrency manager. A second + * work item queued on the same pool then sits in the worklist with + * no worker available to process it. + * + * After ~30s the workqueue watchdog fires: + * BUG: workqueue lockup - pool cpus=N ... + * + * Build: + * make -C <kernel tree> M=samples/workqueue/stall_detector modules + * + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + * Copyright (c) 2026 Breno Leitao <leitao@debian.org> + */ + +#include <linux/module.h> +#include <linux/workqueue.h> +#include <linux/wait.h> +#include <linux/atomic.h> +#include <linux/sched.h> + +static DECLARE_WAIT_QUEUE_HEAD(stall_wq_head); +static atomic_t wake_condition = ATOMIC_INIT(0); +static struct work_struct stall_work1; +static struct work_struct stall_work2; + +static void stall_work2_fn(struct work_struct *work) +{ + pr_info("wq_stall: second work item finally ran\n"); +} + +static void stall_work1_fn(struct work_struct *work) +{ + pr_info("wq_stall: first work item running on cpu %d\n", + raw_smp_processor_id()); + + /* + * Queue second item while we're still counted as running + * (pool->nr_running > 0). Since schedule_work() on a per-CPU + * workqueue targets raw_smp_processor_id(), item 2 lands on the + * same pool. __queue_work -> kick_pool -> need_more_worker() + * sees nr_running > 0 and does NOT wake a new worker. + */ + schedule_work(&stall_work2); + + /* + * Hide from the workqueue concurrency manager. Without + * PF_WQ_WORKER, schedule() won't call wq_worker_sleeping(), + * so nr_running is never decremented and no replacement + * worker is created. Item 2 stays stuck in pool->worklist. + */ + current->flags &= ~PF_WQ_WORKER; + + pr_info("wq_stall: entering wait_event_idle (PF_WQ_WORKER cleared)\n"); + pr_info("wq_stall: expect 'BUG: workqueue lockup' in ~30-60s\n"); + wait_event_idle(stall_wq_head, atomic_read(&wake_condition) != 0); + + /* Restore so process_one_work() cleanup works correctly */ + current->flags |= PF_WQ_WORKER; + pr_info("wq_stall: woke up, PF_WQ_WORKER restored\n"); +} + +static int __init wq_stall_init(void) +{ + pr_info("wq_stall: loading\n"); + + INIT_WORK(&stall_work1, stall_work1_fn); + INIT_WORK(&stall_work2, stall_work2_fn); + schedule_work(&stall_work1); + + return 0; +} + +static void __exit wq_stall_exit(void) +{ + pr_info("wq_stall: unloading\n"); + atomic_set(&wake_condition, 1); + wake_up(&stall_wq_head); + flush_work(&stall_work1); + flush_work(&stall_work2); + pr_info("wq_stall: all work flushed, module unloaded\n"); +} + +module_init(wq_stall_init); +module_exit(wq_stall_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Reproduce workqueue stall caused by PF_WQ_WORKER misuse"); +MODULE_AUTHOR("Breno Leitao <leitao@debian.org>"); |
