lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250113121620.21598-2-me@kloenk.dev>
Date: Mon, 13 Jan 2025 13:16:16 +0100
From: Fiona Behrens <me@...enk.dev>
To: Miguel Ojeda <ojeda@...nel.org>,
	Alex Gaynor <alex.gaynor@...il.com>,
	Pavel Machek <pavel@....cz>,
	Lee Jones <lee@...nel.org>,
	Jean Delvare <jdelvare@...e.com>
Cc: Boqun Feng <boqun.feng@...il.com>,
	Gary Guo <gary@...yguo.net>,
	Björn Roy Baron <bjorn3_gh@...tonmail.com>,
	Benno Lossin <benno.lossin@...ton.me>,
	Andreas Hindborg <a.hindborg@...nel.org>,
	Alice Ryhl <aliceryhl@...gle.com>,
	Trevor Gross <tmgross@...ch.edu>,
	Fiona Behrens <me@...enk.dev>,
	Mark Pearson <mpearson-lenovo@...ebb.ca>,
	Peter Koch <pkoch@...ovo.com>,
	rust-for-linux@...r.kernel.org,
	linux-leds@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	Sebastian Walz <sivizius@...izius.eu>
Subject: [PATCH v2 1/5] rust: dmi: Add abstractions for DMI

Introduce Rust macros and types to support DMI-based system matching,
providing functionality similar to the `MODULE_DEVICE_TABLE(dmi, x)`
macro in C.

- Add the `dmi_system_id!` macro for defining system identifiers and
  matching specific DMI fields.
- Add the `dmi_device_table!` macro, which allows module aliases to be
  available after a full build when compiled as a module.
- Define the `Field` enum for DMI field IDs, ensuring compatibility
  with existing C bindings.

These abstractions enable writing Rust kernel drivers that rely on DMI
data for system-specific behavior and autoloading.

Co-developed-by: Sebastian Walz <sivizius@...izius.eu>
Signed-off-by: Sebastian Walz <sivizius@...izius.eu>
Signed-off-by: Fiona Behrens <me@...enk.dev>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/dmi.rs              | 533 ++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs              |   2 +
 3 files changed, 536 insertions(+)
 create mode 100644 rust/kernel/dmi.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 5c4dfe22f41a..d20b2a6af9c9 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -11,6 +11,7 @@
 #include <linux/blk_types.h>
 #include <linux/blkdev.h>
 #include <linux/cred.h>
+#include <linux/dmi.h>
 #include <linux/errname.h>
 #include <linux/ethtool.h>
 #include <linux/file.h>
diff --git a/rust/kernel/dmi.rs b/rust/kernel/dmi.rs
new file mode 100644
index 000000000000..ba2a010d0e55
--- /dev/null
+++ b/rust/kernel/dmi.rs
@@ -0,0 +1,533 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! DMI support.
+//!
+//! C header: [`inlcude/linux/dmi.h`](srctree/include/linux/dmi.h)
+
+use core::marker::PhantomData;
+use core::num::NonZeroU32;
+use core::ops::Deref;
+
+use crate::prelude::*;
+use crate::str::CStr;
+
+/// Create a new [`SystemId`].
+///
+/// # Examples
+///
+/// Create a [`SystemId`] that matches if the [`BiosVendor`] is `qemu`.
+///
+/// ```
+/// let system_id = kernel::dmi_system_id!("qemu": [
+///     BiosVendor: "qemu" @exact,
+///     ProductFamily: "rust",
+/// ]);
+/// assert_eq!(system_id.matches()[0].slot().unwrap().unwrap(), kernel::dmi::Field::BiosVendor);
+/// assert_eq!(system_id.matches()[0].exact_match(), true);
+/// assert_eq!(system_id.matches()[1].exact_match(), false);
+/// ```
+///
+/// A [`SystemId`] cannot hold more than 4 matches and must not be empty,
+/// otherwise it will fail to compile.
+///
+// TODO: replace with `compile_fail` when supported.
+/// ```ignore
+/// let system_id = kernel::dmi_system_id!("qemu": [
+///     BiosVendor: "qemu",
+///     BiosVersion: "8.0",
+///     ProductName: "doctest",
+///     ProductFamily: "rust",
+///     BoardVendor: "qemu"
+/// ]);
+/// ```
+///
+/// [`BiosVendor`]: Field::BiosVendor
+#[macro_export]
+macro_rules! dmi_system_id {
+    (@strmatch, $slot:ident, $match:literal) => {
+        $crate::dmi::StrMatch::new($crate::dmi::Field::$slot, $match)
+    };
+    (@strmatch, $slot:ident, $match:literal, @exact) => {
+        $crate::dmi::StrMatch::new_exact($crate::dmi::Field::$slot, $match)
+    };
+    ($ident:literal: [$($slot:ident: $match:literal $(@$exact:ident)? $(,)?)+]) => {
+	const {
+	    match $crate::dmi::SystemId::new_from_slice($crate::c_str!($ident), &[
+		$($crate::dmi_system_id!(@strmatch, $slot, $match$(, @$exact)?),)+
+	    ]) {
+		Some(v) => v,
+		_ => $crate::build_error("Invalid length for matches"),
+	    }
+	}
+    };
+}
+
+/// Create a new static [`SystemIdList`] and export it as a device table to generate alias
+/// definitions to load the driver from userspace if compiled as module.
+///
+/// # Examples
+///
+/// Create a device table with the name `QEMU_DMI_TABLE` that loads the driver if the
+/// [`BiosVendor`] is `qemu`.
+///
+/// ```
+/// kernel::dmi_device_table!(1, QEMU_DMI_TABLE, ["qemu": [BiosVendor: "qemu"]]);
+/// # assert_eq!(QEMU_DMI_TABLE.len(), 1);
+/// ```
+///
+/// [`BiosVendor`]: Field::BiosVendor
+#[macro_export]
+macro_rules! dmi_device_table {
+    (
+	$N:literal,
+	$name:ident,
+	[$($ident:literal: [$($slot:ident: $match:literal $(, @$exact:ident)?$(,)?)+]$(,)?)+]
+    ) => {
+	#[cfg_attr(MODULE, export_name = concat!("__mod_device_table__dmi__", stringify!($name)))]
+	#[cfg_attr(MODULE, used)]
+	static $name: $crate::dmi::SystemIdList<'static, $N> =
+	    $crate::dmi::SystemIdList::new([$(
+		$crate::dmi_system_id!($ident: [$($slot: $match $(, @$exact)?)+]),
+	    )+]);
+    };
+}
+
+/// DMI field id.
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+#[repr(u8)]
+pub enum Field {
+    // None
+    /// Bios Vendor
+    BiosVendor = bindings::dmi_field_DMI_BIOS_VENDOR as u8,
+    /// Bios Version
+    BiosVersion = bindings::dmi_field_DMI_BIOS_VERSION as u8,
+    /// Bios Date
+    BiosDate = bindings::dmi_field_DMI_BIOS_DATE as u8,
+    /// Bios Release
+    BiosRelease = bindings::dmi_field_DMI_BIOS_RELEASE as u8,
+    /// Embedded Controller Firmware Release
+    EcFirmwareRelease = bindings::dmi_field_DMI_EC_FIRMWARE_RELEASE as u8,
+    /// System Vendor
+    SysVendor = bindings::dmi_field_DMI_SYS_VENDOR as u8,
+    /// Product Name
+    ProductName = bindings::dmi_field_DMI_PRODUCT_NAME as u8,
+    /// Product Version
+    ProductVersion = bindings::dmi_field_DMI_PRODUCT_VERSION as u8,
+    /// Product Serial
+    ProductSerial = bindings::dmi_field_DMI_PRODUCT_SERIAL as u8,
+    /// Product UUID
+    ProductUuid = bindings::dmi_field_DMI_PRODUCT_UUID as u8,
+    /// Product SKU
+    ProductSku = bindings::dmi_field_DMI_PRODUCT_SKU as u8,
+    /// Product Family
+    ProductFamily = bindings::dmi_field_DMI_PRODUCT_FAMILY as u8,
+    /// Board Vendor
+    BoardVendor = bindings::dmi_field_DMI_BOARD_VENDOR as u8,
+    /// Board Name
+    BoardName = bindings::dmi_field_DMI_BOARD_NAME as u8,
+    /// Board Version
+    BoardVersion = bindings::dmi_field_DMI_BOARD_VERSION as u8,
+    /// Board Serial
+    BoardSerial = bindings::dmi_field_DMI_BOARD_SERIAL as u8,
+    /// Board Asset Tag
+    BoardAssetTag = bindings::dmi_field_DMI_BOARD_ASSET_TAG as u8,
+    /// Chassis Vendor
+    ChassisVendor = bindings::dmi_field_DMI_CHASSIS_VENDOR as u8,
+    /// Chassis Type
+    ChassisType = bindings::dmi_field_DMI_CHASSIS_TYPE as u8,
+    /// Chassis Version
+    ChassisVersion = bindings::dmi_field_DMI_CHASSIS_VERSION as u8,
+    /// Chassis Serial
+    ChassisSerial = bindings::dmi_field_DMI_CHASSIS_SERIAL as u8,
+    /// Chassis Asset Tag
+    ChassisAssetTag = bindings::dmi_field_DMI_CHASSIS_ASSET_TAG as u8,
+    // StringMax,
+    // OemString,
+}
+
+impl Field {
+    /// Return DMI data value.
+    ///
+    /// Returns one DMI data value, can be used to perform complex DMI data checks.
+    pub fn get_system_info(self) -> Option<&'static CStr> {
+        // SAFETY: C call, self is a valid enum
+        let ptr = unsafe { bindings::dmi_get_system_info(self as u8 as _) };
+        if ptr.is_null() {
+            None
+        } else {
+            // SAFETY: ptr checked to be none null above and to be a valid char ptr.
+            Some(unsafe { CStr::from_char_ptr(ptr) })
+        }
+    }
+
+    /// Compare a string to the dmi field (if exists).
+    ///
+    /// Returns true if the requested field equals to the check value (including None).
+    /// wraps the `dmi_match` C function.
+    pub fn compare(self, value: Option<&CStr>) -> bool {
+        // SAFETY: C call, self is valid enum and value is null or a string
+        unsafe {
+            bindings::dmi_match(
+                self as u8 as _,
+                value.map(CStr::as_char_ptr).unwrap_or(core::ptr::null()),
+            )
+        }
+    }
+}
+
+impl TryFrom<u8> for Field {
+    type Error = Error;
+
+    /// Tries to convert a u8 id to a [`Field`].
+    ///
+    /// # Errors
+    ///
+    /// Returns [`EINVAL`] if the id does not match a known field.
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        Ok(match value as u32 {
+            bindings::dmi_field_DMI_BIOS_VENDOR => Self::BiosVendor,
+            bindings::dmi_field_DMI_BIOS_VERSION => Self::BiosVersion,
+            bindings::dmi_field_DMI_BIOS_DATE => Self::BiosDate,
+            bindings::dmi_field_DMI_BIOS_RELEASE => Self::BiosRelease,
+            bindings::dmi_field_DMI_EC_FIRMWARE_RELEASE => Self::EcFirmwareRelease,
+            bindings::dmi_field_DMI_SYS_VENDOR => Self::SysVendor,
+            bindings::dmi_field_DMI_PRODUCT_NAME => Self::ProductName,
+            bindings::dmi_field_DMI_PRODUCT_VERSION => Self::ProductVersion,
+            bindings::dmi_field_DMI_PRODUCT_SERIAL => Self::ProductSerial,
+            bindings::dmi_field_DMI_PRODUCT_UUID => Self::ProductUuid,
+            bindings::dmi_field_DMI_PRODUCT_SKU => Self::ProductSku,
+            bindings::dmi_field_DMI_PRODUCT_FAMILY => Self::ProductFamily,
+            bindings::dmi_field_DMI_BOARD_VENDOR => Self::BoardVendor,
+            bindings::dmi_field_DMI_BOARD_NAME => Self::BoardName,
+            bindings::dmi_field_DMI_BOARD_VERSION => Self::BoardVersion,
+            bindings::dmi_field_DMI_BOARD_SERIAL => Self::BoardSerial,
+            bindings::dmi_field_DMI_BOARD_ASSET_TAG => Self::BoardAssetTag,
+            bindings::dmi_field_DMI_CHASSIS_VENDOR => Self::ChassisVendor,
+            bindings::dmi_field_DMI_CHASSIS_TYPE => Self::ChassisType,
+            bindings::dmi_field_DMI_CHASSIS_VERSION => Self::ChassisVersion,
+            bindings::dmi_field_DMI_CHASSIS_SERIAL => Self::ChassisSerial,
+            bindings::dmi_field_DMI_CHASSIS_ASSET_TAG => Self::ChassisAssetTag,
+            _ => return Err(EINVAL),
+        })
+    }
+}
+
+/// String Match entry for DMI.
+///
+/// Wraps the C struct `dmi_strmatch`.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub struct StrMatch(bindings::dmi_strmatch);
+
+impl StrMatch {
+    /// Zeroed match entry.
+    pub const ZERO: Self = {
+        // SAFETY: all zero is a valid match with slot none.
+        unsafe { core::mem::zeroed() }
+    };
+
+    /// Create a new String match entry that is not an exact match.
+    ///
+    /// This function copies the string into it's own allocation.
+    #[inline]
+    pub const fn new(slot: Field, substr: &str) -> Self {
+        Self::new_with_exact(slot, substr, false)
+    }
+
+    /// Create a new String match entry that wants an exact match.
+    ///
+    /// This function copies the string into it's own allocation.
+    #[inline]
+    pub const fn new_exact(slot: Field, substr: &str) -> Self {
+        Self::new_with_exact(slot, substr, true)
+    }
+
+    /// Create a new String match entry.
+    ///
+    /// This function copies the string into it's own allocation.
+    pub const fn new_with_exact(slot: Field, substr: &str, exact: bool) -> Self {
+        #[cfg(target_endian = "little")]
+        let (exactshift, slotshift) = (7, 0);
+        #[cfg(target_endian = "big")]
+        let (exactshift, slotshift) = (0, 1);
+
+        let bitfield = (exact as u8) << exactshift | (slot as u8) << slotshift;
+
+        let mut this = Self(bindings::dmi_strmatch {
+            _bitfield_1: bindings::__BindgenBitfieldUnit::new([bitfield]),
+            ..Self::ZERO.0
+        });
+
+        // copy substr into this
+        // core::ptr::copy_nonoverlapping is not const as mutable ref.
+        let input = substr.as_bytes();
+        let mut index = input.len();
+        let max = this.0.substr.len() - 1;
+        if index > max {
+            // TODO: find a way to generate warning from const
+            index = max;
+        }
+        loop {
+            if index == 0 {
+                break;
+            }
+
+            index -= 1;
+            this.0.substr[index] = input[index] as i8;
+        }
+
+        this
+    }
+
+    /// Return the slot this entry matches.
+    ///
+    /// # Errors
+    ///
+    /// Returns `Ok(None)` if the slot has the value 0, or [`EINVAL`] if the field id is unknown.
+    ///
+    /// # Examples
+    /// ```
+    /// # use kernel::dmi::{StrMatch, Field};
+    /// let strmatch = StrMatch::new(Field::BiosVendor, "qemu");
+    /// assert_eq!(strmatch.slot().unwrap().unwrap(), Field::BiosVendor);
+    /// ```
+    pub fn slot(&self) -> Result<Option<Field>> {
+        let slot = self.0.slot();
+        if slot == 0 {
+            Ok(None)
+        } else {
+            Some(Field::try_from(slot)).transpose()
+        }
+    }
+
+    /// Return if this match wants an exact match.
+    ///
+    /// # Examples
+    /// ```
+    /// # use kernel::dmi::{StrMatch, Field};
+    /// let strmatch = StrMatch::new_exact(Field::BiosVendor, "qemu");
+    /// assert_eq!(strmatch.exact_match(), true);
+    /// ```
+    #[inline]
+    pub fn exact_match(&self) -> bool {
+        self.0.exact_match() == 1
+    }
+
+    /// Return the substring to match for.
+    ///
+    /// # Examples
+    /// ```
+    /// # use kernel::dmi::{StrMatch, Field};
+    /// let strmatch = StrMatch::new(Field::BiosVendor, "qemu");
+    /// assert_eq!(strmatch.substr(), "qemu");
+    /// ```
+    pub fn substr(&self) -> &str {
+        let len = self.0.substr.into_iter().take_while(|x| *x != 0).count();
+        // SAFETY: substr is a valid str for len of len
+        unsafe {
+            core::str::from_utf8_unchecked(core::slice::from_raw_parts(
+                self.0.substr.as_ptr().cast(),
+                len,
+            ))
+        }
+    }
+}
+
+impl core::fmt::Debug for StrMatch {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_struct("StrMatch")
+            .field("slot", &self.slot().ok().flatten())
+            .field("exact_match", &self.exact_match())
+            .field("substr", &self.substr())
+            .finish()
+    }
+}
+
+impl PartialEq for StrMatch {
+    fn eq(&self, other: &Self) -> bool {
+        self.0._bitfield_1 == other.0._bitfield_1 && self.0.substr == other.0.substr
+    }
+}
+
+impl Eq for StrMatch {}
+
+/// DMI system ID.
+///
+/// To create a system ID in a const context the macro [`kernel::dmi_system_id`] can be used.
+///
+/// Wraps the C struct `dmi_system_id`.
+#[repr(transparent)]
+pub struct SystemId<'a> {
+    id: bindings::dmi_system_id,
+    // lifetime anchor for ident, driver data and callback
+    _a: PhantomData<&'a ()>,
+}
+
+impl SystemId<'static> {
+    /// zeroed [`SystemId`] for trailing list.
+    pub const ZERO: Self = {
+        // SAFETY: all fields zeroed is valid.
+        unsafe { core::mem::zeroed() }
+    };
+}
+
+impl<'a> SystemId<'a> {
+    /// Create a new SystemId from a matches array.
+    pub const fn new(ident: &'a CStr, matches: [StrMatch; 4]) -> Self {
+        Self {
+            id: bindings::dmi_system_id {
+                callback: None,
+                ident: ident.as_char_ptr(),
+                // SAFETY: StrMatch is transparent over bindings::dmi_strmatch
+                matches: unsafe {
+                    core::mem::transmute::<[StrMatch; 4], [bindings::dmi_strmatch; 4]>(matches)
+                },
+                driver_data: core::ptr::null_mut(),
+            },
+            _a: PhantomData,
+        }
+    }
+
+    /// Createa n new SystemId from a slice of matches, filling missing entries with [`StrMatch::ZERO`].
+    ///
+    /// Copying the matches, only provided to be used in macros.
+    #[doc(hidden)]
+    pub const fn new_from_slice(ident: &'a CStr, matches: &[StrMatch]) -> Option<Self> {
+        if matches.is_empty() || matches.len() > 4 {
+            return None;
+        }
+
+        let mut matches_a = [StrMatch::ZERO; 4];
+        let mut index = matches.len();
+        loop {
+            index -= 1;
+            matches_a[index] = matches[index];
+
+            if index == 0 {
+                break;
+            }
+        }
+        Some(Self::new(ident, matches_a))
+    }
+
+    /// Return the ident of the given SystemId.
+    ///
+    /// Returns a option as the C api allows to not set the ident.
+    pub fn ident_cstr(&self) -> Option<&'a CStr> {
+        let ident_ptr = self.id.ident;
+        if ident_ptr.is_null() {
+            return None;
+        }
+
+        // SAFETY: ident_ptr is valid and non null.
+        Some(unsafe { CStr::from_char_ptr(ident_ptr) })
+    }
+
+    /// Return the ident of the given SystemId as a rust [`str`].
+    ///
+    /// Returns a option as the C api allows to not set the ident.
+    /// See [`ident_cstr`].
+    ///
+    /// [`ident_cstr`]: Self::ident_cstr
+    #[inline]
+    pub fn ident(&self) -> Option<&'a str> {
+        self.ident_cstr().map(CStr::to_str).and_then(Result::ok)
+    }
+
+    /// Set the ident from the given optional string.
+    ///
+    /// Set to None to remove the ident.
+    pub fn set_ident(&mut self, ident: Option<&'a CStr>) {
+        if let Some(ident) = ident {
+            self.id.ident = ident.as_char_ptr();
+        } else {
+            self.id.ident = core::ptr::null();
+        }
+    }
+
+    /// Return the [`StrMatch`] array in the SystemId.
+    pub fn matches(&self) -> &[StrMatch; 4] {
+        // SAFETY: StrMatch is transparent over bindings::dmi_strmatch
+        unsafe { core::mem::transmute(&self.id.matches) }
+    }
+
+    /// Return a mutable reference to the StrMatch array in the SystemId.
+    pub fn matches_mut(&mut self) -> &mut [StrMatch; 4] {
+        // SAFETY: StrMatch is transparent over bindings::dmi_strmatch
+        unsafe { core::mem::transmute(&mut self.id.matches) }
+    }
+}
+
+impl core::fmt::Debug for SystemId<'_> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_struct("SystemId")
+            .field("ident", &self.ident())
+            .field("matches", &self.matches())
+            .field("callback", &self.id.callback)
+            .field("driver_data", &self.id.driver_data)
+            .finish()
+    }
+}
+
+// SAFETY: pointer to allocation with sufficent lifetime
+unsafe impl<'a> Sync for SystemId<'a> {}
+// SAFETY: pointer to allocation with sufficent lifetime
+unsafe impl<'a> Send for SystemId<'a> {}
+
+/// List of SystemIds, containing a zero sentinel to be used with dmi functions.
+///
+/// Can be exported as a device table with the [`kernel::dmi_device_table`] macro.
+#[repr(C)]
+pub struct SystemIdList<'a, const N: usize> {
+    ids: [SystemId<'a>; N],
+    sentinel: SystemId<'static>,
+}
+
+impl<'a, const N: usize> SystemIdList<'a, N> {
+    /// Create a new list from the given Id list, adding a zero sentinel.
+    pub const fn new(ids: [SystemId<'a>; N]) -> Self {
+        Self {
+            ids,
+            sentinel: SystemId::ZERO,
+        }
+    }
+
+    /// Check system DMI data
+    ///
+    /// Walk the blacklist table running matching functions until someone
+    /// returns non zero or we hit the end. Callback function is called for
+    /// each successful match. Returns the number of matches.
+    pub fn check_system(&self) -> Option<NonZeroU32> {
+        // SAFETY: C call, self has a sentinel
+        NonZeroU32::new(unsafe { bindings::dmi_check_system(self.as_raw_ptr()) as u32 })
+    }
+
+    /// Find the first match.
+    ///
+    /// Walk the blacklist table until the first match is found, and returns it.
+    pub fn first_match(&self) -> Option<&SystemId<'a>> {
+        // SAFETY: C call, self has a sentinel
+        let ptr = unsafe { bindings::dmi_first_match(self.as_raw_ptr()) };
+        if ptr.is_null() {
+            None
+        } else {
+            // SAFETY: ptr is non null. SystemId is transparent
+            Some(unsafe { &*ptr.cast() })
+        }
+    }
+
+    /// Return the raw pointer to the dmi_system_id array, including a terminating zero sentinel.
+    pub fn as_raw_ptr(&self) -> *const bindings::dmi_system_id {
+        // ids is the first element in the struct, safe to use as base pointer.
+        // SystemId is transparent over bindings::dmi_system_id
+        self.ids.as_ptr().cast()
+    }
+}
+
+impl<'a, const N: usize> Deref for SystemIdList<'a, N> {
+    type Target = [SystemId<'a>; N];
+
+    fn deref(&self) -> &Self::Target {
+        &self.ids
+    }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index e1065a7551a3..e21f2e607963 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -35,6 +35,8 @@
 mod build_assert;
 pub mod cred;
 pub mod device;
+#[cfg(CONFIG_DMI)]
+pub mod dmi;
 pub mod error;
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
 pub mod firmware;
-- 
2.47.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ