From nobody Tue Jun 16 20:36:47 2026 Received: from mail-qk1-f172.google.com (mail-qk1-f172.google.com [209.85.222.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0EF5519F11B for ; Tue, 21 Apr 2026 06:38:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776753520; cv=none; b=ldzPfqBIGUXWvROJ24/XAnyBzvjyzUlkoqvvIZcqisE8brdv6m47jaXQ0Rqi1vKzQpP31xxVrF3Eah3fkxXEntDRJd8kAallX19hw3tcmx7mfW48Gs532BurA9pB6AHOo6iiQ+TacmnmLoUJuvxVJosc6QF9U8rQ+OwW8jTp8GM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776753520; c=relaxed/simple; bh=xg/ZByNETLB0huMMbq+kZi2lpGoptgsURjwnYauWkK0=; h=From:To:Cc:Subject:Date:Message-Id:MIME-Version; b=IxlH7pP09R6g+buvQv6k9z5FuePN6e4wTYSUaEVc1bozta3ed3Ynj09FUsOYXyrZA2tOAMj7MBmvxTWfjiLrf3T++JVDwvXQQzyrM6FGSGJQPh4bCeP90FKItGcag1HFp4CJAWgYMLgPdz3kv68ZvAYuf9v/tr9T074OQsl5BAM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=fA9Hw4g/; arc=none smtp.client-ip=209.85.222.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="fA9Hw4g/" Received: by mail-qk1-f172.google.com with SMTP id af79cd13be357-8d65f4073bfso535099985a.3 for ; Mon, 20 Apr 2026 23:38:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776753518; x=1777358318; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=LmwvOTrvTqLdfW0hMkYtXjjgzkWWT2XP1JL6i/qfBmI=; b=fA9Hw4g/+jat8cDQqltPP307VJB7Bh8xetrL5jtjdePjU8/w7dHH/e0+96bfrjX+bx mbTJSKXYyHIIn1S01oPpVWFZD5Z+XaR/C9lR4Za1RjamI3wAEfqo7z9n6N5OQQvUmyel SnzxE8sYB8rlP2aHaucQd229Njlk6aLrs9Ys85O2zGmNID3w3b6iBvlpX4LQLZlAnxF+ 2Vb2OPqVN4bXOHqajLQUa8kosjgyFum960dMgOw/RDtk+yy6mKF73NnuTNnc8pmpimz8 YaONTAxxw6XRUB3FNmrmb+KpUlyG0qNvtFSCRkvEuUhVJuct1PTDroBVEdrRZ/wZI/D6 Y1cg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776753518; x=1777358318; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=LmwvOTrvTqLdfW0hMkYtXjjgzkWWT2XP1JL6i/qfBmI=; b=HjqP5M3Jp/ms4X7+UN9v8x7uRQE4IjH8+2JVS9FSY0OERewsL1/0IukOC8Z6W1sqP6 oQt9RVUk1CtUr4otlIdgrwNW2gFcQ9IxUGkUcY4eIBosLu0CGhPAp5hImMm2Are3MO6T kQjiLNReq5n82yUjr5qY6ME5ZCDFCQaBEenvQ90RZDfzG5H7d5ou9SzUnhVO4Vk7Cuab wzjQ75jP/GLDgARvjSse+OeOpycsxTC+9vF8ovu/kVgvEZP8HJ+JlIux9gGi1RW+8E4/ 4GNEwQLgYaql2thJFbML06Mc+U3D6XjxNCLNLzcOsP+EvhK5NoTBLduXv8uQ7Tf48p7y NmMA== X-Forwarded-Encrypted: i=1; AFNElJ+Vu5YtVUHLtWBXcbH5bf6EO5+smPIkZaxVLyYJyRx84GRhSaoiL3Nuj74QI78c95dyu1qZAUuX2TVbEIA=@vger.kernel.org X-Gm-Message-State: AOJu0YzLbEGuB0qHFHsMemPf/zcb7HvqvwvVO9y1sOwpr8/d0kl5YFOb /X2gig3TS7oXLdOw5xJcMD0ao0iU4ebIUAWoWqxDo0RgruxdktRGJBSn X-Gm-Gg: AeBDietkQ/tVv8N55iwFAk+qXvoREfCXRJ5fFbpO0TvCnxv4Pxx1yVGPfow+5Rc7MTZ tSzbG6F4E0gA/209byQbBrr23PEsQEabITm0DGxfHfLP6fuPj6x8XnAgVu6+emR2YYx/QB1hjmJ 5gi1mXRUaxShy4AAbcWf7kOW3E6BuOZv7aQuuafysfFAykwhqRYVgrA+5kPiImKAsU8B0bDNuc0 gF4kCnWoifFM+B/gTL+Yd26m1am7GSy2vrSOwx5VWIAAl4P+0TdkH6WW9gHFkQqsgOkrtiVvJWN SQHGhh16pz6UKuFuRPdNo6TRj3jTdGSp0Vhe8x6aH8wxgqYW6IAPNnLCMO49AUyKkPwNGJeVQBD WFLBuTESutxDiqov6SX8Qyufy9xcf42IWfkQ8Cvl+zEQ/dP85Tf2y6a3i0wDZgIZCfgoBYKf80h lIoxi8qfSoJh9JVJflpGkjpnNw6Mm43St+Ayx5hho2tqkAPvcpLZQUOZsbo3toVF/ubEfvSaGFU QhcJwk7qEHPy7+rxgpASSGyqOmjfAmjsQx30E0= X-Received: by 2002:a05:620a:198a:b0:8cd:d872:c2c9 with SMTP id af79cd13be357-8e791d8a342mr2215133185a.50.1776753518062; Mon, 20 Apr 2026 23:38:38 -0700 (PDT) Received: from TDC4045031631.e0cglfehwr0e5gttmepj3hi3hf.ux.internal.cloudapp.net ([20.63.37.123]) by smtp.gmail.com with ESMTPSA id af79cd13be357-8e7d5fe9800sm999108185a.5.2026.04.20.23.38.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 20 Apr 2026 23:38:37 -0700 (PDT) From: Ashutosh Desai To: Miguel Ojeda Cc: Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Ashutosh Desai Subject: [PATCH] rust: add task_work abstraction Date: Tue, 21 Apr 2026 06:38:36 +0000 Message-Id: <20260421063836.742965-1-ashutoshdesai993@gmail.com> X-Mailer: git-send-email 2.34.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The kernel's task_work API (include/linux/task_work.h) allows any kernel thread to queue a callback that runs on a specific task the next time it returns to user space (or on exit). The C interface requires manual lifetime management of the callback_head allocation and careful ordering of init_task_work() and task_work_add(). Add a safe Rust abstraction consisting of: - TaskWork: an owned, heap-allocated work item. Allocating with TaskWork::new() ties a user-supplied value T (bound by the new TaskWorkItem trait) to a callback_head. Scheduling with TaskWork::add() initializes the callback, transfers ownership to the kernel, and returns Err((ESRCH, data)) if the target task is already exiting, so callers can always recover the value. - TaskWorkItem: a trait for types that can be scheduled as a task work item. Implementors provide a run() method that receives the inner value by move when the callback fires. - NotifyMode: a type-safe enum wrapping task_work_notify_mode, covering TWA_NONE, TWA_RESUME, TWA_SIGNAL, TWA_SIGNAL_NO_IPI, and TWA_NMI_CURRENT. The repr(C) layout of the internal TaskWorkInner places callback_head at offset 0, which lets the C callback pointer be cast back to the full allocation. Option in that struct permits moving the data out before dropping the allocation without a Drop impl on TaskWork. Signed-off-by: Ashutosh Desai --- rust/kernel/lib.rs | 1 + rust/kernel/task_work.rs | 158 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 rust/kernel/task_work.rs diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index d93292d47420..22878fd73528 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -154,6 +154,7 @@ pub mod str; pub mod sync; pub mod task; +pub mod task_work; pub mod time; pub mod tracepoint; pub mod transmute; diff --git a/rust/kernel/task_work.rs b/rust/kernel/task_work.rs new file mode 100644 index 000000000000..59635d5fed13 --- /dev/null +++ b/rust/kernel/task_work.rs @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Ashutosh Desai + +//! Task work. +//! +//! Wraps the kernel's task work API, which lets you schedule a callback t= o run +//! on a task the next time it returns to userspace. +//! +//! The entry point is [`TaskWork`]. Allocate one with [`TaskWork::new`] a= nd +//! schedule it with [`TaskWork::add`]. On success the kernel takes owners= hip +//! and calls [`TaskWorkItem::run`] when the task returns to userspace. +//! +//! C header: [`include/linux/task_work.h`](srctree/include/linux/task_wor= k.h) + +use crate::{ + alloc::{AllocError, Flags, KBox}, + bindings, + error::{code::ESRCH, Error}, + task::Task, + types::Opaque, +}; + +/// Notification mode passed to [`TaskWork::add`]. +/// +/// Controls how the target task is woken after the work item is queued. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum NotifyMode { + /// No extra wakeup; the work runs when the task returns to userspace = naturally. + None, + /// Resume the task if it is sleeping in a restartable syscall. + Resume, + /// Send a signal to force the task out of the current syscall. + Signal, + /// Like [`NotifyMode::Signal`] but without an inter-processor interru= pt when + /// the target is running on a remote CPU. + SignalNoIpi, + /// Run the work immediately on the current CPU from NMI context. Only= valid + /// when scheduling on the current task from an NMI handler. + NmiCurrent, +} + +impl NotifyMode { + fn as_raw(self) -> bindings::task_work_notify_mode { + match self { + NotifyMode::None =3D> bindings::task_work_notify_mode_TWA_NONE, + NotifyMode::Resume =3D> bindings::task_work_notify_mode_TWA_RE= SUME, + NotifyMode::Signal =3D> bindings::task_work_notify_mode_TWA_SI= GNAL, + NotifyMode::SignalNoIpi =3D> bindings::task_work_notify_mode_T= WA_SIGNAL_NO_IPI, + NotifyMode::NmiCurrent =3D> bindings::task_work_notify_mode_TW= A_NMI_CURRENT, + } + } +} + +/// Implemented by types that can be scheduled as a task work item. +/// +/// The implementing type is heap-allocated by [`TaskWork::new`] and passe= d by +/// value to [`run`] when the target task returns to userspace. Implemento= rs +/// must be [`Send`] because the callback can run on any CPU. +/// +/// [`run`]: TaskWorkItem::run +pub trait TaskWorkItem: Sized + Send { + /// Called when the target task returns to userspace. + fn run(this: Self); +} + +// The kernel's `struct callback_head` is the first field so that a +// `*mut callback_head` handed back by the kernel can be cast directly to +// `*mut TaskWorkInner`. +#[repr(C)] +struct TaskWorkInner { + callback_head: Opaque, + // Wrapped in Option so that we can move T out before the allocation is + // freed, without needing a Drop impl on TaskWork (which would block + // moving self.inner in add()). + data: Option, +} + +/// A heap-allocated task work item ready to be scheduled on a [`Task`]. +/// +/// Dropping this before calling [`add`] drops the inner value normally. +/// +/// # C counterpart +/// +/// Wraps `struct callback_head` from ``. +/// +/// [`add`]: TaskWork::add +#[doc(alias =3D "callback_head")] +pub struct TaskWork { + inner: KBox>, +} + +impl TaskWork { + /// Allocates a new task work item wrapping `data`. + pub fn new(data: T, flags: Flags) -> Result { + Ok(Self { + inner: KBox::new( + TaskWorkInner { + callback_head: Opaque::uninit(), + data: Some(data), + }, + flags, + )?, + }) + } + + /// Schedules this item to run when `task` next returns to userspace. + /// + /// On success, ownership passes to the kernel and [`TaskWorkItem::run= `] will + /// be called with the inner data when the task returns to userspace o= r exits. + /// + /// On failure (`ESRCH`), the task is exiting and the inner data is re= turned + /// so the caller can clean up. + pub fn add(self, task: &Task, mode: NotifyMode) -> Result<(), (Error, = T)> { + let inner =3D KBox::into_raw(self.inner); + + // SAFETY: We have exclusive access to the allocation and are writ= ing + // the callback pointer before passing it to the kernel. + unsafe { + bindings::init_task_work( + Opaque::cast_into(core::ptr::addr_of!((*inner).callback_he= ad)), + Some(run_callback::), + ); + } + + // SAFETY: The callback_head is initialized above and sits at offs= et 0 + // of the repr(C) TaskWorkInner. On success the kernel owns the + // allocation until run_callback fires; on failure we reclaim it b= elow. + let ret =3D unsafe { + bindings::task_work_add( + task.as_ptr(), + Opaque::cast_into(core::ptr::addr_of!((*inner).callback_he= ad)), + mode.as_raw(), + ) + }; + + if ret !=3D 0 { + // SAFETY: task_work_add failed so we still own the allocation. + let mut boxed =3D unsafe { KBox::from_raw(inner) }; + let data =3D boxed.data.take().expect("data present before add= "); + return Err((ESRCH, data)); + } + + Ok(()) + } +} + +// SAFETY: cb points at the callback_head field of a TaskWorkInner allo= cated +// by TaskWork::new and successfully handed to task_work_add. The kernel g= ives us +// back exclusive ownership of the allocation here. +unsafe extern "C" fn run_callback(cb: *mut bindings::call= back_head) { + // SAFETY: callback_head is at offset 0 of the repr(C) TaskWorkInner and + // Opaque is repr(transparent), so the cast is valid. + let mut inner =3D unsafe { KBox::from_raw(cb.cast::>(= )) }; + let data =3D inner.data.take().expect("data present in callback"); + drop(inner); + T::run(data); +} base-commit: 7f87a5ea75f011d2c9bc8ac0167e5e2d1adb1594 prerequisite-patch-id: 161541863a5d4d8c2a41c6f5cfdf712463bf50c9 prerequisite-patch-id: 3b286037e1aeb5a942ff450ced7ec52048bfea7a prerequisite-patch-id: 14ac8c330c1207cbba2096c4771e000ae82bb765 --=20 2.34.1