内核中关于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