linux/drivers/net/phy/mdio_bus.c
Heiner Kallweit c4399af5e5 net: phy: move remaining provider code to mdio_bus_provider.c
This moves definition of mdio_bus class and bus_type to the provider
side, what allows to make them private to libphy.
As a prerequisite MDIO statistics handling is moved to the
provider side as well.

Note: This patch causes a checkpatch error "Macros with complex values
      should be enclosed in parentheses" for
      MDIO_BUS_STATS_ADDR_ATTR_GROUP. I consider this a false positive
      here, in addition the patch just moves existing code.

Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
Link: https://patch.msgid.link/47b85676-b349-4aa0-a5ef-cd37769a4c69@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-03-14 12:23:02 -07:00

628 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/* MDIO Bus interface
*
* Author: Andy Fleming
*
* Copyright (c) 2004 Freescale Semiconductor, Inc.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/ethtool.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mii.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/unistd.h>
#define CREATE_TRACE_POINTS
#include <trace/events/mdio.h>
static struct mdio_device *mdiobus_find_device(struct mii_bus *bus, int addr)
{
bool addr_valid = addr >= 0 && addr < ARRAY_SIZE(bus->mdio_map);
if (WARN_ONCE(!addr_valid, "addr %d out of range\n", addr))
return NULL;
return bus->mdio_map[addr];
}
struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr)
{
struct mdio_device *mdiodev;
mdiodev = mdiobus_find_device(bus, addr);
if (!mdiodev)
return NULL;
if (!(mdiodev->flags & MDIO_DEVICE_FLAG_PHY))
return NULL;
return container_of(mdiodev, struct phy_device, mdio);
}
EXPORT_SYMBOL(mdiobus_get_phy);
bool mdiobus_is_registered_device(struct mii_bus *bus, int addr)
{
return mdiobus_find_device(bus, addr) != NULL;
}
EXPORT_SYMBOL(mdiobus_is_registered_device);
static void mdiobus_stats_acct(struct mdio_bus_stats *stats, bool op, int ret)
{
preempt_disable();
u64_stats_update_begin(&stats->syncp);
u64_stats_inc(&stats->transfers);
if (ret < 0) {
u64_stats_inc(&stats->errors);
goto out;
}
if (op)
u64_stats_inc(&stats->reads);
else
u64_stats_inc(&stats->writes);
out:
u64_stats_update_end(&stats->syncp);
preempt_enable();
}
/**
* __mdiobus_read - Unlocked version of the mdiobus_read function
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to read
*
* Return: The register value if successful, negative error code on failure
*
* Read a MDIO bus register. Caller must hold the mdio bus lock.
*
* NOTE: MUST NOT be called from interrupt context.
*/
int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
{
int retval;
lockdep_assert_held_once(&bus->mdio_lock);
if (addr >= PHY_MAX_ADDR)
return -ENXIO;
if (bus->read)
retval = bus->read(bus, addr, regnum);
else
retval = -EOPNOTSUPP;
trace_mdio_access(bus, 1, addr, regnum, retval, retval);
mdiobus_stats_acct(&bus->stats[addr], true, retval);
return retval;
}
EXPORT_SYMBOL(__mdiobus_read);
/**
* __mdiobus_write - Unlocked version of the mdiobus_write function
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to write
* @val: value to write to @regnum
*
* Return: Zero if successful, negative error code on failure
*
* Write a MDIO bus register. Caller must hold the mdio bus lock.
*
* NOTE: MUST NOT be called from interrupt context.
*/
int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{
int err;
lockdep_assert_held_once(&bus->mdio_lock);
if (addr >= PHY_MAX_ADDR)
return -ENXIO;
if (bus->write)
err = bus->write(bus, addr, regnum, val);
else
err = -EOPNOTSUPP;
trace_mdio_access(bus, 0, addr, regnum, val, err);
mdiobus_stats_acct(&bus->stats[addr], false, err);
return err;
}
EXPORT_SYMBOL(__mdiobus_write);
/**
* __mdiobus_modify_changed - Unlocked version of the mdiobus_modify function
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to modify
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
* Return: 1 if the register was modified, 0 if no change was needed,
* negative on any error condition
*
* Read, modify, and if any change, write the register value back to the
* device.
*
* NOTE: MUST NOT be called from interrupt context.
*/
int __mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
u16 mask, u16 set)
{
int new, ret;
ret = __mdiobus_read(bus, addr, regnum);
if (ret < 0)
return ret;
new = (ret & ~mask) | set;
if (new == ret)
return 0;
ret = __mdiobus_write(bus, addr, regnum, new);
return ret < 0 ? ret : 1;
}
EXPORT_SYMBOL_GPL(__mdiobus_modify_changed);
/**
* __mdiobus_c45_read - Unlocked version of the mdiobus_c45_read function
* @bus: the mii_bus struct
* @addr: the phy address
* @devad: device address to read
* @regnum: register number to read
*
* Return: The register value if successful, negative error code on failure
*
* Read a MDIO bus register. Caller must hold the mdio bus lock.
*
* NOTE: MUST NOT be called from interrupt context.
*/
int __mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
{
int retval;
lockdep_assert_held_once(&bus->mdio_lock);
if (addr >= PHY_MAX_ADDR)
return -ENXIO;
if (bus->read_c45)
retval = bus->read_c45(bus, addr, devad, regnum);
else
retval = -EOPNOTSUPP;
trace_mdio_access(bus, 1, addr, regnum, retval, retval);
mdiobus_stats_acct(&bus->stats[addr], true, retval);
return retval;
}
EXPORT_SYMBOL(__mdiobus_c45_read);
/**
* __mdiobus_c45_write - Unlocked version of the mdiobus_write function
* @bus: the mii_bus struct
* @addr: the phy address
* @devad: device address to read
* @regnum: register number to write
* @val: value to write to @regnum
*
* Return: Zero if successful, negative error code on failure
*
* Write a MDIO bus register. Caller must hold the mdio bus lock.
*
* NOTE: MUST NOT be called from interrupt context.
*/
int __mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
u16 val)
{
int err;
lockdep_assert_held_once(&bus->mdio_lock);
if (addr >= PHY_MAX_ADDR)
return -ENXIO;
if (bus->write_c45)
err = bus->write_c45(bus, addr, devad, regnum, val);
else
err = -EOPNOTSUPP;
trace_mdio_access(bus, 0, addr, regnum, val, err);
mdiobus_stats_acct(&bus->stats[addr], false, err);
return err;
}
EXPORT_SYMBOL(__mdiobus_c45_write);
/**
* __mdiobus_c45_modify_changed - Unlocked version of the mdiobus_modify function
* @bus: the mii_bus struct
* @addr: the phy address
* @devad: device address to read
* @regnum: register number to modify
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
* Return: 1 if the register was modified, 0 if no change was needed,
* negative on any error condition
*
* Read, modify, and if any change, write the register value back to the
* device. Any error returns a negative number.
*
* NOTE: MUST NOT be called from interrupt context.
*/
static int __mdiobus_c45_modify_changed(struct mii_bus *bus, int addr,
int devad, u32 regnum, u16 mask,
u16 set)
{
int new, ret;
ret = __mdiobus_c45_read(bus, addr, devad, regnum);
if (ret < 0)
return ret;
new = (ret & ~mask) | set;
if (new == ret)
return 0;
ret = __mdiobus_c45_write(bus, addr, devad, regnum, new);
return ret < 0 ? ret : 1;
}
/**
* mdiobus_read_nested - Nested version of the mdiobus_read function
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to read
*
* Return: The register value if successful, negative error code on failure
*
* In case of nested MDIO bus access avoid lockdep false positives by
* using mutex_lock_nested().
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_read_nested(struct mii_bus *bus, int addr, u32 regnum)
{
int retval;
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
retval = __mdiobus_read(bus, addr, regnum);
mutex_unlock(&bus->mdio_lock);
return retval;
}
EXPORT_SYMBOL(mdiobus_read_nested);
/**
* mdiobus_read - Convenience function for reading a given MII mgmt register
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to read
*
* Return: The register value if successful, negative error code on failure
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
{
int retval;
mutex_lock(&bus->mdio_lock);
retval = __mdiobus_read(bus, addr, regnum);
mutex_unlock(&bus->mdio_lock);
return retval;
}
EXPORT_SYMBOL(mdiobus_read);
/**
* mdiobus_c45_read - Convenience function for reading a given MII mgmt register
* @bus: the mii_bus struct
* @addr: the phy address
* @devad: device address to read
* @regnum: register number to read
*
* Return: The register value if successful, negative error code on failure
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
{
int retval;
mutex_lock(&bus->mdio_lock);
retval = __mdiobus_c45_read(bus, addr, devad, regnum);
mutex_unlock(&bus->mdio_lock);
return retval;
}
EXPORT_SYMBOL(mdiobus_c45_read);
/**
* mdiobus_c45_read_nested - Nested version of the mdiobus_c45_read function
* @bus: the mii_bus struct
* @addr: the phy address
* @devad: device address to read
* @regnum: register number to read
*
* Return: The register value if successful, negative error code on failure
*
* In case of nested MDIO bus access avoid lockdep false positives by
* using mutex_lock_nested().
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_c45_read_nested(struct mii_bus *bus, int addr, int devad,
u32 regnum)
{
int retval;
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
retval = __mdiobus_c45_read(bus, addr, devad, regnum);
mutex_unlock(&bus->mdio_lock);
return retval;
}
EXPORT_SYMBOL(mdiobus_c45_read_nested);
/**
* mdiobus_write_nested - Nested version of the mdiobus_write function
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to write
* @val: value to write to @regnum
*
* Return: Zero if successful, negative error code on failure
*
* In case of nested MDIO bus access avoid lockdep false positives by
* using mutex_lock_nested().
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_write_nested(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{
int err;
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
err = __mdiobus_write(bus, addr, regnum, val);
mutex_unlock(&bus->mdio_lock);
return err;
}
EXPORT_SYMBOL(mdiobus_write_nested);
/**
* mdiobus_write - Convenience function for writing a given MII mgmt register
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to write
* @val: value to write to @regnum
*
* Return: Zero if successful, negative error code on failure
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{
int err;
mutex_lock(&bus->mdio_lock);
err = __mdiobus_write(bus, addr, regnum, val);
mutex_unlock(&bus->mdio_lock);
return err;
}
EXPORT_SYMBOL(mdiobus_write);
/**
* mdiobus_c45_write - Convenience function for writing a given MII mgmt register
* @bus: the mii_bus struct
* @addr: the phy address
* @devad: device address to read
* @regnum: register number to write
* @val: value to write to @regnum
*
* Return: Zero if successful, negative error code on failure
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
u16 val)
{
int err;
mutex_lock(&bus->mdio_lock);
err = __mdiobus_c45_write(bus, addr, devad, regnum, val);
mutex_unlock(&bus->mdio_lock);
return err;
}
EXPORT_SYMBOL(mdiobus_c45_write);
/**
* mdiobus_c45_write_nested - Nested version of the mdiobus_c45_write function
* @bus: the mii_bus struct
* @addr: the phy address
* @devad: device address to read
* @regnum: register number to write
* @val: value to write to @regnum
*
* Return: Zero if successful, negative error code on failure
*
* In case of nested MDIO bus access avoid lockdep false positives by
* using mutex_lock_nested().
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_c45_write_nested(struct mii_bus *bus, int addr, int devad,
u32 regnum, u16 val)
{
int err;
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
err = __mdiobus_c45_write(bus, addr, devad, regnum, val);
mutex_unlock(&bus->mdio_lock);
return err;
}
EXPORT_SYMBOL(mdiobus_c45_write_nested);
/*
* __mdiobus_modify - Convenience function for modifying a given mdio device
* register
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to write
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
* Return: 0 on success, negative on any error condition
*/
int __mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask,
u16 set)
{
int err;
err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);
return err < 0 ? err : 0;
}
EXPORT_SYMBOL_GPL(__mdiobus_modify);
/**
* mdiobus_modify - Convenience function for modifying a given mdio device
* register
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to write
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
* Return: 0 on success, negative on any error condition
*/
int mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask, u16 set)
{
int err;
mutex_lock(&bus->mdio_lock);
err = __mdiobus_modify(bus, addr, regnum, mask, set);
mutex_unlock(&bus->mdio_lock);
return err;
}
EXPORT_SYMBOL_GPL(mdiobus_modify);
/**
* mdiobus_c45_modify - Convenience function for modifying a given mdio device
* register
* @bus: the mii_bus struct
* @addr: the phy address
* @devad: device address to read
* @regnum: register number to write
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
* Return: 0 on success, negative on any error condition
*/
int mdiobus_c45_modify(struct mii_bus *bus, int addr, int devad, u32 regnum,
u16 mask, u16 set)
{
int err;
mutex_lock(&bus->mdio_lock);
err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum,
mask, set);
mutex_unlock(&bus->mdio_lock);
return err < 0 ? err : 0;
}
EXPORT_SYMBOL_GPL(mdiobus_c45_modify);
/**
* mdiobus_modify_changed - Convenience function for modifying a given mdio
* device register and returning if it changed
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to write
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
* Return: 1 if the register was modified, 0 if no change was needed,
* negative on any error condition
*/
int mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
u16 mask, u16 set)
{
int err;
mutex_lock(&bus->mdio_lock);
err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);
mutex_unlock(&bus->mdio_lock);
return err;
}
EXPORT_SYMBOL_GPL(mdiobus_modify_changed);
/**
* mdiobus_c45_modify_changed - Convenience function for modifying a given mdio
* device register and returning if it changed
* @bus: the mii_bus struct
* @addr: the phy address
* @devad: device address to read
* @regnum: register number to write
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
* Return: 1 if the register was modified, 0 if no change was needed,
* negative on any error condition
*/
int mdiobus_c45_modify_changed(struct mii_bus *bus, int addr, int devad,
u32 regnum, u16 mask, u16 set)
{
int err;
mutex_lock(&bus->mdio_lock);
err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum, mask, set);
mutex_unlock(&bus->mdio_lock);
return err;
}
EXPORT_SYMBOL_GPL(mdiobus_c45_modify_changed);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MDIO bus/device layer");