Search code examples
linuxlinux-kernellinux-device-driverembedded-linuxethernet

How to connect an ethernet device directly to a switch in linux?


We have an embedded board where the ethernet device is directly connected to a switch without a phy in between. To make things more complicated the ethernet device's mdio bus is connected to the switch's mdio for control.

I have managed to use the fixed mdio/phy driver to enable ethernet and that works by matching the switch's default configuration to the fixed phy's.

How do I now connect to the mdio bus to change the switch settings? Since the ethernet device's attached phy is filled by the fixed phy how do I now attach the real mdio bus to the system so I can configure it. There seems to be no direct userspace interface to an mdio bus. Do I create a fake ethernet device whose only purpose is to access the mdio bus or do I somehow attach it to the ethernet device which will then have two mdio busses attached?

PS: It seems like the physical mdio bus driver finds the switch but how do I talk to it?


Solution

  • This patch allows me to read and write all the registers in the mdio devices detected in a system.

    diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
    index dc92097..668150e 100644
    --- a/drivers/net/phy/mdio_bus.c
    +++ b/drivers/net/phy/mdio_bus.c
    @@ -439,8 +439,85 @@ phy_id_show(struct device *dev, struct device_attribute *attr, char *buf)
        return sprintf(buf, "0x%.8lx\n", (unsigned long)phydev->phy_id);
     }
    
    +static ssize_t
    +mdio_reg_show(struct device *dev, struct device_attribute *attr, char *buf)
    +{
    +   struct phy_device *phydev = to_phy_device(dev);
    +   struct mii_bus* bus = phydev->bus;
    +   int regnum;
    +   int val;
    +
    +   if (sscanf(attr->attr.name, "%d", &regnum) != 1)
    +       return -EINVAL;
    +
    +   val = mdiobus_read(bus, phydev->addr, regnum);
    +   if (val < 0)
    +       return -EIO;
    +
    +   return sprintf(buf, "0x%.4x\n", val);
    +}
    +
    +static ssize_t
    +mdio_reg_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
    +{
    +   struct phy_device *phydev = to_phy_device(dev);
    +   struct mii_bus* bus = phydev->bus;
    +   int regnum;
    +   int val;
    +   int err;
    +
    +   if (sscanf(attr->attr.name, "%d", &regnum) != 1)
    +       return -EINVAL;
    +
    +   if (sscanf(buf, "%d", &val) != 1)
    +       return -EINVAL;
    +
    +   if (val < 0 || val > 0xffff)
    +       return -EINVAL;
    +
    +   err = mdiobus_write(bus, phydev->addr, regnum, val);
    +   if (err < 0)
    +       return -EIO;
    +
    +   return size;
    +}
    +
    +#define MDIO_REG(_name) __ATTR(_name, (S_IWUSR | S_IRUGO), mdio_reg_show, mdio_reg_store)
    +
     static struct device_attribute mdio_dev_attrs[] = {
        __ATTR_RO(phy_id),
    +   MDIO_REG(0),
    +   MDIO_REG(1),
    +   MDIO_REG(2),
    +   MDIO_REG(3),
    +   MDIO_REG(4),
    +   MDIO_REG(5),
    +   MDIO_REG(6),
    +   MDIO_REG(7),
    +   MDIO_REG(8),
    +   MDIO_REG(9),
    +   MDIO_REG(10),
    +   MDIO_REG(11),
    +   MDIO_REG(12),
    +   MDIO_REG(13),
    +   MDIO_REG(14),
    +   MDIO_REG(15),
    +   MDIO_REG(16),
    +   MDIO_REG(17),
    +   MDIO_REG(18),
    +   MDIO_REG(19),
    +   MDIO_REG(20),
    +   MDIO_REG(21),
    +   MDIO_REG(22),
    +   MDIO_REG(23),
    +   MDIO_REG(24),
    +   MDIO_REG(25),
    +   MDIO_REG(26),
    +   MDIO_REG(27),
    +   MDIO_REG(28),
    +   MDIO_REG(29),
    +   MDIO_REG(30),
    +   MDIO_REG(31),
        __ATTR_NULL
     };
    

    It extends the sysfs interface already present with the 32 register addresses each mdio device can contain. Since the mdio devices were not phys they didn't follow the phy standard so I had to hack the phy detection to allow all of the devices to appear:

    --- a/drivers/net/phy/phy_device.c
    +++ b/drivers/net/phy/phy_device.c
    @@ -339,9 +339,12 @@ struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)
            if (r)
                    return ERR_PTR(r);
    
    +       /* BRM: this is patently not the case for our marvell switch */
    +#if 0
            /* If the phy_id is mostly Fs, there is no device there */
            if ((phy_id & 0x1fffffff) == 0x1fffffff)
                    return NULL;
    +#endif
    
    
        dev = phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);
    

    Hope this is helpful for someone else.