Linux Serial subsystem development
 help / color / mirror / Atom feed
* Re: [PATCH v8 3/5] rust: add basic serial device bus abstractions
From: Markus Probst @ 2026-05-30 13:53 UTC (permalink / raw)
  To: Danilo Krummrich, Markus Probst via B4 Relay
  Cc: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Kari Argillander, Rafael J. Wysocki,
	Viresh Kumar, Boqun Feng, David Airlie, Simona Vetter,
	linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
	dri-devel
In-Reply-To: <4638946fc49a38797b716ea173c93327eb751479.camel@posteo.de>

[-- Attachment #1: Type: text/plain, Size: 7051 bytes --]

On Sat, 2026-05-30 at 15:37 +0200, Markus Probst wrote:
> On Sat, 2026-05-30 at 15:10 +0200, Danilo Krummrich wrote:
> > On Sat May 30, 2026 at 3:13 AM CEST, Markus Probst via B4 Relay wrote:
> > > +    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 private_data = devres::register(
> > > +                sdev.as_ref(),
> > > +                try_pin_init!(PrivateData {
> > > +                    probe_complete <- Completion::new(),
> > > +                    error: false.into(),
> > > +                }),
> > > +                GFP_KERNEL,
> > > +            )?;
> > > +
> > > +            // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
> > > +            unsafe {
> > > +                (*sdev.as_raw()).rust_private_data =
> > > +                    (&raw const *private_data).cast::<c_void>().cast_mut()
> > > +            };
> > > +
> > > +            // 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) };
> > > +
> > > +            // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer
> > > +            // to a `serdev_device`.
> > > +            to_result(unsafe {
> > > +                bindings::devm_serdev_device_open(sdev.as_ref().as_raw(), sdev.as_raw())
> > > +            })?;
> > 
> > The bus device private data drops before devres callbacks run, thus you can't
> > use the devm helper here. I suggest to use serdev_device_open() and call
> > serdev_device_close() from remove_callback() instead.
> > 
> > You may also want to consider whether you potentially want T::unbind() to allow
> > to have concurrent transmits in flight regardless. IOW, you may want to call
> > serdev_device_close() before T::unbind() anyway.
> `serdev::Device<Core>` will be passed as argument to unbind, i.e.
> set_baudrate, set_flow_control etc. calls are possible. If we close the
> serdev device before unbind, calling those would result in a null
> pointer dereference. Maybe a driver even wants to send a message here,
> so it should stay open.
> 
> If I think about it, I can't even close it inside remove_callback after
> `T::unbind`. With the driver lifetimes, the driver could store the
> `serdev::Device<Bound>` pointer in its DriverData and could still make
> calls to the device.
> 
> Any suggestions?
I could keep calling devm, but close it before unbind and reopen it
without the receive ops. The devm will close it then again.

This way it stays open until the last devres callback has run, but
doesn't call the receive_buf_callback while the drvdata has been
dropped.

I will test it and see if it works.

Thanks
- Markus Probst

> 
> > 
> > > +
> > > +            let data = T::probe(sdev, info);
> > > +            let result = sdev.as_ref().set_drvdata(data);
> > > +
> > > +            // SAFETY: We have exclusive access to `private_data.error`.
> > > +            unsafe { *private_data.error.get() = result.is_err() };
> > > +
> > > +            private_data.probe_complete.complete_all();
> > > +
> > > +            result.map(|()| 0)
> > > +        })
> > > +    }
> > > +
> > > +    extern "C" fn remove_callback(sdev: *mut bindings::serdev_device) {
> > > +        // SAFETY: The serial device bus only ever calls the remove callback with a valid pointer
> > > +        // to a `struct serdev_device`.
> > > +        //
> > > +        // INVARIANT: `sdev` is valid for the duration of `remove_callback()`.
> > > +        let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal<'_>>>() };
> > > +
> > > +        // SAFETY: `remove_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<T::Data<'_>>>`.
> > > +        let data = unsafe { sdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
> > > +
> > > +        T::unbind(sdev, data);
> > > +    }
> > > +
> > > +    extern "C" fn receive_buf_callback(
> > > +        sdev: *mut bindings::serdev_device,
> > > +        buf: *const u8,
> > > +        length: usize,
> > > +    ) -> usize {
> > > +        // 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<'_>>>() };
> > 
> > CoreInternal dereferences to Core, which is technically unsound within this
> > callback.
> > 
> > I think you want CoreInternal for drvdata_borrow(), so we should add a
> > BoundInternal type state for this; I will send a patch for this.
> > 
> > With this and the above fixed,
> > 
> > Reviewed-by: Danilo Krummrich <dakr@kernel.org>
> > 
> > from the driver-core side of things.
> > 
> > > +        // SAFETY:
> > > +        // - The serial device bus only ever calls the receive buf callback with a valid pointer to
> > > +        //   a `struct serdev_device`.
> > > +        // - `receive_buf_callback` is only ever called after a successful call to
> > > +        //   `probe_callback`, hence it's guaranteed that `sdev.private_data` is a pointer
> > > +        //   to a valid `PrivateData`.
> > > +        let private_data = unsafe { &*(*sdev.as_raw()).rust_private_data.cast::<PrivateData>() };
> > > +
> > > +        private_data.probe_complete.wait_for_completion();
> > > +
> > > +        // SAFETY: No one has exclusive access to `private_data.error`.
> > > +        if unsafe { *private_data.error.get() } {
> > > +            return length;
> > > +        }
> > > +
> > > +        // SAFETY: `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<T::Data<'_>>>`.
> > > +        let data = unsafe { sdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
> > > +
> > > +        // SAFETY: `buf` is guaranteed to be non-null and has the size of `length`.
> > > +        let buf = unsafe { core::slice::from_raw_parts(buf, length) };
> > > +
> > > +        T::receive(sdev, data, buf)
> > > +    }
> > > +}
> 
> Thanks
> - Markus Probst

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 870 bytes --]

^ permalink raw reply

* Re: [PATCH v8 3/5] rust: add basic serial device bus abstractions
From: Markus Probst @ 2026-05-30 13:37 UTC (permalink / raw)
  To: Danilo Krummrich, Markus Probst via B4 Relay
  Cc: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Kari Argillander, Rafael J. Wysocki,
	Viresh Kumar, Boqun Feng, David Airlie, Simona Vetter,
	linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
	dri-devel
In-Reply-To: <DIW1B4FUIXO7.28GSUA0CEA4EV@kernel.org>

[-- Attachment #1: Type: text/plain, Size: 6380 bytes --]

On Sat, 2026-05-30 at 15:10 +0200, Danilo Krummrich wrote:
> On Sat May 30, 2026 at 3:13 AM CEST, Markus Probst via B4 Relay wrote:
> > +    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 private_data = devres::register(
> > +                sdev.as_ref(),
> > +                try_pin_init!(PrivateData {
> > +                    probe_complete <- Completion::new(),
> > +                    error: false.into(),
> > +                }),
> > +                GFP_KERNEL,
> > +            )?;
> > +
> > +            // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
> > +            unsafe {
> > +                (*sdev.as_raw()).rust_private_data =
> > +                    (&raw const *private_data).cast::<c_void>().cast_mut()
> > +            };
> > +
> > +            // 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) };
> > +
> > +            // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer
> > +            // to a `serdev_device`.
> > +            to_result(unsafe {
> > +                bindings::devm_serdev_device_open(sdev.as_ref().as_raw(), sdev.as_raw())
> > +            })?;
> 
> The bus device private data drops before devres callbacks run, thus you can't
> use the devm helper here. I suggest to use serdev_device_open() and call
> serdev_device_close() from remove_callback() instead.
> 
> You may also want to consider whether you potentially want T::unbind() to allow
> to have concurrent transmits in flight regardless. IOW, you may want to call
> serdev_device_close() before T::unbind() anyway.
`serdev::Device<Core>` will be passed as argument to unbind, i.e.
set_baudrate, set_flow_control etc. calls are possible. If we close the
serdev device before unbind, calling those would result in a null
pointer dereference. Maybe a driver even wants to send a message here,
so it should stay open.

If I think about it, I can't even close it inside remove_callback after
`T::unbind`. With the driver lifetimes, the driver could store the
`serdev::Device<Bound>` pointer in its DriverData and could still make
calls to the device.

Any suggestions?

> 
> > +
> > +            let data = T::probe(sdev, info);
> > +            let result = sdev.as_ref().set_drvdata(data);
> > +
> > +            // SAFETY: We have exclusive access to `private_data.error`.
> > +            unsafe { *private_data.error.get() = result.is_err() };
> > +
> > +            private_data.probe_complete.complete_all();
> > +
> > +            result.map(|()| 0)
> > +        })
> > +    }
> > +
> > +    extern "C" fn remove_callback(sdev: *mut bindings::serdev_device) {
> > +        // SAFETY: The serial device bus only ever calls the remove callback with a valid pointer
> > +        // to a `struct serdev_device`.
> > +        //
> > +        // INVARIANT: `sdev` is valid for the duration of `remove_callback()`.
> > +        let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal<'_>>>() };
> > +
> > +        // SAFETY: `remove_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<T::Data<'_>>>`.
> > +        let data = unsafe { sdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
> > +
> > +        T::unbind(sdev, data);
> > +    }
> > +
> > +    extern "C" fn receive_buf_callback(
> > +        sdev: *mut bindings::serdev_device,
> > +        buf: *const u8,
> > +        length: usize,
> > +    ) -> usize {
> > +        // 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<'_>>>() };
> 
> CoreInternal dereferences to Core, which is technically unsound within this
> callback.
> 
> I think you want CoreInternal for drvdata_borrow(), so we should add a
> BoundInternal type state for this; I will send a patch for this.
> 
> With this and the above fixed,
> 
> Reviewed-by: Danilo Krummrich <dakr@kernel.org>
> 
> from the driver-core side of things.
> 
> > +        // SAFETY:
> > +        // - The serial device bus only ever calls the receive buf callback with a valid pointer to
> > +        //   a `struct serdev_device`.
> > +        // - `receive_buf_callback` is only ever called after a successful call to
> > +        //   `probe_callback`, hence it's guaranteed that `sdev.private_data` is a pointer
> > +        //   to a valid `PrivateData`.
> > +        let private_data = unsafe { &*(*sdev.as_raw()).rust_private_data.cast::<PrivateData>() };
> > +
> > +        private_data.probe_complete.wait_for_completion();
> > +
> > +        // SAFETY: No one has exclusive access to `private_data.error`.
> > +        if unsafe { *private_data.error.get() } {
> > +            return length;
> > +        }
> > +
> > +        // SAFETY: `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<T::Data<'_>>>`.
> > +        let data = unsafe { sdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
> > +
> > +        // SAFETY: `buf` is guaranteed to be non-null and has the size of `length`.
> > +        let buf = unsafe { core::slice::from_raw_parts(buf, length) };
> > +
> > +        T::receive(sdev, data, buf)
> > +    }
> > +}

Thanks
- Markus Probst


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 870 bytes --]

^ permalink raw reply

* Re: [PATCH v8 3/5] rust: add basic serial device bus abstractions
From: Danilo Krummrich @ 2026-05-30 13:10 UTC (permalink / raw)
  To: Markus Probst via B4 Relay
  Cc: markus.probst, Rob Herring, Greg Kroah-Hartman, Jiri Slaby,
	Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Kari Argillander,
	Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
	Simona Vetter, linux-serial, linux-kernel, rust-for-linux,
	linux-pm, driver-core, dri-devel
In-Reply-To: <20260530-rust_serdev-v8-3-2a95f1da22a7@posteo.de>

On Sat May 30, 2026 at 3:13 AM CEST, Markus Probst via B4 Relay wrote:
> +    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 private_data = devres::register(
> +                sdev.as_ref(),
> +                try_pin_init!(PrivateData {
> +                    probe_complete <- Completion::new(),
> +                    error: false.into(),
> +                }),
> +                GFP_KERNEL,
> +            )?;
> +
> +            // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
> +            unsafe {
> +                (*sdev.as_raw()).rust_private_data =
> +                    (&raw const *private_data).cast::<c_void>().cast_mut()
> +            };
> +
> +            // 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) };
> +
> +            // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer
> +            // to a `serdev_device`.
> +            to_result(unsafe {
> +                bindings::devm_serdev_device_open(sdev.as_ref().as_raw(), sdev.as_raw())
> +            })?;

The bus device private data drops before devres callbacks run, thus you can't
use the devm helper here. I suggest to use serdev_device_open() and call
serdev_device_close() from remove_callback() instead.

You may also want to consider whether you potentially want T::unbind() to allow
to have concurrent transmits in flight regardless. IOW, you may want to call
serdev_device_close() before T::unbind() anyway.

> +
> +            let data = T::probe(sdev, info);
> +            let result = sdev.as_ref().set_drvdata(data);
> +
> +            // SAFETY: We have exclusive access to `private_data.error`.
> +            unsafe { *private_data.error.get() = result.is_err() };
> +
> +            private_data.probe_complete.complete_all();
> +
> +            result.map(|()| 0)
> +        })
> +    }
> +
> +    extern "C" fn remove_callback(sdev: *mut bindings::serdev_device) {
> +        // SAFETY: The serial device bus only ever calls the remove callback with a valid pointer
> +        // to a `struct serdev_device`.
> +        //
> +        // INVARIANT: `sdev` is valid for the duration of `remove_callback()`.
> +        let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal<'_>>>() };
> +
> +        // SAFETY: `remove_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<T::Data<'_>>>`.
> +        let data = unsafe { sdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
> +
> +        T::unbind(sdev, data);
> +    }
> +
> +    extern "C" fn receive_buf_callback(
> +        sdev: *mut bindings::serdev_device,
> +        buf: *const u8,
> +        length: usize,
> +    ) -> usize {
> +        // 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<'_>>>() };

CoreInternal dereferences to Core, which is technically unsound within this
callback.

I think you want CoreInternal for drvdata_borrow(), so we should add a
BoundInternal type state for this; I will send a patch for this.

With this and the above fixed,

Reviewed-by: Danilo Krummrich <dakr@kernel.org>

from the driver-core side of things.

> +        // SAFETY:
> +        // - The serial device bus only ever calls the receive buf callback with a valid pointer to
> +        //   a `struct serdev_device`.
> +        // - `receive_buf_callback` is only ever called after a successful call to
> +        //   `probe_callback`, hence it's guaranteed that `sdev.private_data` is a pointer
> +        //   to a valid `PrivateData`.
> +        let private_data = unsafe { &*(*sdev.as_raw()).rust_private_data.cast::<PrivateData>() };
> +
> +        private_data.probe_complete.wait_for_completion();
> +
> +        // SAFETY: No one has exclusive access to `private_data.error`.
> +        if unsafe { *private_data.error.get() } {
> +            return length;
> +        }
> +
> +        // SAFETY: `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<T::Data<'_>>>`.
> +        let data = unsafe { sdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
> +
> +        // SAFETY: `buf` is guaranteed to be non-null and has the size of `length`.
> +        let buf = unsafe { core::slice::from_raw_parts(buf, length) };
> +
> +        T::receive(sdev, data, buf)
> +    }
> +}

^ permalink raw reply

* Re: [PATCH v8 1/5] rust: devres: return reference in `devres::register`
From: Danilo Krummrich @ 2026-05-30 11:54 UTC (permalink / raw)
  To: Markus Probst via B4 Relay
  Cc: markus.probst, Rob Herring, Greg Kroah-Hartman, Jiri Slaby,
	Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Kari Argillander,
	Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
	Simona Vetter, linux-serial, linux-kernel, rust-for-linux,
	linux-pm, driver-core, dri-devel
In-Reply-To: <20260530-rust_serdev-v8-1-2a95f1da22a7@posteo.de>

On Sat May 30, 2026 at 3:13 AM CEST, Markus Probst via B4 Relay wrote:
> From: Markus Probst <markus.probst@posteo.de>
>
> Return the reference to the initialized data in the `devres::register`
> function.
>
> This is needed in a following commit (rust: add basic serial device bus
> abstractions).
>
> Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
> Signed-off-by: Markus Probst <markus.probst@posteo.de>

For driver-core and DRM,

Acked-by: Danilo Krummrich <dakr@kernel.org>

This is useful in the context of the HRT work in general, should this series not
make it this cycle, please let me know, then I'm going to pick this through the
driver-core tree.

^ permalink raw reply

* [GIT PULL] TTY/Serial driver fixes for 7.1-rc6
From: Greg KH @ 2026-05-30 11:29 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Jiri Slaby, Andrew Morton, linux-kernel, linux-serial

The following changes since commit 254f49634ee16a731174d2ae34bc50bd5f45e731:

  Linux 7.1-rc1 (2026-04-26 14:19:00 -0700)

are available in the Git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tags/tty-7.1-rc6

for you to fetch changes up to e4240d8845445d58b4b96f7066adfe175a61bd0c:

  serial: dz: Enable modular build (2026-05-22 11:52:34 +0200)

----------------------------------------------------------------
TTY/Serial driver fixes for 7.1-rc6

Here are some small serial driver fixes for 7.1-rc6.  Included in here
are:
  - mips serial driver fixes to resolve some long-standing issues with
    how they interacted with the console.  That's the "majority" of the
    changes in this merge request
  - sh-sci driver regression fix
  - 8250 driver regression fixes
  - other small serial driver fixes for reported problems.

All of these have been in linux-next for over a week with no reported
issues.

Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

----------------------------------------------------------------
Hongling Zeng (1):
      serial: sh-sci: fix memory region release in error path

Jacques Nilo (3):
      serial: core: introduce guard(uart_port_lock_check_sysrq_irqsave)
      serial: 8250: dispatch SysRq character in serial8250_handle_irq()
      serial: 8250_dw: dispatch SysRq character in dw8250_handle_irq()

Johan Hovold (1):
      tty: add missing tty_driver include to tty_port.h

Maciej W. Rozycki (8):
      serial: zs: Fix swapped RI/DSR modem line transition counting
      serial: dz: Fix bootconsole message clobbering at chip reset
      serial: dz: Fix bootconsole handover lockup
      serial: zs: Fix bootconsole handover lockup
      serial: zs: Switch to using channel reset
      serial: dz: Convert to use a platform device
      serial: zs: Convert to use a platform device
      serial: dz: Enable modular build

Myeonghun Pak (1):
      serial: altera_jtaguart: handle uart_add_one_port() failures

Prasanna S (1):
      serial: qcom-geni: fix UART_RX_PAR_EN bit position

Shitalkumar Gandhi (1):
      serial: fsl_lpuart: fix rx buffer and DMA map leaks in start_rx_dma

Tudor Ambarus (1):
      tty: serial: samsung: Remove redundant port lock acquisition in rx helpers

Viken Dadhaniya (1):
      serial: qcom_geni: fix kfifo underflow when flush precedes DMA completion IRQ

Zhaoyang Yu (1):
      tty: serial: pch_uart: add check for dma_alloc_coherent()

 arch/mips/dec/platform.c              | 109 ++++++++++++++++-
 drivers/tty/serial/8250/8250_dw.c     |   2 +-
 drivers/tty/serial/8250/8250_port.c   |   7 +-
 drivers/tty/serial/Kconfig            |   2 +-
 drivers/tty/serial/altera_jtaguart.c  |   7 +-
 drivers/tty/serial/dz.c               | 171 +++++++++++++-------------
 drivers/tty/serial/fsl_lpuart.c       |  15 ++-
 drivers/tty/serial/pch_uart.c         |  19 ++-
 drivers/tty/serial/qcom_geni_serial.c |  16 ++-
 drivers/tty/serial/samsung_tty.c      |   8 --
 drivers/tty/serial/sh-sci.c           |   2 +-
 drivers/tty/serial/zs.c               | 218 ++++++++++++----------------------
 drivers/tty/serial/zs.h               |   1 -
 include/linux/serial_core.h           |  12 ++
 include/linux/tty_port.h              |   2 +-
 15 files changed, 341 insertions(+), 250 deletions(-)

^ permalink raw reply

* Re: [PATCH 2/3] dt-bindings: serial: maxim,max310x: allow per-port subnodes for rs485
From: Krzysztof Kozlowski @ 2026-05-30 11:26 UTC (permalink / raw)
  To: Tapio Reijonen
  Cc: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hugo Villeneuve, linux-kernel, linux-serial,
	devicetree
In-Reply-To: <20260525-b4-max310x-rs485-dt-v1-2-e6c19b4d5592@vaisala.com>

On Mon, May 25, 2026 at 09:43:38AM +0000, Tapio Reijonen wrote:
> The MAX310x is a multi-port UART (up to four ports). The existing
> binding pulls in /schemas/serial/rs485.yaml at the top level, which
> only describes a single port - sufficient for max3107 but ambiguous
> for max14830 where each port can have its own RS485 wiring.
> 
> Add a "port@N" pattern (N = 0..3) carrying rs485 properties on a
> per-port basis. When port@N subnodes are present, the chip node also
> needs #address-cells = <1> and #size-cells = <0>; allow both. Top-
> level rs485 properties remain accepted for compatibility.
> 
> Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
> ---
>  .../devicetree/bindings/serial/maxim,max310x.yaml  | 60 ++++++++++++++++++++++
>  1 file changed, 60 insertions(+)

That's a total mess now in the binding.
1. maxim,max3107 does not have ports, but you add there. You need to
constrain (see writing bindings) or split.
2. So where do you place serial devices? Did you validate any of this?

Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH] serial: mxs-auart: fix probe error paths and clock handling
From: Greg Kroah-Hartman @ 2026-05-30  5:21 UTC (permalink / raw)
  To: Rosen Penev
  Cc: linux-serial, Jiri Slaby, Frank Li, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam,
	open list:TTY LAYER AND SERIAL DRIVERS,
	open list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE
In-Reply-To: <20260528230616.186792-1-rosenp@gmail.com>

On Thu, May 28, 2026 at 04:06:16PM -0700, Rosen Penev wrote:
> Sashiko reported three pre-existing bugs in the mxs-auart driver:
> 
> - For non-ASM9260 variants, mxs_get_clks() obtained the clock but never
>   prepared/enabled it, leaving register accesses in probe at risk of
>   faulting if the bootloader had gated the clock.
> - The error path and remove function used pdev->id instead of
>   s->port.line to clear the auart_port[] slot.  For DT-probed devices
>   pdev->id is -1, causing an out-of-bounds write and leaving a dangling
>   pointer in the array.

This feels like it should be a different patch, and not burried in this
change, right?

> - The probe error path called iounmap() while the IRQ was still
>   registered. An interrupt during that window would dereference the
>   unmapped membase.
> 
> All of this is a consequence of using mixed devm and non devm. Instead
> of working around these issues, go full devm so that everything can be
> cleaned up properly.

Are you sure that devm and irq code is going to work properly?  That's
always a very tricky code path, was this tested with the hardware to
ensure it still works on teardown correctly?

thanks,

greg k-h

^ permalink raw reply

* Re: [PATCH tty] tty: n_gsm: fix use-after-free in gsm_queue vs gsm_cleanup_mux race
From: Greg KH @ 2026-05-30  5:19 UTC (permalink / raw)
  To: Zhenghang Xiao; +Cc: jirislaby, linux-serial
In-Reply-To: <CACQTXGDb0vHRxjYTJo8r9Sq_ozu8Z=Y7GR4+F-2HGKGsA17kcw@mail.gmail.com>

On Fri, May 29, 2026 at 02:28:22PM +0800, Zhenghang Xiao wrote:
> Resending this email as plain text, since the previous reply was
> rejected by the mailing list for containing an HTML part.
> -------------------
> Thanks for your reply!
> 
> > How to test
> I built two identical arm64 kernels (v7.1.0-rc4), differing only by
> this patch. Both kernel carry a msleep(20) instrumentation in
> gsm_queue() to widen the race window. Then run PoC and the patched
> kernel do not report kASAN again.

What PoC?  Do you have the hardware that uses this ldisc?

> > What prevents dead from changing
> Nothing prevents it and it doesn't need to. tty_ldisc_flush() is the
> sync mechanism not dead check.
> If gsmld_receive_buf() is already past the check, it's running under
> buf->lock. The patch moves tty_ldisc_flush() before DLCI release. And
> tty_buffer_flush() acquires the same buf->lock, so it blocks until the
> in-flight receive completes. DLCIs are freed only after that.
> 
> The dead check handles the other direction, any receive that starts
> after the flush sees dead == true and returns early. But I'm not sure
> if silent drop here is fine or not.

I don't think dropping is ok, test this on real hardware to see what
happens.

Also, this should be 2 patches, not 1, right?

thanks,

greg k-h

^ permalink raw reply

* [PATCH] tty: serial: mpc52xx_uart: add bounds check for psc_num array index
From: Rosen Penev @ 2026-05-30  6:10 UTC (permalink / raw)
  To: linux-serial
  Cc: Greg Kroah-Hartman, Jiri Slaby,
	open list:TTY LAYER AND SERIAL DRIVERS

psc_num is derived from port->mapbase bits 11:8, giving a range of
0-15, but the psc_mclk_clk and psc_ipg_clk arrays are sized to
MPC52xx_PSC_MAXNUM (12 when CONFIG_PPC_MPC512x is set). A malformed
device tree with bits 11:8 >= 12 would cause out-of-bounds writes
in mpc512x_psc_alloc_clock() and out-of-bounds reads/writes in
mpc512x_psc_relse_clock() and mpc512x_psc_endis_clock(). The same
unchecked index also appears in mpc512x_psc_handle_irq().

Add ARRAY_SIZE() bounds checks to all four functions before using
psc_num as an array index.

Assisted-by: Opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 drivers/tty/serial/mpc52xx_uart.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/tty/serial/mpc52xx_uart.c b/drivers/tty/serial/mpc52xx_uart.c
index 37eb701b0b46..b566206f42a2 100644
--- a/drivers/tty/serial/mpc52xx_uart.c
+++ b/drivers/tty/serial/mpc52xx_uart.c
@@ -645,6 +645,8 @@ static irqreturn_t mpc512x_psc_handle_irq(struct uart_port *port)
 
 	/* Check if it is an interrupt for this port */
 	psc_num = (port->mapbase & 0xf00) >> 8;
+	if (psc_num >= ARRAY_SIZE(psc_mclk_clk))
+		return IRQ_NONE;
 	if (test_bit(psc_num, &fifoc_int) ||
 	    test_bit(psc_num + 16, &fifoc_int))
 		return mpc5xxx_uart_process_int(port);
@@ -663,6 +665,8 @@ static int mpc512x_psc_alloc_clock(struct uart_port *port)
 	int err;
 
 	psc_num = (port->mapbase & 0xf00) >> 8;
+	if (psc_num >= ARRAY_SIZE(psc_mclk_clk))
+		return -EINVAL;
 
 	clk = devm_clk_get(port->dev, "mclk");
 	if (IS_ERR(clk)) {
@@ -711,6 +715,8 @@ static void mpc512x_psc_relse_clock(struct uart_port *port)
 	struct clk *clk;
 
 	psc_num = (port->mapbase & 0xf00) >> 8;
+	if (psc_num >= ARRAY_SIZE(psc_mclk_clk))
+		return;
 	clk = psc_mclk_clk[psc_num];
 	if (clk) {
 		clk_disable_unprepare(clk);
@@ -733,6 +739,8 @@ static int mpc512x_psc_endis_clock(struct uart_port *port, int enable)
 		return 0;
 
 	psc_num = (port->mapbase & 0xf00) >> 8;
+	if (psc_num >= ARRAY_SIZE(psc_mclk_clk))
+		return -ENODEV;
 	psc_clk = psc_mclk_clk[psc_num];
 	if (!psc_clk) {
 		dev_err(port->dev, "Failed to get PSC clock entry!\n");
-- 
2.54.0


^ permalink raw reply related

* [PATCH v8 5/5] MAINTAINERS: serdev: Add self for serdev
From: Markus Probst via B4 Relay @ 2026-05-30  1:13 UTC (permalink / raw)
  To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
	Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
	Simona Vetter, Boqun Feng
  Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
	dri-devel, Markus Probst
In-Reply-To: <20260530-rust_serdev-v8-0-2a95f1da22a7@posteo.de>

From: Markus Probst <markus.probst@posteo.de>

Rob mentioned he needs to find someone else to maintain serdev.

Link: https://lore.kernel.org/rust-for-linux/20260430195858.GA1650658-robh@kernel.org/
Link: https://lore.kernel.org/rust-for-linux/da85ceb81f51079d4a8248a1ffde6a27d2ef24ad.camel@posteo.de/
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
 MAINTAINERS | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index fa3f20f0cc4f..58db6aedf45a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24270,7 +24270,7 @@ F:	drivers/iio/chemical/sps30_i2c.c
 F:	drivers/iio/chemical/sps30_serial.c
 
 SERIAL DEVICE BUS
-M:	Rob Herring <robh@kernel.org>
+M:	Markus Probst <markus.probst@posteo.de>
 L:	linux-serial@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/serial/serial.yaml

-- 
2.53.0



^ permalink raw reply related

* [PATCH v8 4/5] samples: rust: add Rust serial device bus sample device driver
From: Markus Probst via B4 Relay @ 2026-05-30  1:13 UTC (permalink / raw)
  To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
	Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
	Simona Vetter, Boqun Feng
  Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
	dri-devel, Markus Probst
In-Reply-To: <20260530-rust_serdev-v8-0-2a95f1da22a7@posteo.de>

From: Markus Probst <markus.probst@posteo.de>

Add a sample Rust serial device bus device driver illustrating the usage
of the serial device bus abstractions.

This drivers probes through either a match of device / driver name or a
match within the OF ID table.

Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
 MAINTAINERS                        |  1 +
 samples/rust/Kconfig               | 11 +++++
 samples/rust/Makefile              |  1 +
 samples/rust/rust_driver_serdev.rs | 91 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 104 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index d2f608ff8ca0..fa3f20f0cc4f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24278,6 +24278,7 @@ F:	drivers/tty/serdev/
 F:	include/linux/serdev.h
 F:	rust/helpers/serdev.c
 F:	rust/kernel/serdev.rs
+F:	samples/rust/rust_driver_serdev.rs
 
 SERIAL IR RECEIVER
 M:	Sean Young <sean@mess.org>
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index c49ab9106345..31d62533ef25 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -161,6 +161,17 @@ config SAMPLE_RUST_DRIVER_AUXILIARY
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_DRIVER_SERDEV
+	tristate "Serial Device Bus Device Driver"
+	select RUST_SERIAL_DEV_BUS_ABSTRACTIONS
+	help
+	  This option builds the Rust serial device bus driver sample.
+
+	  To compile this as a module, choose M here:
+	  the module will be called rust_driver_serdev.
+
+	  If unsure, say N.
+
 config SAMPLE_RUST_SOC
 	tristate "SoC Driver"
 	select SOC_BUS
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index 6c0aaa58cccc..b986b681cde5 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM)	+= rust_driver_platform.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB)		+= rust_driver_usb.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX)		+= rust_driver_faux.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY)	+= rust_driver_auxiliary.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_SERDEV)		+= rust_driver_serdev.o
 obj-$(CONFIG_SAMPLE_RUST_CONFIGFS)		+= rust_configfs.o
 obj-$(CONFIG_SAMPLE_RUST_SOC)			+= rust_soc.o
 
diff --git a/samples/rust/rust_driver_serdev.rs b/samples/rust/rust_driver_serdev.rs
new file mode 100644
index 000000000000..824affbf6593
--- /dev/null
+++ b/samples/rust/rust_driver_serdev.rs
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust Serial device bus device driver sample.
+
+use kernel::{
+    acpi,
+    device::{
+        Bound,
+        Core, //
+    },
+    of,
+    prelude::*,
+    serdev,
+    sync::aref::ARef, //
+};
+
+struct SampleDriver {
+    sdev: ARef<serdev::Device>,
+}
+
+kernel::of_device_table!(
+    OF_TABLE,
+    MODULE_OF_TABLE,
+    <SampleDriver as serdev::Driver>::IdInfo,
+    [(of::DeviceId::new(c"test,rust_driver_serdev"), ())]
+);
+
+kernel::acpi_device_table!(
+    ACPI_TABLE,
+    MODULE_ACPI_TABLE,
+    <SampleDriver as serdev::Driver>::IdInfo,
+    [(acpi::DeviceId::new(c"LNUXBEEF"), ())]
+);
+
+#[vtable]
+impl serdev::Driver for SampleDriver {
+    type IdInfo = ();
+    type Data<'bound> = Self;
+    const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+    const ACPI_ID_TABLE: Option<acpi::IdTable<Self::IdInfo>> = Some(&ACPI_TABLE);
+
+    fn probe<'bound>(
+        sdev: &'bound serdev::Device<Core<'_>>,
+        _info: Option<&'bound Self::IdInfo>,
+    ) -> impl PinInit<Self, Error> + 'bound {
+        let dev = sdev.as_ref();
+
+        dev_dbg!(dev, "Probe Rust Serial device bus device driver sample.\n");
+
+        if sdev
+            .set_baudrate(
+                dev.fwnode()
+                    .and_then(|fwnode| fwnode.property_read(c"baudrate").optional())
+                    .unwrap_or(115200),
+            )
+            .is_err()
+        {
+            return Err(EINVAL);
+        }
+        sdev.set_flow_control(false);
+        sdev.set_parity(serdev::Parity::None)?;
+
+        Ok(Self { sdev: sdev.into() })
+    }
+
+    fn receive<'bound>(
+        sdev: &'bound serdev::Device<Bound>,
+        _this: Pin<&Self>,
+        data: &[u8],
+    ) -> usize {
+        let _ = sdev.write_all(data, serdev::Timeout::Max);
+        data.len()
+    }
+}
+
+impl Drop for SampleDriver {
+    fn drop(&mut self) {
+        dev_dbg!(
+            self.sdev.as_ref(),
+            "Remove Rust Serial device bus device driver sample.\n"
+        );
+    }
+}
+
+kernel::module_serdev_device_driver! {
+    type: SampleDriver,
+    name: "rust_driver_serdev",
+    authors: ["Markus Probst"],
+    description: "Rust Serial device bus device driver",
+    license: "GPL v2",
+}

-- 
2.53.0



^ permalink raw reply related

* [PATCH v8 3/5] rust: add basic serial device bus abstractions
From: Markus Probst via B4 Relay @ 2026-05-30  1:13 UTC (permalink / raw)
  To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
	Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
	Simona Vetter, Boqun Feng
  Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
	dri-devel, Markus Probst
In-Reply-To: <20260530-rust_serdev-v8-0-2a95f1da22a7@posteo.de>

From: Markus Probst <markus.probst@posteo.de>

Implement the basic serial device bus abstractions required to write a
serial device bus device driver with or without the need for initial device
data. This includes the following data structures:

The `serdev::Driver` trait represents the interface to the driver.

The `serdev::Device` abstraction represents a `struct serdev_device`.

In order to provide the Serdev specific parts to a generic
`driver::Registration` the `driver::RegistrationOps` trait is
implemented by `serdev::Adapter`.

Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
 MAINTAINERS                     |   2 +
 drivers/tty/serdev/Kconfig      |   7 +
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/serdev.c           |  22 ++
 rust/kernel/lib.rs              |   2 +
 rust/kernel/serdev.rs           | 548 ++++++++++++++++++++++++++++++++++++++++
 7 files changed, 583 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 4e118f704699..d2f608ff8ca0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24276,6 +24276,8 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/serial/serial.yaml
 F:	drivers/tty/serdev/
 F:	include/linux/serdev.h
+F:	rust/helpers/serdev.c
+F:	rust/kernel/serdev.rs
 
 SERIAL IR RECEIVER
 M:	Sean Young <sean@mess.org>
diff --git a/drivers/tty/serdev/Kconfig b/drivers/tty/serdev/Kconfig
index 46ae732bfc68..e6dfe949ad01 100644
--- a/drivers/tty/serdev/Kconfig
+++ b/drivers/tty/serdev/Kconfig
@@ -9,6 +9,13 @@ menuconfig SERIAL_DEV_BUS
 
 	  Note that you typically also want to enable TTY port controller support.
 
+config RUST_SERIAL_DEV_BUS_ABSTRACTIONS
+	bool "Rust Serial device bus abstractions"
+	depends on RUST
+	select SERIAL_DEV_BUS
+	help
+	  This enables the Rust abstraction for the serial device bus API.
+
 if SERIAL_DEV_BUS
 
 config SERIAL_DEV_CTRL_TTYPORT
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 446dbeaf0866..4e42635b8607 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -84,6 +84,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/sched.h>
 #include <linux/security.h>
+#include <linux/serdev.h>
 #include <linux/slab.h>
 #include <linux/sys_soc.h>
 #include <linux/task_work.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 38b34518eff1..2fb8506a748a 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -86,6 +86,7 @@
 #include "regulator.c"
 #include "scatterlist.c"
 #include "security.c"
+#include "serdev.c"
 #include "signal.c"
 #include "slab.c"
 #include "spinlock.c"
diff --git a/rust/helpers/serdev.c b/rust/helpers/serdev.c
new file mode 100644
index 000000000000..c52b78ca3fc7
--- /dev/null
+++ b/rust/helpers/serdev.c
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/serdev.h>
+
+__rust_helper
+void rust_helper_serdev_device_driver_unregister(struct serdev_device_driver *sdrv)
+{
+	serdev_device_driver_unregister(sdrv);
+}
+
+__rust_helper
+void rust_helper_serdev_device_put(struct serdev_device *serdev)
+{
+	serdev_device_put(serdev);
+}
+
+__rust_helper
+void rust_helper_serdev_device_set_client_ops(struct serdev_device *serdev,
+					      const struct serdev_device_ops *ops)
+{
+	serdev_device_set_client_ops(serdev, ops);
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index b72b2fbe046d..83bc2c312241 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -118,6 +118,8 @@
 pub mod scatterlist;
 pub mod security;
 pub mod seq_file;
+#[cfg(CONFIG_RUST_SERIAL_DEV_BUS_ABSTRACTIONS)]
+pub mod serdev;
 pub mod sizes;
 #[cfg(CONFIG_SOC_BUS)]
 pub mod soc;
diff --git a/rust/kernel/serdev.rs b/rust/kernel/serdev.rs
new file mode 100644
index 000000000000..e78dac480533
--- /dev/null
+++ b/rust/kernel/serdev.rs
@@ -0,0 +1,548 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for the serial device bus.
+//!
+//! C header: [`include/linux/serdev.h`](srctree/include/linux/serdev.h)
+
+use crate::{
+    acpi,
+    device,
+    devres,
+    driver,
+    error::{
+        from_result,
+        to_result,
+        VTABLE_DEFAULT_ERROR, //
+    },
+    of,
+    prelude::*,
+    sync::{
+        aref::AlwaysRefCounted,
+        Completion, //
+    },
+    time::{
+        msecs_to_jiffies,
+        Jiffies,
+        Msecs, //
+    },
+    types::Opaque, //
+};
+
+use core::{
+    cell::UnsafeCell,
+    marker::PhantomData,
+    mem::offset_of,
+    num::NonZero,
+    ptr::NonNull, //
+};
+
+/// Parity bit to use with a serial device.
+#[repr(u32)]
+pub enum Parity {
+    /// No parity bit.
+    None = bindings::serdev_parity_SERDEV_PARITY_NONE,
+    /// Even partiy.
+    Even = bindings::serdev_parity_SERDEV_PARITY_EVEN,
+    /// Odd parity.
+    Odd = bindings::serdev_parity_SERDEV_PARITY_ODD,
+}
+
+/// Timeout in Jiffies.
+pub enum Timeout {
+    /// Wait for a specific amount of [`Jiffies`].
+    Jiffies(NonZero<Jiffies>),
+    /// Wait for a specific amount of [`Msecs`].
+    Milliseconds(NonZero<Msecs>),
+    /// Wait as long as possible.
+    ///
+    /// This is equivalent to [`kernel::task::MAX_SCHEDULE_TIMEOUT`].
+    Max,
+}
+
+impl Timeout {
+    fn into_jiffies(self) -> isize {
+        match self {
+            Self::Jiffies(value) => value.get().try_into().unwrap_or_default(),
+            Self::Milliseconds(value) => {
+                msecs_to_jiffies(value.get()).try_into().unwrap_or_default()
+            }
+            Self::Max => 0,
+        }
+    }
+}
+
+/// An adapter for the registration of serial device bus device drivers.
+pub struct Adapter<T: Driver>(T);
+
+// SAFETY:
+// - `bindings::serdev_device_driver` is a C type declared as `repr(C)`.
+// - `T::Data` is the type of the driver's device private data.
+// - `struct serdev_device_driver` embeds a `struct device_driver`.
+// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`.
+unsafe impl<T: Driver> driver::DriverLayout for Adapter<T> {
+    type DriverType = bindings::serdev_device_driver;
+    type DriverData<'bound> = T::Data<'bound>;
+    const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver);
+}
+
+// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if
+// a preceding call to `register` has been successful.
+unsafe impl<T: Driver> driver::RegistrationOps for Adapter<T> {
+    unsafe fn register(
+        sdrv: &Opaque<Self::DriverType>,
+        name: &'static CStr,
+        module: &'static ThisModule,
+    ) -> Result {
+        let of_table = match T::OF_ID_TABLE {
+            Some(table) => table.as_ptr(),
+            None => core::ptr::null(),
+        };
+
+        let acpi_table = match T::ACPI_ID_TABLE {
+            Some(table) => table.as_ptr(),
+            None => core::ptr::null(),
+        };
+
+        // SAFETY: It's safe to set the fields of `struct serdev_device_driver` on initialization.
+        unsafe {
+            (*sdrv.get()).driver.name = name.as_char_ptr();
+            (*sdrv.get()).probe = Some(Self::probe_callback);
+            (*sdrv.get()).remove = Some(Self::remove_callback);
+            (*sdrv.get()).driver.of_match_table = of_table;
+            (*sdrv.get()).driver.acpi_match_table = acpi_table;
+        }
+
+        // SAFETY: `sdrv` is guaranteed to be a valid `DriverType`.
+        to_result(unsafe { bindings::__serdev_device_driver_register(sdrv.get(), module.0) })
+    }
+
+    unsafe fn unregister(sdrv: &Opaque<Self::DriverType>) {
+        // SAFETY: `sdrv` is guaranteed to be a valid `DriverType`.
+        unsafe { bindings::serdev_device_driver_unregister(sdrv.get()) };
+    }
+}
+
+#[pin_data]
+struct PrivateData {
+    #[pin]
+    probe_complete: Completion,
+    error: UnsafeCell<bool>,
+}
+
+impl<T: Driver> 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: 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 private_data = devres::register(
+                sdev.as_ref(),
+                try_pin_init!(PrivateData {
+                    probe_complete <- Completion::new(),
+                    error: false.into(),
+                }),
+                GFP_KERNEL,
+            )?;
+
+            // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
+            unsafe {
+                (*sdev.as_raw()).rust_private_data =
+                    (&raw const *private_data).cast::<c_void>().cast_mut()
+            };
+
+            // 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) };
+
+            // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer
+            // to a `serdev_device`.
+            to_result(unsafe {
+                bindings::devm_serdev_device_open(sdev.as_ref().as_raw(), sdev.as_raw())
+            })?;
+
+            let data = T::probe(sdev, info);
+            let result = sdev.as_ref().set_drvdata(data);
+
+            // SAFETY: We have exclusive access to `private_data.error`.
+            unsafe { *private_data.error.get() = result.is_err() };
+
+            private_data.probe_complete.complete_all();
+
+            result.map(|()| 0)
+        })
+    }
+
+    extern "C" fn remove_callback(sdev: *mut bindings::serdev_device) {
+        // SAFETY: The serial device bus only ever calls the remove callback with a valid pointer
+        // to a `struct serdev_device`.
+        //
+        // INVARIANT: `sdev` is valid for the duration of `remove_callback()`.
+        let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal<'_>>>() };
+
+        // SAFETY: `remove_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<T::Data<'_>>>`.
+        let data = unsafe { sdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
+
+        T::unbind(sdev, data);
+    }
+
+    extern "C" fn receive_buf_callback(
+        sdev: *mut bindings::serdev_device,
+        buf: *const u8,
+        length: usize,
+    ) -> usize {
+        // 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:
+        // - The serial device bus only ever calls the receive buf callback with a valid pointer to
+        //   a `struct serdev_device`.
+        // - `receive_buf_callback` is only ever called after a successful call to
+        //   `probe_callback`, hence it's guaranteed that `sdev.private_data` is a pointer
+        //   to a valid `PrivateData`.
+        let private_data = unsafe { &*(*sdev.as_raw()).rust_private_data.cast::<PrivateData>() };
+
+        private_data.probe_complete.wait_for_completion();
+
+        // SAFETY: No one has exclusive access to `private_data.error`.
+        if unsafe { *private_data.error.get() } {
+            return length;
+        }
+
+        // SAFETY: `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<T::Data<'_>>>`.
+        let data = unsafe { sdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
+
+        // SAFETY: `buf` is guaranteed to be non-null and has the size of `length`.
+        let buf = unsafe { core::slice::from_raw_parts(buf, length) };
+
+        T::receive(sdev, data, buf)
+    }
+}
+
+impl<T: Driver> driver::Adapter for Adapter<T> {
+    type IdInfo = T::IdInfo;
+
+    fn of_id_table() -> Option<of::IdTable<Self::IdInfo>> {
+        T::OF_ID_TABLE
+    }
+
+    fn acpi_id_table() -> Option<acpi::IdTable<Self::IdInfo>> {
+        T::ACPI_ID_TABLE
+    }
+}
+
+/// Declares a kernel module that exposes a single serial device bus device driver.
+///
+/// # Examples
+///
+/// ```ignore
+/// kernel::module_serdev_device_driver! {
+///     type: MyDriver,
+///     name: "Module name",
+///     authors: ["Author name"],
+///     description: "Description",
+///     license: "GPL v2",
+/// }
+/// ```
+#[macro_export]
+macro_rules! module_serdev_device_driver {
+    ($($f:tt)*) => {
+        $crate::module_driver!(<T>, $crate::serdev::Adapter<T>, { $($f)* });
+    };
+}
+
+/// The serial device bus device driver trait.
+///
+/// Drivers must implement this trait in order to get a serial device bus device driver registered.
+///
+/// # Examples
+///
+///```
+/// # use kernel::{
+///     acpi,
+///     bindings,
+///     device::{
+///         Bound,
+///         Core, //
+///     },
+///     of,
+///     serdev, //
+/// };
+///
+/// struct MyDriver;
+///
+/// kernel::of_device_table!(
+///     OF_TABLE,
+///     MODULE_OF_TABLE,
+///     <MyDriver as serdev::Driver>::IdInfo,
+///     [
+///         (of::DeviceId::new(c"test,device"), ())
+///     ]
+/// );
+///
+/// kernel::acpi_device_table!(
+///     ACPI_TABLE,
+///     MODULE_ACPI_TABLE,
+///     <MyDriver as serdev::Driver>::IdInfo,
+///     [
+///         (acpi::DeviceId::new(c"LNUXBEEF"), ())
+///     ]
+/// );
+///
+/// #[vtable]
+/// impl serdev::Driver for MyDriver {
+///     type IdInfo = ();
+///     type Data<'bound> = Self;
+///     const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+///     const ACPI_ID_TABLE: Option<acpi::IdTable<Self::IdInfo>> = Some(&ACPI_TABLE);
+///
+///     fn probe<'bound>(
+///         sdev: &'bound serdev::Device<Core<'_>>,
+///         _id_info: Option<&'bound Self::IdInfo>,
+///     ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
+///         sdev.set_baudrate(115200);
+///         sdev.write_all(b"Hello\n", serdev::Timeout::Max)?;
+///         Ok(MyDriver)
+///     }
+/// }
+///```
+#[vtable]
+pub trait Driver {
+    /// The type holding driver private data about each device id supported by the driver.
+    // TODO: Use associated_type_defaults once stabilized:
+    //
+    // ```
+    // type IdInfo: 'static = ();
+    // ```
+    type IdInfo: 'static;
+
+    /// The type of the driver's bus device private data.
+    type Data<'bound>: Send + 'bound;
+
+    /// The table of OF device ids supported by the driver.
+    const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = None;
+
+    /// The table of ACPI device ids supported by the driver.
+    const ACPI_ID_TABLE: Option<acpi::IdTable<Self::IdInfo>> = None;
+
+    /// Serial device bus device driver probe.
+    ///
+    /// Called when a new serial device bus device is added or discovered.
+    /// Implementers should attempt to initialize the device here.
+    fn probe<'bound>(
+        sdev: &'bound Device<device::Core<'_>>,
+        id_info: Option<&'bound Self::IdInfo>,
+    ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound;
+
+    /// Serial device bus device driver unbind.
+    ///
+    /// Called when a [`Device`] is unbound from its bound [`Driver`]. Implementing this callback
+    /// is optional.
+    ///
+    /// This callback serves as a place for drivers to perform teardown operations that require a
+    /// `&Device<Core>` or `&Device<Bound>` reference. For instance.
+    ///
+    /// Otherwise, release operations for driver resources should be performed in `Drop`.
+    fn unbind<'bound>(sdev: &'bound Device<device::Core<'_>>, this: Pin<&Self::Data<'bound>>) {
+        let _ = (sdev, this);
+    }
+
+    /// Serial device bus device data receive callback.
+    ///
+    /// Called when data got received from device.
+    ///
+    /// Returns the number of bytes accepted.
+    fn receive<'bound>(
+        sdev: &'bound Device<device::Bound>,
+        this: Pin<&Self::Data<'bound>>,
+        data: &[u8],
+    ) -> usize {
+        let _ = (sdev, this, data);
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+}
+
+/// The serial device bus device representation.
+///
+/// This structure represents the Rust abstraction for a C `struct serdev_device`. The
+/// implementation abstracts the usage of an already existing C `struct serdev_device` within Rust
+/// code that we get passed from the C side.
+///
+/// # Invariants
+///
+/// A [`Device`] instance represents a valid `struct serdev_device` created by the C portion of
+/// the kernel.
+#[repr(transparent)]
+pub struct Device<Ctx: device::DeviceContext = device::Normal>(
+    Opaque<bindings::serdev_device>,
+    PhantomData<Ctx>,
+);
+
+impl<Ctx: device::DeviceContext> Device<Ctx> {
+    fn as_raw(&self) -> *mut bindings::serdev_device {
+        self.0.get()
+    }
+}
+
+impl Device<device::Bound> {
+    /// Set the baudrate in bits per second.
+    ///
+    /// Common baudrates are 115200, 9600, 19200, 57600, 4800.
+    ///
+    /// Use [`Device::write_flush`] before calling this if you have written data prior to this call.
+    pub fn set_baudrate(&self, speed: u32) -> Result<(), u32> {
+        // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+        let ret = unsafe { bindings::serdev_device_set_baudrate(self.as_raw(), speed) };
+        if ret == speed {
+            Ok(())
+        } else {
+            Err(ret)
+        }
+    }
+
+    /// Set if flow control should be enabled.
+    ///
+    /// Use [`Device::write_flush`] before calling this if you have written data prior to this call.
+    pub fn set_flow_control(&self, enable: bool) {
+        // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+        unsafe { bindings::serdev_device_set_flow_control(self.as_raw(), enable) };
+    }
+
+    /// Set parity to use.
+    ///
+    /// Use [`Device::write_flush`] before calling this if you have written data prior to this call.
+    pub fn set_parity(&self, parity: Parity) -> Result {
+        // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+        to_result(unsafe { bindings::serdev_device_set_parity(self.as_raw(), parity as u32) })
+    }
+
+    /// Write data to the serial device until the controller has accepted all the data or has
+    /// been interrupted by a timeout or signal.
+    ///
+    /// Note that any accepted data has only been buffered by the controller. Use
+    /// [`Device::wait_until_sent`] to make sure the controller write buffer has actually been
+    /// emptied.
+    ///
+    /// Returns the number of bytes written (less than `data.len()` if interrupted).
+    /// [`kernel::error::code::ETIMEDOUT`] or [`kernel::error::code::ERESTARTSYS`] if interrupted
+    /// before any bytes were written.
+    pub fn write_all(&self, data: &[u8], timeout: Timeout) -> Result<usize> {
+        // SAFETY:
+        // - `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+        // - `data.as_ptr()` is guaranteed to be a valid array pointer with the size of
+        //   `data.len()`.
+        let ret = unsafe {
+            bindings::serdev_device_write(
+                self.as_raw(),
+                data.as_ptr(),
+                data.len(),
+                timeout.into_jiffies(),
+            )
+        };
+        // CAST: negative return values are guaranteed to be between `-MAX_ERRNO` and `-1`,
+        // which always fit into a `i32`.
+        to_result(ret as i32).map(|()| ret.unsigned_abs())
+    }
+
+    /// Write data to the serial device.
+    ///
+    /// If you want to write until the controller has accepted all the data, use
+    /// [`Device::write_all`].
+    ///
+    /// Note that any accepted data has only been buffered by the controller. Use
+    /// [ Device::wait_until_sent`] to make sure the controller write buffer has actually been
+    /// emptied.
+    ///
+    /// Returns the number of bytes written (less than `data.len()` if not enough room in the
+    /// write buffer).
+    pub fn write(&self, data: &[u8]) -> Result<u32> {
+        // SAFETY:
+        // - `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+        // - `data.as_ptr()` is guaranteed to be a valid array pointer with the size of
+        //   `data.len()`.
+        let ret =
+            unsafe { bindings::serdev_device_write_buf(self.as_raw(), data.as_ptr(), data.len()) };
+
+        to_result(ret as i32).map(|()| ret.unsigned_abs())
+    }
+
+    /// Send data to the serial device immediately.
+    ///
+    /// Note that this doesn't guarantee that the data has been transmitted.
+    /// Use [`Device::wait_until_sent`] for this purpose.
+    pub fn write_flush(&self) {
+        // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+        unsafe { bindings::serdev_device_write_flush(self.as_raw()) };
+    }
+
+    /// Wait for the data to be sent.
+    ///
+    /// After this function, the write buffer of the controller should be empty.
+    pub fn wait_until_sent(&self, timeout: Timeout) {
+        // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+        unsafe { bindings::serdev_device_wait_until_sent(self.as_raw(), timeout.into_jiffies()) };
+    }
+}
+
+// SAFETY: `serdev::Device` is a transparent wrapper of `struct serdev_device`.
+// The offset is guaranteed to point to a valid device field inside `serdev::Device`.
+unsafe impl<Ctx: device::DeviceContext> device::AsBusDevice<Ctx> for Device<Ctx> {
+    const OFFSET: usize = offset_of!(bindings::serdev_device, dev);
+}
+
+// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic
+// argument.
+kernel::impl_device_context_deref!(unsafe { Device });
+kernel::impl_device_context_into_aref!(Device);
+
+// SAFETY: Instances of `Device` are always reference-counted.
+unsafe impl AlwaysRefCounted for Device {
+    fn inc_ref(&self) {
+        self.as_ref().inc_ref();
+    }
+
+    unsafe fn dec_ref(obj: NonNull<Self>) {
+        // SAFETY: The safety requirements guarantee that the refcount is non-zero.
+        unsafe { bindings::serdev_device_put(obj.cast().as_ptr()) }
+    }
+}
+
+impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> {
+    fn as_ref(&self) -> &device::Device<Ctx> {
+        // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+        // `struct serdev_device`.
+        let dev = unsafe { &raw mut (*self.as_raw()).dev };
+
+        // SAFETY: `dev` points to a valid `struct device`.
+        unsafe { device::Device::from_raw(dev) }
+    }
+}
+
+// SAFETY: A `Device` is always reference-counted and can be released from any thread.
+unsafe impl Send for Device {}
+
+// SAFETY: `Device` can be shared among threads because all methods of `Device`
+// (i.e. `Device<Normal>) are thread safe.
+unsafe impl Sync for Device {}
+
+// SAFETY: Same as `Device<Normal>` -- the underlying `struct serdev_device` is the same;
+// `Bound` is a zero-sized type-state marker that does not affect thread safety.
+unsafe impl Sync for Device<device::Bound> {}

-- 
2.53.0



^ permalink raw reply related

* [PATCH v8 0/5] rust: add basic serial device bus abstractions
From: Markus Probst via B4 Relay @ 2026-05-30  1:13 UTC (permalink / raw)
  To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
	Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
	Simona Vetter, Boqun Feng
  Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
	dri-devel, Markus Probst

This patch series adds the serdev device bus rust abstraction into the
kernel.

This abstraction will be used by a driver,
which targets the MCU devices in Synology devices.

Kari Argillander also messaged me, stating that he wants to write a
watchdog driver with this abstraction (needing initial device data).

This series depends on [1].

[1]
https://lore.kernel.org/rust-for-linux/20260525202921.124698-1-dakr@kernel.org/

Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
Changes in v8:
- adapted to driver-lifetime v5 patch series
- add MAINTAINERS file patch
- Link to v7: https://patch.msgid.link/20260429-rust_serdev-v7-0-0d89c791b5c8@posteo.de

Changes in v7:
- adapted to driver-lifetime patch series
- Link to v6: https://patch.msgid.link/20260427-rust_serdev-v6-0-173798d5e1a3@posteo.de

Changes in v6:
- rebased onto v7.1-rc1
- Link to v5: https://patch.msgid.link/20260420-rust_serdev-v5-0-57e8ba0519f3@posteo.de

Changes in v5:
- fix typo in documentation
- Link to v4: https://lore.kernel.org/r/20260411-rust_serdev-v4-0-845e960c6627@posteo.de

Changes in v4:
- fixed not selecting rust serdev abstraction in sample
- Link to v3: https://lore.kernel.org/r/20260313-rust_serdev-v3-0-c9a3af214f7f@posteo.de

Changes in v3:
- fix vertical import style
- add Kconfig entry for the rust abstraction
- fix documentation in include/linux/serdev.h
- rename private_data to rust_private_data
- fix `complete_all` <-> `wait_for_completion` typo
- move drvdata_borrow call after the completion
- Link to v2: https://lore.kernel.org/r/20260306-rust_serdev-v2-0-e9b23b42b255@posteo.de

Changes in v2:
- fix documentation in `serdev::Driver::write` and
  `serdev::Driver::write_all`
- remove use of `dev_info` in probe from the sample
- remove `properties_parse` from the sample
- add optional `baudrate` property to the sample
- remove 1. patch
- remove `TryFrom<&device::Device<Ctx>> for &serdev::Device<Ctx>`
  implementation
- fix import style
- add patch to return reference in `devres::register` to fix safety
  issue
- add patch to add private data to serdev_device, to fix
  `Device.drvdata()` from failing
- simplify abstraction by removing ability to receive the initial
  transmission. It may be added later in a separate patch series if
  needed.
- Link to v1: https://lore.kernel.org/r/20251220-rust_serdev-v1-0-e44645767621@posteo.de

---
Markus Probst (5):
      rust: devres: return reference in `devres::register`
      serdev: add rust private data to serdev_device
      rust: add basic serial device bus abstractions
      samples: rust: add Rust serial device bus sample device driver
      MAINTAINERS: serdev: Add self for serdev

 MAINTAINERS                        |   5 +-
 drivers/tty/serdev/Kconfig         |   7 +
 include/linux/serdev.h             |  15 +-
 rust/bindings/bindings_helper.h    |   1 +
 rust/helpers/helpers.c             |   1 +
 rust/helpers/serdev.c              |  22 ++
 rust/kernel/cpufreq.rs             |   3 +-
 rust/kernel/devres.rs              |  15 +-
 rust/kernel/drm/driver.rs          |   3 +-
 rust/kernel/lib.rs                 |   2 +
 rust/kernel/serdev.rs              | 548 +++++++++++++++++++++++++++++++++++++
 samples/rust/Kconfig               |  11 +
 samples/rust/Makefile              |   1 +
 samples/rust/rust_driver_serdev.rs |  91 ++++++
 14 files changed, 714 insertions(+), 11 deletions(-)
---
base-commit: 9e171fc1d7d7ab847a750c03571c87ac3c17bd84
change-id: 20251217-rust_serdev-ee5481e9085c
prerequisite-message-id: 20260505152400.3905096-1-dakr@kernel.org
prerequisite-patch-id: d2aebf69b153c039bbed1d0ed26906708fd22534
prerequisite-patch-id: 84b28da2f5de20fc1785095c647b2ffc35d969a5
prerequisite-patch-id: 67318671a5eed5fb4ad23a450f1cf0e442bf8ca2
prerequisite-message-id: 20260525202921.124698-1-dakr@kernel.org
prerequisite-patch-id: b84db329d4372a175cb8d49e4e88c3eecf7eb228
prerequisite-patch-id: 2c30303f409cc8288cc87e241920219f5ddd8390
prerequisite-patch-id: 4e4f0ad370d763ad00b0f75b91fa216f2cc95953
prerequisite-patch-id: 5bcd6b37f3498feebda275dfef78136eba34004e
prerequisite-patch-id: 872b0982f3e5e7d1698d9df3b325e4cd27b27789
prerequisite-patch-id: 3a3c7749e017d9335f58497404d1350e96caf471
prerequisite-patch-id: 3526c9154f581497a11465b936d83ef61a875454
prerequisite-patch-id: 65d8c757b52475c2acc7d22ddc92cd3f0152b55d
prerequisite-patch-id: 4bd31f1414d5248dc080884caadf5f21684a8427
prerequisite-patch-id: 7beadbb0da3e589ed86d12f512d1c83427dd82b4
prerequisite-patch-id: 12cd0f67ffd27347f90c065db491945908206b7f
prerequisite-patch-id: 4642e31f66331f6c3b579377111ea733dbb3a11c
prerequisite-patch-id: 52d67b40b4396c741e2222d6a5bc7927abcb77aa
prerequisite-patch-id: 74ca82ff26cf9c7a993757c87db8be62006e820f
prerequisite-patch-id: 466fb9fa7febbffd8ef51b311c7d9893c11fc0f0
prerequisite-patch-id: e515cd98b06e26721cbbe6a4fbacd251d0073b63
prerequisite-patch-id: 8dc8e75d9f6499a554ef7e474bbacdbf3660a9f2
prerequisite-patch-id: 5fdb9f71dca2f44dd293760a60db125b770f1f55
prerequisite-patch-id: c766a24c2d5064f5ec09daada0b8e8fba862d3aa
prerequisite-patch-id: b768f6456d35fa7a80c015e34bbdba6082dbd593
prerequisite-patch-id: 6a8b17234f12f7084e6e2ce843a7031b0a891ce4
prerequisite-patch-id: 98b2deb9e60c1f28f90c5ee34fd608aaa9fd9420
prerequisite-patch-id: 774b29be66e641ee50cedb4704cf49d8b9fabf50
prerequisite-patch-id: cf95dc936cfc4b3a7a363435a51a48d9009645b3



^ permalink raw reply

* [PATCH v8 2/5] serdev: add rust private data to serdev_device
From: Markus Probst via B4 Relay @ 2026-05-30  1:13 UTC (permalink / raw)
  To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
	Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
	Simona Vetter, Boqun Feng
  Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
	dri-devel, Markus Probst
In-Reply-To: <20260530-rust_serdev-v8-0-2a95f1da22a7@posteo.de>

From: Markus Probst <markus.probst@posteo.de>

Add rust private data to `struct serdev_device`, as it is required by the
rust abstraction added in the following commit
(rust: add basic serial device bus abstractions).

Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
 include/linux/serdev.h | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/include/linux/serdev.h b/include/linux/serdev.h
index b6c3d957ec15..048ef5857786 100644
--- a/include/linux/serdev.h
+++ b/include/linux/serdev.h
@@ -33,12 +33,14 @@ struct serdev_device_ops {
 
 /**
  * struct serdev_device - Basic representation of an serdev device
- * @dev:	Driver model representation of the device.
- * @nr:		Device number on serdev bus.
- * @ctrl:	serdev controller managing this device.
- * @ops:	Device operations.
- * @write_comp:	Completion used by serdev_device_write() internally
- * @write_lock:	Lock to serialize access when writing data
+ * @dev:		Driver model representation of the device.
+ * @nr:			Device number on serdev bus.
+ * @ctrl:		serdev controller managing this device.
+ * @ops:		Device operations.
+ * @write_comp:		Completion used by serdev_device_write() internally
+ * @write_lock:		Lock to serialize access when writing data
+ * @rust_private_data:	Private data for the rust abstraction. This should
+ *			not be used by the C drivers.
  */
 struct serdev_device {
 	struct device dev;
@@ -47,6 +49,7 @@ struct serdev_device {
 	const struct serdev_device_ops *ops;
 	struct completion write_comp;
 	struct mutex write_lock;
+	void *rust_private_data;
 };
 
 #define to_serdev_device(d) container_of_const(d, struct serdev_device, dev)

-- 
2.53.0



^ permalink raw reply related

* [PATCH v8 1/5] rust: devres: return reference in `devres::register`
From: Markus Probst via B4 Relay @ 2026-05-30  1:13 UTC (permalink / raw)
  To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
	Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
	Simona Vetter, Boqun Feng
  Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
	dri-devel, Markus Probst
In-Reply-To: <20260530-rust_serdev-v8-0-2a95f1da22a7@posteo.de>

From: Markus Probst <markus.probst@posteo.de>

Return the reference to the initialized data in the `devres::register`
function.

This is needed in a following commit (rust: add basic serial device bus
abstractions).

Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
 rust/kernel/cpufreq.rs    |  3 ++-
 rust/kernel/devres.rs     | 15 +++++++++++++--
 rust/kernel/drm/driver.rs |  3 ++-
 3 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs
index d94c6cdbc45a..bbbd4f54218a 100644
--- a/rust/kernel/cpufreq.rs
+++ b/rust/kernel/cpufreq.rs
@@ -1053,7 +1053,8 @@ pub fn new_foreign_owned(dev: &Device<Bound>) -> Result
     where
         T: 'static,
     {
-        devres::register(dev, Self::new()?, GFP_KERNEL)
+        devres::register(dev, Self::new()?, GFP_KERNEL)?;
+        Ok(())
     }
 }
 
diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs
index 82cbd8b969fb..e3e954478caa 100644
--- a/rust/kernel/devres.rs
+++ b/rust/kernel/devres.rs
@@ -419,15 +419,26 @@ fn register_foreign<P>(dev: &Device<Bound>, data: P) -> Result
 /// }
 ///
 /// fn from_bound_context(dev: &Device<Bound>) -> Result {
-///     devres::register(dev, Registration::new(), GFP_KERNEL)
+///     devres::register(dev, Registration::new(), GFP_KERNEL)?;
+///     Ok(())
 /// }
 /// ```
-pub fn register<T, E>(dev: &Device<Bound>, data: impl PinInit<T, E>, flags: Flags) -> Result
+pub fn register<'a, T, E>(
+    dev: &'a Device<Bound>,
+    data: impl PinInit<T, E>,
+    flags: Flags,
+) -> Result<&'a T>
 where
     T: Send + 'static,
     Error: From<E>,
 {
     let data = KBox::pin_init(data, flags)?;
 
+    let data_ptr = &raw const *data;
+
     register_foreign(dev, data)
+        // SAFETY: `dev` is valid for the lifetime of 'a. As long as there is a reference to
+        // `Device<Bound>`, it is guaranteed that the device is not unbound and data has not been
+        // dropped. Thus `data_ptr` is also valid for the lifetime of 'a.
+        .map(|()| unsafe { &*data_ptr })
 }
diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs
index 5233bdebc9fc..1edfd7bacddb 100644
--- a/rust/kernel/drm/driver.rs
+++ b/rust/kernel/drm/driver.rs
@@ -147,7 +147,8 @@ pub fn new_foreign_owned(
 
         let reg = Registration::<T>::new(drm, flags)?;
 
-        devres::register(dev, reg, GFP_KERNEL)
+        devres::register(dev, reg, GFP_KERNEL)?;
+        Ok(())
     }
 
     /// Returns a reference to the `Device` instance for this registration.

-- 
2.53.0



^ permalink raw reply related

* Re: [PATCH v4 1/2] serial: qcom-geni: trace: Add tracepoint support for Qualcomm GENI serial
From: Steven Rostedt @ 2026-05-29 14:14 UTC (permalink / raw)
  To: Praveen Talari
  Cc: Masami Hiramatsu, Mathieu Desnoyers, Greg Kroah-Hartman,
	Jiri Slaby, konrad.dybcio, linux-kernel, linux-trace-kernel,
	linux-arm-msm, linux-serial, mukesh.savaliya, aniket.randive,
	chandana.chiluveru
In-Reply-To: <20260526-add-tracepoints-for-qcom-geni-serial-v4-1-e94fbaec0232@oss.qualcomm.com>

On Tue, 26 May 2026 23:07:39 +0530
Praveen Talari <praveen.talari@oss.qualcomm.com> wrote:

> +DECLARE_EVENT_CLASS(geni_serial_data,
> +		    TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
> +		    TP_ARGS(dev, buf, len),
> +
> +		    TP_STRUCT__entry(__string(name, dev_name(dev))
> +				     __field(unsigned int, len)
> +				     __dynamic_array(u8, data, len)
> +		    ),
> +
> +		    TP_fast_assign(__assign_str(name);
> +				   __entry->len = len;
> +				   memcpy(__get_dynamic_array(data), buf, len);
> +		    ),
> +
> +		    TP_printk("%s: len=%u data=%s",
> +			      __get_str(name), __entry->len,
> +			      __print_hex(__get_dynamic_array(data), __entry->len))
> +);

No need to save the length of the dynamic array in __entry->len because
it's already saved in the metadata of the dynamic array that is stored
on the buffer. Instead you can have:

DECLARE_EVENT_CLASS(geni_serial_data,
		    TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
		    TP_ARGS(dev, buf, len),

		    TP_STRUCT__entry(__string(name, dev_name(dev))
				     __dynamic_array(u8, data, len)
		    ),

		    TP_fast_assign(__assign_str(name);
				   memcpy(__get_dynamic_array(data), buf, len);
		    ),

		    TP_printk("%s: len=%u data=%s",
			      __get_str(name), __entry->len,
			      __print_hex(__get_dynamic_array(data),
					__get_dynamic_array_len(data)))
);

That will save you 4 bytes per event on the ring buffer. And a few
cycles not having to store the redundant information.

-- Steve

^ permalink raw reply

* Re: [PATCH 3/4] tty: serial: men_z135_uart: replace __get_free_page() with kmalloc()
From: Jiri Slaby @ 2026-05-29  8:52 UTC (permalink / raw)
  To: Mike Rapoport; +Cc: Greg Kroah-Hartman, linux-kernel, linux-mm, linux-serial
In-Reply-To: <ahlRqd3e94ojSfWE@kernel.org>

On 29. 05. 26, 10:43, Mike Rapoport wrote:
> On Fri, May 29, 2026 at 09:47:33AM +0200, Jiri Slaby wrote:
>> On 28. 05. 26, 12:24, Mike Rapoport (Microsoft) wrote:
>>> men_z135_probe() allocates a receive staging buffer filled by the
>>> CPU via memcpy_fromio() from the device MMIO region.
>>>
>>> This buffer can be allocated with kmalloc() as there's nothing special
>>> about it to go directly to the page allocator.
>>>
>>> kmalloc() provides a better API that does not require ugly casts and
>>> kfree() does not need to know the size of the freed object.
>>>
>>> Replace use of __get_free_page() with kmalloc() and free_page() with
>>> kfree().
>>>
>>> Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com
>>> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
>>> ---
>>>    drivers/tty/serial/men_z135_uart.c | 7 ++++---
>>>    1 file changed, 4 insertions(+), 3 deletions(-)
>>>
>>> diff --git a/drivers/tty/serial/men_z135_uart.c b/drivers/tty/serial/men_z135_uart.c
>>> index 6fad57fee912..9c32b01edc9e 100644
>>> --- a/drivers/tty/serial/men_z135_uart.c
>>> +++ b/drivers/tty/serial/men_z135_uart.c
>>> @@ -17,6 +17,7 @@
>>>    #include <linux/bitops.h>
>>>    #include <linux/mcb.h>
>>> +#include <linux/slab.h>
>>>    #define MEN_Z135_MAX_PORTS		12
>>
>> This one is misplaced.
> 
> Do you mean an empty line is missing? Or there's particular order of
> includes here?

You added it after an empty line -- along to #defines. You should had 
added it along #includes instead -- before the empty line.

>> thanks,
>> -- 
>> js
>> suse labs
> 


-- 
js
suse labs

^ permalink raw reply

* Re: [PATCH 3/4] tty: serial: men_z135_uart: replace __get_free_page() with kmalloc()
From: Mike Rapoport @ 2026-05-29  8:43 UTC (permalink / raw)
  To: Jiri Slaby; +Cc: Greg Kroah-Hartman, linux-kernel, linux-mm, linux-serial
In-Reply-To: <18bb4a45-c26b-4a89-b598-a844d3aadafa@kernel.org>

On Fri, May 29, 2026 at 09:47:33AM +0200, Jiri Slaby wrote:
> On 28. 05. 26, 12:24, Mike Rapoport (Microsoft) wrote:
> > men_z135_probe() allocates a receive staging buffer filled by the
> > CPU via memcpy_fromio() from the device MMIO region.
> > 
> > This buffer can be allocated with kmalloc() as there's nothing special
> > about it to go directly to the page allocator.
> > 
> > kmalloc() provides a better API that does not require ugly casts and
> > kfree() does not need to know the size of the freed object.
> > 
> > Replace use of __get_free_page() with kmalloc() and free_page() with
> > kfree().
> > 
> > Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com
> > Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> > ---
> >   drivers/tty/serial/men_z135_uart.c | 7 ++++---
> >   1 file changed, 4 insertions(+), 3 deletions(-)
> > 
> > diff --git a/drivers/tty/serial/men_z135_uart.c b/drivers/tty/serial/men_z135_uart.c
> > index 6fad57fee912..9c32b01edc9e 100644
> > --- a/drivers/tty/serial/men_z135_uart.c
> > +++ b/drivers/tty/serial/men_z135_uart.c
> > @@ -17,6 +17,7 @@
> >   #include <linux/bitops.h>
> >   #include <linux/mcb.h>
> > +#include <linux/slab.h>
> >   #define MEN_Z135_MAX_PORTS		12
> 
> This one is misplaced.

Do you mean an empty line is missing? Or there's particular order of
includes here?
 
> thanks,
> -- 
> js
> suse labs

-- 
Sincerely yours,
Mike.

^ permalink raw reply

* [PATCH v4] serial: 8250: fix use-after-free in IRQ chain handling
From: Qiliang Yuan @ 2026-05-29  8:23 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Anton Vorontsov, Alan Cox
  Cc: linux-kernel, linux-serial, Wang Zhaolong, Qiliang Yuan

serial_unlink_irq_chain() holds hash_mutex and calls free_irq() + kfree(i)
when it sees an empty port list.  serial_link_irq_chain() released
hash_mutex after serial_get_or_create_irq_info() but before acquiring
i->lock.  This gap allowed a concurrent unlink to observe list_empty()
as true while a new port was still being added, free i, and trigger a
use-after-free.

Dropping hash_mutex before request_irq() completes also allows another
port sharing the same IRQ to join the chain and run the shared-IRQ THRE
test while IRQ startup is still in progress, which can also trigger the
"Unbalanced enable for IRQ" warning (kernel/irq/manage.c:774) because
irq_shutdown() in the premature free_irq() path increments desc->depth,
breaking the disable_irq/enable_irq pairing in serial8250_THRE_test().

Fix by pulling hash_mutex into serial_link_irq_chain() and holding it
across the first request_irq() completion (including the error path)
so that no concurrent unlink or second-port join can race with IRQ
setup or cleanup.
serial_unlink_irq_chain() already holds hash_mutex throughout, so the
race window is closed.

Fixes: 768aec0b5bcc ("serial: 8250: fix shared interrupts issues with SMP and RT kernels")
Reported-by: Wang Zhaolong <wangzhaolong@fnnas.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221579
Signed-off-by: Qiliang Yuan <realwujing@gmail.com>
---
V3 -> V4:
- Move cleanup under hash_mutex on request_irq() failure to prevent a
  second port from joining the chain before the irq_info is cleaned up.
- Fix inaccurate description of irq_shutdown() in commit message.

V2 -> V3:
- Hold hash_mutex across the first request_irq() completion to prevent a
  second port from joining the chain and running the shared-IRQ THRE test
  while IRQ startup is still in progress.

V1 -> V2:
- Add Reported-by tag from Wang Zhaolong.

v3: https://lore.kernel.org/r/20260529-bug-221579-8250-shared-irq-race-v3-1-fe4d430862a9@gmail.com
v2: https://lore.kernel.org/r/20260528-bug-221579-8250-shared-irq-race-v2-1-06531202e54d@gmail.com
v1: https://lore.kernel.org/r/20260528-bug-221579-8250-shared-irq-race-v1-1-30980cca02f3@gmail.com
---
 drivers/tty/serial/8250/8250_core.c | 55 ++++++++++++++++++++++++++++---------
 1 file changed, 42 insertions(+), 13 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index a428e88938eb7..70d5acfa591bf 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -134,7 +134,7 @@ static struct irq_info *serial_get_or_create_irq_info(const struct uart_8250_por
 {
 	struct irq_info *i;
 
-	guard(mutex)(&hash_mutex);
+	lockdep_assert_held(&hash_mutex);
 
 	hash_for_each_possible(irq_lists, i, node, up->port.irq)
 		if (i->irq == up->port.irq)
@@ -151,31 +151,60 @@ static struct irq_info *serial_get_or_create_irq_info(const struct uart_8250_por
 	return i;
 }
 
+/*
+ * serial_link_irq_chain() hooks the given 8250 port into the IRQ chain.
+ *
+ * hash_mutex must be held from the hash lookup through the first
+ * request_irq() completion.  Dropping it earlier allows a concurrent
+ * serial_unlink_irq_chain() to race in after i->head is published but
+ * before the IRQ is fully set up — another port sharing the IRQ can then
+ * join the chain and run the shared-IRQ THRE test while IRQ startup is
+ * still in progress, triggering an "Unbalanced enable for IRQ" warning
+ * in kernel/irq/manage.c.
+ */
 static int serial_link_irq_chain(struct uart_8250_port *up)
 {
 	struct irq_info *i;
 	int ret;
 
+	mutex_lock(&hash_mutex);
+
 	i = serial_get_or_create_irq_info(up);
-	if (IS_ERR(i))
+	if (IS_ERR(i)) {
+		mutex_unlock(&hash_mutex);
 		return PTR_ERR(i);
+	}
 
-	scoped_guard(spinlock_irq, &i->lock) {
-		if (i->head) {
-			list_add(&up->list, i->head);
-
-			return 0;
-		}
+	/*
+	 * Serialise against the list manipulation in the interrupt handler
+	 * and in serial_unlink_irq_chain().  hash_mutex is still held which
+	 * prevents serial_unlink_irq_chain() from entering and freeing the
+	 * irq_info until the first request_irq() completes.
+	 */
+	spin_lock_irq(&i->lock);
+	if (i->head) {
+		list_add(&up->list, i->head);
+		spin_unlock_irq(&i->lock);
+		mutex_unlock(&hash_mutex);
 
-		INIT_LIST_HEAD(&up->list);
-		i->head = &up->list;
+		return 0;
 	}
 
-	ret = request_irq(up->port.irq, serial8250_interrupt, up->port.irqflags, up->port.name, i);
-	if (ret < 0)
+	INIT_LIST_HEAD(&up->list);
+	i->head = &up->list;
+	spin_unlock_irq(&i->lock);
+
+	ret = request_irq(up->port.irq, serial8250_interrupt,
+			  up->port.irqflags, up->port.name, i);
+	if (ret < 0) {
 		serial_do_unlink(i, up);
+		mutex_unlock(&hash_mutex);
+		return ret;
+	}
 
-	return ret;
+	mutex_unlock(&hash_mutex);
+
+	return 0;
 }
 
 static void serial_unlink_irq_chain(struct uart_8250_port *up)

---
base-commit: eb3f4b7426cfd2b79d65b7d37155480b32259a11
change-id: 20260528-bug-221579-8250-shared-irq-race-581e4900a178

Best regards,
-- 
Qiliang Yuan <realwujing@gmail.com>


^ permalink raw reply related

* Re: [PATCH 3/4] tty: serial: men_z135_uart: replace __get_free_page() with kmalloc()
From: Jiri Slaby @ 2026-05-29  7:47 UTC (permalink / raw)
  To: Mike Rapoport (Microsoft), Greg Kroah-Hartman
  Cc: linux-kernel, linux-mm, linux-serial
In-Reply-To: <20260528-b4-tty-v1-3-9da9f7aec5f2@kernel.org>

On 28. 05. 26, 12:24, Mike Rapoport (Microsoft) wrote:
> men_z135_probe() allocates a receive staging buffer filled by the
> CPU via memcpy_fromio() from the device MMIO region.
> 
> This buffer can be allocated with kmalloc() as there's nothing special
> about it to go directly to the page allocator.
> 
> kmalloc() provides a better API that does not require ugly casts and
> kfree() does not need to know the size of the freed object.
> 
> Replace use of __get_free_page() with kmalloc() and free_page() with
> kfree().
> 
> Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
>   drivers/tty/serial/men_z135_uart.c | 7 ++++---
>   1 file changed, 4 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/tty/serial/men_z135_uart.c b/drivers/tty/serial/men_z135_uart.c
> index 6fad57fee912..9c32b01edc9e 100644
> --- a/drivers/tty/serial/men_z135_uart.c
> +++ b/drivers/tty/serial/men_z135_uart.c
> @@ -17,6 +17,7 @@
>   #include <linux/bitops.h>
>   #include <linux/mcb.h>
>   
> +#include <linux/slab.h>
>   #define MEN_Z135_MAX_PORTS		12

This one is misplaced.

thanks,
-- 
js
suse labs

^ permalink raw reply

* Re: [PATCH 4/4] vc_screen: replace __get_free_pages() with kmalloc()
From: Jiri Slaby @ 2026-05-29  7:47 UTC (permalink / raw)
  To: Mike Rapoport (Microsoft), Greg Kroah-Hartman
  Cc: linux-kernel, linux-mm, linux-serial
In-Reply-To: <20260528-b4-tty-v1-4-9da9f7aec5f2@kernel.org>

On 28. 05. 26, 12:24, Mike Rapoport (Microsoft) wrote:
> vcs_read() and vcs_write() allocate staging buffers with
> __get_free_pages().
> 
> These buffers can be allocated with kmalloc() as there's nothing special
> about them to go directly to the page allocator.
> 
> kmalloc() provides a better API that does not require ugly casts and it's a
> modern way of saying "I need a page-sized buffer"
> 
> Replace use of __get_free_page() with kmalloc() and drop unused now
> DEFINE_FREE(free_page_ptr ...)
> 
> Link: https://lore.kernel.org/all/700c5a5f-3128-4671-99aa-827ca73f5cdf@kernel.org
> Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
>   drivers/tty/vt/vc_screen.c | 6 ++----
>   1 file changed, 2 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c
> index 4d2d46c95fef..386c80efc672 100644
> --- a/drivers/tty/vt/vc_screen.c
> +++ b/drivers/tty/vt/vc_screen.c
> @@ -53,8 +53,6 @@
>   #define HEADER_SIZE	4u
>   #define CON_BUF_SIZE (IS_ENABLED(CONFIG_BASE_SMALL) ? 256 : PAGE_SIZE)
>   
> -DEFINE_FREE(free_page_ptr, void *, if (_T) free_page((unsigned long)_T));
> -

Indeed, I don't know why I came up with this in the first place :P.

Reviewed-by: Jiri Slaby <jirislaby@kernel.org>

> @@ -371,7 +369,7 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
>   	loff_t pos;
>   	bool viewed, attr, uni_mode;
>   
> -	char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL);
> +	char *con_buf __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL);
>   	if (!con_buf)
>   		return -ENOMEM;
>   
> @@ -596,7 +594,7 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
>   	if (use_unicode(inode))
>   		return -EOPNOTSUPP;
>   
> -	char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL);
> +	char *con_buf __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL);
>   	if (!con_buf)
>   		return -ENOMEM;
>   
> 


-- 
js
suse labs

^ permalink raw reply

* Re: [PATCH] serial: mxs-auart: fix 64-bit cast in probe
From: Rosen Penev @ 2026-05-29  7:40 UTC (permalink / raw)
  To: Jiri Slaby
  Cc: linux-serial, Greg Kroah-Hartman, Frank Li, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam,
	open list:TTY LAYER AND SERIAL DRIVERS,
	open list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE
In-Reply-To: <038ae6be-03f7-4e6a-8c1e-70cbcf8424cb@kernel.org>

On Fri, May 29, 2026 at 12:37 AM Jiri Slaby <jirislaby@kernel.org> wrote:
>
> On 28. 05. 26, 22:30, Rosen Penev wrote:
> > of_device_get_match_data() returns a pointer. Casting it directly to
> > enum truncates on 64-bit platforms. Cast to unsigned long instead.
>
> This is a misleading commit log. It still truncates during the assignment.
>
> > Fixes compilation with W=1.
>
> Fixes a warning, not compilation, right?
compilation. I believe -Werror is passed.
>
> > Assisted-by: Opencode:Big-pickle
> > Signed-off-by: Rosen Penev <rosenp@gmail.com>
> > ---
> >   drivers/tty/serial/mxs-auart.c | 2 +-
> >   1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c
> > index 697318dbb146..de97c0f74e7d 100644
> > --- a/drivers/tty/serial/mxs-auart.c
> > +++ b/drivers/tty/serial/mxs-auart.c
> > @@ -1598,7 +1598,7 @@ static int mxs_auart_probe(struct platform_device *pdev)
> >               return -EINVAL;
> >       }
> >
> > -     s->devtype = (enum mxs_auart_type)of_device_get_match_data(&pdev->dev);
> > +     s->devtype = (unsigned long)of_device_get_match_data(&pdev->dev);
> >
> >       ret = mxs_get_clks(s, pdev);
> >       if (ret)
> > --
> > 2.54.0
> >
>
>
> --
> js
> suse labs

^ permalink raw reply

* Re: [PATCH] serial: mxs-auart: fix 64-bit cast in probe
From: Jiri Slaby @ 2026-05-29  7:37 UTC (permalink / raw)
  To: Rosen Penev, linux-serial
  Cc: Greg Kroah-Hartman, Frank Li, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam,
	open list:TTY LAYER AND SERIAL DRIVERS,
	open list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE
In-Reply-To: <20260528203011.137338-1-rosenp@gmail.com>

On 28. 05. 26, 22:30, Rosen Penev wrote:
> of_device_get_match_data() returns a pointer. Casting it directly to
> enum truncates on 64-bit platforms. Cast to unsigned long instead.

This is a misleading commit log. It still truncates during the assignment.

> Fixes compilation with W=1.

Fixes a warning, not compilation, right?

> Assisted-by: Opencode:Big-pickle
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
> ---
>   drivers/tty/serial/mxs-auart.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c
> index 697318dbb146..de97c0f74e7d 100644
> --- a/drivers/tty/serial/mxs-auart.c
> +++ b/drivers/tty/serial/mxs-auart.c
> @@ -1598,7 +1598,7 @@ static int mxs_auart_probe(struct platform_device *pdev)
>   		return -EINVAL;
>   	}
> 
> -	s->devtype = (enum mxs_auart_type)of_device_get_match_data(&pdev->dev);
> +	s->devtype = (unsigned long)of_device_get_match_data(&pdev->dev);
> 
>   	ret = mxs_get_clks(s, pdev);
>   	if (ret)
> --
> 2.54.0
> 


-- 
js
suse labs

^ permalink raw reply

* Re: [PATCH v3] serial: 8250: fix use-after-free in IRQ chain handling
From: Wang Zhaolong @ 2026-05-29  7:27 UTC (permalink / raw)
  To: Qiliang Yuan
  Cc: Greg Kroah-Hartman, Jiri Slaby, Anton Vorontsov, Alan Cox,
	linux-kernel, linux-serial
In-Reply-To: <20260529-bug-221579-8250-shared-irq-race-v3-1-fe4d430862a9@gmail.com>

v3 fixes the Bugzilla reproducer on my setup.

But this error path is still racy:

> +
> +	ret = request_irq(up->port.irq, serial8250_interrupt,
> +			  up->port.irqflags, up->port.name, i);
> +
> +	mutex_unlock(&hash_mutex);
> +
>  	if (ret < 0)
>  		serial_do_unlink(i, up);
>  
> 

i is already in irq_lists and i->head is already visible here.  On
request_irq() failure, another port can join the chain and return success
without any IRQ handler installed.

The cleanup must happen before dropping hash_mutex.

> Dropping hash_mutex before request_irq() completes also allows another
> port sharing the same IRQ to join the chain and run the shared-IRQ THRE
> test while IRQ startup is still in progress, which can also trigger the
> "Unbalanced enable for IRQ" warning (kernel/irq/manage.c:774) because
> irq_shutdown() in the premature free_irq() path hard-sets desc->depth
> to 1, breaking the disable_irq/enable_irq pairing in
> serial8250_THRE_test().

The changelog is also still inaccurate: irq_shutdown() does not hard-set
desc->depth to 1 on current mainline; it increments desc->depth.

Best regards,
Wang Zhaolong

^ permalink raw reply

* Re: [PATCH tty] tty: n_gsm: fix use-after-free in gsm_queue vs gsm_cleanup_mux race
From: Zhenghang Xiao @ 2026-05-29  6:28 UTC (permalink / raw)
  To: Greg KH; +Cc: jirislaby, linux-serial
In-Reply-To: <2026052743-probation-anything-aa72@gregkh>

Resending this email as plain text, since the previous reply was
rejected by the mailing list for containing an HTML part.
-------------------
Thanks for your reply!

> How to test
I built two identical arm64 kernels (v7.1.0-rc4), differing only by
this patch. Both kernel carry a msleep(20) instrumentation in
gsm_queue() to widen the race window. Then run PoC and the patched
kernel do not report kASAN again.

> What prevents dead from changing
Nothing prevents it and it doesn't need to. tty_ldisc_flush() is the
sync mechanism not dead check.
If gsmld_receive_buf() is already past the check, it's running under
buf->lock. The patch moves tty_ldisc_flush() before DLCI release. And
tty_buffer_flush() acquires the same buf->lock, so it blocks until the
in-flight receive completes. DLCIs are freed only after that.

The dead check handles the other direction, any receive that starts
after the flush sees dead == true and returns early. But I'm not sure
if silent drop here is fine or not.

Thanks!
Zhenghang


Greg KH <gregkh@linuxfoundation.org> 于2026年5月27日周三 16:17写道:
>
> On Tue, May 26, 2026 at 06:29:24PM +0800, Zhenghang Xiao wrote:
> > gsm_queue() reads gsm->dlci[address] into a local pointer in the
> > flush_to_ldisc workqueue without any lock. Concurrently,
> > gsm_cleanup_mux() (triggered by GSMIOC_SETCONF ioctl) frees DLCIs under
> > gsm->mutex — which the receive path never holds. The cached pointer in
> > gsm_queue() becomes dangling, and the subsequent dlci->data() call
> > dereferences freed memory.
> >
> > Fix this by:
> > 1. Checking gsm->dead at the start of gsmld_receive_buf() to reject
> >    frame processing after cleanup has begun.
> > 2. Moving tty_ldisc_flush() before the DLCI release loop in
> >    gsm_cleanup_mux(). tty_ldisc_flush() acquires the tty buffer lock
> >    (buf->lock), which serializes against any in-flight flush_to_ldisc
> >    work. After it returns, in-flight receive processing has completed,
> >    and subsequent calls see gsm->dead and return early.
> >
> > Fixes: e1eaea46bb40 ("tty: n_gsm line discipline")
> > Signed-off-by: Zhenghang Xiao <kipreyyy@gmail.com>
> > ---
> >  drivers/tty/n_gsm.c | 13 +++++++++++--
> >  1 file changed, 11 insertions(+), 2 deletions(-)
>
> Cool, how did you test this?
>
>
> >
> > diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
> > index c13e050de83b..8322fffbaeba 100644
> > --- a/drivers/tty/n_gsm.c
> > +++ b/drivers/tty/n_gsm.c
> > @@ -3156,12 +3156,18 @@ static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc)
> >               gsm_unregister_devices(gsm_tty_driver, gsm->num);
> >               gsm->has_devices = false;
> >       }
> > +     /*
> > +      * Flush the ldisc before releasing DLCIs. tty_ldisc_flush() waits
> > +      * for any in-flight flush_to_ldisc work to complete via buf->lock,
> > +      * and the gsm->dead check added to gsmld_receive_buf() rejects any
> > +      * future receive processing. This ensures gsm_queue() cannot access
> > +      * a DLCI being freed.
> > +      */
> > +     tty_ldisc_flush(gsm->tty);
> >       for (i = NUM_DLCI - 1; i >= 0; i--)
> >               if (gsm->dlci[i])
> >                       gsm_dlci_release(gsm->dlci[i]);
> >       mutex_unlock(&gsm->mutex);
> > -     /* Now wipe the queues */
> > -     tty_ldisc_flush(gsm->tty);
> >
> >       guard(spinlock_irqsave)(&gsm->tx_lock);
> >       list_for_each_entry_safe(txq, ntxq, &gsm->tx_ctrl_list, list)
> > @@ -3604,6 +3610,9 @@ static void gsmld_receive_buf(struct tty_struct *tty, const u8 *cp,
> >       struct gsm_mux *gsm = tty->disc_data;
> >       u8 flags = TTY_NORMAL;
> >
> > +     if (gsm->dead)
> > +             return;
> > +
>
> What prevents dead from changing right after you test this?
>
> thanks,
>
> greg k-h

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox