* [PATCH net-next v2 0/5] rust: phy: extend abstractions for real-world PHY drivers
@ 2026-03-24 15:52 Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 1/5] rust: phy: add read-only device field accessors Artem Lytkin
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Artem Lytkin @ 2026-03-24 15:52 UTC (permalink / raw)
To: netdev, rust-for-linux
Cc: fujita.tomonori, andrew, hkallweit1, tmgross, ojeda, dakr
This series extends the Rust PHY abstractions in rust/kernel/net/phy.rs
with features needed to write production-quality Rust PHY drivers, and
adds a Realtek RTL8211F driver as the first user of these APIs.
Patches 1-4 add new Device accessors, register manipulation helpers,
driver callbacks (config_init, read_page/write_page, config_intr,
handle_interrupt), and interrupt support infrastructure.
Patch 5 adds a Rust RTL8211F Gigabit Ethernet PHY driver that exercises
all the new abstractions. The driver implements RGMII delay
configuration, PHY EEE disable, paged register access, and
interrupt-driven link detection. ALDPS, CLKOUT, and WoL are omitted
as they require per-device private data not yet available in the
abstraction. PME interrupts are not handled for the same reason.
config_init was previously proposed in an RFC for the Rockchip PHY
driver [1] but not merged at that time. This series re-introduces it
in a form consistent with the existing vtable infrastructure.
Changes since v1:
- Added cover letter (Andrew)
- Fixed Subject prefix to net-next (Andrew)
- Added RTL8211F driver as API user (Andrew)
- Fixed rustfmt issues reported by kernel test robot
- Removed RGMII from interface() doc example (Andrew)
[1] https://lore.kernel.org/lkml/20240201-rockchip-rust-phy_depend-v2-0-c5fa4faab924@christina-quast.de/
Artem Lytkin (5):
rust: phy: add read-only device field accessors
rust: phy: add paged register access and bit manipulation helpers
rust: phy: add config_init, read_page, and write_page callbacks
rust: phy: add interrupt support
net: phy: realtek: add Rust RTL8211F PHY driver
drivers/net/phy/realtek/Kconfig | 12 ++
drivers/net/phy/realtek/Makefile | 1 +
drivers/net/phy/realtek/rtl8211f_rust.rs | 140 +++++++++++++++++++
rust/kernel/net/phy.rs | 260 +++++++++++++++++++++++++++++++
4 files changed, 413 insertions(+)
create mode 100644 drivers/net/phy/realtek/rtl8211f_rust.rs
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 1/5] rust: phy: add read-only device field accessors
2026-03-24 15:52 [PATCH net-next v2 0/5] rust: phy: extend abstractions for real-world PHY drivers Artem Lytkin
@ 2026-03-24 15:52 ` Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 2/5] rust: phy: add paged register access and bit manipulation helpers Artem Lytkin
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Artem Lytkin @ 2026-03-24 15:52 UTC (permalink / raw)
To: netdev, rust-for-linux
Cc: fujita.tomonori, andrew, hkallweit1, tmgross, ojeda, dakr
Add getter methods for commonly needed PHY device fields:
- speed(): Returns the current link speed as i32, matching the
C struct field type (int speed).
- duplex(): Returns the current duplex mode as DuplexMode enum.
Only the setter (set_duplex) existed previously.
- interface(): Returns the PHY interface mode (RGMII, SGMII, etc.)
as a raw phy_interface_t value. A typed Rust enum is future work
since phy_interface_t has 40+ variants tied to DT bindings.
- irq(): Returns the PHY's IRQ number.
These accessors are needed by Rust PHY drivers that must inspect
hardware state, particularly during config_init and read_status
callbacks.
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
rust/kernel/net/phy.rs | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 3ca99db5cccf2..646b2a78a2710 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -179,6 +179,47 @@ pub fn set_duplex(&mut self, mode: DuplexMode) {
unsafe { (*phydev).duplex = v as c_int };
}
+ /// Gets the current speed setting.
+ pub fn speed(&self) -> i32 {
+ let phydev = self.0.get();
+ // SAFETY: The struct invariant ensures that we may access
+ // this field without additional synchronization.
+ unsafe { (*phydev).speed }
+ }
+
+ /// Gets the current duplex mode.
+ pub fn duplex(&self) -> DuplexMode {
+ let phydev = self.0.get();
+ // SAFETY: The struct invariant ensures that we may access
+ // this field without additional synchronization.
+ let v = unsafe { (*phydev).duplex };
+ match v as u32 {
+ bindings::DUPLEX_FULL => DuplexMode::Full,
+ bindings::DUPLEX_HALF => DuplexMode::Half,
+ _ => DuplexMode::Unknown,
+ }
+ }
+
+ /// Gets the PHY interface mode as a raw `phy_interface_t` value.
+ ///
+ /// Common values include `bindings::phy_interface_t_PHY_INTERFACE_MODE_RGMII`,
+ /// `bindings::phy_interface_t_PHY_INTERFACE_MODE_SGMII`, etc.
+ /// A typed Rust enum is planned for future work.
+ pub fn interface(&self) -> u32 {
+ let phydev = self.0.get();
+ // SAFETY: The struct invariant ensures that we may access
+ // this field without additional synchronization.
+ unsafe { (*phydev).interface }
+ }
+
+ /// Gets the PHY's IRQ number.
+ pub fn irq(&self) -> i32 {
+ let phydev = self.0.get();
+ // SAFETY: The struct invariant ensures that we may access
+ // this field without additional synchronization.
+ unsafe { (*phydev).irq }
+ }
+
/// Reads a PHY register.
// This function reads a hardware register and updates the stats so takes `&mut self`.
pub fn read<R: reg::Register>(&mut self, reg: R) -> Result<u16> {
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next v2 2/5] rust: phy: add paged register access and bit manipulation helpers
2026-03-24 15:52 [PATCH net-next v2 0/5] rust: phy: extend abstractions for real-world PHY drivers Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 1/5] rust: phy: add read-only device field accessors Artem Lytkin
@ 2026-03-24 15:52 ` Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 3/5] rust: phy: add config_init, read_page, and write_page callbacks Artem Lytkin
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Artem Lytkin @ 2026-03-24 15:52 UTC (permalink / raw)
To: netdev, rust-for-linux
Cc: fujita.tomonori, andrew, hkallweit1, tmgross, ojeda, dakr
Add Device methods for register manipulation beyond simple read/write:
- write_paged(): Writes a register on a specific page, completing
the paged access surface (read_paged already existed upstream).
Wraps phy_write_paged().
- modify() / modify_paged(): Atomic read-modify-write operations
that clear bits in mask and set bits in set. Wraps phy_modify()
and phy_modify_paged(). These return Result (not the old register
value) since the C functions return 0 on success.
- set_bits() / clear_bits(): Convenience wrappers around modify()
for the common case of setting or clearing specific bits.
These helpers are heavily used by real-world PHY drivers for
configuring vendor-specific registers without races.
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
rust/kernel/net/phy.rs | 45 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 43 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 646b2a78a2710..142f7101723c8 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -202,8 +202,7 @@ pub fn duplex(&self) -> DuplexMode {
/// Gets the PHY interface mode as a raw `phy_interface_t` value.
///
- /// Common values include `bindings::phy_interface_t_PHY_INTERFACE_MODE_RGMII`,
- /// `bindings::phy_interface_t_PHY_INTERFACE_MODE_SGMII`, etc.
+ /// Values are `bindings::phy_interface_t_PHY_INTERFACE_MODE_*` constants.
/// A typed Rust enum is planned for future work.
pub fn interface(&self) -> u32 {
let phydev = self.0.get();
@@ -241,6 +240,48 @@ pub fn read_paged(&mut self, page: u16, regnum: u16) -> Result<u16> {
to_result(ret).map(|()| ret as u16)
}
+ /// Writes a paged register.
+ pub fn write_paged(&mut self, page: u16, regnum: u16, val: u16) -> Result {
+ let phydev = self.0.get();
+ // SAFETY: `phydev` is pointing to a valid object by the type invariant of `Self`.
+ // So it's just an FFI call.
+ let ret = unsafe { bindings::phy_write_paged(phydev, page.into(), regnum.into(), val) };
+
+ to_result(ret)
+ }
+
+ /// Performs a read-modify-write on a PHY register.
+ ///
+ /// Clears the bits set in `mask` and sets the bits in `set`.
+ pub fn modify(&mut self, regnum: u16, mask: u16, set: u16) -> Result {
+ let phydev = self.0.get();
+ // SAFETY: `phydev` is pointing to a valid object by the type invariant of `Self`.
+ // So it's just an FFI call.
+ to_result(unsafe { bindings::phy_modify(phydev, regnum.into(), mask, set) })
+ }
+
+ /// Performs a read-modify-write on a paged PHY register.
+ ///
+ /// Selects the page, performs the modify, and restores the original page.
+ pub fn modify_paged(&mut self, page: u16, regnum: u16, mask: u16, set: u16) -> Result {
+ let phydev = self.0.get();
+ // SAFETY: `phydev` is pointing to a valid object by the type invariant of `Self`.
+ // So it's just an FFI call.
+ to_result(unsafe {
+ bindings::phy_modify_paged(phydev, page.into(), regnum.into(), mask, set)
+ })
+ }
+
+ /// Sets bits in a PHY register.
+ pub fn set_bits(&mut self, regnum: u16, val: u16) -> Result {
+ self.modify(regnum, 0, val)
+ }
+
+ /// Clears bits in a PHY register.
+ pub fn clear_bits(&mut self, regnum: u16, val: u16) -> Result {
+ self.modify(regnum, val, 0)
+ }
+
/// Resolves the advertisements into PHY settings.
pub fn resolve_aneg_linkmode(&mut self) {
let phydev = self.0.get();
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next v2 3/5] rust: phy: add config_init, read_page, and write_page callbacks
2026-03-24 15:52 [PATCH net-next v2 0/5] rust: phy: extend abstractions for real-world PHY drivers Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 1/5] rust: phy: add read-only device field accessors Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 2/5] rust: phy: add paged register access and bit manipulation helpers Artem Lytkin
@ 2026-03-24 15:52 ` Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 4/5] rust: phy: add interrupt support Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 5/5] net: phy: realtek: add Rust RTL8211F PHY driver Artem Lytkin
4 siblings, 0 replies; 7+ messages in thread
From: Artem Lytkin @ 2026-03-24 15:52 UTC (permalink / raw)
To: netdev, rust-for-linux
Cc: fujita.tomonori, andrew, hkallweit1, tmgross, ojeda, dakr
Add three new driver callbacks to the PHY abstraction:
- config_init: Called to initialize the PHY after a reset. This is
needed by nearly all real-world PHY drivers to configure
vendor-specific registers (RGMII delays, LED settings, etc.).
config_init was previously proposed in an RFC for the Rockchip PHY
driver but not merged at that time. This re-introduces it in a
form consistent with the existing vtable infrastructure.
- read_page / write_page: Required by page-based PHY chips (Realtek,
Marvell) for the kernel's paged register access infrastructure.
When these callbacks are registered, phy_read_paged(),
phy_write_paged(), and phy_modify_paged() use them to switch
register pages. Both must be implemented together.
Each callback follows the established Adapter<T> pattern with
unsafe extern "C" fn bridging to the Rust Driver trait method, and
conditional registration in create_phy_driver() via HAS_* flags
generated by the #[vtable] macro.
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
rust/kernel/net/phy.rs | 83 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 142f7101723c8..9ffece7ee41c7 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -388,6 +388,51 @@ struct Adapter<T: Driver> {
}
impl<T: Driver> Adapter<T> {
+ /// # Safety
+ ///
+ /// `phydev` must be passed by the corresponding callback in `phy_driver`.
+ unsafe extern "C" fn config_init_callback(phydev: *mut bindings::phy_device) -> c_int {
+ from_result(|| {
+ // SAFETY: The C core calls config_init with the PHY mutex held
+ // (from phy_init_hw), so the accessors on `Device` are okay to call.
+ let dev = unsafe { Device::from_raw(phydev) };
+ T::config_init(dev)?;
+ Ok(0)
+ })
+ }
+
+ /// # Safety
+ ///
+ /// `phydev` must be passed by the corresponding callback in `phy_driver`.
+ unsafe extern "C" fn read_page_callback(phydev: *mut bindings::phy_device) -> c_int {
+ from_result(|| {
+ // SAFETY: This callback is called only in contexts
+ // where we hold `phy_device->lock`, so the accessors on
+ // `Device` are okay to call.
+ let dev = unsafe { Device::from_raw(phydev) };
+ let page = T::read_page(dev)?;
+ Ok(page.into())
+ })
+ }
+
+ /// # Safety
+ ///
+ /// `phydev` must be passed by the corresponding callback in `phy_driver`.
+ unsafe extern "C" fn write_page_callback(
+ phydev: *mut bindings::phy_device,
+ page: c_int,
+ ) -> c_int {
+ from_result(|| {
+ // SAFETY: This callback is called only in contexts
+ // where we hold `phy_device->lock`, so the accessors on
+ // `Device` are okay to call.
+ let dev = unsafe { Device::from_raw(phydev) };
+ let page = u16::try_from(page).map_err(|_| code::EINVAL)?;
+ T::write_page(dev, page)?;
+ Ok(0)
+ })
+ }
+
/// # Safety
///
/// `phydev` must be passed by the corresponding callback in `phy_driver`.
@@ -580,6 +625,11 @@ pub const fn create_phy_driver<T: Driver>() -> DriverVTable {
flags: T::FLAGS,
phy_id: T::PHY_DEVICE_ID.id(),
phy_id_mask: T::PHY_DEVICE_ID.mask_as_int(),
+ config_init: if T::HAS_CONFIG_INIT {
+ Some(Adapter::<T>::config_init_callback)
+ } else {
+ None
+ },
soft_reset: if T::HAS_SOFT_RESET {
Some(Adapter::<T>::soft_reset_callback)
} else {
@@ -630,6 +680,16 @@ pub const fn create_phy_driver<T: Driver>() -> DriverVTable {
} else {
None
},
+ read_page: if T::HAS_READ_PAGE {
+ Some(Adapter::<T>::read_page_callback)
+ } else {
+ None
+ },
+ write_page: if T::HAS_WRITE_PAGE {
+ Some(Adapter::<T>::write_page_callback)
+ } else {
+ None
+ },
link_change_notify: if T::HAS_LINK_CHANGE_NOTIFY {
Some(Adapter::<T>::link_change_notify_callback)
} else {
@@ -657,6 +717,11 @@ pub trait Driver {
/// The default id and mask are zero.
const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_custom_mask(0, 0);
+ /// Called to initialize the PHY, including after a reset.
+ fn config_init(_dev: &mut Device) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
/// Issues a PHY software reset.
fn soft_reset(_dev: &mut Device) -> Result {
build_error!(VTABLE_DEFAULT_ERROR)
@@ -709,6 +774,24 @@ fn write_mmd(_dev: &mut Device, _devnum: u8, _regnum: u16, _val: u16) -> Result
build_error!(VTABLE_DEFAULT_ERROR)
}
+ /// Returns the current PHY register page number.
+ ///
+ /// Must be implemented together with [`Driver::write_page`]. The kernel's
+ /// paged register access infrastructure requires both callbacks to be
+ /// present.
+ fn read_page(_dev: &mut Device) -> Result<u16> {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Sets the current PHY register page number.
+ ///
+ /// Must be implemented together with [`Driver::read_page`]. The kernel's
+ /// paged register access infrastructure requires both callbacks to be
+ /// present.
+ fn write_page(_dev: &mut Device, _page: u16) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
/// Callback for notification of link change.
fn link_change_notify(_dev: &mut Device) {}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next v2 4/5] rust: phy: add interrupt support
2026-03-24 15:52 [PATCH net-next v2 0/5] rust: phy: extend abstractions for real-world PHY drivers Artem Lytkin
` (2 preceding siblings ...)
2026-03-24 15:52 ` [PATCH net-next v2 3/5] rust: phy: add config_init, read_page, and write_page callbacks Artem Lytkin
@ 2026-03-24 15:52 ` Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 5/5] net: phy: realtek: add Rust RTL8211F PHY driver Artem Lytkin
4 siblings, 0 replies; 7+ messages in thread
From: Artem Lytkin @ 2026-03-24 15:52 UTC (permalink / raw)
To: netdev, rust-for-linux
Cc: fujita.tomonori, andrew, hkallweit1, tmgross, ojeda, dakr
Add interrupt handling support to the Rust PHY abstraction:
New driver callbacks:
- config_intr: Enables or disables PHY link state change interrupts.
- handle_interrupt: Called when the PHY's interrupt fires. Returns
IrqReturn (reused from kernel::irq). Unlike other callbacks that
use from_result(), this directly returns irqreturn_t matching the
C callback signature.
New Device methods:
- trigger_machine(): Schedules a PHY state machine update. Used in
interrupt handlers after detecting a link change event.
- phy_error(): Reports a PHY error and transitions the state machine
to the error state. Produces a WARN_ON. Must be called with
phy_device->lock held (which is the case inside handle_interrupt).
Both callbacks run with phy_device->lock held (from phy_interrupt()
in the threaded IRQ context), consistent with the existing callback
safety model.
These additions enable Rust PHY drivers to use interrupt-driven link
detection, which is required for production-quality drivers like the
Realtek RTL8211F.
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
rust/kernel/net/phy.rs | 93 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 90 insertions(+), 3 deletions(-)
diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 9ffece7ee41c7..e8f920620841c 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -6,7 +6,7 @@
//!
//! C headers: [`include/linux/phy.h`](srctree/include/linux/phy.h).
-use crate::{device_id::RawDeviceId, error::*, prelude::*, types::Opaque};
+use crate::{device_id::RawDeviceId, error::*, irq::IrqReturn, prelude::*, types::Opaque};
use core::{marker::PhantomData, ptr::addr_of_mut};
pub mod reg;
@@ -211,6 +211,20 @@ pub fn interface(&self) -> u32 {
unsafe { (*phydev).interface }
}
+ /// Returns `true` if PHY interrupts are enabled.
+ ///
+ /// This reflects the `phydev->interrupts` field which is set by the PHY
+ /// core before calling [`Driver::config_intr`] to indicate whether
+ /// interrupts should be enabled or disabled.
+ pub fn is_interrupts_enabled(&self) -> bool {
+ // TODO: the code to access the bit field will be replaced with automatically
+ // generated code by bindgen when it becomes possible.
+ // SAFETY: The struct invariant ensures that we may access
+ // this field without additional synchronization.
+ let bit_field = unsafe { &(*self.0.get())._bitfield_1 };
+ bit_field.get(18, 1) == 1
+ }
+
/// Gets the PHY's IRQ number.
pub fn irq(&self) -> i32 {
let phydev = self.0.get();
@@ -358,6 +372,29 @@ pub fn genphy_read_abilities(&mut self) -> Result {
// So it's just an FFI call.
to_result(unsafe { bindings::genphy_read_abilities(phydev) })
}
+
+ /// Triggers the PHY state machine to run.
+ ///
+ /// Used in interrupt handlers to schedule a state machine update
+ /// after processing an interrupt event.
+ pub fn trigger_machine(&mut self) {
+ let phydev = self.0.get();
+ // SAFETY: `phydev` is pointing to a valid object by the type invariant of `Self`.
+ // So it's just an FFI call.
+ unsafe { bindings::phy_trigger_machine(phydev) };
+ }
+
+ /// Reports a PHY error and moves the state machine to the error state.
+ ///
+ /// Used in interrupt handlers when a register read fails. This will
+ /// produce a kernel `WARN_ON`. Must be called with `phy_device->lock`
+ /// held (which is the case inside [`Driver::handle_interrupt`]).
+ pub fn phy_error(&mut self) {
+ let phydev = self.0.get();
+ // SAFETY: `phydev` is pointing to a valid object by the type invariant of `Self`.
+ // So it's just an FFI call.
+ unsafe { bindings::phy_error(phydev) };
+ }
}
impl AsRef<kernel::device::Device> for Device {
@@ -393,8 +430,9 @@ impl<T: Driver> Adapter<T> {
/// `phydev` must be passed by the corresponding callback in `phy_driver`.
unsafe extern "C" fn config_init_callback(phydev: *mut bindings::phy_device) -> c_int {
from_result(|| {
- // SAFETY: The C core calls config_init with the PHY mutex held
- // (from phy_init_hw), so the accessors on `Device` are okay to call.
+ // SAFETY: This callback is called only in contexts
+ // where we hold `phy_device->lock`, so the accessors on
+ // `Device` are okay to call.
let dev = unsafe { Device::from_raw(phydev) };
T::config_init(dev)?;
Ok(0)
@@ -507,6 +545,30 @@ impl<T: Driver> Adapter<T> {
/// # Safety
///
/// `phydev` must be passed by the corresponding callback in `phy_driver`.
+ unsafe extern "C" fn config_intr_callback(phydev: *mut bindings::phy_device) -> c_int {
+ from_result(|| {
+ // SAFETY: This callback is called only in contexts
+ // where we hold `phy_device->lock`, so the accessors on
+ // `Device` are okay to call.
+ let dev = unsafe { Device::from_raw(phydev) };
+ T::config_intr(dev)?;
+ Ok(0)
+ })
+ }
+
+ /// # Safety
+ ///
+ /// `phydev` must be passed by the corresponding callback in `phy_driver`.
+ unsafe extern "C" fn handle_interrupt_callback(
+ phydev: *mut bindings::phy_device,
+ ) -> bindings::irqreturn_t {
+ // SAFETY: This callback is called only in contexts
+ // where we hold `phy_device->lock` (from phy_interrupt),
+ // so the accessors on `Device` are okay to call.
+ let dev = unsafe { Device::from_raw(phydev) };
+ T::handle_interrupt(dev) as core::ffi::c_uint
+ }
+
unsafe extern "C" fn config_aneg_callback(phydev: *mut bindings::phy_device) -> c_int {
from_result(|| {
// SAFETY: This callback is called only in contexts
@@ -660,6 +722,16 @@ pub const fn create_phy_driver<T: Driver>() -> DriverVTable {
} else {
None
},
+ config_intr: if T::HAS_CONFIG_INTR {
+ Some(Adapter::<T>::config_intr_callback)
+ } else {
+ None
+ },
+ handle_interrupt: if T::HAS_HANDLE_INTERRUPT {
+ Some(Adapter::<T>::handle_interrupt_callback)
+ } else {
+ None
+ },
config_aneg: if T::HAS_CONFIG_ANEG {
Some(Adapter::<T>::config_aneg_callback)
} else {
@@ -743,6 +815,21 @@ fn match_phy_device(_dev: &Device) -> bool {
false
}
+ /// Enables or disables PHY interrupts.
+ fn config_intr(_dev: &mut Device) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Handles a PHY interrupt.
+ ///
+ /// Called when the PHY's interrupt line fires. The driver should read the
+ /// interrupt status register to determine the cause and call
+ /// [`Device::trigger_machine`] to schedule a state machine update.
+ /// Returns [`IrqReturn::Handled`] if the interrupt was from this PHY.
+ fn handle_interrupt(_dev: &mut Device) -> IrqReturn {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
/// Configures the advertisement and resets auto-negotiation
/// if auto-negotiation is enabled.
fn config_aneg(_dev: &mut Device) -> Result {
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next v2 5/5] net: phy: realtek: add Rust RTL8211F PHY driver
2026-03-24 15:52 [PATCH net-next v2 0/5] rust: phy: extend abstractions for real-world PHY drivers Artem Lytkin
` (3 preceding siblings ...)
2026-03-24 15:52 ` [PATCH net-next v2 4/5] rust: phy: add interrupt support Artem Lytkin
@ 2026-03-24 15:52 ` Artem Lytkin
2026-03-24 15:57 ` Andrew Lunn
4 siblings, 1 reply; 7+ messages in thread
From: Artem Lytkin @ 2026-03-24 15:52 UTC (permalink / raw)
To: netdev, rust-for-linux
Cc: fujita.tomonori, andrew, hkallweit1, tmgross, ojeda, dakr
Add a Rust implementation of the Realtek RTL8211F Gigabit Ethernet PHY
driver. This exercises the extended Rust PHY abstraction with
config_init, read_page/write_page, and interrupt support callbacks.
The driver implements:
- config_init: Configures RGMII TX/RX internal delays based on the
PHY interface mode, and disables PHY-level EEE so LPI is passed
to the MAC. This matches the C driver's essential initialization.
ALDPS and CLKOUT are left at hardware defaults since they depend
on device tree properties and per-device private data not yet
supported by the Rust PHY abstraction.
- read_page / write_page: Standard RTL821x page select register.
- config_intr: Enables/disables link status change interrupts.
- handle_interrupt: Reads INSR and triggers the state machine on
link change. PME/WoL interrupts are documented as unsupported.
The PHY ID 0x001cc916 matches the RTL8211F specifically.
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
drivers/net/phy/realtek/Kconfig | 12 +++
drivers/net/phy/realtek/Makefile | 1 +
drivers/net/phy/realtek/rtl8211f_rust.rs | 127 +++++++++++++++++++++++
3 files changed, 140 insertions(+)
create mode 100644 drivers/net/phy/realtek/rtl8211f_rust.rs
diff --git a/drivers/net/phy/realtek/Kconfig b/drivers/net/phy/realtek/Kconfig
index b05c2a1e90243..4cc880683367d 100644
--- a/drivers/net/phy/realtek/Kconfig
+++ b/drivers/net/phy/realtek/Kconfig
@@ -13,3 +13,15 @@ config REALTEK_PHY_HWMON
Optional hwmon support for the temperature sensor
endif # REALTEK_PHY
+
+config REALTEK_RTL8211F_RUST_PHY
+ tristate "Rust Realtek RTL8211F PHY driver"
+ depends on RUST && RUST_PHYLIB_ABSTRACTIONS && !REALTEK_PHY
+ help
+ Rust driver for the Realtek RTL8211F Gigabit Ethernet PHY.
+ This is a Rust implementation demonstrating the extended PHY
+ abstraction with config_init, paged register access, and
+ interrupt support.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rtl8211f_rust.
diff --git a/drivers/net/phy/realtek/Makefile b/drivers/net/phy/realtek/Makefile
index dd21cf87f2f18..56ae5d2e39f65 100644
--- a/drivers/net/phy/realtek/Makefile
+++ b/drivers/net/phy/realtek/Makefile
@@ -2,3 +2,4 @@
realtek-y += realtek_main.o
realtek-$(CONFIG_REALTEK_PHY_HWMON) += realtek_hwmon.o
obj-$(CONFIG_REALTEK_PHY) += realtek.o
+obj-$(CONFIG_REALTEK_RTL8211F_RUST_PHY) += rtl8211f_rust.o
diff --git a/drivers/net/phy/realtek/rtl8211f_rust.rs b/drivers/net/phy/realtek/rtl8211f_rust.rs
new file mode 100644
index 0000000000000..f89f2fa55c677
--- /dev/null
+++ b/drivers/net/phy/realtek/rtl8211f_rust.rs
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2026 Artem Lytkin <iprintercanon@gmail.com>
+
+//! Rust Realtek RTL8211F Gigabit Ethernet PHY driver.
+
+use kernel::irq::IrqReturn;
+use kernel::net::phy::{self, reg::C22, DeviceId};
+use kernel::prelude::*;
+
+// Page select register (vendor-specific, register 0x1f).
+const PAGE_SELECT: C22 = C22::vendor_specific::<0x1f>();
+
+// Interrupt status register (vendor-specific, register 0x1d).
+const INSR: C22 = C22::vendor_specific::<0x1d>();
+
+// Interrupt enable register number (used with paged access on page 0xa42).
+const INER_REG: u16 = 0x12;
+const INER_LINK_STATUS: u16 = 1 << 4;
+
+// RGMII TX delay register on page 0xd08.
+const TX_DELAY_REG: u16 = 0x11;
+const TX_DELAY_EN: u16 = 1 << 8;
+
+// RGMII RX delay register on page 0xd08.
+const RX_DELAY_REG: u16 = 0x15;
+const RX_DELAY_EN: u16 = 1 << 3;
+
+// PHY EEE enable bit in PHYCR2 (default page, register 0x19).
+const PHYCR2: u16 = 0x19;
+const PHYCR2_PHY_EEE_ENABLE: u16 = 1 << 5;
+
+kernel::module_phy_driver! {
+ drivers: [Rtl8211f],
+ device_table: [
+ DeviceId::new_with_driver::<Rtl8211f>(),
+ ],
+ name: "rtl8211f_rust",
+ authors: ["Artem Lytkin"],
+ description: "Rust Realtek RTL8211F PHY driver",
+ license: "GPL",
+}
+
+struct Rtl8211f;
+
+#[vtable]
+impl phy::Driver for Rtl8211f {
+ const NAME: &'static CStr = c"RTL8211F Gigabit Ethernet (Rust)";
+ const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_exact_mask(0x001cc916);
+
+ fn config_init(dev: &mut phy::Device) -> Result {
+ // Configure RGMII delays based on the PHY interface mode.
+ // This matches the C driver's rtl8211f_config_rgmii_delay().
+ let iface = dev.interface();
+ let tx_delay = iface == bindings::phy_interface_t_PHY_INTERFACE_MODE_RGMII_ID
+ || iface == bindings::phy_interface_t_PHY_INTERFACE_MODE_RGMII_TXID;
+ let rx_delay = iface == bindings::phy_interface_t_PHY_INTERFACE_MODE_RGMII_ID
+ || iface == bindings::phy_interface_t_PHY_INTERFACE_MODE_RGMII_RXID;
+
+ dev.modify_paged(
+ 0xd08,
+ TX_DELAY_REG,
+ TX_DELAY_EN,
+ if tx_delay { TX_DELAY_EN } else { 0 },
+ )?;
+ dev.modify_paged(
+ 0xd08,
+ RX_DELAY_REG,
+ RX_DELAY_EN,
+ if rx_delay { RX_DELAY_EN } else { 0 },
+ )?;
+
+ // Disable PHY-level EEE so LPI is passed up to the MAC layer.
+ // PHYCR2 is on the default page, no page switch needed.
+ dev.modify(PHYCR2, PHYCR2_PHY_EEE_ENABLE, 0)?;
+
+ // Note: ALDPS and CLKOUT configuration are omitted because they
+ // depend on device tree properties and per-device private data,
+ // which are not yet supported by the Rust PHY abstraction.
+ // The hardware defaults are preserved for these settings.
+
+ Ok(())
+ }
+
+ fn read_page(dev: &mut phy::Device) -> Result<u16> {
+ dev.read(PAGE_SELECT)
+ }
+
+ fn write_page(dev: &mut phy::Device, page: u16) -> Result {
+ dev.write(PAGE_SELECT, page)
+ }
+
+ fn config_intr(dev: &mut phy::Device) -> Result {
+ if dev.is_interrupts_enabled() {
+ // Acknowledge any pending interrupt.
+ dev.read(INSR)?;
+ // Enable link status change interrupt.
+ dev.write_paged(0xa42, INER_REG, INER_LINK_STATUS)?;
+ } else {
+ // Disable all interrupts.
+ dev.write_paged(0xa42, INER_REG, 0)?;
+ // Acknowledge any pending interrupt.
+ dev.read(INSR)?;
+ }
+ Ok(())
+ }
+
+ fn handle_interrupt(dev: &mut phy::Device) -> IrqReturn {
+ let irq_status = match dev.read(INSR) {
+ Ok(val) => val,
+ Err(_) => {
+ dev.phy_error();
+ return IrqReturn::None;
+ }
+ };
+
+ if irq_status & INER_LINK_STATUS != 0 {
+ dev.trigger_machine();
+ return IrqReturn::Handled;
+ }
+
+ // Note: PME/Wake-on-LAN interrupt (bit 7) is not handled because
+ // WoL support requires pm_wakeup_event() binding and per-device
+ // private data, which are not yet available.
+
+ IrqReturn::None
+ }
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH net-next v2 5/5] net: phy: realtek: add Rust RTL8211F PHY driver
2026-03-24 15:52 ` [PATCH net-next v2 5/5] net: phy: realtek: add Rust RTL8211F PHY driver Artem Lytkin
@ 2026-03-24 15:57 ` Andrew Lunn
0 siblings, 0 replies; 7+ messages in thread
From: Andrew Lunn @ 2026-03-24 15:57 UTC (permalink / raw)
To: Artem Lytkin
Cc: netdev, rust-for-linux, fujita.tomonori, hkallweit1, tmgross,
ojeda, dakr
On Tue, Mar 24, 2026 at 06:52:49PM +0300, Artem Lytkin wrote:
> Add a Rust implementation of the Realtek RTL8211F Gigabit Ethernet PHY
> driver.
We don't want duplicate C and Rust drivers. Sorry.
Just out of interest, what hardware have you tested on?
Andrew
---
pw-bot: cr
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-03-24 15:57 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-24 15:52 [PATCH net-next v2 0/5] rust: phy: extend abstractions for real-world PHY drivers Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 1/5] rust: phy: add read-only device field accessors Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 2/5] rust: phy: add paged register access and bit manipulation helpers Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 3/5] rust: phy: add config_init, read_page, and write_page callbacks Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 4/5] rust: phy: add interrupt support Artem Lytkin
2026-03-24 15:52 ` [PATCH net-next v2 5/5] net: phy: realtek: add Rust RTL8211F PHY driver Artem Lytkin
2026-03-24 15:57 ` Andrew Lunn
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox