From: Tristan de Cacqueray <tristan@midirus.com>
To: Takashi Iwai <tiwai@suse.de>, Miguel Ojeda <ojeda@kernel.org>
Cc: linux-sound@vger.kernel.org, rust-for-linux@vger.kernel.org,
linux-kernel@vger.kernel.org,
Tristan de Cacqueray <tristan@midirus.com>
Subject: [PATCH RFC 3/3] rust: snd: Add active rawmidi_substream helper
Date: Sun, 11 Oct 2026 13:02:27 +0000 [thread overview]
Message-ID: <20261011-rust-snd-v1-3-7974ecdcd345@midirus.com> (raw)
In-Reply-To: <20261011-rust-snd-v1-0-7974ecdcd345@midirus.com>
Add helper to share the substream across execution contexts in Rust.
Signed-off-by: Tristan de Cacqueray <tristan@midirus.com>
---
rust/kernel/snd.rs | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
samples/rust/rust_midi_device.rs | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 154 insertions(+), 6 deletions(-)
diff --git a/rust/kernel/snd.rs b/rust/kernel/snd.rs
index 7285575c6257..d9df5af155b9 100644
--- a/rust/kernel/snd.rs
+++ b/rust/kernel/snd.rs
@@ -7,13 +7,13 @@
//!
//! C header: [`include/sound/core.h`](srctree/include/sound/core.h)
-use core::marker::PhantomData;
+use core::{marker::PhantomData, ptr::NonNull};
use kernel::{
bindings,
device::Device,
error::{to_result, Result},
prelude::*,
- sync::Arc,
+ sync::{Arc, SpinLockIrq},
types::ForeignOwnable,
};
@@ -338,3 +338,66 @@ pub fn set_name(&mut self, name: &CStr) -> Result<()> {
copy_to_c_buf(&mut rmidi.name, name)
}
}
+
+/// A thread-safe helper for sharing ALSA substream across execution contexts.
+///
+/// # Safety
+///
+/// Implementations MUST clear any [`ActiveMidiSubstream`] before `close` returns.
+/// TODO: enforce in framework.
+#[pin_data(PinnedDrop)]
+pub struct ActiveMidiSubstream {
+ #[pin]
+ ptr: SpinLockIrq<Option<NonNull<bindings::snd_rawmidi_substream>>>,
+}
+
+impl ActiveMidiSubstream {
+ /// Creates a new initialiser for an empty substream.
+ pub fn new() -> impl PinInit<Self> {
+ pin_init!(Self {
+ ptr <- kernel::sync::new_spinlock_irq!(None),
+ })
+ }
+
+ /// Stores the given substream reference as the active target.
+ pub fn store(&self, substream: &RawMidiSubstream) {
+ let mut guard = self.ptr.lock();
+ *guard = NonNull::new(substream.ptr);
+ }
+
+ /// Clears the tracked substream reference.
+ pub fn clear(&self) {
+ let mut guard = self.ptr.lock();
+ *guard = None;
+ }
+
+ /// Executes a closure while holding the spinlock with a temporary reference
+ /// to the active substream, if present.
+ ///
+ /// Returns `None` if no stream is currently active.
+ pub fn with_active<F, R>(&self, f: F) -> Option<R>
+ where
+ F: FnOnce(&RawMidiSubstream) -> R,
+ {
+ let guard = self.ptr.lock();
+ let nn_ptr = (*guard)?;
+ // SAFETY: The raw ptr can only be stored using a valid rawmidi_substream.
+ let safe_substream = unsafe { RawMidiSubstream::from_raw(nn_ptr.as_ptr()) };
+
+ Some(f(&safe_substream))
+ }
+}
+
+#[pinned_drop]
+impl PinnedDrop for ActiveMidiSubstream {
+ fn drop(self: Pin<&mut Self>) {
+ self.clear()
+ }
+}
+
+// SAFETY: snd_rawmidi_substream is accessed only under the SpinLockIrq and it is
+// only ever valid between the ALSA `open` and `close` callbacks, during
+// which the ALSA core guarantees the substream is alive (see the type docs).
+unsafe impl Send for ActiveMidiSubstream {}
+// SAFETY: snd_rawmidi_substream is accessed only under the SpinLockIrq. See above.
+unsafe impl Sync for ActiveMidiSubstream {}
diff --git a/samples/rust/rust_midi_device.rs b/samples/rust/rust_midi_device.rs
index 1465ba9544d6..9c6a03baa42e 100644
--- a/samples/rust/rust_midi_device.rs
+++ b/samples/rust/rust_midi_device.rs
@@ -5,19 +5,88 @@
//! This driver demonstrates usage of the kernel::snd subsystem:
//! - Create a rawmidi device
//! - Print received event
+//! - Periodically send note events
+//!
+//! Its goal is to simulate a real device driver usage.
+use core::sync::atomic::{AtomicBool, Ordering};
use kernel::{
faux,
prelude::*,
snd,
sync::Arc,
+ workqueue,
+ workqueue::{DelayedWork, WorkItem},
};
-struct RustMidiDevice;
+// The internal state data to perform a hardware simulation.
+#[pin_data]
+struct RustMidiDevice {
+ // toggle sent note on and off.
+ note_playing: AtomicBool,
+ #[pin]
+ // substream to send note.
+ input_substream: snd::ActiveMidiSubstream,
+ #[pin]
+ // delayed work to send note periodically.
+ timer_work: DelayedWork<RustMidiDevice>,
+}
+
+kernel::impl_has_delayed_work! {
+ impl HasDelayedWork<Self> for RustMidiDevice { self.timer_work }
+}
+
+impl RustMidiDevice {
+ // Initialize the simulation internal state.
+ fn new() -> Result<Arc<Self>> {
+ Arc::pin_init(
+ pin_init!(RustMidiDevice {
+ note_playing: AtomicBool::new(false),
+ input_substream <- snd::ActiveMidiSubstream::new(),
+ timer_work <- kernel::new_delayed_work!("RustMidiDevice::timer_work"),
+ }),
+ GFP_KERNEL,
+ )
+ }
+}
+
+impl WorkItem for RustMidiDevice {
+ type Pointer = Arc<Self>;
+
+ // Send a note event, as-if coming from external interrupt.
+ fn run(this: Self::Pointer) {
+ this.input_substream.with_active(|substream| {
+ let playing = this.note_playing.fetch_not(Ordering::Relaxed);
+ let velocity = if playing { 0x0 } else { 0x7F };
+ let event = [0x90, 0x42, velocity];
+ match substream.receive(&event) {
+ Ok(count) => pr_info!("worker submitted {} ({:?})", count, event),
+ Err(err) => pr_err!("worker submit failed: {:?}", err),
+ };
+ });
+ pr_debug!("Periodic worker completed!");
+
+ // Re-enqueue continuously every 500 jiffies
+ let _ = workqueue::system().enqueue_delayed(this, 500);
+ }
+}
+// Implement midi input ops: store the substream for external context.
impl snd::RawMidiInput for RustMidiDevice {
- fn trigger(&self, _substream: &snd::RawMidiSubstream, up: bool) {
+ fn close(&self, _substream: &snd::RawMidiSubstream) -> Result<()> {
+ pr_debug!("input close!\n");
+ // note: maybe input_substream should be managed by the binding,
+ // otherwise the user may forget to call clear when the device is closed.
+ self.input_substream.clear();
+ Ok(())
+ }
+ fn trigger(&self, substream: &snd::RawMidiSubstream, up: bool) {
pr_debug!("input trigger up:{up}!\n");
+ if up {
+ self.input_substream.store(substream);
+ } else {
+ self.input_substream.clear();
+ }
}
}
@@ -37,7 +106,7 @@ fn trigger(&self, substream: &snd::RawMidiSubstream, up: bool) {
impl kernel::Module for RustMidiDeviceModule {
fn init(_module: &'static ThisModule) -> Result<Self> {
- let state = Arc::new(RustMidiDevice, GFP_KERNEL)?;
+ let state = RustMidiDevice::new()?;
pr_debug!("Initializing sound card\n");
let reg = faux::Registration::new(c"rust-midi-device", None)?;
@@ -57,14 +126,30 @@ fn init(_module: &'static ThisModule) -> Result<Self> {
// Register the sndcard to ALSA.
card.register()?;
- Ok(Self { _reg: reg })
+ pr_debug!("Kick off the background worker\n");
+ let _ = workqueue::system().enqueue_delayed(state.clone(), 0);
+
+ Ok(Self { _reg: reg, state })
}
}
struct RustMidiDeviceModule {
+ state: Arc<RustMidiDevice>,
_reg: faux::Registration,
}
+impl Drop for RustMidiDeviceModule {
+ fn drop(&mut self) {
+ // Safety: the timer_work is a valid delayed work. It seems necessary to cancel the work
+ // to avoid a panic when unloading the module. This is just for simulation purpose.
+ // TODO: add a safe cancel helper to kernel::workqueue and remove unsafe bindings here
+ unsafe {
+ let dwork_ptr = core::ptr::from_ref(&self.state.timer_work) as *mut _;
+ kernel::bindings::cancel_delayed_work_sync(dwork_ptr);
+ }
+ }
+}
+
module! {
type: RustMidiDeviceModule,
name: "rust_midi_device",
--
2.56.0
prev parent reply other threads:[~2026-10-11 13:02 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-11 13:02 [PATCH RFC 0/3] rust: snd: Introduce experimental ALSA rawmidi abstractions Tristan de Cacqueray
2026-10-11 13:02 ` [PATCH RFC 1/3] rust: snd: Add initial snd_card binding Tristan de Cacqueray
2026-10-11 13:02 ` [PATCH RFC 2/3] rust: snd: Add snd_rawmidi binding Tristan de Cacqueray
2026-10-11 13:02 ` Tristan de Cacqueray [this message]
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=20261011-rust-snd-v1-3-7974ecdcd345@midirus.com \
--to=tristan@midirus.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-sound@vger.kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tiwai@suse.de \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®