From: Danilo Krummrich <dakr@kernel.org>
To: tj@kernel.org, jiangshanlai@gmail.com, aliceryhl@google.com,
ojeda@kernel.org, boqun@kernel.org, gary@garyguo.net,
bjorn3_gh@protonmail.com, lossin@kernel.org,
a.hindborg@kernel.org, tmgross@umich.edu,
daniel.almeida@collabora.com, tamird@kernel.org,
acourbot@nvidia.com, work@onurozkan.dev, jhubbard@nvidia.com
Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
driver-core@lists.linux.dev, Danilo Krummrich <dakr@kernel.org>
Subject: [PATCH v2 6/6] rust: workqueue: add ScopedWork for non-'static work items
Date: Fri, 7 Aug 2026 18:52:49 +0200 [thread overview]
Message-ID: <20260807165252.3849875-7-dakr@kernel.org> (raw)
In-Reply-To: <20260807165252.3849875-1-dakr@kernel.org>
Add ScopedWork<T>, a work item wrapper whose destructor calls
cancel_work_sync(), allowing T to carry non-'static lifetimes. Ownership
of the data is not transferred to the workqueue; instead, the
synchronous cancellation on drop guarantees the work function is not
running when the data is freed.
ScopedWork uses the existing Work/HasWork/WorkItem infrastructure with
NonNull<ScopedWorkRef<T>> as WorkItem::Pointer for the callback path,
and implements RawWorkItem for &ScopedWork<T> and &ScopedWorkRef<T> for
the enqueue path (requiring T: Sync for cross-thread shared access
safety).
Two enqueue paths are provided:
- Queue::enqueue_scoped() (unsafe): the caller must ensure the work
item is not forgotten.
- ScopedQueue::enqueue() (safe): when the work item's lifetime
satisfies the queue's 'scope bound.
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
rust/kernel/workqueue/mod.rs | 46 +++-
rust/kernel/workqueue/scoped.rs | 442 +++++++++++++++++++++++++++++---
2 files changed, 444 insertions(+), 44 deletions(-)
diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs
index 551fa1401b85..be5bfb4cfc30 100644
--- a/rust/kernel/workqueue/mod.rs
+++ b/rust/kernel/workqueue/mod.rs
@@ -213,7 +213,13 @@
pub use self::builder::Builder;
mod scoped;
-pub use self::scoped::ScopedQueue;
+pub use self::scoped::{
+ new_scoped_work,
+ ScopedQueue,
+ ScopedWork,
+ ScopedWorkItem,
+ ScopedWorkRef, //
+};
/// Creates a [`Work`] initialiser with the given name and a newly-created lock class.
#[macro_export]
@@ -283,23 +289,39 @@ pub unsafe fn from_raw<'a>(ptr: *const bindings::workqueue_struct) -> &'a Queue
/// This may fail if the work item is already enqueued in a workqueue.
///
/// The work item will be submitted using `WORK_CPU_UNBOUND`.
+ #[inline]
pub fn enqueue<W, const ID: u64>(&self, w: W) -> W::EnqueueOutput
where
W: RawWorkItem<ID> + Send + 'static,
+ {
+ // SAFETY: `W: 'static` guarantees the work item remains valid indefinitely,
+ // so the `enqueue_scoped` requirement that the work item stays valid until
+ // the work function runs (or is cancelled) is trivially satisfied.
+ unsafe { self.enqueue_scoped(w) }
+ }
+
+ /// Enqueues a work item that may not be `'static`.
+ ///
+ /// Unlike [`Queue::enqueue`], this does not require the work item to be `'static`.
+ ///
+ /// The work item will be submitted using `WORK_CPU_UNBOUND`.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that the work item's destructor runs before any
+ /// lifetime it captures expires (i.e., the work item must not be forgotten).
+ #[inline]
+ pub unsafe fn enqueue_scoped<W, const ID: u64>(&self, w: W) -> W::EnqueueOutput
+ where
+ W: RawWorkItem<ID> + Send,
{
let queue_ptr = self.0.get();
- // SAFETY: We only return `false` if the `work_struct` is already in a workqueue. The other
- // `__enqueue` requirements are not relevant since `W` is `Send` and static.
- //
- // The call to `bindings::queue_work_on` will dereference the provided raw pointer, which
- // is ok because `__enqueue` guarantees that the pointer is valid for the duration of this
- // closure.
- //
- // Furthermore, if the C workqueue code accesses the pointer after this call to
- // `__enqueue`, then the work item was successfully enqueued, and `bindings::queue_work_on`
- // will have returned true. In this case, `__enqueue` promises that the raw pointer will
- // stay valid until we call the function pointer in the `work_struct`, so the access is ok.
+ // SAFETY: We only return `false` if the `work_struct` is already in a workqueue. The
+ // caller guarantees the work item remains valid until the work function runs or the item
+ // is cancelled, satisfying the `__enqueue` requirement that the pointer stays valid until
+ // the function pointer in the `work_struct` is called. `W: Send` satisfies the
+ // cross-thread safety requirement.
unsafe {
w.__enqueue(move |work_ptr| {
bindings::queue_work_on(
diff --git a/rust/kernel/workqueue/scoped.rs b/rust/kernel/workqueue/scoped.rs
index 18a4b6f6cf18..1adf96f7eccd 100644
--- a/rust/kernel/workqueue/scoped.rs
+++ b/rust/kernel/workqueue/scoped.rs
@@ -1,13 +1,95 @@
// SPDX-License-Identifier: GPL-2.0
-//! Lifetime-scoped workqueues.
+//! Lifetime-scoped workqueues and work items.
//!
-//! Provides [`ScopedQueue`] for work items that may borrow data with some
-//! non-`'static` lifetime.
+//! Provides [`ScopedQueue`] and [`ScopedWork`] for work items that may borrow
+//! data with some non-`'static` lifetime.
//!
-//! Unlike [`Queue`] which only accepts `'static` work items, [`ScopedQueue`]
-//! owns its underlying queue and relies on that queue being dropped to drain
-//! pending and running work before borrowed data can go out of scope.
+//! [`ScopedQueue`] owns its underlying queue and relies on that queue being
+//! dropped to drain pending and running work before borrowed data can go out
+//! of scope.
+//!
+//! [`ScopedWork`] wraps a work item whose destructor calls `cancel_work_sync()`,
+//! so ownership of the data is not transferred to the workqueue. This allows the
+//! inner data to carry non-`'static` lifetimes.
+//!
+//! Drivers should prefer [`ScopedWork`] with either a [`ScopedQueue`] or a
+//! system queue over [`Work`]-based items. When used with a [`ScopedQueue`],
+//! the work item must already outlive the queue, making [`Work`]'s separate
+//! allocation and reference count unnecessary.
+//!
+//! # Examples
+//!
+//! Enqueue on the system workqueue (unsafe, caller must not forget the work):
+//!
+//! ```
+//! # use kernel::time::{Delta, delay::fsleep};
+//! use kernel::workqueue::{
+//! self,
+//! new_scoped_work,
+//! ScopedWork,
+//! ScopedWorkItem,
+//! ScopedWorkRef,
+//! };
+//!
+//! struct MyWork {
+//! value: u32,
+//! }
+//!
+//! impl ScopedWorkItem for MyWork {
+//! fn run(work: &ScopedWorkRef<Self>) {
+//! pr_info!("value = {}\n", work.value);
+//! }
+//! }
+//!
+//! let work = KBox::pin_init(
+//! new_scoped_work!("MyWork", MyWork { value: 42 }),
+//! GFP_KERNEL,
+//! )?;
+//!
+//! // SAFETY: `work` is not forgotten.
+//! unsafe { workqueue::system_dfl().enqueue_scoped(&*work) };
+//! # fsleep(Delta::from_millis(100));
+//! # Ok::<(), Error>(())
+//! ```
+//!
+//! Enqueue on a [`ScopedQueue`] using the safe path (work outlives the queue):
+//!
+//! ```
+//! use kernel::workqueue::{
+//! new_scoped_work,
+//! ScopedQueue,
+//! ScopedWork,
+//! ScopedWorkItem,
+//! ScopedWorkRef,
+//! };
+//!
+//! struct MyWork {
+//! value: u32,
+//! }
+//!
+//! impl ScopedWorkItem for MyWork {
+//! fn run(work: &ScopedWorkRef<Self>) {
+//! pr_info!("value = {}\n", work.value);
+//! }
+//! }
+//!
+//! let work = KBox::pin_init(
+//! new_scoped_work!("MyWork", MyWork { value: 42 }),
+//! GFP_KERNEL,
+//! )?;
+//!
+//! // SAFETY: The queue is not forgotten.
+//! let queue = unsafe { ScopedQueue::new(c"example_wq")? };
+//!
+//! // Safe since `work` outlives `queue`.
+//! queue.enqueue(&*work);
+//! # Ok::<(), Error>(())
+//! ```
+//!
+//! [`ScopedQueue`] can also be used with regular [`Work`]-based items. The
+//! following `compile_fail` examples demonstrate the lifetime enforcement that
+//! [`ScopedQueue`] provides in that case.
//!
//! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
//! ```compile_fail,ignore
@@ -112,18 +194,30 @@
//! ```
use super::{
+ impl_has_work,
+ HasWork,
OwnedQueue,
Queue,
- RawWorkItem, //
+ RawWorkItem,
+ Work,
+ WorkItem,
+ WorkItemPointer, //
};
use crate::{
bindings,
- ffi,
- prelude::*, //
+ prelude::*,
+ sync::LockClassKey,
+ types::Opaque, //
};
-use core::marker::PhantomData;
+use pin_init::Wrapper;
+
+use core::{
+ marker::PhantomData,
+ ops::Deref,
+ ptr::NonNull, //
+};
/// An owned workqueue that can enqueue work items borrowing from `'scope`.
///
@@ -133,6 +227,15 @@ pub struct ScopedQueue<'scope> {
_scope: PhantomData<&'scope mut &'scope ()>,
}
+impl Deref for ScopedQueue<'_> {
+ type Target = Queue;
+
+ #[inline]
+ fn deref(&self) -> &Queue {
+ &self.inner
+ }
+}
+
impl<'scope> ScopedQueue<'scope> {
/// Creates an ordered scoped workqueue.
///
@@ -155,28 +258,11 @@ pub fn enqueue<W, const ID: u64>(&self, work: W) -> W::EnqueueOutput
where
W: RawWorkItem<ID> + Send + 'scope,
{
- let queue_ptr = self.inner.0.get();
-
- // SAFETY:
- // - Closure returns `false` only if `queue_work_on` returns `false`
- // and that means `work_ptr` is already in a workqueue.
- //
- // - `W: 'scope` and dropck keep borrowed data alive until this queue is
- // dropped. The constructor requires that the queue is not leaked and
- // dropping `inner` drains pending and running work so the function
- // pointer is not called after any lifetime in `W` expires.
- //
- // - The last requirement of `__enqueue` is not relevant here because `W`
- // is `Send`.
- unsafe {
- work.__enqueue(move |work_ptr| {
- bindings::queue_work_on(
- bindings::wq_misc_consts_WORK_CPU_UNBOUND as ffi::c_int,
- queue_ptr,
- work_ptr,
- )
- })
- }
+ // SAFETY: `W: 'scope` and dropck keep borrowed data alive until this queue
+ // is dropped. The constructor requires that the queue is not leaked and
+ // dropping `inner` drains pending and running work, so the function pointer
+ // is not called after any lifetime in `W` expires.
+ unsafe { self.enqueue_scoped(work) }
}
}
@@ -188,3 +274,295 @@ fn drop(&mut self) {
let _ = &self._scope;
}
}
+
+/// Trait for types that can be used as scoped work items.
+///
+/// Implementers define the work function that executes when the item is dequeued by a workqueue
+/// thread. The callback receives a reference to the containing [`ScopedWorkRef`], which provides
+/// access to the inner data via [`Deref`] and can be used to re-enqueue the work item.
+pub trait ScopedWorkItem: Sized {
+ /// Called when the work item is executed.
+ fn run(work: &ScopedWorkRef<Self>);
+}
+
+/// The work function's view of a [`ScopedWork`] item.
+///
+/// The work function callback receives `&ScopedWorkRef<T>`, which [`Deref`]s to `&T` and can be
+/// passed to queue enqueue methods for re-enqueueing from within the work function.
+#[pin_data]
+pub struct ScopedWorkRef<T: ScopedWorkItem> {
+ #[pin]
+ work: Work<Self>,
+ #[pin]
+ data: T,
+}
+
+impl_has_work! {
+ impl{T: ScopedWorkItem} HasWork<ScopedWorkRef<T>> for ScopedWorkRef<T> { self.work }
+}
+
+impl<T: ScopedWorkItem> Deref for ScopedWorkRef<T> {
+ type Target = T;
+
+ #[inline]
+ fn deref(&self) -> &T {
+ &self.data
+ }
+}
+
+impl<T: ScopedWorkItem> WorkItem for ScopedWorkRef<T> {
+ type Pointer = NonNull<Self>;
+
+ #[inline]
+ fn run(this: NonNull<Self>) {
+ // SAFETY: `this` points to a valid, pinned `ScopedWorkRef`. `cancel_work_sync()` in
+ // `ScopedWork`'s `PinnedDrop` prevents use-after-drop.
+ let work = unsafe { &*this.as_ptr() };
+
+ T::run(work);
+ }
+}
+
+// SAFETY: The `run` callback uses the `work_struct` pointer to recover a pointer to
+// `ScopedWorkRef<T>` via `HasWork`, wraps it in `NonNull`, and calls `WorkItem::run`.
+unsafe impl<T: ScopedWorkItem, const ID: u64> WorkItemPointer<ID> for NonNull<ScopedWorkRef<T>>
+where
+ ScopedWorkRef<T>: WorkItem<ID, Pointer = Self>,
+ ScopedWorkRef<T>: HasWork<ScopedWorkRef<T>, ID>,
+{
+ unsafe extern "C" fn run(ptr: *mut bindings::work_struct) {
+ let ptr = ptr.cast::<Work<ScopedWorkRef<T>, ID>>();
+
+ // SAFETY: The `work_struct` is embedded in `ScopedWorkRef<T>` via `HasWork`.
+ let ptr =
+ unsafe { <ScopedWorkRef<T> as HasWork<ScopedWorkRef<T>, ID>>::work_container_of(ptr) };
+
+ // SAFETY: `work_container_of` returns a valid, non-null pointer.
+ let nn = unsafe { NonNull::new_unchecked(ptr) };
+
+ <ScopedWorkRef<T> as WorkItem<ID>>::run(nn);
+ }
+}
+
+// Required because `WorkItemPointer<ID>: RawWorkItem<ID>` is a supertrait bound. This `__enqueue`
+// is never called; the enqueue path goes through the `RawWorkItem` impl for `&ScopedWork<T>` or
+// `&ScopedWorkRef<T>` instead.
+//
+// SAFETY: `__enqueue` is unreachable.
+unsafe impl<T: ScopedWorkItem, const ID: u64> RawWorkItem<ID> for NonNull<ScopedWorkRef<T>>
+where
+ ScopedWorkRef<T>: HasWork<ScopedWorkRef<T>, ID>,
+{
+ type EnqueueOutput = bool;
+
+ unsafe fn __enqueue<F>(self, _queue_work_on: F) -> Self::EnqueueOutput
+ where
+ F: FnOnce(*mut bindings::work_struct) -> bool,
+ {
+ unreachable!()
+ }
+}
+
+// SAFETY: `&ScopedWorkRef<T>` points to a valid `ScopedWorkRef` with a valid `work_struct`.
+// The pointer remains valid until `cancel_work_sync()` completes in `ScopedWork`'s drop.
+unsafe impl<'a, T: ScopedWorkItem + Sync, const ID: u64> RawWorkItem<ID> for &'a ScopedWorkRef<T>
+where
+ ScopedWorkRef<T>: HasWork<ScopedWorkRef<T>, ID>,
+{
+ type EnqueueOutput = bool;
+
+ unsafe fn __enqueue<F>(self, queue_work_on: F) -> Self::EnqueueOutput
+ where
+ F: FnOnce(*mut bindings::work_struct) -> bool,
+ {
+ let self_ptr = core::ptr::from_ref(self);
+
+ // SAFETY: `self_ptr` points to a valid `ScopedWorkRef` with a `Work` field.
+ let work_ptr = unsafe {
+ <ScopedWorkRef<T> as HasWork<ScopedWorkRef<T>, ID>>::raw_get_work(self_ptr.cast_mut())
+ };
+
+ // SAFETY: `work_ptr` points to a valid `Work`.
+ let work_ptr = unsafe { Work::raw_get(work_ptr) };
+
+ queue_work_on(work_ptr)
+ }
+}
+
+// SAFETY: `&ScopedWork<T>` accesses the inner `ScopedWorkRef` through `Opaque::get()`.
+// The pointer remains valid until `cancel_work_sync()` completes in `ScopedWork`'s drop.
+unsafe impl<'a, T: ScopedWorkItem + Sync, const ID: u64> RawWorkItem<ID> for &'a ScopedWork<T>
+where
+ ScopedWorkRef<T>: HasWork<ScopedWorkRef<T>, ID>,
+{
+ type EnqueueOutput = bool;
+
+ unsafe fn __enqueue<F>(self, queue_work_on: F) -> Self::EnqueueOutput
+ where
+ F: FnOnce(*mut bindings::work_struct) -> bool,
+ {
+ // SAFETY: The inner ScopedWorkRef is valid and initialized.
+ let inner: &ScopedWorkRef<T> = unsafe { &*self.inner.get() };
+
+ // SAFETY: Delegates to the `&ScopedWorkRef<T>` impl.
+ unsafe { inner.__enqueue(queue_work_on) }
+ }
+}
+
+/// A scoped work item that cancels synchronously on drop.
+///
+/// `ScopedWork<T>` contains a `work_struct` and the user data `T`. Its destructor calls
+/// `cancel_work_sync()`, guaranteeing the work function is not running when the data is dropped.
+///
+/// This allows `T` to carry non-`'static` lifetimes.
+///
+/// Construct via [`new_scoped_work!`] which returns an `impl PinInit` suitable for embedding
+/// in-place inside other pinned structs.
+///
+/// # Examples
+///
+/// Self-re-enqueueing from within the work function:
+///
+/// ```
+/// # use kernel::sync::atomic::{Atomic, Relaxed};
+/// # use kernel::time::{Delta, delay::fsleep};
+/// use kernel::workqueue::{
+/// new_scoped_work,
+/// Queue,
+/// ScopedQueue,
+/// ScopedWork,
+/// ScopedWorkItem,
+/// ScopedWorkRef,
+/// };
+///
+/// struct RequeueWork<'a> {
+/// counter: Atomic<u32>,
+/// queue: &'a Queue,
+/// }
+///
+/// impl ScopedWorkItem for RequeueWork<'_> {
+/// fn run(work: &ScopedWorkRef<Self>) {
+/// if work.counter.fetch_add(1u32, Relaxed) < 2 {
+/// // SAFETY: The `ScopedWork` is not forgotten.
+/// unsafe { work.queue.enqueue_scoped(work) };
+/// }
+/// }
+/// }
+///
+/// // SAFETY: The queue is not forgotten.
+/// let queue = unsafe { ScopedQueue::new(c"requeue_wq")? };
+///
+/// let work = KBox::pin_init(
+/// new_scoped_work!("RequeueWork", RequeueWork { counter: Atomic::new(0u32), queue: &queue }),
+/// GFP_KERNEL,
+/// )?;
+///
+/// // SAFETY: `work` is not forgotten.
+/// unsafe { queue.enqueue_scoped(&*work) };
+/// # fsleep(Delta::from_millis(300));
+///
+/// assert_eq!(work.counter.load(Relaxed), 3);
+/// # Ok::<(), Error>(())
+/// ```
+#[pin_data(PinnedDrop)]
+pub struct ScopedWork<T: ScopedWorkItem> {
+ #[pin]
+ inner: Opaque<ScopedWorkRef<T>>,
+}
+
+// SAFETY: `&ScopedWork<T>` only provides `&ScopedWorkRef<T>` (via `Deref`), which is safe to share
+// when `T: Sync`.
+unsafe impl<T: ScopedWorkItem + Sync> Sync for ScopedWork<T> {}
+
+// SAFETY: ScopedWork can be sent to another thread when T: Send.
+unsafe impl<T: ScopedWorkItem + Send> Send for ScopedWork<T> {}
+
+impl<T: ScopedWorkItem> Deref for ScopedWork<T> {
+ type Target = ScopedWorkRef<T>;
+
+ #[inline]
+ fn deref(&self) -> &ScopedWorkRef<T> {
+ // SAFETY: The inner `ScopedWorkRef` is always valid and initialized.
+ unsafe { &*self.inner.get() }
+ }
+}
+
+impl<T: ScopedWorkItem> ScopedWork<T> {
+ /// Creates a pin-initializer for a new scoped work item.
+ ///
+ /// Use [`new_scoped_work!`] to automatically provide the lock class key.
+ #[inline]
+ pub fn new<E>(
+ name: &'static CStr,
+ key: Pin<&'static LockClassKey>,
+ init: impl PinInit<T, E>,
+ ) -> impl PinInit<Self, Error>
+ where
+ Error: From<E>,
+ {
+ try_pin_init!(Self {
+ inner <- Opaque::pin_init(try_pin_init!(ScopedWorkRef::<T> {
+ work <- Work::new(name, key),
+ data <- init,
+ })),
+ })
+ }
+}
+
+#[pinned_drop]
+impl<T: ScopedWorkItem> PinnedDrop for ScopedWork<T> {
+ #[inline]
+ fn drop(self: Pin<&mut Self>) {
+ let inner = self.inner.get();
+
+ // SAFETY: `inner` points to a valid `ScopedWorkRef`. After `cancel_work_sync()` returns,
+ // the work function is guaranteed to not be running.
+ unsafe { bindings::cancel_work_sync(Work::raw_get(&raw const (*inner).work)) };
+ }
+}
+
+/// Creates a [`ScopedWork`] pin-initializer with a new lock class.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::workqueue::{
+/// new_scoped_work,
+/// ScopedWork,
+/// ScopedWorkItem,
+/// ScopedWorkRef,
+/// };
+///
+/// struct MyWork {
+/// value: u32,
+/// }
+///
+/// impl ScopedWorkItem for MyWork {
+/// fn run(work: &ScopedWorkRef<Self>) {
+/// pr_info!("value = {}\n", work.value);
+/// }
+/// }
+///
+/// #[pin_data]
+/// struct MyData {
+/// #[pin]
+/// work: ScopedWork<MyWork>,
+/// }
+///
+/// fn init_data() -> impl PinInit<MyData, Error> {
+/// try_pin_init!(MyData {
+/// work <- new_scoped_work!("MyWork", MyWork { value: 7 }),
+/// })
+/// }
+/// ```
+#[macro_export]
+macro_rules! new_scoped_work {
+ ($name:literal, $init:expr) => {
+ $crate::workqueue::ScopedWork::new(
+ $crate::c_str!($name),
+ $crate::static_lock_class!(),
+ $init,
+ )
+ };
+}
+pub use new_scoped_work;
--
2.55.0
next prev parent reply other threads:[~2026-08-07 16:53 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-07 16:52 [PATCH v2 0/6] workqueue: OwnedQueue, ScopedQueue and ScopedWork Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 1/6] rust: workqueue: replace deprecated system_wq with system_{percpu,dfl}_wq Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 2/6] rust: workqueue: restrict delayed work to global wqs Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 3/6] rust: workqueue: create workqueue subdirectory Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 4/6] rust: workqueue: add creation of workqueues Danilo Krummrich
2026-08-07 22:39 ` Danilo Krummrich
2026-08-07 16:52 ` [PATCH v2 5/6] rust: workqueue: add ScopedQueue for lifetime bound items Danilo Krummrich
2026-08-07 16:52 ` Danilo Krummrich [this message]
2026-08-07 18:35 ` [PATCH v2 6/6] rust: workqueue: add ScopedWork for non-'static work items Danilo Krummrich
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260807165252.3849875-7-dakr@kernel.org \
--to=dakr@kernel.org \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=driver-core@lists.linux.dev \
--cc=gary@garyguo.net \
--cc=jhubbard@nvidia.com \
--cc=jiangshanlai@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tj@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.