* [PATCH RFC 1/3] rust: snd: Add initial snd_card binding
2026-10-11 13:02 [PATCH RFC 0/3] rust: snd: Introduce experimental ALSA rawmidi abstractions Tristan de Cacqueray
@ 2026-10-11 13:02 ` 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 ` [PATCH RFC 3/3] rust: snd: Add active rawmidi_substream helper Tristan de Cacqueray
2 siblings, 0 replies; 4+ messages in thread
From: Tristan de Cacqueray @ 2026-10-11 13:02 UTC (permalink / raw)
To: Takashi Iwai, Miguel Ojeda
Cc: linux-sound, rust-for-linux, linux-kernel, Tristan de Cacqueray
Add minimal abstraction to create a snd_card in Rust.
Signed-off-by: Tristan de Cacqueray <tristan@midirus.com>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/lib.rs | 2 ++
rust/kernel/snd.rs | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
samples/rust/Kconfig | 11 +++++++++++
samples/rust/Makefile | 1 +
samples/rust/rust_midi_device.rs | 41 +++++++++++++++++++++++++++++++++++++++++
6 files changed, 145 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index c706dda5b5ca..c624f073db04 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -97,6 +97,7 @@
#include <linux/xarray.h>
#include <net/genetlink.h>
#include <net/netlink.h>
+#include <sound/core.h>
#include <trace/events/rust_sample.h>
/*
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 7225abc64084..974b3e5ce7ac 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -130,6 +130,8 @@
#[cfg(CONFIG_RUST_SERIAL_DEV_BUS_ABSTRACTIONS)]
pub mod serdev;
pub mod sizes;
+#[cfg(CONFIG_SND)]
+pub mod snd;
#[cfg(CONFIG_SOC_BUS)]
pub mod soc;
#[doc(hidden)]
diff --git a/rust/kernel/snd.rs b/rust/kernel/snd.rs
new file mode 100644
index 000000000000..aaffe942b360
--- /dev/null
+++ b/rust/kernel/snd.rs
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (C) 2026 Tristan de Cacqueray
+
+//! Abstractions for the snd subsystem.
+//!
+//! This module provides bindings for working with ALSA in kernel modules.
+//!
+//! C header: [`include/sound/core.h`](srctree/include/sound/core.h)
+
+use kernel::{
+ bindings,
+ device::Device,
+ error::{Result},
+ prelude::*,
+};
+
+/// A sound card instance.
+pub struct SoundCard {
+ ptr: *mut bindings::snd_card,
+}
+
+impl SoundCard {
+ /// Creates a new sound card bound to a device lifecycle
+ pub fn new(dev: &Device, id: &CStr, module: &'static kernel::ThisModule) -> Result<Self> {
+ let mut card_ptr: *mut bindings::snd_card = core::ptr::null_mut();
+
+ // SAFETY: C helper copies the arguments.
+ let ret = unsafe {
+ bindings::snd_devm_card_new(
+ dev.as_raw(),
+ -1,
+ id.as_char_ptr(),
+ module.as_ptr(),
+ 0,
+ &mut card_ptr,
+ )
+ };
+
+ if ret < 0 {
+ return Err(Error::from_errno(ret));
+ }
+
+ Ok(Self { ptr: card_ptr })
+ }
+
+ /// Sets the driver field of the sound card.
+ pub fn set_driver(&mut self, name: &CStr) -> Result<()> {
+ // SAFETY: `self.ptr` is valid and points to an initialized `snd_card`.
+ let card = unsafe { &mut *self.ptr };
+ copy_to_c_buf(&mut card.driver, name)
+ }
+
+ /// Sets the shortname field of the sound card.
+ pub fn set_shortname(&mut self, name: &CStr) -> Result<()> {
+ // SAFETY: `self.ptr` is valid and points to an initialized `snd_card`.
+ let card = unsafe { &mut *self.ptr };
+ copy_to_c_buf(&mut card.shortname, name)
+ }
+
+ /// Triggers registration of the card components to the user-space ALSA layers
+ pub fn register(self) -> Result<RegisteredSoundCard> {
+ // SAFETY: `self.ptr` is valid and points to an initialized `snd_card`.
+ let ret = unsafe { bindings::snd_card_register(self.ptr) };
+ if ret < 0 {
+ return Err(Error::from_errno(ret));
+ }
+ Ok(RegisteredSoundCard { _inner: self })
+ }
+}
+
+/// Helper to copy `src` (including its NUL) into the fixed-size C buffer `dest`.
+/// Returns `EINVAL` if `src` does not fit.
+fn copy_to_c_buf(dest: &mut [ffi::c_char], src: &CStr) -> Result<()> {
+ let bytes = src.to_bytes_with_nul();
+ if bytes.len() > dest.len() {
+ return Err(EINVAL);
+ }
+ let (used, rest) = dest.split_at_mut(bytes.len());
+ for (d, &b) in used.iter_mut().zip(bytes) {
+ *d = b as ffi::c_char;
+ }
+ rest.fill(0);
+ Ok(())
+}
+
+/// A sound card that is already registered.
+pub struct RegisteredSoundCard {
+ _inner: SoundCard,
+}
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index 31d62533ef25..1cf8cf18cd2d 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -31,6 +31,17 @@ config SAMPLE_RUST_MINIMAL
If unsure, say N.
+config SAMPLE_RUST_MIDI_DEVICE
+ tristate "MIDI device"
+ depends on SND_RAWMIDI
+ help
+ This option builds the Rust MIDI device.
+
+ To compile this as a module, choose M here:
+ the module will be called rust_midi_device.
+
+ If unsure, say N.
+
config SAMPLE_RUST_MISC_DEVICE
tristate "Misc device"
help
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index b986b681cde5..170b7e3e5590 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -2,6 +2,7 @@
ccflags-y += -I$(src) # needed for trace events
obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o
+obj-$(CONFIG_SAMPLE_RUST_MIDI_DEVICE) += rust_midi_device.o
obj-$(CONFIG_SAMPLE_RUST_MISC_DEVICE) += rust_misc_device.o
obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o
obj-$(CONFIG_SAMPLE_RUST_DEBUGFS) += rust_debugfs.o
diff --git a/samples/rust/rust_midi_device.rs b/samples/rust/rust_midi_device.rs
new file mode 100644
index 000000000000..5561a0437b59
--- /dev/null
+++ b/samples/rust/rust_midi_device.rs
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust MIDI minimal sample.
+
+use kernel::{
+ faux,
+ prelude::*,
+ snd,
+};
+
+impl kernel::Module for RustMidiDeviceModule {
+ fn init(_module: &'static ThisModule) -> Result<Self> {
+ pr_debug!("Initializing sound card\n");
+ let reg = faux::Registration::new(c"rust-midi-device", None)?;
+ let fdev = reg.as_ref();
+ let mut card = snd::SoundCard::new(
+ fdev.as_ref(),
+ c"RustMIDI",
+ kernel::module::this_module::<RustMidiDeviceModule>(),
+ )?;
+ card.set_driver(c"rust-midi")?;
+ card.set_shortname(c"RustMIDI")?;
+
+ // Register the sndcard to ALSA.
+ card.register()?;
+
+ Ok(Self { _reg: reg })
+ }
+}
+
+struct RustMidiDeviceModule {
+ _reg: faux::Registration,
+}
+
+module! {
+ type: RustMidiDeviceModule,
+ name: "rust_midi_device",
+ authors: ["Rust for Linux Contributors"],
+ description: "Rust MIDI device sample",
+ license: "GPL",
+}
--
2.56.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH RFC 2/3] rust: snd: Add snd_rawmidi binding
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 ` Tristan de Cacqueray
2026-10-11 13:02 ` [PATCH RFC 3/3] rust: snd: Add active rawmidi_substream helper Tristan de Cacqueray
2 siblings, 0 replies; 4+ messages in thread
From: Tristan de Cacqueray @ 2026-10-11 13:02 UTC (permalink / raw)
To: Takashi Iwai, Miguel Ojeda
Cc: linux-sound, rust-for-linux, linux-kernel, Tristan de Cacqueray
Add minimal abstraction to create a snd_rawmidi along with
the input/output ops in Rust.
Signed-off-by: Tristan de Cacqueray <tristan@midirus.com>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/snd.rs | 253 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
samples/rust/rust_midi_device.rs | 33 +++++++++++++
3 files changed, 286 insertions(+), 1 deletion(-)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index c624f073db04..71999651aebf 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -98,6 +98,7 @@
#include <net/genetlink.h>
#include <net/netlink.h>
#include <sound/core.h>
+#include <sound/rawmidi.h>
#include <trace/events/rust_sample.h>
/*
diff --git a/rust/kernel/snd.rs b/rust/kernel/snd.rs
index aaffe942b360..7285575c6257 100644
--- a/rust/kernel/snd.rs
+++ b/rust/kernel/snd.rs
@@ -7,11 +7,14 @@
//!
//! C header: [`include/sound/core.h`](srctree/include/sound/core.h)
+use core::marker::PhantomData;
use kernel::{
bindings,
device::Device,
- error::{Result},
+ error::{to_result, Result},
prelude::*,
+ sync::Arc,
+ types::ForeignOwnable,
};
/// A sound card instance.
@@ -87,3 +90,251 @@ fn copy_to_c_buf(dest: &mut [ffi::c_char], src: &CStr) -> Result<()> {
pub struct RegisteredSoundCard {
_inner: SoundCard,
}
+
+/// A rawmidi substream instance. Must not be shared outside of the RawMidiInput/Output operations.
+pub struct RawMidiSubstream {
+ ptr: *mut bindings::snd_rawmidi_substream,
+}
+
+impl RawMidiSubstream {
+ /// # Safety
+ ///
+ /// The caller must ensure that `ptr` is a valid pointer provided by the ALSA core.
+ unsafe fn from_raw(ptr: *mut bindings::snd_rawmidi_substream) -> Self {
+ Self { ptr }
+ }
+
+ /// Copies data from hardware to the ALSA core input ring buffer.
+ ///
+ /// Returns the number of bytes successfully copied
+ pub fn receive(&self, buffer: &[u8]) -> Result<usize> {
+ let count: c_int = buffer.len().try_into().map_err(|_| EINVAL)?;
+ // SAFETY: `self.ptr` is valid and the buffer length matched the buffer ptr.
+ let ret = unsafe { bindings::snd_rawmidi_receive(self.ptr, buffer.as_ptr(), count) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ Ok(ret as usize)
+ }
+ }
+
+ /// Retrieves data from the ALSA core output ring buffer to be transmitted to hardware.
+ ///
+ /// Returns the number of bytes successfully copied into `buffer`.
+ pub fn transmit(&self, buffer: &mut [u8]) -> Result<usize> {
+ let count: c_int = buffer.len().try_into().map_err(|_| EINVAL)?;
+ // SAFETY: `self.ptr` is valid between `open` and `close`.
+ // C writes at most `count` bytes into the `buffer`.
+ let ret = unsafe { bindings::snd_rawmidi_transmit(self.ptr, buffer.as_mut_ptr(), count) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ Ok(ret as usize)
+ }
+ }
+}
+
+/// Operations for a MIDI input stream.
+pub trait RawMidiInput: Send + Sync {
+ /// Call when the input substream is opened.
+ fn open(&self, _substream: &RawMidiSubstream) -> Result<()> {
+ Ok(())
+ }
+ /// Called when the input substream is closed.
+ fn close(&self, _substream: &RawMidiSubstream) -> Result<()> {
+ Ok(())
+ }
+ /// Called to start or stop reading input data.
+ fn trigger(&self, _substream: &RawMidiSubstream, _up: bool);
+}
+
+/// Operations for a MIDI output stream.
+pub trait RawMidiOutput: Send + Sync {
+ /// Called when the output substream is opened.
+ fn open(&self, _substream: &RawMidiSubstream) -> Result<()> {
+ Ok(())
+ }
+ /// Called when the output substream is closed.
+ fn close(&self, _substream: &RawMidiSubstream) -> Result<()> {
+ Ok(())
+ }
+ /// Called to start or stop transmitting output data.
+ fn trigger(&self, _substream: &RawMidiSubstream, _up: bool);
+}
+
+struct RawMidiOps<T>(PhantomData<T>);
+impl<T: 'static> RawMidiOps<T> {
+ /// # Safety
+ ///
+ /// rawmidi must contain Arc<T> in private_data.
+ unsafe extern "C" fn private_free_cb(rmidi: *mut bindings::snd_rawmidi) {
+ // SAFETY: paired with `into_foreign` in `new`.
+ drop::<Arc<T>>(unsafe { Arc::from_foreign((*rmidi).private_data) });
+ }
+}
+
+impl<T: RawMidiInput + 'static> RawMidiOps<T> {
+ const INPUT: bindings::snd_rawmidi_ops = bindings::snd_rawmidi_ops {
+ open: Some(Self::open_input),
+ close: Some(Self::close_input),
+ trigger: Some(Self::trigger_input),
+ drain: None,
+ };
+
+ /// # SAFETY
+ ///
+ /// substream must contain Arc<T> in private_data.
+ unsafe extern "C" fn open_input(s: *mut bindings::snd_rawmidi_substream) -> c_int {
+ with_ctx_err::<T>(s, |dev, sub| T::open(dev, sub))
+ }
+
+ /// # SAFETY
+ ///
+ /// substream must contain Arc<T> in private_data.
+ unsafe extern "C" fn close_input(s: *mut bindings::snd_rawmidi_substream) -> c_int {
+ with_ctx_err::<T>(s, |dev, sub| T::close(dev, sub))
+ }
+
+ /// # SAFETY
+ ///
+ /// substream must contain Arc<T> in private_data.
+ unsafe extern "C" fn trigger_input(s: *mut bindings::snd_rawmidi_substream, up: c_int) {
+ with_ctx::<T, _>(s, |dev, sub| T::trigger(dev, sub, up != 0))
+ }
+}
+
+impl<T: RawMidiOutput + 'static> RawMidiOps<T> {
+ const OUTPUT: bindings::snd_rawmidi_ops = bindings::snd_rawmidi_ops {
+ open: Some(Self::open_output),
+ close: Some(Self::close_output),
+ trigger: Some(Self::trigger_output),
+ drain: None,
+ };
+
+ /// # SAFETY
+ ///
+ /// substream must contain Arc<T> in private_data.
+ unsafe extern "C" fn open_output(s: *mut bindings::snd_rawmidi_substream) -> c_int {
+ with_ctx_err::<T>(s, |dev, sub| T::open(dev, sub))
+ }
+
+ /// # SAFETY
+ ///
+ /// substream must contain Arc<T> in private_data.
+ unsafe extern "C" fn close_output(s: *mut bindings::snd_rawmidi_substream) -> c_int {
+ with_ctx_err::<T>(s, |dev, sub| T::close(dev, sub))
+ }
+
+ /// # SAFETY
+ ///
+ /// substream must contain Arc<T> in private_data.
+ unsafe extern "C" fn trigger_output(s: *mut bindings::snd_rawmidi_substream, up: c_int) {
+ with_ctx::<T, _>(s, |dev, sub| T::trigger(dev, sub, up != 0))
+ }
+}
+
+// Helper for C FFI
+fn with_ctx_err<T: 'static>(
+ s: *mut bindings::snd_rawmidi_substream,
+ f: impl FnOnce(&T, &RawMidiSubstream) -> Result,
+) -> c_int {
+ with_ctx::<T, _>(s, |dev, sub| match f(dev, sub) {
+ Ok(()) => 0,
+ Err(e) => e.to_errno(),
+ })
+}
+
+// Helper for C FFI
+fn with_ctx<T: 'static, R>(
+ s: *mut bindings::snd_rawmidi_substream,
+ f: impl FnOnce(&T, &RawMidiSubstream) -> R,
+) -> R {
+ // SAFETY: caller contract (see callbacks): `s` and `rmidi` are valid,
+ // `private_data` is a live foreign-owned `Arc<T>`.
+ let data = unsafe { <Arc<T> as ForeignOwnable>::borrow((*(*s).rmidi).private_data) };
+ // SAFETY: substream is valid.
+ let substream = unsafe { RawMidiSubstream::from_raw(s) };
+ f(&data, &substream)
+}
+
+/// A rawmidi instance.
+///
+/// TODO: track lifetime with the parent sndcard?
+pub struct RawMidi {
+ ptr: *mut bindings::snd_rawmidi,
+}
+
+impl RawMidi {
+ /// Creates a new rawmidi instance bound to a parent soundcard.
+ ///
+ /// Note: this presently requires both a Input and Output implementation
+ /// even if the input_count or output_count is 0.
+ /// TODO: enforce correctness in the API.
+ pub fn new<T: RawMidiInput + RawMidiOutput + 'static>(
+ card: &SoundCard,
+ id: &CStr,
+ device_idx: u16,
+ output_count: u16,
+ input_count: u16,
+ data: Arc<T>,
+ ) -> Result<Self> {
+ let mut rmidi_ptr: *mut bindings::snd_rawmidi = core::ptr::null_mut();
+
+ // SAFETY: `card.ptr` is a valid soundcard allocation.
+ // id is converted to match the C API.
+ // The bindings expect a mut but snd_rawmidi_init just uses strcpy.
+ to_result(unsafe {
+ bindings::snd_rawmidi_new(
+ card.ptr,
+ id.as_char_ptr().cast_mut(),
+ device_idx.into(),
+ output_count.into(),
+ input_count.into(),
+ &mut rmidi_ptr,
+ )
+ })?;
+
+ // SAFETY: `rmidi_ptr` is validated by the return check above.
+ let rmidi = unsafe { &mut *rmidi_ptr };
+
+ rmidi.private_data = Arc::into_foreign(data).cast();
+ // Note: private_free will drop the private_data from the foreign ptr.
+ // need to make sure it is freed in the event the cb is not called!
+ rmidi.private_free = Some(RawMidiOps::<T>::private_free_cb);
+
+ if input_count > 0 {
+ rmidi.info_flags |= bindings::SNDRV_RAWMIDI_INFO_INPUT;
+ // SAFETY: rmidi_ptr is validated above.
+ unsafe {
+ bindings::snd_rawmidi_set_ops(
+ rmidi_ptr,
+ bindings::SNDRV_RAWMIDI_STREAM_INPUT as i32,
+ &RawMidiOps::<T>::INPUT,
+ )
+ };
+ }
+ if output_count > 0 {
+ rmidi.info_flags |= bindings::SNDRV_RAWMIDI_INFO_OUTPUT;
+ // SAFETY: rmidi_ptr is validated above.
+ unsafe {
+ bindings::snd_rawmidi_set_ops(
+ rmidi_ptr,
+ bindings::SNDRV_RAWMIDI_STREAM_OUTPUT as i32,
+ &RawMidiOps::<T>::OUTPUT,
+ );
+ }
+ }
+ if input_count > 0 && output_count > 0 {
+ rmidi.info_flags |= bindings::SNDRV_RAWMIDI_INFO_DUPLEX;
+ }
+
+ Ok(Self { ptr: rmidi_ptr })
+ }
+
+ /// Set the rawmidi device name.
+ pub fn set_name(&mut self, name: &CStr) -> Result<()> {
+ // SAFETY: `self.ptr` is valid between `new` and card free.
+ let mut rmidi = unsafe { *self.ptr };
+ copy_to_c_buf(&mut rmidi.name, name)
+ }
+}
diff --git a/samples/rust/rust_midi_device.rs b/samples/rust/rust_midi_device.rs
index 5561a0437b59..1465ba9544d6 100644
--- a/samples/rust/rust_midi_device.rs
+++ b/samples/rust/rust_midi_device.rs
@@ -1,15 +1,44 @@
// SPDX-License-Identifier: GPL-2.0
//! Rust MIDI minimal sample.
+//!
+//! This driver demonstrates usage of the kernel::snd subsystem:
+//! - Create a rawmidi device
+//! - Print received event
use kernel::{
faux,
prelude::*,
snd,
+ sync::Arc,
};
+struct RustMidiDevice;
+
+impl snd::RawMidiInput for RustMidiDevice {
+ fn trigger(&self, _substream: &snd::RawMidiSubstream, up: bool) {
+ pr_debug!("input trigger up:{up}!\n");
+ }
+}
+
+// Implement midi output ops: just display transmitted event.
+impl snd::RawMidiOutput for RustMidiDevice {
+ fn trigger(&self, substream: &snd::RawMidiSubstream, up: bool) {
+ pr_debug!("output trigger up:{up}!\n");
+ if up {
+ let mut buf = [0; 64];
+ match substream.transmit(&mut buf) {
+ Ok(count) => pr_info!("transmit got: {:?}\n", buf[..count]),
+ Err(err) => pr_err!("transmit failed: {err:?}"),
+ };
+ }
+ }
+}
+
impl kernel::Module for RustMidiDeviceModule {
fn init(_module: &'static ThisModule) -> Result<Self> {
+ let state = Arc::new(RustMidiDevice, GFP_KERNEL)?;
+
pr_debug!("Initializing sound card\n");
let reg = faux::Registration::new(c"rust-midi-device", None)?;
let fdev = reg.as_ref();
@@ -21,6 +50,10 @@ fn init(_module: &'static ThisModule) -> Result<Self> {
card.set_driver(c"rust-midi")?;
card.set_shortname(c"RustMIDI")?;
+ pr_debug!("Initializing rawmidi card\n");
+ let mut rmidi = snd::RawMidi::new(&card, c"RustRMIDI", 0, 1, 1, state.clone())?;
+ rmidi.set_name(c"RustRMIDI")?;
+
// Register the sndcard to ALSA.
card.register()?;
--
2.56.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH RFC 3/3] rust: snd: Add active rawmidi_substream helper
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
2 siblings, 0 replies; 4+ messages in thread
From: Tristan de Cacqueray @ 2026-10-11 13:02 UTC (permalink / raw)
To: Takashi Iwai, Miguel Ojeda
Cc: linux-sound, rust-for-linux, linux-kernel, Tristan de Cacqueray
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
^ permalink raw reply [flat|nested] 4+ messages in thread