9

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?

slobobaby
  • 696
  • 7
  • 10
  • I think the approach to create a fake ethernet device seems reasonable. That is, if the physical medium is all setup and all you need to do is represent the device to the network stack so user space can access it. – Peter L. Mar 06 '14 at 18:05
  • 1
    To answer my own question I have extended the sysfs interface for mdio to contain a register interface file 00-31 for each mdio device detected and now I can directly read and write the mdio registers without involving an Ethernet device from userspace. The patch is only 20 lines or so. – slobobaby Mar 10 '14 at 17:58
  • @slobobaby , It is great that you found solution, can you post [self-answer](http://stackoverflow.com/help/self-answer) for posterity ? – Abhijeet Kasurde Mar 11 '14 at 13:38

3 Answers3

6

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.

slobobaby
  • 696
  • 7
  • 10
0

You can write pseudo phy driver based on your phy id. You can get phy id by reading the phy registers. This driver will give you handle to the mdio bus the switch is connected to. This is my driver, In my case, i.MX6 was connected to marvell 88E6065 switch. Then i have exported sysfs interface and i was able to configure switch from the user-space through sysfs interface. Hope this will help someone.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/asai_iotg.h>

MODULE_DESCRIPTION("psuedo phy driver");
MODULE_LICENSE("GPLv2");
MODULE_AUTHOR("jags");

static int phy_id = 0;
static u32 reg_num = 0;
static u16 data = 0;
static struct mii_bus *mvl_bus = NULL;
static struct kobject *kobj_switch;

/* Supported Device ID Tables */
static struct mdio_device_id marvell_88E6065_id[] = {
    {0x01410c89, 0xfffffc00},
    {}
};
MODULE_DEVICE_TABLE(mdio, marvell_88E6065_id);

static ssize_t switch_conf_show(struct kobject *kobj, struct kobj_attribute *attr,
        char *buf)
{
    return sprintf(buf, "%x\n", data);
}

static ssize_t  switch_conf_store(struct kobject *kobj, struct attribute *attr,
        const char *buffer, size_t size)
{
    u32 value;
    sscanf(buffer, "%x", &value);
    if (value & 0xFF000000) {
        phy_id = (value & 0xFF000000) >> 24;
        reg_num = (value & 0x00FF0000) >> 16;
        data =  (value & 0x0000FFFF);
        mdiobus_write(mvl_bus, phy_id, reg_num, data);
    }
    else {
        phy_id = (value & 0xFF00) >> 8;
        reg_num = (value & 0x00FF);
        data = mdiobus_read(mvl_bus, phy_id, reg_num);
    }
    return size;
}

static struct kobj_attribute switch_conf_attribute =
__ATTR(switch_conf, 0666, switch_conf_show, switch_conf_store);

static struct attribute *attrs_switch[] = {
    &switch_conf_attribute.attr,
    NULL,
};

static struct attribute_group attr_group_switch = {
    .attrs = attrs_switch,
};

/* Initialize the Marvell 88E6065 switch in managed mode */
static int marvell_88E6065_probe(struct phy_device *phydev)
{
    int err = 0;
    mvl_bus = phydev->bus;
    if(mvl_bus == NULL)
        return -1;

    pr_debug("Detected Marvell switch !!\n");
    pr_debug("switch id is %04x%04x\n", mdiobus_read(mvl_bus, 0x8, 0x2), mdiobus_read(mvl_bus, 0x08, 0x03));

    if(err) {
        printk(KERN_INFO "mdio write failed for marvell pseudo phy\n");
        return -1;
    }
    return 0;
}

/* PHY Driver */
static struct phy_driver marvell_88E6065_driver = {
    .phy_id     = 0x01410c89,
    .phy_id_mask    = 0xffffffff,
    .name       = "Marvell 88E6065",
    .features   = (PHY_BASIC_FEATURES),
    .flags      = PHY_HAS_INTERRUPT,
    .probe      = marvell_88E6065_probe,
    .config_aneg    = genphy_config_aneg,
    .read_status    = genphy_read_status,
    .driver     = { .owner = THIS_MODULE },
};
/*Switch initialize function */

/* Init exit */
static int __init mdio_88E6065_init()
{
    int ret = 0;
    kobj_switch = kobject_create_and_add("switch", &asai_iotg_kset->kobj);
    if (!kobj_switch)
        return -ENOMEM;
    ret = sysfs_create_group(kobj_switch, &attr_group_switch);
    if (ret) {
        kobject_put(kobj_switch);
        return ret;
    }
    return phy_driver_register(&marvell_88E6065_driver);
}

static void __exit mdio_88E6065_exit()
{
    kobject_put(kobj_switch);
    phy_driver_unregister(&marvell_88E6065_driver);
}

module_init(mdio_88E6065_init);
module_exit(mdio_88E6065_exit);                                               
Jagdish
  • 1,848
  • 3
  • 22
  • 33
0

One can use the following patch to extend sysfs in newer kernel versions. It patches 5.4.72 cleanly.

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 5d1d63690d6e..a47fb6d06746 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -543,10 +543,123 @@ phy_has_fixups_show(struct device *dev, struct device_attribute *attr,
 }
 static DEVICE_ATTR_RO(phy_has_fixups);
 
+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->mdio.bus;
+   int regnum;
+   int val;
+
+   if (sscanf(attr->attr.name, "%d", &regnum) != 1)
+       return -EINVAL;
+
+   val = mdiobus_read(bus, phydev->mdio.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->mdio.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->mdio.addr, regnum, val);
+   if (err < 0)
+       return -EIO;
+
+   return size;
+}
+
+#define __ATTR_RW_MOD(_name) __ATTR(_name, (S_IWUSR | S_IRUGO),        \
+            mdio_reg_show, mdio_reg_store)
+
+#define MDIO_REG(_name)  struct device_attribute dev_attr_##_name = __ATTR_RW_MOD(_name)
+
+static MDIO_REG(0);
+static MDIO_REG(1);
+static MDIO_REG(2);
+static MDIO_REG(3);
+static MDIO_REG(4);
+static MDIO_REG(5);
+static MDIO_REG(6);
+static MDIO_REG(7);
+static MDIO_REG(8);
+static MDIO_REG(9);
+static MDIO_REG(10);
+static MDIO_REG(11);
+static MDIO_REG(12);
+static MDIO_REG(13);
+static MDIO_REG(14);
+static MDIO_REG(15);
+static MDIO_REG(16);
+static MDIO_REG(17);
+static MDIO_REG(18);
+static MDIO_REG(19);
+static MDIO_REG(20);
+static MDIO_REG(21);
+static MDIO_REG(22);
+static MDIO_REG(23);
+static MDIO_REG(24);
+static MDIO_REG(25);
+static MDIO_REG(26);
+static MDIO_REG(27);
+static MDIO_REG(28);
+static MDIO_REG(29);
+static MDIO_REG(30);
+static MDIO_REG(31);
+
 static struct attribute *phy_dev_attrs[] = {
    &dev_attr_phy_id.attr,
    &dev_attr_phy_interface.attr,
    &dev_attr_phy_has_fixups.attr,
+   &dev_attr_0.attr,
+   &dev_attr_1.attr,
+   &dev_attr_2.attr,
+   &dev_attr_3.attr,
+   &dev_attr_4.attr,
+   &dev_attr_5.attr,
+   &dev_attr_6.attr,
+   &dev_attr_7.attr,
+   &dev_attr_8.attr,
+   &dev_attr_9.attr,
+   &dev_attr_10.attr,
+   &dev_attr_11.attr,
+   &dev_attr_12.attr,
+   &dev_attr_13.attr,
+   &dev_attr_14.attr,
+   &dev_attr_15.attr,
+   &dev_attr_16.attr,
+   &dev_attr_17.attr,
+   &dev_attr_18.attr,
+   &dev_attr_19.attr,
+   &dev_attr_20.attr,
+   &dev_attr_21.attr,
+   &dev_attr_22.attr,
+   &dev_attr_23.attr,
+   &dev_attr_24.attr,
+   &dev_attr_25.attr,
+   &dev_attr_26.attr,
+   &dev_attr_27.attr,
+   &dev_attr_28.attr,
+   &dev_attr_29.attr,
+   &dev_attr_30.attr,
+   &dev_attr_31.attr,
    NULL,
 };
 ATTRIBUTE_GROUPS(phy_dev);

Mahdi_Sh
  • 11
  • 2