Re: [PATCH RFC v2 1/1] rust: Add bindings for device properties

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Nov 22, 2024 at 9:13 AM Dirk Behme <dirk.behme@xxxxxxxxxxxx> wrote:
>
> From: "Rob Herring (Arm)" <robh@xxxxxxxxxx>
>
> The device property API is a firmware agnostic API for reading
> properties from firmware (DT/ACPI) devices nodes and swnodes.
>
> While the C API takes a pointer to a caller allocated variable/buffer,

nit: "caller-allocated" or "to a variable/buffer allocated by the caller"

> the rust API is designed to return a value and can be used in struct
> initialization. Rust generics are also utilized to support different
> sizes of properties (e.g. u8, u16, u32).
>
> To build and run the Examples as `rustdoc` tests the kernel Kconfig
> options `CONFIG_OF` and `CONFIG_OF_UNITTEST` need to be enabled
> additionally. Besides the default `rustdoc` test options
> `CONFIG_KUNIT` and `CONFIG_RUST_KERNEL_DOCTESTS`. This even works
> on non-ARM architectures as a test device tree is built into the
> kernel, then.
>
> The Integer trait is proposed by Alic Ryhl [1].

typo ;)

>
> Link: https://lore.kernel.org/rust-for-linux/CAH5fLgiXPZqKpWSSNdx-Ww-E9h2tOLcF3_8Y4C_JQ0eU8EMwFw@xxxxxxxxxxxxxx/ [1]
> Co-developed-by: Dirk Behme <dirk.behme@xxxxxxxxxxxx>
> Signed-off-by: Dirk Behme <dirk.behme@xxxxxxxxxxxx>
> Signed-off-by: Rob Herring (Arm) <robh@xxxxxxxxxx>
> ---
>
> This is an update of Rob's initial patch
>
> https://lore.kernel.org/rust-for-linux/20241025-rust-platform-dev-v1-0-0df8dcf7c20b@xxxxxxxxxx/
>
> condensed in one patch (see below). Rob's initial cover
> letter still stands, esp. the part regarding the dependency
> on Danilo's PCI and platform device series [2].
>
> Changes in v2:
>
> * Move the major parts to property.rs
> * Use the Integer Trait proposed by Alice
> * Use MaybeUninit proposed by Alex
> * Use Option<> to distinguish between optional and mandatory properties
>   proposed by Rob
> * Introduce a FwProperty trait. The prefix 'Fw' reads as 'Firmware'.
>   Just 'Property' seems to be conflicting with existing.
> * Add some rustdoc documentation and Examples (based on Danilo's
>   platform sample module). With that I squashed the test device tree
>   changes into this patch as we don't need to change Danilo's platform
>   sample any more. That change is trivial. Please let me know if you
>   rather like a separate patch for it.
> * Some more I most probably missed to mention ;)
>
> It would be nice to get some improvement hints for the FIXMEs :)

See below :)

> Trying to use the assert_eq!() fails with
>
> error[E0425]: cannot find value `__DOCTEST_ANCHOR` in this scope
>     --> rust/doctests_kernel_generated.rs:4252:102
>      |
> 4252 |             kernel::kunit_assert_eq!("rust_doctest_kernel_property_rs_0", "rust/kernel/property.rs", __DOCTEST_ANCHOR - 150, $left, $right);
>      |                                                                                                      ^^^^^^^^^^^^^^^^ not found in this scope
> ...
> 4369 | assert_eq!(idx, Ok(0)); // FIXME: How to build this?
>      | ---------------------- in this macro invocation
>      |
>      = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
>
>   CC      drivers/base/firmware_loader/main.o
>   CC      kernel/module/main.o
> error: aborting due to 1 previous error
>
> [2] https://lore.kernel.org/all/20241022213221.2383-1-dakr@xxxxxxxxxx/

I don't know about this one.

> +/// A trait for integer types.
> +///
> +/// This trait limits [`FromBytes`] and [`AsBytes`] to integer types only. Excluding the
> +/// array types. The integer types are `u8`, `u16`, `u32`, `u64`, `usize`, `i8`, `i16`,
> +/// `i32`, `i64` and `isize`. Additionally, the size of these types is encoded in the
> +/// `IntSize` enum.
> +trait Integer: FromBytes + AsBytes + Copy {
> +    /// The size of the integer type.
> +    const SIZE: IntSize;
> +}

This trait should be unsafe with a safety requirement saying that SIZE
must be correct.

Is it intentional that Integer is a private trait? I guess you're
using it as an implementation detail of FwProperty? It would be nice
to mention that 128-bit ints are intentionally not included here.

> +/// The sizes of the integer types. Either 8, 16, 32 or 64 bits.
> +enum IntSize {
> +    /// 8 bits
> +    S8,
> +    /// 16 bits
> +    S16,
> +    /// 32 bits
> +    S32,
> +    /// 64 bits
> +    S64,
> +}
> +
> +macro_rules! impl_int {
> +    ($($typ:ty),* $(,)?) => {$(
> +        impl Integer for $typ {
> +            const SIZE: IntSize = match size_of::<Self>() {
> +                1 => IntSize::S8,
> +                2 => IntSize::S16,
> +                4 => IntSize::S32,
> +                8 => IntSize::S64,
> +                _ => panic!("invalid size"),
> +            };
> +        }
> +    )*};
> +}
> +
> +impl_int! {
> +    u8, u16, u32, u64, usize,
> +    i8, i16, i32, i64, isize,
> +}
> +
> +/// Reads an array of integers from the device firmware.
> +fn read_array<T: Integer>(
> +    device: &Device,
> +    name: &CStr,
> +    val: Option<&mut [MaybeUninit<T>]>,
> +) -> Result<usize> {
> +    let (ptr, len) = match val {
> +        // The read array data case.
> +        Some(val) => (val.as_mut_ptr(), val.len()),
> +        // The read count case.
> +        None => (core::ptr::null_mut(), 0_usize),
> +    };
> +    let ret = match T::SIZE {
> +        // SAFETY: `device_property_read_u8_array` is called with a valid device pointer and name.
> +        IntSize::S8 => unsafe {
> +            bindings::device_property_read_u8_array(
> +                device.as_raw(),
> +                name.as_ptr() as *const i8,
> +                ptr as *mut u8,
> +                len,

Instead of using the i8 type for the name, you should be using
crate::ffi::c_char. Actually, in this case, you can also use
`name.as_char_ptr()` instead, which requires no cast.

> +        },
> +        // SAFETY: `device_property_read_u16_array` is called with a valid device pointer and name.
> +        IntSize::S16 => unsafe {
> +            bindings::device_property_read_u16_array(
> +                device.as_raw(),
> +                name.as_ptr() as *const i8,
> +                ptr as *mut u16,
> +                len,
> +            )
> +        },
> +        // SAFETY: `device_property_read_u32_array` is called with a valid device pointer and name.
> +        IntSize::S32 => unsafe {
> +            bindings::device_property_read_u32_array(
> +                device.as_raw(),
> +                name.as_ptr() as *const i8,
> +                ptr as *mut u32,
> +                len,
> +            )
> +        },
> +        // SAFETY: `device_property_read_u64_array` is called with a valid device pointer and name.
> +        IntSize::S64 => unsafe {
> +            bindings::device_property_read_u64_array(
> +                device.as_raw(),
> +                name.as_ptr() as *const i8,
> +                ptr as *mut u64,
> +                len,
> +            )
> +        },
> +    };
> +    to_result(ret)?;
> +    Ok(ret.try_into()?)
> +}

[...]

> +/// *Note*: To build and run below examples as `rustdoc` tests the additional kernel
> +/// Kconfig options `CONFIG_OF` and `CONFIG_OF_UNITTEST` need to be enabled. This
> +/// even works on non-ARM architectures as a test device tree is built into the
> +/// kernel, then.

Incomplete sentence?

Does this mean that running kunit without those options enabled
results in an error?

> +/// The above examples intentionally don't print any error messages (e.g. with `dev_err!()`).
> +/// The called abstractions already print error messages if needed what is considered to be
> +/// sufficient. The goal is to be less verbose regarding error messages.

typo: "if needed, which is"

> +pub trait FwProperty: Sized {
> +    /// Reads a property from the device.
> +    fn read_property(device: &Device, name: &CStr, default: Option<Self>) -> Result<Self>;
> +
> +    /// Gets the properties element count.
> +    fn count_elem(device: &Device, name: &CStr) -> Result<usize>;
> +
> +    /// Returns if a firmware string property `name` has match for `match_str`.
> +    fn match_string(device: &Device, name: &CStr, match_str: &CStr) -> Result<usize> {

As far as I can tell, you never override this implementation anywhere.
In that case, it shouldn't be a trait method. You can just put its
implementation directly in property_match_string.

> +        // SAFETY: `device_property_match_string` is called with a valid device pointer and name.
> +        let ret = unsafe {
> +            bindings::device_property_match_string(
> +                device.as_raw(),
> +                name.as_ptr() as *const i8,
> +                match_str.as_ptr() as *const i8,
> +            )

Ditto here about i8.

> +    /// Gets the properties element count.
> +    // FIXME: Could this be made to be a build time error?
> +    fn count_elem(device: &Device, _name: &CStr) -> Result<usize> {

Yes, you should create two traits:

pub trait FwPropertyRead: Sized {
    fn read_property(device: &Device, name: &CStr, default:
Option<Self>) -> Result<Self>;
}

pub trait FwPropertyCount: Sized {
    fn count_elem(device: &Device, name: &CStr) -> Result<usize>;
}

Then don't implement FwPropertyCount for types that can't be counted.

I wonder if it also makes more sense to split FwPropertyRead into two traits:
FwPropertyReadMandatory
FwPropertyReadOptional
to handle the boolean case?

> +impl<T: Integer + Copy> FwProperty for T {
> +    fn read_property(device: &Device, name: &CStr, default: Option<Self>) -> Result<Self> {
> +        let mut val: [MaybeUninit<T>; 1] = [const { MaybeUninit::uninit() }; 1];
> +        match read_array(device, name, Some(&mut val)) {
> +            // SAFETY: `read_array` returns with valid data
> +            Ok(_) => Ok(unsafe { mem::transmute_copy(&val[0]) }),

This can be simplified:

fn read_property(device: &Device, name: &CStr, default: Option<Self>)
-> Result<Self> {
    let mut val: MaybeUninit<T> = MaybeUninit::uninit();
    match read_array(device, name, Some(core::array::from_mut(&mut val))) {
        // SAFETY: `read_array` returns with valid data
        Ok(()) => Ok(val.assume_init()),

> +impl<T: Integer, const N: usize> FwProperty for [T; N] {
> +    fn read_property(device: &Device, name: &CStr, default: Option<Self>) -> Result<Self> {
> +        let mut val: [MaybeUninit<T>; N] = [const { MaybeUninit::uninit() }; N];
> +        match read_array(device, name, Some(&mut val)) {
> +            // SAFETY: `read_array` returns with valid data
> +            Ok(_) => Ok(unsafe { mem::transmute_copy(&val) }),

I don't think this one can avoid transmute_copy, though.

Alice





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux