[PATCH net-next 2/2] rust: net: phy: pass a MaybeLocked device to soft_reset and match_phy_device

From: SeungJong Ha

Date: Tue Nov 10 2026 - 06:31:58 EST


The SAFETY comments of `soft_reset_callback` and
`match_phy_device_callback` state that PHYLIB holds `phy_device->lock`
when it calls them. It does on some paths but not on others:
phy_init_hw() calls `soft_reset` both with and without the lock (e.g.
from phy_attach_direct() and phy_mii_ioctl()), and phy_bus_match() calls
`match_phy_device` without it.

Hand both callbacks a `&Device<MaybeLocked>` so that the type matches
what PHYLIB provides, and correct the SAFETY comments. Update
ax88796b_rust, the only Rust implementation, to the new signature; it
only does register writes and genphy_soft_reset(), so its behaviour does
not change.

This was found while auditing SAFETY comments of Rust abstractions
against their C callers, and confirmed with Clang's context analysis:
annotating the two `phy_driver` slots with `__must_hold(&phydev->lock)`
flags exactly the lock-less paths above. It was not triggered at runtime.

Built with AX88796B_RUST_PHY=y and CLIPPY=1; a `soft_reset` that calls
`start_aneg()` or `set_speed()` no longer compiles. Not tested on
hardware.

Signed-off-by: SeungJong Ha <engineer.jjhama@xxxxxxxxx>
---
drivers/net/phy/ax88796b_rust.rs | 8 ++++----
rust/kernel/net/phy.rs | 44 ++++++++++++++++++++++++++++------------
2 files changed, 35 insertions(+), 17 deletions(-)

diff --git a/drivers/net/phy/ax88796b_rust.rs b/drivers/net/phy/ax88796b_rust.rs
index 2d24628a4e58..bde801df305c 100644
--- a/drivers/net/phy/ax88796b_rust.rs
+++ b/drivers/net/phy/ax88796b_rust.rs
@@ -30,7 +30,7 @@
// BMCR_RESET bit and poll for the reset bit to be cleared.
// Toggle BMCR_RESET bit off to accommodate broken AX8796B PHY implementation
// such as used on the Individual Computers' X-Surf 100 Zorro card.
-fn asix_soft_reset(dev: &mut phy::Device) -> Result {
+fn asix_soft_reset(dev: &phy::Device<phy::MaybeLocked>) -> Result {
dev.write(C22::BMCR, 0)?;
dev.genphy_soft_reset()
}
@@ -85,7 +85,7 @@ fn resume(dev: &mut phy::Device) -> Result {
dev.genphy_resume()
}

- fn soft_reset(dev: &mut phy::Device) -> Result {
+ fn soft_reset(dev: &phy::Device<phy::MaybeLocked>) -> Result {
asix_soft_reset(dev)
}

@@ -115,7 +115,7 @@ fn resume(dev: &mut phy::Device) -> Result {
dev.genphy_resume()
}

- fn soft_reset(dev: &mut phy::Device) -> Result {
+ fn soft_reset(dev: &phy::Device<phy::MaybeLocked>) -> Result {
asix_soft_reset(dev)
}
}
@@ -127,7 +127,7 @@ impl Driver for PhyAX88796B {
const NAME: &'static CStr = c"Asix Electronics AX88796B";
const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_model_mask(0x003b1841);

- fn soft_reset(dev: &mut phy::Device) -> Result {
+ fn soft_reset(dev: &phy::Device<phy::MaybeLocked>) -> Result {
asix_soft_reset(dev)
}
}
diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 60c58bbc12d6..c2dfc58dbe55 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -72,10 +72,10 @@ pub trait PhyContext: private::Sealed {}
/// other access to the `phy_device` made under `phy_device->lock`.
pub struct Locked;

-/// The [`Device`] context of callbacks that PHYLIB calls both with and without
-/// `phy_device->lock` held.
+/// The [`Device`] context of [`Driver::soft_reset`] and [`Driver::match_phy_device`].
///
-/// A callback in this context must not take `phy_device->lock`.
+/// PHYLIB calls these callbacks both with and without `phy_device->lock` held. A callback in this
+/// context must not take `phy_device->lock`.
///
/// # Invariants
///
@@ -322,6 +322,22 @@ pub fn genphy_soft_reset(&self) -> Result {
}
}

+impl Device<MaybeLocked> {
+ /// Creates a new [`Device`] reference in the [`MaybeLocked`] context from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// For the duration of `'a`, the pointer must point at a valid `phy_device` whose
+ /// `mdio.dev` is valid.
+ unsafe fn from_raw_maybe_locked<'a>(ptr: *mut bindings::phy_device) -> &'a Self {
+ // CAST: `Self` is a `repr(transparent)` wrapper around `bindings::phy_device`.
+ let ptr = ptr.cast::<Self>();
+ // SAFETY: The pointer is valid for `'a`. A shared reference does not assert exclusive
+ // access, and `Opaque` permits concurrent mutation by the C side.
+ unsafe { &*ptr }
+ }
+}
+
impl core::ops::Deref for Device<Locked> {
type Target = Device<MaybeLocked>;

@@ -370,10 +386,9 @@ impl<T: Driver> Adapter<T> {
/// `phydev` must be passed by the corresponding callback in `phy_driver`.
unsafe extern "C" fn soft_reset_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) };
+ // SAFETY: `phydev` is valid for the callback. `phy_init_hw()` calls `soft_reset` both
+ // with and without `phy_device->lock`, which is the `MaybeLocked` context.
+ let dev = unsafe { Device::from_raw_maybe_locked(phydev) };
T::soft_reset(dev)?;
Ok(0)
})
@@ -471,10 +486,9 @@ impl<T: Driver> Adapter<T> {
phydev: *mut bindings::phy_device,
_phydrv: *const bindings::phy_driver,
) -> c_int {
- // 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) };
+ // SAFETY: `phydev` is valid for the callback. `phy_bus_match()` holds no PHY lock, which
+ // is the `MaybeLocked` context.
+ let dev = unsafe { Device::from_raw_maybe_locked(phydev) };
T::match_phy_device(dev).into()
}

@@ -635,7 +649,9 @@ pub trait Driver {
const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_custom_mask(0, 0);

/// Issues a PHY software reset.
- fn soft_reset(_dev: &mut Device) -> Result {
+ ///
+ /// Called in the [`MaybeLocked`] context.
+ fn soft_reset(_dev: &Device<MaybeLocked>) -> Result {
build_error!(VTABLE_DEFAULT_ERROR)
}

@@ -651,7 +667,9 @@ fn get_features(_dev: &mut Device) -> Result {

/// Returns true if this is a suitable driver for the given phydev.
/// If not implemented, matching is based on [`Driver::PHY_DEVICE_ID`].
- fn match_phy_device(_dev: &Device) -> bool {
+ ///
+ /// Called in the [`MaybeLocked`] context.
+ fn match_phy_device(_dev: &Device<MaybeLocked>) -> bool {
false
}


--
2.54.0