public inbox for linux-serial@vger.kernel.org
 help / color / mirror / Atom feed
From: "Danilo Krummrich" <dakr@kernel.org>
To: "Markus Probst" <markus.probst@posteo.de>
Cc: "Kari Argillander" <kari.argillander@gmail.com>,
	"Rob Herring" <robh@kernel.org>,
	"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
	"Jiri Slaby" <jirislaby@kernel.org>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun.feng@gmail.com>,
	"Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	linux-serial@vger.kernel.org, linux-kernel@vger.kernel.org,
	rust-for-linux@vger.kernel.org
Subject: Re: [PATCH RFC 2/4] rust: add basic serial device bus abstractions
Date: Tue, 13 Jan 2026 18:37:31 +0100	[thread overview]
Message-ID: <DFNN75KWL8B9.1YHK1ZRV43W7O@kernel.org> (raw)
In-Reply-To: <76491897ad6e0ff2749935c39702b93adc9951d6.camel@posteo.de>

On Tue Jan 13, 2026 at 5:15 PM CET, Markus Probst wrote:
>> > +impl<T: Driver + 'static> Adapter<T> {
>> > +    const OPS: &'static bindings::serdev_device_ops = &bindings::serdev_device_ops {
>> > +        receive_buf: if T::HAS_RECEIVE {
>> > +            Some(Self::receive_buf_callback)
>> > +        } else {
>> > +            None
>> > +        },
>> > +        write_wakeup: if T::HAS_WRITE_WAKEUP {
>> > +            Some(Self::write_wakeup_callback)
>> > +        } else {
>> > +            Some(bindings::serdev_device_write_wakeup)
>> > +        },
>> > +    };
>> > +    const INITIAL_OPS: &'static bindings::serdev_device_ops = &bindings::serdev_device_ops {
>> > +        receive_buf: Some(Self::initial_receive_buf_callback),
>> > +        write_wakeup: if T::HAS_WRITE_WAKEUP_INITIAL {
>> > +            Some(Self::initial_write_wakeup_callback)
>> > +        } else {
>> > +            Some(bindings::serdev_device_write_wakeup)
>> > +        },
>> > +    };
>> > +    const NO_OPS: &'static bindings::serdev_device_ops = &bindings::serdev_device_ops {
>> > +        receive_buf: None,
>> > +        write_wakeup: Some(bindings::serdev_device_write_wakeup),
>> > +    };
>> > +
>> > +    extern "C" fn probe_callback(sdev: *mut bindings::serdev_device) -> kernel::ffi::c_int {
>> > +        // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer to
>> > +        // a `struct serdev_device`.
>> > +        //
>> > +        // INVARIANT: `sdev` is valid for the duration of `probe_callback()`.
>> > +        let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal>>() };
>> > +        let info = <Self as driver::Adapter>::id_info(sdev.as_ref());
>> > +
>> > +        from_result(|| {
>> > +            let data = try_pin_init!(Drvdata {
>> > +                driver <- T::probe(sdev, info),
>> > +                initial_data: Some(Default::default()).into(),
>> > +                late_probe_data: None.into(),
>> > +            });
>> > +
>> > +            sdev.as_ref().set_drvdata(data)?;

This does not work, a driver can obtain its device private data with
Device::<Bound>::drvdata() [1].

For this the driver must assert the correct type, but since you use a private
type instead of the type given by the driver, i.e. T, Device::<Bound>::drvdata()
will always fail for the driver.

[1] https://rust.docs.kernel.org/kernel/device/struct.Device.html#method.drvdata

>> > +
>> > +            // SAFETY: We just set drvdata to `Drvdata<T>`.
>> > +            let data = unsafe { sdev.as_ref().drvdata_borrow::<Drvdata<T>>() };
>> > +
>> > +            // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
>> > +            unsafe { bindings::serdev_device_set_client_ops(sdev.as_raw(), Self::INITIAL_OPS) };
>> > +
>> > +            // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer
>> > +            // to a `struct serdev_device`.
>> > +            to_result(unsafe {
>> > +                bindings::devm_serdev_device_open(sdev.as_ref().as_raw(), sdev.as_raw())
>> > +            })?;

I don't think it is a good idea to hardcode this into the probe() callback and
split it up in multiple stages, we can always solve things like ordering with
new types, type states and guards.

>> > +
>> > +            // SAFETY: `&data.driver` is guaranteed to be pinned.
>> > +            T::configure(sdev, unsafe { Pin::new_unchecked(&data.driver) }, info)?;
>> > +
>> > +            if !T::HAS_RECEIVE_INITIAL {
>> > +                // SAFETY:
>> > +                // - It is guaranteed that we have exclusive access to `data.initial_data` and
>> > +                //   `data.late_probe_data`.

How is it ensured that this does not run concurrently with
initial_receive_buf_callback(), etc.?

>> > +                // - We just initialized `data.initial_data` with Some.
>> > +                unsafe { Self::do_late_probe(sdev, data)? };
>> > +            }

It is a bit unclear to me what you try to achieve here.

Do you want to synchronize an initial data transfer? Then something along the
lines of what Kari proposes seems reasonable.

Or is the intention that this will run entirely async? But then distinct
initialization stages as they appear above won't work.

>> > +
>> > +            Ok(0)
>> > +        })
>> > +    }
>> > +
>> > +    /// # Safety
>> > +    ///
>> > +    /// The caller must guarantee, that we have exclusive access to `data.initial_data` and
>> > +    /// `data.late_probe_data`. `data.initial_data` must be Some.
>> > +    /// (i. e. `late_probe` has not been called yet).
>> > +    unsafe fn do_late_probe(sdev: &Device<device::CoreInternal>, data: Pin<&Drvdata<T>>) -> Result {
>> > +        // SAFETY: `&data.driver` is guaranteed to be pinned.
>> > +        let data_driver = unsafe { Pin::new_unchecked(&data.driver) };
>> > +
>> > +        // SAFETY: The function contract guarantees that we have exclusive access to
>> > +        // `data.initial_data`.
>> > +        let initial_data = unsafe { &mut *data.initial_data.get() };
>> > +
>> > +        // SAFETY: The function contract guarantees that we have exclusive access to
>> > +        // `data.late_probe_data`.
>> > +        let late_probe_data = unsafe { &mut *data.late_probe_data.get() };
>> > +
>> > +        *late_probe_data = Some(KBox::pin_init(
>> > +            T::late_probe(
>> > +                sdev,
>> > +                data_driver,
>> > +                // SAFETY: The function contract guarantees that `data.initial_data` is Some.
>> > +                unsafe { initial_data.take().unwrap_unchecked() },
>> > +            ),
>> > +            GFP_KERNEL,
>> > +        )?);
>> > +        // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
>> > +        unsafe { bindings::serdev_device_set_client_ops(sdev.as_raw(), Self::OPS) };
>> > +        Ok(())
>> > +    }

<snip>

>> > +    extern "C" fn initial_receive_buf_callback(
>> > +        sdev: *mut bindings::serdev_device,
>> > +        buf: *const u8,
>> > +        length: usize,
>> > +    ) -> usize {
>> > +        if !T::HAS_RECEIVE_INITIAL {
>> > +            return 0;
>> > +        }
>> > +
>> > +        // SAFETY: The serial device bus only ever calls the receive buf callback with a valid
>> > +        // pointer to a `struct serdev_device`.
>> > +        //
>> > +        // INVARIANT: `sdev` is valid for the duration of `receive_buf_callback()`.
>> > +        let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal>>() };
>> > +
>> > +        // SAFETY: `buf` is guaranteed to be non-null and has the size of `length`.
>> > +        let buf = unsafe { core::slice::from_raw_parts(buf, length) };
>> > +
>> > +        // SAFETY: `initial_receive_buf_callback` is only ever called after a successful call to
>> > +        // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
>> > +        // and stored a `Pin<KBox<Drvdata<T>>>`.
>> > +        let data = unsafe { sdev.as_ref().drvdata_borrow::<Drvdata<T>>() };
>> > +
>> > +        // SAFETY: `&data.driver` is guaranteed to be pinned.
>> > +        let driver_data = unsafe { Pin::new_unchecked(&data.driver) };
>> > +
>> > +        // SAFETY:
>> > +        // - `data.initial_data` is always Some until `InitialReceiveResult::Ready` is
>> > +        //   returned below.
>> > +        // - It is guaranteed that we have exclusive access to `data.initial_data`.
>> > +        let initial_data = unsafe { (*data.initial_data.get()).as_mut().unwrap_unchecked() };
>> > +
>> > +        match T::receive_initial(sdev, driver_data, initial_data, buf) {
>> > +            Ok(InitialReceiveResult::Pending(size)) => size,
>> > +            Ok(InitialReceiveResult::Ready(size)) => {
>> > +                // SAFETY:
>> > +                // - It is guaranteed that we have exclusive access to `data.initial_data` and
>> > +                //   `data.late_probe_data`.
>> > +                // - We just initialized `data.initial_data` with Some.
>> > +                if let Err(err) = unsafe { Self::do_late_probe(sdev, data) } {

We are calling late_probe() again from initial_receive_buf_callback()? Why?

>> > +                    dev_err!(sdev.as_ref(), "Unable to late probe device: {err:?}\n");
>> > +                    // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to
>> > +                    // `serdev_device`.
>> > +                    unsafe { bindings::serdev_device_set_client_ops(sdev.as_raw(), Self::NO_OPS) };
>> > +                    return length;
>> > +                }
>> > +                size
>> > +            }
>> > +            Err(err) => {
>> > +                dev_err!(
>> > +                    sdev.as_ref(),
>> > +                    "Unable to receive initial data for probe: {err:?}.\n"
>> > +                );
>> > +                // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
>> > +                unsafe { bindings::serdev_device_set_client_ops(sdev.as_raw(), Self::NO_OPS) };
>> > +                length
>> > +            }
>> > +        }
>> > +    }

<snip>

> There currently is only one serial device bus driver in the kernel,
> which needs initial data:
> drivers/bluetooth/hci_uart.h
> drivers/bluetooth/hci_ldisc.c
> drivers/bluetooth/hci_serdev.c
>
> This driver retrieves this initial data after probe (not in the probe)
> and then initializes it with a workqueue. Given it is part of the
> kernel, I assume this is the intended behaviour.

In this case I assume the driver has a lock protected buffer in its private
data? Which would be entirely different than what you implement above, no?

  reply	other threads:[~2026-01-13 17:37 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-20 18:44 [PATCH RFC 0/4] rust: add basic serial device bus abstractions Markus Probst
2025-12-20 18:44 ` [PATCH RFC 1/4] serdev: Export internal is_serdev_device() for drivers Markus Probst
2025-12-21 16:10   ` Greg Kroah-Hartman
2025-12-21 16:28     ` Markus Probst
2025-12-21 16:46       ` Greg Kroah-Hartman
2025-12-21 17:36       ` Danilo Krummrich
2025-12-21 17:40   ` Danilo Krummrich
2025-12-20 18:44 ` [PATCH RFC 2/4] rust: add basic serial device bus abstractions Markus Probst
2025-12-21  9:19   ` Dirk Behme
2025-12-21 12:41     ` Markus Probst
2025-12-25 15:13   ` Kari Argillander
2026-01-13 16:15     ` Markus Probst
2026-01-13 17:37       ` Danilo Krummrich [this message]
2026-01-13 17:59         ` Markus Probst
2026-01-13 19:10           ` Danilo Krummrich
2026-02-08 14:30         ` Markus Probst
2025-12-26 15:09   ` Kari Argillander
2025-12-20 18:44 ` [PATCH RFC 3/4] samples: rust: add Rust serial device bus sample device driver Markus Probst
2025-12-21  9:11   ` Dirk Behme
2025-12-21 12:39     ` Markus Probst
2025-12-20 18:44 ` [PATCH RFC 4/4] rust: Add serdev rust abstractions to MAINTAINERS file Markus Probst

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=DFNN75KWL8B9.1YHK1ZRV43W7O@kernel.org \
    --to=dakr@kernel.org \
    --cc=a.hindborg@kernel.org \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=gary@garyguo.net \
    --cc=gregkh@linuxfoundation.org \
    --cc=jirislaby@kernel.org \
    --cc=kari.argillander@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=markus.probst@posteo.de \
    --cc=ojeda@kernel.org \
    --cc=robh@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tmgross@umich.edu \
    /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