4 --> linux PHY 驱动框架解析

内核中关于phy 的驱动代码有两个部分,路径相对如下:drivers/net/phy/*

linux-4.14.200$ ls drivers/net/phy/
adm6996.c   bcm-cygnus.c   fixed_phy.c        mdio-bitbang.c     mdio-i2c.h            microchip.c      phy_device.c        rtl8366_smi.h    swphy.h
adm6996.h   bcm-phy-lib.c  fixed_phy.o        mdio-boardinfo.c   mdio-moxart.c         modules.builtin  phy_device.o        rtl8367b.c       swphy.o
amd.c       bcm-phy-lib.h  icplus.c           mdio-boardinfo.h   mdio-mux-bcm-iproc.c  mscc.c           phy_led_triggers.c  rtl8367.c        teranetics.c
aquantia.c  broadcom.c     intel-xway.c       mdio-boardinfo.o   mdio-mux.c            mvsw61xx.c       phylink.c           sfp-bus.c        vitesse.c
ar8216.c    built-in.o     ip17xx.c           mdio_bus.c         mdio-mux-gpio.c       mvsw61xx.h       phy.o               sfp.c            xilinx_gmii2rgmii.c
ar8216.h    cicada.c       Kconfig            mdio_bus.o         mdio-mux-mmioreg.c    mvswitch.c       psb6970.c           sfp.h
ar8327.c    cortina.c      libphy.o           mdio-cavium.c      mdio-octeon.c         mvswitch.h       qsemi.c             smsc.c
ar8327.h    davicom.c      lxt.c              mdio-cavium.h      mdio-sun4i.c          national.c       realtek.c           spi_ks8995.c
at803x.c    dp83640.c      Makefile           mdio_device.c      mdio-thunder.c        phy.c            rockchip.c          ste10Xp.c
b53         dp83640_reg.h  marvell10g.c       mdio_device.o      mdio-xgene.c          phy-c45.c        rtl8306.c           swconfig.c
bcm63xx.c   dp83848.c      marvell.c          mdio-gpio.c        mdio-xgene.h          phy-c45.o        rtl8366rb.c         swconfig_leds.c
bcm7xxx.c   dp83867.c      mdio-bcm-iproc.c   mdio-hisi-femac.c  meson-gxl.c           phy-core.c       rtl8366s.c          swconfig.o
bcm87xx.c   et1011c.c      mdio-bcm-unimac.c  mdio-i2c.c         micrel.c              phy-core.o       rtl8366_smi.c       swphy.c

此部分驱动主要有 switch、纯phy芯片、sfp通用光模块的驱动程序。

phy自身要解决与对端网络链接时链路参数协商的功能,并且提供寄存器接口让驱动来确定当前选择的配置,用户可用通过 ethtool 配置 phy 运行的参数,通过swconfig配置 switch 运行参数。

phy 也是一种设备,用于访问 phy 的管理总线实际上 mdio 总线,在常见的网卡驱动中,phy 一般作为驱动内部的组件,每个驱动都有独立的操作函数来对 phy 进行操作,也就是说网卡会直接调用类似 phy_ops 中的虚函数来使用 phy 的功能。如 rtl8367 的交换芯片 phy_ops 函数列表:

static struct rtl8366_smi_ops rtl8367_smi_ops = {
        .detect         = rtl8367_detect,
        .reset_chip     = rtl8367_reset_chip,
        .setup          = rtl8367_setup,

        .mii_read       = rtl8367_mii_read,
        .mii_write      = rtl8367_mii_write,

        .get_vlan_mc    = rtl8367_get_vlan_mc,
        .set_vlan_mc    = rtl8367_set_vlan_mc,
        .get_vlan_4k    = rtl8367_get_vlan_4k,
        .set_vlan_4k    = rtl8367_set_vlan_4k,
        .get_mc_index   = rtl8367_get_mc_index,
        .set_mc_index   = rtl8367_set_mc_index,
        .get_mib_counter = rtl8367_get_mib_counter,
        .is_vlan_valid  = rtl8367_is_vlan_valid,
        .enable_vlan    = rtl8367_enable_vlan,
        .enable_vlan4k  = rtl8367_enable_vlan4k,
        .enable_port    = rtl8367_enable_port,
};

交换部分 sw_ops 函数列表:

static const struct switch_dev_ops rtl8367_sw_ops = {
        .attr_global = {
                .attr = rtl8367_globals,
                .n_attr = ARRAY_SIZE(rtl8367_globals),
        },
        .attr_port = {
                .attr = rtl8367_port,
                .n_attr = ARRAY_SIZE(rtl8367_port),
        },
        .attr_vlan = {
                .attr = rtl8367_vlan,
                .n_attr = ARRAY_SIZE(rtl8367_vlan),
        },

        .get_vlan_ports = rtl8366_sw_get_vlan_ports,
        .set_vlan_ports = rtl8366_sw_set_vlan_ports,
        .get_port_pvid = rtl8366_sw_get_port_pvid,
        .set_port_pvid = rtl8366_sw_set_port_pvid,
        .reset_switch = rtl8366_sw_reset_switch,
        .get_port_link = rtl8367_sw_get_port_link,
        .get_port_stats = rtl8367_sw_get_port_stats,
};

此部分内容是 switch 部分功能,swconfig用户空间控制工具,通过 ioctl 方式调用此部分功能。

那么怎样将switch 与 phy 驱动关联起来呢?

查看 struct rtl8366_smi ,phy和switch如何关联到 pdev 设备的,详情如下:

struct rtl8366_smi {
        struct device           *parent;
        unsigned int            gpio_sda;
        unsigned int            gpio_sck;
        void                    (*hw_reset)(struct rtl8366_smi *smi, bool active);
        unsigned int            clk_delay;      /* ns */
        u8                      cmd_read;
        u8                      cmd_write;
        spinlock_t              lock;
        struct mii_bus          *mii_bus;
        int                     mii_irq[PHY_MAX_ADDR];
        
        struct switch_dev       sw_dev;                // switch 部分 ops 结构体,

        unsigned int            cpu_port;
        unsigned int            num_ports;
        unsigned int            num_vlan_mc;
        unsigned int            num_mib_counters;
        struct rtl8366_mib_counter *mib_counters;
        struct rtl8366_smi_ops  *ops;                // phy 部分 ops 结构体

        int                     vlan_enabled;
        int                     vlan4k_enabled;
        char                    buf[4096];

        struct reset_control    *reset;

#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
        struct dentry           *debugfs_root;
        u16                     dbg_reg;
        u8                      dbg_vlan_4k_page;
#endif
        struct mii_bus          *ext_mbus;
};

在 rtl8366 的结构体中,直接封装 smi 和 switch 两个部分内容。

rtl8367_probe 函数把两个部分内容结合起来。

static int rtl8367_probe(struct platform_device *pdev)
{
        struct rtl8366_smi *smi;
        int err;

        smi = rtl8366_smi_probe(pdev);
        if (IS_ERR(smi))
                return PTR_ERR(smi);

        smi->clk_delay = 1500;
        smi->cmd_read = 0xb9;
        smi->cmd_write = 0xb8;
        smi->ops = &rtl8367_smi_ops;
        smi->cpu_port = RTL8367_CPU_PORT_NUM;
        smi->num_ports = RTL8367_NUM_PORTS;
        smi->num_vlan_mc = RTL8367_NUM_VLANS;
        smi->mib_counters = rtl8367_mib_counters;
        smi->num_mib_counters = ARRAY_SIZE(rtl8367_mib_counters);

        err = rtl8366_smi_init(smi);      // phy 部分初始化
        if (err)
                goto err_free_smi;

        platform_set_drvdata(pdev, smi);

        err = rtl8367_switch_init(smi);  // switch 部分初始化,此函数调用 register_switch() 注册交换机
        if (err)
                goto err_clear_drvdata;

        return 0;

 err_clear_drvdata:
        platform_set_drvdata(pdev, NULL);
        rtl8366_smi_cleanup(smi);
 err_free_smi:
        kfree(smi);
        return err;
}

register_switch 函数内容

int
register_switch(struct switch_dev *dev, struct net_device *netdev)
{
        struct switch_dev *sdev;
        const int max_switches = 8 * sizeof(unsigned long);
        unsigned long in_use = 0;
        int err;
        int i;

        INIT_LIST_HEAD(&dev->dev_list);
        if (netdev) {
                dev->netdev = netdev;
                if (!dev->alias)
                        dev->alias = netdev->name;
        }
        BUG_ON(!dev->alias);

        /* Make sure swdev_id doesn't overflow */
        if (swdev_id == INT_MAX) {
                return -ENOMEM;
        }

        if (dev->ports > 0) {
                dev->portbuf = kzalloc(sizeof(struct switch_port) *
                                dev->ports, GFP_KERNEL);
                if (!dev->portbuf)
                        return -ENOMEM;
                dev->portmap = kzalloc(sizeof(struct switch_portmap) *
                                dev->ports, GFP_KERNEL);
                if (!dev->portmap) {
                        kfree(dev->portbuf);
                        return -ENOMEM;
                }
        }
        swconfig_defaults_init(dev);
        mutex_init(&dev->sw_mutex);
        swconfig_lock();
        dev->id = ++swdev_id;

        list_for_each_entry(sdev, &swdevs, dev_list) {
                if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i))
                        continue;
                if (i < 0 || i > max_switches)
                        continue;

                set_bit(i, &in_use);
        }
        i = find_first_zero_bit(&in_use, max_switches);

        if (i == max_switches) {
                swconfig_unlock();
                return -ENFILE;
        }

#ifdef CONFIG_OF
        if (dev->ports)
                of_switch_load_portmap(dev);
#endif

        /* fill device name */
        snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i);
        list_add_tail(&dev->dev_list, &swdevs);
        swconfig_unlock();

        err = swconfig_create_led_trigger(dev);
        if (err)
                return err;

        return 0;
}
EXPORT_SYMBOL_GPL(register_switch);

phy 设备的硬件结构

阅读 phy 驱动框架发现 phy 设备实际是挂到 mdio 总线中,phy 驱动框架的主要源文件名称如下:

phy_device.c
phy-core.c
phy.c

phy_device.c 中是 phy 设备注册与释放相关的代码。

phy-core.c、phy.c 中是对获取、设定 phy 信息的封装层,这一层的函数大都要访问 phy_driver 实例中的不同字段来完成其功能。

内核参考文档
Documentation/net/phy.txt

  • 2
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值