From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 40654389454; Sat, 11 Apr 2026 15:10:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920248; cv=none; b=XiZymhrFmd1vnuhu4PPCA9mqZI9kmyPrfLtdv7zjqe5r839ZAr2iGQoRnc3iO/Unh1RkxjbkK3KF0EDEG+1F+3E2cEfsL5BTIIe0t5WTSZCTMcbXlM9NW/fuw5RvN3XSFIryGOjNMvTzWFvz0oKXvd+ksNIWMRyJhERb03wywFU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920248; c=relaxed/simple; bh=jiT1Tbc2P/6C6mUJ5JWCmUH6p6XtQMv1JyOGz7KturE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=n9FRxSow0yHJCoAYGCtdFqn3NDQcq4BKDQ7mxnE1rka8Lq0lSqtJ269lQ5TpG2ckhgk6yHk6cRuec81CglBv2HynRb9Z3I87tWJ/jtu7dPaANox7Zf2k7vBRccJGW++D5hlYad5wD5KuYh0p6OOvOiREoUjZXe8MmRFVlYQt7jI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=YGnR0uzE; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="YGnR0uzE" Received: by smtp.kernel.org (Postfix) with ESMTPS id C091BC2BCB5; Sat, 11 Apr 2026 15:10:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1775920247; bh=jiT1Tbc2P/6C6mUJ5JWCmUH6p6XtQMv1JyOGz7KturE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=YGnR0uzEjSWi8yzXbE/kfQCyAVjmq99DFx9q2RArV1tYPuIVhTgI/VO76QaJpSnl5 j3YliFC1i5Vh9+y+++XcSL4FE5YUAA4ami+309pfMxRnVP8BYqZ4ASCGyglEIfokMz 37fs+1ZOb4vjJHcyOjIAQFAeATxYhYH5vT5cD1J+ZaBjIFHYNUJn0jvs2hMGAUijaP 3wTohgM59Fd7wKsWX/cLHvllk1ynnHWBzV8Yw4fxxKwIWyK9Ude6kelcP4D1+nGCN8 xKJqaM8YjuI9h/Zt0BYFy0Gw8Nu2p+r8xyv/+9iBcVzG1gLqRE1nxAcuW7GHL3Qu/t u6XU2Vy0kM75A== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id B3842F3ED50; Sat, 11 Apr 2026 15:10:47 +0000 (UTC) From: Markus Probst via B4 Relay Date: Sat, 11 Apr 2026 17:10:37 +0200 Subject: [PATCH v4 3/4] rust: add basic serial device bus abstractions Precedence: bulk X-Mailing-List: rust-for-linux@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260411-rust_serdev-v4-3-845e960c6627@posteo.de> References: <20260411-rust_serdev-v4-0-845e960c6627@posteo.de> In-Reply-To: <20260411-rust_serdev-v4-0-845e960c6627@posteo.de> To: Rob Herring , Greg Kroah-Hartman , Jiri Slaby , Miguel Ojeda , Gary Guo , =?utf-8?q?Bj=C3=B6rn_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@vger.kernel.org, linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-pm@vger.kernel.org, driver-core@lists.linux.dev, dri-devel@lists.freedesktop.org, Markus Probst X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=23561; i=markus.probst@posteo.de; h=from:subject:message-id; bh=OKjaB9lirY06FMoBZPcCkryNKHLKitND76hEcnp5xHI=; b=owEBiQJ2/ZANAwAIATR2H/jnrUPSAcsmYgBp2mR1D44Rss2CI/Fle++qLt/bF6GP7x89UJ4wD FphZT/xf9qJAk8EAAEIADkWIQSCdBjE9KxY53IwxHM0dh/4561D0gUCadpkdRsUgAAAAAAEAA5t YW51MiwyLjUrMS4xMiwyLDIACgkQNHYf+OetQ9ID/Q/+O7ovjuyvbVNRZUPMTmwuqxBJKv8Kk6D 7a9BoonPsramhWCDsdW1MhdbjtXccM6k/pHXSGsupMyFYsjCq/i/LuWHYoPOYa6m6Nillc89rXU lPlG5DDlnWonbB6Tv3rklePYnOKzzPlRpEgNYDCW+LWHwOhtbLLbC14GgZGlActQhHpu5GO524M LxibFC4tQ1iQMz/6qET4no9Bvw683gvA0ICU3h/RZVLxV2NCjT6+2sdSGxDZKoBsKw3Zhx3ODcV ngBgPMfZ868us5HdWj99liDPXrXiyfsEnTD+oDhMXp6tBSwHhXcyNK9ha2DJExUaq/c819mAkEC EKgIW84lU5Xp3S3ERBt8LxIY8mq1oPgsEN7FJx4KIasXEPswDhX1pPgp6RN0YAaM75otfWGSeEN zHlmHZX3uVpk/G0Y6K68Y2sLwcodwu9hNuSpK8KZoEuxen7rjzDHMbx3Rnxyit1T4uXMaVj2Ftu bUF+35sgu7nI6ZKQ8wN3gN0OnCi/f+4umcBupKs3JCAC8H4DxpZm1675KFM4jN/EOvDeGI5yBVw hyrjuWLbAxdRYdxG9D49ME3S5+IbGjoUSZ83oB+nL0NWYWADbPgdsPOW/gSHQxGM7TKA6w6X85w O/n+rUrI/XrAdeI5Tw0OZ7gNQG/IRRjo+ATo7c7hXx2ViPf/XpFc= X-Developer-Key: i=markus.probst@posteo.de; a=openpgp; fpr=827418C4F4AC58E77230C47334761FF8E7AD43D2 X-Endpoint-Received: by B4 Relay for markus.probst@posteo.de/default with auth_id=680 X-Original-From: Markus Probst Reply-To: markus.probst@posteo.de From: Markus Probst 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 --- 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 | 536 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 569 insertions(+) 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 083cc44aa952..ab521ba42673 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -80,6 +80,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index a3c42e51f00a..9b87e9591cfd 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -53,6 +53,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 + +__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 d93292d47420..5107c9c1be07 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -144,6 +144,8 @@ pub mod scatterlist; pub mod security; pub mod seq_file; +#[cfg(CONFIG_RUST_SERIAL_DEV_BUS_ABSTRACTIONS)] +pub mod serdev; pub mod sizes; pub mod slice; #[cfg(CONFIG_SOC_BUS)] diff --git a/rust/kernel/serdev.rs b/rust/kernel/serdev.rs new file mode 100644 index 000000000000..d9fea4bd4439 --- /dev/null +++ b/rust/kernel/serdev.rs @@ -0,0 +1,536 @@ +// 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::Completion, + time::{ + msecs_to_jiffies, + Jiffies, + Msecs, // + }, + types::{ + AlwaysRefCounted, + 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), + /// Wait for a specific amount of [`Msecs`]. + Milliseconds(NonZero), + /// 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); + +// SAFETY: +// - `bindings::serdev_device_driver` is a C type declared as `repr(C)`. +// - `Drvdata` 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 driver::DriverLayout for Adapter { + type DriverType = bindings::serdev_device_driver; + type DriverData = T; + 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 driver::RegistrationOps for Adapter { + unsafe fn register( + sdrv: &Opaque, + 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) { + // 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, +} + +impl Adapter { + 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::>() }; + let id_info = ::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::().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, id_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::>() }; + + // 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>`. + let data = unsafe { sdev.as_ref().drvdata_borrow::() }; + + 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::>() }; + + // 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::() }; + + 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>`. + let data = unsafe { sdev.as_ref().drvdata_borrow::() }; + + // 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 driver::Adapter for Adapter { + type IdInfo = T::IdInfo; + + fn of_id_table() -> Option> { + T::OF_ID_TABLE + } + + fn acpi_id_table() -> Option> { + 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!(, $crate::serdev::Adapter, { $($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, +/// ::IdInfo, +/// [ +/// (of::DeviceId::new(c"test,device"), ()) +/// ] +/// ); +/// +/// kernel::acpi_device_table!( +/// ACPI_TABLE, +/// MODULE_ACPI_TABLE, +/// ::IdInfo, +/// [ +/// (acpi::DeviceId::new(c"LNUXBEEF"), ()) +/// ] +/// ); +/// +/// #[vtable] +/// impl serdev::Driver for MyDriver { +/// type IdInfo = (); +/// const OF_ID_TABLE: Option> = Some(&OF_TABLE); +/// const ACPI_ID_TABLE: Option> = Some(&ACPI_TABLE); +/// +/// fn probe( +/// sdev: &serdev::Device, +/// _id_info: Option<&Self::IdInfo>, +/// ) -> impl PinInit { +/// sdev.set_baudrate(115200); +/// sdev.write_all(b"Hello\n", serdev::Timeout::Max)?; +/// Ok(MyDriver) +/// } +/// } +///``` +#[vtable] +pub trait Driver: Send { + /// 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 table of OF device ids supported by the driver. + const OF_ID_TABLE: Option> = None; + + /// The table of ACPI device ids supported by the driver. + const ACPI_ID_TABLE: Option> = 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( + sdev: &Device, + id_info: Option<&Self::IdInfo>, + ) -> impl PinInit; + + /// 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` or `&Device` reference. For instance. + /// + /// Otherwise, release operations for driver resources should be performed in `Self::drop`. + fn unbind(sdev: &Device, this: Pin<&Self>) { + 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(sdev: &Device, this: Pin<&Self>, 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( + Opaque, + PhantomData, +); + +impl Device { + fn as_raw(&self) -> *mut bindings::serdev_device { + self.0.get() + } +} + +impl Device { + /// 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 { + // 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 { + // 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 device::AsBusDevice for Device { + 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) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::serdev_device_put(obj.cast().as_ptr()) } + } +} + +impl AsRef> for Device { + fn as_ref(&self) -> &device::Device { + // 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) are thread safe. +unsafe impl Sync for Device {} -- 2.52.0