mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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



      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®