初次写博客,不好的地方欢迎提出建议。
目 标 :在ZYNQ7020处理器的linux平台调通Marvell 88e6020交换机,实现局域网内可以通讯。
硬件平台:zynq7020(ARM CORTEX-A9),Marvell 88E6020交换机芯片
内核版本:linux4.14.0
说到网络,肯定是先想到网络OSI7层协议模型,直接度娘可以找到很多7层模型的详细介绍,以下介绍简单介绍相关的MAC层和PHY层。
•MAC是Media Access Control的缩写,即媒体访问控制子层协议。该协议位于OSI七层协议中数据链路层的下半部分,主要负责控制与连接物理层的物理介质。
在发送数据的时候,MAC协议可以事先判断是否可以发送数据,如果可以发送将给数据加上一些控制信息,最终将数据以及控制信息以规定的格式发送到物理层;
在接收数据的时候,MAC协议首先判断输入的信息并是否发生传输错误,如果没有错误,则去掉控制信息发送至LLC层。以太网MAC由IEEE-802.3以太网标准定义。
•PHY是物理接口收发器,它实现物理层。包括MII/GMII(介质独立接口)子层、PCS(物理编码子层)、PMA(物理介质附加)子层、PMD(物理介质相关)子层、MDI子层。
PHY在发送数据时,收到MAC过来的数据(对PHY来说,没有帧的概念,对它来说,都是数据而不管什么地址,数据还是CRC),每4bit就增加1bit的检错码,然后把并行数据转化为串行流数据,再按照物理层的编码规则把数据编码,再变为模拟信号把数据送出去。
收数据时的流程反之。
PHY与MAC的通迅接口
MAC和PHY是通过MII(Medium Independent Interface(介质独立接口))进行通信。包括如下:
- 数据接口,有TX/RX两条独立的通道。
- 管理接口(SMI),由时钟信号和数据信号组成,控制监控PHY的工作状态。
以MII的基础,又有了以下多种数据接口:
- RMII(Reduced Media Independant Interface),简化的MII,信号线更少了。
- GMII(Gigabit Media Independent Interface)千兆的MII接口。
- RGMII (Reduced Gigabit Media Independent Interface)简化的千兆接口。
SMI(MII管理接口):
- 串行管理接口(Serial Management Interface)也被称作MII管理接口(MII Management Interface),包括MDC和MDIO两条信号线。MDIO是一个PHY的管理接口,用来读/写PHY的寄存器,以控制PHY的行为或获取PHY的状态,MDC为MDIO提供时钟。
这次的硬件使用的是RGMII接口:
- RGMII均采用4位数据接口,工作时钟125MHz,并且在上升沿和下降沿同时传输数据,因此传输速率可达1000Mbps。
PHY层之下是RJ45,RJ45就是我们平常使用的网线水晶头插线口。
以下为MAC和PHY通讯的整体框图
以下为研究代码过程
ZYNQ7020 MAC驱动
-
在源码包的位置 source_root/drivers/net/ethernet/cadence/
-
drivers/net/ethernet/cadence/macb_main.c 为MAC驱动代码。
macb_main.c
- 平台注册到probe执行,在probe中执行了macb_mii_init函数。
/*平台注册*/
static struct platform_driver macb_driver = {
.probe = macb_probe,
.remove = macb_remove,
.driver = {
.name = "macb",
.of_match_table = of_match_ptr(macb_dt_ids),
.pm = &macb_pm_ops,
},
};
module_platform_driver(macb_driver);
/*macb_probe*/
static int macb_probe(struct platform_device *pdev)
{
int (*clk_init)(struct platform_device *, struct clk **,
struct clk **, struct clk **, struct clk **,
struct clk **) = macb_clk_init;
int (*init)(struct platform_device *) = macb_init;
struct device_node *np = pdev->dev.of_node;
struct device_node *phy_node;
const struct macb_config *macb_config = NULL;
struct clk *pclk, *hclk = NULL, *tx_clk = NULL, *rx_clk = NULL;
struct clk *tsu_clk = NULL;
unsigned int queue_mask, num_queues;
struct macb_platform_data *pdata;
bool native_io;
struct phy_device *phydev;
struct net_device *dev;
struct resource *regs;
void __iomem *mem;
const char *mac;
struct macb *bp;
int err;
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mem = devm_ioremap_resource(&pdev->dev, regs);
if (IS_ERR(mem))
return PTR_ERR(mem);
if (np) {
const struct of_device_id *match;
match = of_match_node(macb_dt_ids, np);
if (match && match->data) {
macb_config = match->data;
clk_init = macb_config->clk_init;
init = macb_config->init;
}
}
err = clk_init(pdev, &pclk, &hclk, &tx_clk, &rx_clk, &tsu_clk);
if (err)
return err;
pm_runtime_set_autosuspend_delay(&pdev->dev, MACB_PM_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
native_io = hw_is_native_io(mem);
macb_probe_queues(mem, native_io, &queue_mask, &num_queues);
dev = alloc_etherdev_mq(sizeof(*bp), num_queues);
if (!dev) {
err = -ENOMEM;
goto err_disable_clocks;
}
dev->base_addr = regs->start;
SET_NETDEV_DEV(dev, &pdev->dev);
bp = netdev_priv(dev);
bp->pdev = pdev;
bp->dev = dev;
bp->regs = mem;
bp->native_io = native_io;
if (native_io) {
bp->macb_reg_readl = hw_readl_native;
bp->macb_reg_writel = hw_writel_native;
} else {
bp->macb_reg_readl = hw_readl;
bp->macb_reg_writel = hw_writel;
}
bp->num_queues = num_queues;
bp->queue_mask = queue_mask;
if (macb_config)
bp->dma_burst_length = macb_config->dma_burst_length;
bp->pclk = pclk;
bp->hclk = hclk;
bp->tx_clk = tx_clk;
bp->rx_clk = rx_clk;
bp->tsu_clk = tsu_clk;
if (tsu_clk)
bp->tsu_rate = clk_get_rate(tsu_clk);
if (macb_config)
bp->jumbo_max_len = macb_config->jumbo_max_len;
spin_lock_init(&bp->lock);
/* setup capabilities */
macb_configure_caps(bp, macb_config);
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
if (GEM_BFEXT(DAW64, gem_readl(bp, DCFG6))) {
dma_set_mask(&pdev->dev, DMA_BIT_MASK(44));
bp->hw_dma_cap |= HW_DMA_CAP_64B;
}
#endif
platform_set_drvdata(pdev, dev);
dev->irq = platform_get_irq(pdev, 0);
if (dev->irq < 0) {
err = dev->irq;
goto err_out_free_netdev;
}
/* MTU range: 68 - 1500 or 10240 */
dev->min_mtu = GEM_MTU_MIN_SIZE;
if (bp->caps & MACB_CAPS_JUMBO)
dev->max_mtu = gem_readl(bp, JML) - ETH_HLEN - ETH_FCS_LEN;
else
dev->max_mtu = ETH_DATA_LEN;
mac = of_get_mac_address(np);
if (mac)
ether_addr_copy(bp->dev->dev_addr, mac);
else
macb_get_hwaddr(bp);
/* Power up the PHY if there is a GPIO reset */
phy_node = of_parse_phandle(np, "phy-handle", 0);
if (!phy_node && of_phy_is_fixed_link(np)) {
err = of_phy_register_fixed_link(np);
if (err < 0) {
dev_err(&pdev->dev, "broken fixed-link specification");
goto failed_phy;
}
phy_node = of_node_get(np);
bp->phy_node = phy_node;
} else {
int gpio = of_get_named_gpio(phy_node, "reset-gpios", 0);
if (gpio_is_valid(gpio)) {
bp->reset_gpio = gpio_to_desc(gpio);
gpiod_direction_output(bp->reset_gpio, 1);
}
}
err = of_get_phy_mode(np);
if (err < 0) {
pdata = dev_get_platdata(&pdev->dev);
if (pdata && pdata->is_rmii)
bp->phy_interface = PHY_INTERFACE_MODE_RMII;
else
bp->phy_interface = PHY_INTERFACE_MODE_MII;
} else {
bp->phy_interface = err;
}
macb_reset_phy(pdev);
/* IP specific init */
err = init(pdev);
if (err)
goto err_out_free_netdev;
err = register_netdev(dev);
if (err) {
dev_err(&pdev->dev, "Cannot register net device, aborting.\n");
goto err_out_unregister_netdev;
}
err = macb_mii_init(bp); /*此处为PHY初始化,匹配PHY的过程*/
if (err)
goto err_out_unregister_netdev;
netif_carrier_off(dev);
tasklet_init(&bp->hresp_err_tasklet, macb_hresp_error_task,
(unsigned long)bp);
if (bp->caps & MACB_CAPS_WOL)
device_set_wakeup_capable(&bp->dev->dev, 1);
netdev_info(dev, "Cadence %s rev 0x%08x at 0x%08lx irq %d (%pM)\n",
macb_is_gem(bp) ? "GEM" : "MACB", macb_readl(bp, MID),
dev->base_addr, dev->irq, dev->dev_addr);
phydev = bp->phy_dev;
phy_attached_info(phydev);
pm_runtime_mark_last_busy(&bp->pdev->dev);
pm_runtime_put_autosuspend(&bp->pdev->dev);
return 0;
err_out_unregister_netdev:
unregister_netdev(dev);
err_out_free_netdev:
free_netdev(dev);
failed_phy:
of_node_put(phy_node);
err_disable_clocks:
clk_disable_unprepare(tx_clk);
clk_disable_unprepare(hclk);
clk_disable_unprepare(pclk);
clk_disable_unprepare(rx_clk);
clk_disable_unprepare(tsu_clk);
pm_runtime_disable(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
return err;
}
- macb_mii_init函数为初始化SMI总线,分配空间,封装SMI读写PHY寄存器函数,然后通过MDIO总线去扫描物理连接的PHY,匹配驱动,代码追踪如下。
static int macb_mii_init(struct macb *bp)
{
struct macb_platform_data *pdata;
struct device_node *np, *mdio_np;
int err = -ENXIO, i;
/* Enable management port */
macb_writel(bp, NCR, MACB_BIT(MPE));
/* ***分配空间*****/
bp->mii_bus = mdiobus_alloc();
if (!bp->mii_bus) {
err = -ENOMEM;
goto err_out;
}
bp->mii_bus->name = "MACB_mii_bus";
/****封装读写PHY寄存器函数****/
bp->mii_bus->read = &macb_mdio_read;
bp->mii_bus->write = &macb_mdio_write;
// bp->mii_bus->read = &mdio_phy_read;
// bp->mii_bus->write = &mdio_phy_write;
snprintf(bp->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x",
bp->pdev->name, bp->pdev->id);
bp->mii_bus->priv = bp;
bp->mii_bus->parent = &bp->dev->dev;
pdata = dev_get_platdata(&bp->pdev->dev);
dev_set_drvdata(&bp->dev->dev, bp->mii_bus);
np = bp->pdev->dev.of_node;
mdio_np = of_get_child_by_name(np, "mdio");
if (mdio_np) {
of_node_put(mdio_np);
err = of_mdiobus_register(bp->mii_bus, mdio_np);
if (err)
goto err_out_unregister_bus;
} else if (np) {
/* try dt phy registration */
err = of_mdiobus_register(bp->mii_bus, np);
/* fallback to standard phy registration if no phy were
* found during dt phy registration
*/
if (!err && !phy_find_first(bp->mii_bus)) {
for (i = 1; i < 2; i++) {
struct phy_device *phydev;
/*********扫描物理连接的PHY********/
phydev = mdiobus_scan(bp->mii_bus, i);
if (IS_ERR(phydev) &&
PTR_ERR(phydev) != -ENODEV) {
err = PTR_ERR(phydev);
break;
}
}
if (err)
goto err_out_unregister_bus;
}
} else {
if (pdata)
bp->mii_bus->phy_mask = pdata->phy_mask;
err = mdiobus_register(bp->mii_bus);
}
if (err)
goto err_out_free_mdiobus;
err = macb_mii_probe(bp->dev);
if (err)
goto err_out_unregister_bus;
return 0;
err_out_unregister_bus:
mdiobus_unregister(bp->mii_bus);
err_out_free_mdiobus:
mdiobus_free(bp->mii_bus);
err_out:
return err;
}
- 执行mdiobus_scan去扫描MDIO总线上挂载的PHY设备。
struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)
{
struct phy_device *phydev;
int err;
/*通过设备ID号拿到设备的实例结构体*/
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);
/*注册PHY设备*/
err = phy_device_register(phydev);
if (err) {
phy_device_free(phydev);
return ERR_PTR(-ENODEV);
}
return phydev;
}
- 执行get_phy_device通过读取设备的ID号去拿到设备的驱动
struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)
{
struct phy_c45_device_ids c45_ids = {0};
u32 phy_id = 0;
int r;
r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);
if (r)
return ERR_PTR(r);
/* If the phy_id is mostly Fs, there is no device there */
if ((phy_id & 0x1fffffff) == 0x1fffffff)
return ERR_PTR(-ENODEV);
return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);
}
- 执行phy_device_register 注册PHY设备
int phy_device_register(struct phy_device *phydev)
{
int err;
//注册到MDIO总线上
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;
}
return 0;
out:
mdiobus_unregister_device(&phydev->mdio);
return err;
}
MDIO总线的实现和目的
目的:总线涉及驱动和设备,匹配注册在总线上的驱动和设备来执行驱动。上面的MAC驱动中执行了设备注册过程,这里讲解MDIO总线,最后面讲解驱动的注册过程。
实现:drivers/net/phy/mdio_bus.c
总线通过match去匹配设备和驱动。
struct bus_type mdio_bus_type = {
.name = "mdio_bus",
.match = mdio_bus_match,
.uevent = mdio_uevent,
.pm = MDIO_BUS_PM_OPS,
};
PHY6020驱动
查看数据手册查找设备88e6020 ID为0x01410db0,通过phy_drivers_register函数注册驱动,驱动和设备匹配上后会执行config_init函数。
static struct phy_driver marvell_drivers[] = {
{
.phy_id = 0x01410db0,
.phy_id_mask = 0x0fffffff,
.name = "Marvell 88E6020",
.features = PHY_100BT_FEATURES|PHY_10BT_FEATURES|SUPPORTED_MII |
SUPPORTED_AUI | SUPPORTED_FIBRE |SUPPORTED_BNC,
.flags = PHY_HAS_INTERRUPT,
.config_init = &m88e6020_config_init,
.config_aneg = &m88e6020_config_aneg,
.read_status = &m88e6020_genphy_read_status,
},
};
static int __init mv88e6020_init(void)
{
int ret = 0;
mv88e6020_phydev = NULL;
ret = phy_drivers_register(marvell_drivers,
ARRAY_SIZE(marvell_drivers),THIS_MODULE);
if ( ret )
goto err;
swicth_to_cpu_link_status = 0;
err:
return ret;
}