这次新平台采用了与之前不同的以太网方案, MAC是内置在SoC(System On Chip)上,而PHY采用了Marvell的一款100Mps的车规级的芯片,MAC/PHY的驱动都要重新开发适配,工作难度比之前预想的要大了很多,完成时间比预想的慢了近一个星期。不过,往后看,这种直接与硬件打交道的经验很能锻炼人,在一定程度改善了我对系统的认知与理解。这篇文章重点在梳理总结下车在以太网MAC/PHY遇到的一些问题,以及Linux下MAC/PHY驱动的一些基本流程。
而PHY(Physical layer)即物理层, 其主要负责物理信号的传输, 其通过线束(如光纤/铜线)与其他设备进行连接。一个PHY芯片主要包含了两个部分: PCS(Physical Coding Sublayer), PMD(Physical Medium Dependent), 对车载PHY芯片来说,通常还包含了一个PMA(Physical Media Attachment)子层, 位于PCS与PMD之间; 下图是一个以太网的大致结构图:
那么,MAC与PHY是具体如何通讯的? 其通讯接口实际分控制接口与数据接口。控制接口是用于访问控制PHY的寄存器的MDIO(Management Data Input/Output)/MDC(Management Data Clock), 其中MDIO是数据传输用,而MDC是为MDIO的访问提供时序。MDIO最初是在IEEE RFC802.3中定义,只有Clause22一种标准,允许MAC访问32个PHY的寄存器;后来,为了适应千兆以太网PHY,提供了clause45协议,最多支持65,536个寄存器的访问,同时兼容clause22的方式来访问clause45的寄存器。下图是Clause22协议访问PHY寄存器的帧结构:
/* * The Bus class for PHYs. Devices which provide access to * PHYs should register using this structure */ structmii_bus { structmodule *owner; constchar *name; char id[MII_BUS_ID_SIZE]; void *priv; int (*read)(struct mii_bus *bus, int addr, int regnum); int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val); int (*reset)(struct mii_bus *bus);
/* * A lock to ensure that only one thing can read/write * the MDIO bus at a time */ structmutexmdio_lock;
struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr) { structphy_device *phydev; int err;
phydev = get_phy_device(bus, addr, false); if (IS_ERR(phydev)) return phydev;
/* * For DT, see if the auto-probed phy has a correspoding child * in the bus node, and set the of_node pointer in this case. */ of_mdiobus_link_mdiodev(bus, &phydev->mdio);
err = phy_device_register(phydev); if (err) { phy_device_free(phydev); return ERR_PTR(-ENODEV); }
intphy_device_register(struct phy_device *phydev) { int err;
err = mdiobus_register_device(&phydev->mdio); if (err) return err;
/* Run all of the fixups for this PHY */ err = phy_scan_fixups(phydev); if (err) { pr_err("PHY %d failed to initialize\n", phydev->mdio.addr); goto out; }
phydev->mdio.dev.groups = phy_dev_groups;
err = device_add(&phydev->mdio.dev); if (err) { pr_err("PHY %d failed to add\n", phydev->mdio.addr); goto out; }
/** * phy_connect_direct - connect an ethernet device to a specific phy_device * @dev: the network device to connect * @phydev: the pointer to the phy device * @handler: callback function for state change notifications * @interface: PHY device's interface */ intphy_connect_direct(struct net_device *dev, struct phy_device *phydev, void (*handler)(struct net_device *), phy_interface_t interface) { int rc;
if (!dev) return -EINVAL;
rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface); if (rc) return rc;
phy_prepare_link(phydev, handler); phy_start_machine(phydev); if (phydev->irq > 0) phy_start_interrupts(phydev);
/* Assume that if there is no driver, that it doesn't * exist, and we should use the genphy driver. */ if (!d->driver) { if (phydev->is_c45) d->driver = &genphy_10g_driver.mdiodrv.driver; else d->driver = &genphy_driver.mdiodrv.driver;
using_genphy = true; }
if (!try_module_get(d->driver->owner)) { dev_err(&dev->dev, "failed to get the device driver module\n"); err = -EIO; goto error_put_device; }
if (using_genphy) { err = d->driver->probe(d); if (err >= 0) err = device_bind_driver(d);
/* Some Ethernet drivers try to connect to a PHY device before * calling register_netdevice() -> netdev_register_kobject() and * does the dev->dev.kobj initialization. Here we only check for * success which indicates that the network device kobject is * ready. Once we do that we still need to keep track of whether * links were successfully set up or not for phy_detach() to * remove them accordingly. */ phydev->sysfs_links = false;
err = sysfs_create_link(&phydev->mdio.dev.kobj, &dev->dev.kobj, "attached_dev"); if (!err) { err = sysfs_create_link_nowarn(&dev->dev.kobj, &phydev->mdio.dev.kobj, "phydev"); if (err) { dev_err(&dev->dev, "could not add device link to %s err %d\n", kobject_name(&phydev->mdio.dev.kobj), err); /* non-fatal - some net drivers can use one netdevice * with more then one phy */ }
phydev->sysfs_links = true; }
phydev->dev_flags = flags;
phydev->interface = interface;
phydev->state = PHY_READY;
/* Initial carrier state is off as the phy is about to be * (re)initialized. */ netif_carrier_off(phydev->attached_dev);
/* Do initial configuration here, now that * we have certain key parameters * (dev_flags and interface) */ err = phy_init_hw(phydev); if (err) goto error;
/* * Called to initialize the PHY, * including after a reset */ int (*config_init)(struct phy_device *phydev);
/* * Called during discovery. Used to set * up device-specific structures, if any */ int (*probe)(struct phy_device *phydev);
/* PHY Power Management */ int (*suspend)(struct phy_device *phydev); int (*resume)(struct phy_device *phydev); ...
/* Determines the negotiated speed and duplex */ int (*read_status)(struct phy_device *phydev);
/* Clears any pending interrupts */ int (*ack_interrupt)(struct phy_device *phydev);
/* Enables or disables interrupts */ int (*config_intr)(struct phy_device *phydev);
/* * Checks if the PHY generated an interrupt. * For multi-PHY devices with shared PHY interrupt pin */ int (*did_interrupt)(struct phy_device *phydev); ... };