PHY 与 MAC 的组织方式

总览

PHY 的作用

文章试图解决的问题

含有 PHY 层的协议,在 Linux 中 PHY 层的内存布局,在 SOC 上,外部,Controller 内外都有哪些内存空间

目前只涵盖了涉及的数据结构和初始化,文章重点在初始化过程上

PHY 框架

PHY 和 Controller 及 SOC 的模式有三种

PHY WITHIN THE CONTROLLER

PHY WITHIN THE SoC

PHY EXTERNAL TO THE SOC

EXTERNAL PHY 框架

结构示意图

以太网

PHY 的整个硬件系统组成比较复杂,PHY 与 MAC 相连 (也可以通过一个中间设备相连),MAC 与 CPU 相连(有集成在内部的,也有外接的方式),PHY 与 MAC 通过 MII 和 MDIO/MDC 相连,MII 是走网络数据的,MDIO/MDC 是用来与 PHY 的寄存器通讯的,对 PHY 进行配置。

PHY 的驱动与 I2C/SPI 的驱动一样,分为控制器驱动设备器驱动

sunxi-gmac.c 中把以太网的 phy 驱动的常规 probe 放在了 geth_phy_init 中,在网卡 geth_open 中调用,创建了 mdio_bus 设备,mdio_bus 在网络上博客描述是类似 SPI_Master 结构,spi_master 创建 spi 总线让 spi device 和 spi driver 挂接,mdio_bus 创建 mdio 总线让 phy device, phy driver 挂接,需要注意的是这里创建的是,mdio_bus 类下的一个设备,但是他的设备名字也叫 mdio_bus

c 复制代码
// drivers/net/phy/mdio_bus.c

struct bus_type mdio_bus_type = {
        .name                = "mdio_bus",
        .match                = mdio_bus_match,
        .uevent                = mdio_uevent,
};

static struct class mdio_bus_class = {
        .name                = "mdio_bus",
        .dev_release        = mdiobus_release,
};
c 复制代码
// drivers/net/phy/phy_device.c
static int phy_bus_match(struct device *dev, struct device_driver *drv)
{
        struct phy_device *phydev = to_phy_device(dev);
        struct phy_driver *phydrv = to_phy_driver(drv);
        const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
        int i;

        if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))
                return 0;

        if (phydrv->match_phy_device)
                return phydrv->match_phy_device(phydev);

        if (phydev->is_c45) {
                for (i = 1; i < num_ids; i++) {
                        if (phydev->c45_ids.device_ids[i] == 0xffffffff)
                                continue;

                        if ((phydrv->phy_id & phydrv->phy_id_mask) ==
                            (phydev->c45_ids.device_ids[i] &
                             phydrv->phy_id_mask))
                                return 1;
                }
                return 0;
        } else {
                return (phydrv->phy_id & phydrv->phy_id_mask) ==
                        (phydev->phy_id & phydrv->phy_id_mask);
        }
}

mdio_bus 类和 mdio_bus 总线设备在 phy.c

d1-h PHY 初始化

  1. 全志的 sunxi-gmac.c 用作了 ti 那边以太网的两个文件的用处
    1. mdio_bus 相关,对应 drivers/net/ethernet/ti/davinci_mdio.c ,在 sunxi-gmac.c geth_open 中
    2. net_device 相关,对应 drivers/net/ethernet/ti/cpsw.c 在 sunxi-gmac geth_probe 中

这个文章讲了 Ti 的 TI phy 初始化及和 mac 连接过程

这里记录一下 d1-h 板子 phy 初始化过程

  1. 读硬件芯片的方法是怎么实现的

前面提到 mdio_bus 实例刚注册就可以进行在总线上的获取 phy_id 的操作,说明 mdio_bus 总线的读写不依赖 mdio_bus 实例,在 d1-h 手册里可以看到对 phy 进行读写的寄存器

一个 commad register 和一个 data register,读操作把想读的 PHY_REG_ADDR 写入 command,随后从 data register 读即可,写操作把想写的 PHY_REG_ADDR 和 写入 command 数据写入 data register 即可。

关键点

这个问题有两个点花了很多时间

  1. phy_id 的获取:一直在软件层面根据 0x001cc916 找软件层面的代码,最后发现是硬件芯片负责初始化,phy_address 也是同理,硬件通过某机制协调引脚的电平,芯片手册描述是会通过机制算出未被占用的最小。
  2. phy_driver:查到 0x001cc916 后一直在找这个文件中的信息是怎么被加载到内核中的。realtek

module_phy_driver 是只要文件编成 ko 在 init 阶段执行,MODULE_DEVICE_TABLE 宏根据网上搜索的结果也是内核编译成 ko 处理。 phy_device_create 中间代码有 request_module 的过程。原本意味是这个 realtek.c 提供 phy_driver phy_device,在 request_module 中加载 phy_driver。

结果看 Kconfig 和 Makefile 发现 realtek.c 文件根本没有编译进去,phy_device_create 的 request_module 的返回值不是根据有没有找到 driver 返回,而是有没有执行下去,即不管找没找到 driver 都会返回正常值并继续往下进行。正确的流程是另有一条线进行即 device_bind_driver 函数

小结:

这个方式下对 PHY 的控制就像 SPI I2C 从设备似的,只能通过 MDIO 格式配置寄存器,无法直接访问 PHY 内部的寄存器地址

SOC 内部 PHY 框架

结构示意图

SOC 内部 PHY 框架遵循下图

GENERIC PHY FRAMEWORK 使用方法

  1. 绑定设备与驱动

    1. 设备树
    2. 不通过设备树
  2. PHY 驱动

    1. 要提供一系列 phy_ops (init, exit, power_on, power_off)
    2. 注册到 GENERIC PHY FRAMEWORK 上
  3. 控制器驱动

    1. 获得 PHY 的引用
    2. 调用 PHY Framework APIs(phy_init, phy_exit, phy_power_on, phy_power_off)

PCIE

pci-j721e.c 中的 PHY 初始化

根据PCIe控制器的工作模式(RC模式或EP模式)采取不同的初始化。

如果是 RC 模式,进行主机模式的初始化,包括配置 PCIe 主机桥、PHY 初始化等;

如果是 EP 模式,进行端点模式的初始化,包括初始化 PHY 等;

所以不管是 RC 还是 EP,都执行 PHY 初始化 cdns_pcie_init_phy()

注:of_parse_phandle_with_args 的使用:

ret = of_parse_phandle_with_args(np, "phys", "#phy-cells",index, &args);

猜想:pcie 中没有实体的 phy 芯片,虚拟的 phy 也是调的 serdies 的 phy,TI 上关于 PCIE 部分的用户手册如下。

CONTROLLER 内部 PHY 框架

USB

USB 芯片分为 Controller 和 PHY

USB 中 PHY 结构

CTL 主要实现 USB 的协议和控制。内部逻辑主要有 MAC 层、 CSR 层和 FIFO 控制层,还有其他低功耗管理之类层次。

MAC 实现按 USB 协议进行数据包打包和解包,并把数据按照 PIPE 总线格式发送给 PHY(USB2 . 0 为 UTMI)。

CSR 层进行寄存器控制,软件对 USB 芯片的控制就是通过 CSR 寄存器,这部分和 CPU 进行交互访问,主要作为 Slave 通过 AXI 或者 AHB 进行交互。

FIFO 控制层主要是和 DDR 进行数据交互,控制 USB 从 DDR 搬运数据的通道,主要作为 Master 通过 AXI / AHB 进行交互。

PHY 部分功能主要实现并转串的功能,把 UTMI 或者 PIPE 口的并行数据转换成串行数据,再通过差分数据线输出到芯片外部。

USB 芯片内部实现的功能就是接受软件的控制,进而从内存搬运数据(FIFO)并按照 USB 协议进行数据打包(MAC),并串转换(PHY)后输出到芯片外部。或者从芯片外部接收差分数据信号,串并转换后进行数据解包并写到内存里

初始化

代码分析

phy->ops 结构体(linux\include\linux\phy\phy.h)

phy 结构体(linux\include\linux\phy\phy.h)

DI-H 开发板 USB PHY 总结

上图为 D1-H 开发板 USB2.0HOST 控制器框图,由此可以认为 phy 初始化过程在 usb host 中定义,在实际代码当中也未找到关于 phy 单独的初始化过程,设备树中也未发现 phy 相关的定义。

c 复制代码
usbc0:usbc0@0 {
            device_type = "usbc0";
            compatible = "allwinner,sunxi-otg-manager";
            usb_port_type = <2>;
            usb_detect_type = <1>;
            usb_id_gpio;
            usb_det_vbus_gpio;
            usb_regulator_io = "nocare";
            usb_wakeup_suspend = <0>;
            usb_luns = <3>;
            usb_serial_unique = <0>;
            usb_serial_number = "20080411";
            rndis_wceis = <1>;
            status = "okay";
        };

        udc:udc-controller@0x04100000 {
            compatible = "allwinner,sunxi-udc";
            reg = <0x0 0x04100000 0x0 0x1000>, /*udc base*/
                  <0x0 0x00000000 0x0 0x100>; /*sram base*/
            interrupts-extended = <&plic0 45 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&ccu CLK_BUS_OTG>;
            clock-names = "bus_otg";
            resets = <&ccu RST_BUS_OTG>, <&ccu RST_USB_PHY0>;
            reset-names = "otg", "phy";
            status = "okay";
        };

        ehci0:ehci0-controller@0x04101000 {
            compatible = "allwinner,sunxi-ehci0";
            reg = <0x0 0x04101000 0x0 0xFFF>, /*hci0 base*/
                  <0x0 0x00000000 0x0 0x100>, /*sram base*/
                  <0x0 0x04100000 0x0 0x1000>; /*otg base*/
            interrupts-extended = <&plic0 46 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&ccu CLK_BUS_EHCI0>;
            clock-names = "bus_hci";
            resets = <&ccu RST_BUS_EHCI0>, <&ccu RST_USB_PHY0>;
            reset-names = "hci", "phy";
            hci_ctrl_no = <0>;
            status = "okay";
        };

        ohci0:ohci0-controller@0x04101400 {
            compatible = "allwinner,sunxi-ohci0";
            reg = <0x0 0x04101400 0x0 0xFFF>, /*hci0 base*/
                  <0x0 0x00000000 0x0 0x100>, /*sram base*/
                  <0x0 0x04100000 0x0 0x1000>; /*otg base*/
            interrupts-extended = <&plic0 47 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&ccu CLK_BUS_OHCI0>, <&ccu CLK_USB_OHCI0>;
            clock-names = "bus_hci", "ohci";
            resets = <&ccu RST_BUS_OHCI0>, <&ccu RST_USB_PHY0>;
            reset-names = "hci", "phy";
            hci_ctrl_no = <0>;
            status = "okay";
        };

        usbc1:usbc1@0 {
            device_type = "usbc1";
            usb_regulator_io = "nocare";
            usb_wakeup_suspend = <0>;
            status = "disable";
        };

        ehci1:ehci1-controller@0x04200000 {
            compatible = "allwinner,sunxi-ehci1";
            reg = <0x0 0x04200000 0x0 0xFFF>, /*ehci1 base*/
                  <0x0 0x00000000 0x0 0x100>, /*sram base*/
                  <0x0 0x04100000 0x0 0x1000>; /*otg base*/
            interrupts-extended = <&plic0 49 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&ccu CLK_BUS_EHCI1>;
            clock-names = "bus_hci";
            resets = <&ccu RST_BUS_EHCI1>, <&ccu RST_USB_PHY1>;
            reset-names = "hci", "phy";
            hci_ctrl_no = <1>;
            status = "disable";
        };

        ohci1:ohci1-controller@0x04200400 {
            compatible = "allwinner,sunxi-ohci1";
            reg = <0x0 0x04200400 0x0 0xFFF>, /*ohci1 base*/
                  <0x0 0x00000000 0x0 0x100>, /*sram base*/
                  <0x0 0x04100000 0x0 0x1000>; /*otg base*/
            interrupts-extended = <&plic0 50 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&ccu CLK_BUS_OHCI1>, <&ccu CLK_USB_OHCI1>;
            clock-names = "bus_hci", "ohci";
            resets = <&ccu RST_BUS_OHCI1>, <&ccu RST_USB_PHY1>;
            reset-names = "hci", "phy";
            hci_ctrl_no = <1>;
            status = "disable";
        };
c 复制代码
static int sunxi_ehci_hcd_probe(struct platform_device *pdev)
{
    int ret = 0;
#if defined(CONFIG_ARCH_SUN50IW10)
    int val = 0;
#endif
    struct sunxi_hci_hcd *sunxi_ehci = NULL;
    if (pdev == NULL) {
        DMSG_PANIC("ERR: %s, Argment is invalid\n", __func__);
        return -1;
    }

    /* if usb is disabled, can not probe */
    if (usb_disabled()) {
        DMSG_PANIC("ERR: usb hcd is disabled\n");
        return -ENODEV;
    }

    ret = init_sunxi_hci(pdev, SUNXI_USB_EHCI);
    if (ret != 0) {
        dev_err(&pdev->dev, "init_sunxi_hci is fail\n");
        return -1;
    }

    sunxi_insmod_ehci(pdev);

    sunxi_ehci = pdev->dev.platform_data;
    if (sunxi_ehci == NULL) {
        DMSG_PANIC("ERR: %s, sunxi_ehci is null\n", __func__);
        return -1;
    }

    if (sunxi_ehci->usbc_no == HCI0_USBC_NO) {
        ret = register_pm_notifier(&sunxi_ehci_pm_nb);
        if (ret) {
            DMSG_PANIC("ERR: %s, can not register suspend notifier\n", __func__);
            return -1;
        }
    }

    init_completion(&sunxi_ehci->standby_complete);
    
    if (ehci_enable[sunxi_ehci->usbc_no]) {
        device_create_file(&pdev->dev, &dev_attr_ehci_enable);
        ehci_enable[sunxi_ehci->usbc_no] = 0;
    }

    return 0;
}

总结

EXTERNAL(以太网 WITHIN SOCPCIE WITHIN CONTROLLER (USB)
Controller 的连接 通过各种接口 通过 UTMI PIPE3 和 Controller 共享地址空间
是否有单独的驱动 可以有
有无设备树 可以没有 可以没有
需要关注程度
相关推荐
hakesashou44 分钟前
python如何比较字符串
linux·开发语言·python
Ljubim.te1 小时前
Linux基于CentOS学习【进程状态】【进程优先级】【调度与切换】【进程挂起】【进程饥饿】
linux·学习·centos
cooldream20091 小时前
Linux性能调优技巧
linux
QMCY_jason2 小时前
Ubuntu 安装RUST
linux·ubuntu·rust
慕雪华年2 小时前
【WSL】wsl中ubuntu无法通过useradd添加用户
linux·ubuntu·elasticsearch
苦逼IT运维2 小时前
YUM 源与 APT 源的详解及使用指南
linux·运维·ubuntu·centos·devops
仍有未知等待探索2 小时前
Linux 传输层UDP
linux·运维·udp
zeruns8023 小时前
如何搭建自己的域名邮箱服务器?Poste.io邮箱服务器搭建教程,Linux+Docker搭建邮件服务器的教程
linux·运维·服务器·docker·网站
卑微求AC3 小时前
(C语言贪吃蛇)16.贪吃蛇食物位置随机(完结撒花)
linux·c语言·开发语言·嵌入式·c语言贪吃蛇
Hugo_McQueen3 小时前
pWnos1.0 靶机渗透 (Perl CGI 的反弹 shell 利用)
linux·服务器·网络安全