public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [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