phy 驅動與 switch 驅動

phy 驅動與 switch 驅動

phy 驅動

phy 與 cpu 的硬件連接

一般爲 MAC-PHY 模式:

  
  -----------        
  | CPU     |   RGMII/
  |   ------|   MII   ---------
  |   | MAC |---------|  PHY  |
  |   |     |---------|       |
  |   |-----|   SMI   |_______|
  ----------

MAC -PHY 之間使用 RGMII /MII 接口連接,用於傳輸數據。 SMI 接口,通常爲 MDIO 接口或 I2C接口,用於讀寫PHY上面的寄存器。

phy device

1,2 phy device 的數據結構

struct phy_device {
	struct mdio_device mdio;               // mdio 設備,提供mdio的讀寫接口

	/* Information about the PHY type */
	/* And management functions */
	struct phy_driver *drv;                // 匹配到phy驅動後賦值
	
	u32 phy_id;                            // phy_id ,匹配驅動的時候用到
	
	struct phy_c45_device_ids c45_ids;
	bool is_c45;
	bool is_internal;
	bool is_pseudo_fixed_link;
	bool has_fixups;
	bool suspended;
	bool sysfs_links;
	bool loopback_enabled;
	
	enum phy_state state;
	
	u32 dev_flags;
	
	phy_interface_t interface;
	
	/*
	 * forced speed & duplex (no autoneg)
	 * partner speed & duplex & pause (autoneg)
	 */
	int speed;
	int duplex;
	int pause;
	int asym_pause;
	
	/* The most recently read link state */
	int link;
	
	/* Enabled Interrupts */
	u32 interrupts;
	
	/* Union of PHY and Attached devices' supported modes */
	/* See mii.h for more info */
	u32 supported;
	u32 advertising;
	u32 lp_advertising;
	
	/* Energy efficient ethernet modes which should be prohibited */
	u32 eee_broken_modes;
	
	int autoneg;
	
	int link_timeout;

#ifdef CONFIG_LED_TRIGGER_PHY
	struct phy_led_trigger *phy_led_triggers;
	unsigned int phy_num_led_triggers;
	struct phy_led_trigger *last_triggered;
#endif

	/*
	 * Interrupt number for this PHY
	 * -1 means no interrupt
	 */
	int irq;                         // 表示phy_statu的查詢方式,大於0 爲中斷方式,-1 爲POLL 方式
	
	/* private data pointer */
	/* For use by PHYs to maintain extra state */
	void *priv;
	
	/* Interrupt and Polling infrastructure */
	struct work_struct phy_queue;
	struct delayed_work state_queue;
	atomic_t irq_disable;
	
	struct mutex lock;
	
	struct phylink *phylink;
	struct net_device *attached_dev;
	
	u8 mdix;
	u8 mdix_ctrl;
	
	void (*phy_link_change)(struct phy_device *, bool up, bool do_carrier);
	void (*adjust_link)(struct net_device *dev);
}

1.3 phy device 的註冊函數

/**
 * phy_device_register - Register the phy device on the MDIO bus
 * @phydev: phy_device structure to be added to the MDIO bus
 */
int phy_device_register(struct phy_device *phydev)
{
	int err;

	err = mdiobus_register_device(&phydev->mdio);  //本質是在mdiobus 上註冊 mdio 設備
	if (err)                                       //從面向對象的角度 pdy device 繼承自 mdio device
		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;
}

1.4 phy device 在哪註冊的?

以 orangepi-one 爲例,先看它的dts

/*arch/arm/boot/dts/sunxi-h3-h5.dtsi */
emac: ethernet@1c30000 {
                        compatible = "allwinner,sun8i-h3-emac";
                        syscon = <&syscon>;
                        reg = <0x01c30000 0x10000>;
                        interrupts = <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH>;
                        interrupt-names = "macirq";
                        resets = <&ccu RST_BUS_EMAC>;
                        reset-names = "stmmaceth";
                        clocks = <&ccu CLK_BUS_EMAC>;
                        clock-names = "stmmaceth";
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";

                        mdio: mdio {
                                #address-cells = <1>;
                                #size-cells = <0>;
                                compatible = "snps,dwmac-mdio";
                        };
    
                        mdio-mux {
                                compatible = "allwinner,sun8i-h3-mdio-mux";
                                #address-cells = <1>;
                                #size-cells = <0>;
    
                                mdio-parent-bus = <&mdio>;
                                /* Only one MDIO is usable at the time */
                                internal_mdio: mdio@1 {
                                        compatible = "allwinner,sun8i-h3-mdio-internal";
                                        reg = <1>;
                                        #address-cells = <1>;
                                        #size-cells = <0>;
    
                                        int_mii_phy: ethernet-phy@1 {
                                                compatible = "ethernet-phy-ieee802.3-c22";
                                                reg = <1>;
                                                clocks = <&ccu CLK_BUS_EPHY>;
                                                resets = <&ccu RST_BUS_EPHY>;
                                        };
                                };
    
                                external_mdio: mdio@2 {
                                        reg = <2>;
                                        #address-cells = <1>;
                                        #size-cells = <0>;
                                };
                        };
                };

/*arch/arm/boot/dts/sun8i-h3-orangepi-one.dts */
&emac {
    phy-handle = <&int_mii_phy>;
    phy-mode = "mii";
    allwinner,use-internal-phy;
    status = "okay";
};

compatible = “allwinner,sun8i-h3-emac” ,對應驅動代碼爲 drivers\net\ethernet\stmicro\stmmac\dwmac-sun8i.c

這是一個mac 驅動,在probe 過程中會註冊phy device。

sun8i_dwmac_probe
-> stmmac_dvr_probe
  -> stmmac_mdio_register         // 這裏註冊的是mdio bus,提供讀寫接口
  -> sun8i_dwmac_register_mdio_mux   // 爲mdio-mux子節點創建設備
    -> mdio_mux_init
      -> of_mdiobus_register   
        -> of_mdiobus_register_phy
          -> phy_device_register

這個過程中會根據 dts 內容決定這個是不是 phy

if (of_mdiobus_child_is_phy(child))
			rc = of_mdiobus_register_phy(mdio, child, addr);
		elsec
			rc = of_mdiobus_register_device(mdio, child, addr);

mdiobus_child 中符合以下條件之一的就認爲是phy ,否則只是個 mdio device

/*
 * Return true if the child node is for a phy. It must either:
 * o Compatible string of "ethernet-phy-idX.X"
 * o Compatible string of "ethernet-phy-ieee802.3-c45"
 * o Compatible string of "ethernet-phy-ieee802.3-c22"
 * o In the white list above (and issue a warning)
 * o No compatibility string
 *
 * A device which is not a phy is expected to have a compatible string
 * indicating what sort of device it is.
 */
static bool of_mdiobus_child_is_phy(struct device_node *child)
{
	u32 phy_id;

	if (of_get_phy_id(child, &phy_id) != -EINVAL)
		return true;

	if (of_device_is_compatible(child, "ethernet-phy-ieee802.3-c45"))
		return true;

	if (of_device_is_compatible(child, "ethernet-phy-ieee802.3-c22"))
		return true;

	if (of_match_node(whitelist_phys, child)) {
		pr_warn(FW_WARN
			"%pOF: Whitelisted compatible string. Please remove\n",
			child);
		return true;
	}

	if (!of_find_property(child, "compatible", NULL))
		return true;

	return false;
}

phy_id 的初始化,在of_mdiobus_register_phy 中

	if (!is_c45 && !of_get_phy_id(child, &phy_id))
		phy = phy_device_create(mdio, addr, phy_id, 0, NULL);  // dts 裏用"ethernet-phy-idX.X"指定
	else
		phy = get_phy_device(mdio, addr, is_c45);              // 通過讀MII_PHYSID1 MII_PHYSID2 獲取

1.5 phy_device_create

struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id,
				     bool is_c45,
				     struct phy_c45_device_ids *c45_ids)
{
	struct phy_device *dev;
	struct mdio_device *mdiodev;

	/* We allocate the device, and initialize the default values */
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return ERR_PTR(-ENOMEM);
	
	mdiodev = &dev->mdio;
	mdiodev->dev.release = phy_device_release;
	mdiodev->dev.parent = &bus->dev;
	mdiodev->dev.bus = &mdio_bus_type;
	mdiodev->bus = bus;
	mdiodev->pm_ops = MDIO_BUS_PHY_PM_OPS;
	mdiodev->bus_match = phy_bus_match;    // phy_bus_match 使用 phy_id 匹配 driver
	mdiodev->addr = addr;
	mdiodev->flags = MDIO_DEVICE_FLAG_PHY;
	mdiodev->device_free = phy_mdio_device_free;
	mdiodev->device_remove = phy_mdio_device_remove;
	
	dev->speed = 0;
	dev->duplex = -1;
	dev->pause = 0;
	dev->asym_pause = 0;
	dev->link = 1;
	dev->interface = PHY_INTERFACE_MODE_GMII;
	
	dev->autoneg = AUTONEG_ENABLE;
	
	dev->is_c45 = is_c45;
	dev->phy_id = phy_id;
	if (c45_ids)
		dev->c45_ids = *c45_ids;
	dev->irq = bus->irq[addr];
	dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr);
	
	dev->state = PHY_DOWN;
	
	mutex_init(&dev->lock);
	INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);  // 初始化phy狀態機
	INIT_WORK(&dev->phy_queue, phy_change_work);
	
	/* Request the appropriate module unconditionally; don't
	 * bother trying to do so only if it isn't already loaded,
	 * because that gets complicated. A hotplug event would have
	 * done an unconditional modprobe anyway.
	 * We don't do normal hotplug because it won't work for MDIO
	 * -- because it relies on the device staying around for long
	 * enough for the driver to get loaded. With MDIO, the NIC
	 * driver will get bored and give up as soon as it finds that
	 * there's no driver _already_ loaded.
	 */
	request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id));
	
	device_initialize(&mdiodev->dev);
	
	return dev;
}

phy driver

2.1 phy driver 的數據結構

struct phy_driver {
	struct mdio_driver_common mdiodrv;   // 註冊的時候,填充
	u32 phy_id;                          // phy id  用於匹配
	char *name;
	unsigned int phy_id_mask;
	u32 features;
	u32 flags;
	const void *driver_data;

	/*
	 * Called to issue a PHY software reset
	 */
	int (*soft_reset)(struct phy_device *phydev);
	
	/*
	 * Called to initialize the PHY,
	 * including after a reset
	 */
	int (*config_init)(struct phy_device *phydev);
	
	...其它方法...

}

struct phy_driver 結構相對簡單,大部分是接口函數,而這些接口函數大部分都不需要實現。

2.2 phy driver 的註冊函數

int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{
	int retval;

	new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
	new_driver->mdiodrv.driver.name = new_driver->name;
	new_driver->mdiodrv.driver.bus = &mdio_bus_type;
	new_driver->mdiodrv.driver.probe = phy_probe;
	new_driver->mdiodrv.driver.remove = phy_remove;
	new_driver->mdiodrv.driver.owner = owner;
	
	retval = driver_register(&new_driver->mdiodrv.driver);
	if (retval) {
		pr_err("%s: Error %d in registering driver\n",
		       new_driver->name, retval);
	
		return retval;
	}
	
	pr_debug("%s: Registered new driver\n", new_driver->name);
	
	return 0;
}

2.3 phy device 與 phy driver 的匹配

首先 通過 phy_bus_match 函數,使用phy_id 進行匹配。

static int phy_bus_match(struct device *dev, struct device_driver *drv)
{	
		return (phydrv->phy_id & phydrv->phy_id_mask) ==
			(phydev->phy_id & phydrv->phy_id_mask);	
}

當一個phy device 沒有匹配到 driver 時,默認使用 genphy driver 。

內核默認會添加兩種 genphy driver ,分別是 genphy_10g_driver ,genphy_driver。

static int __init phy_init(void)
{
	rc = phy_driver_register(&genphy_10g_driver, THIS_MODULE);
	rc = phy_driver_register(&genphy_driver, THIS_MODULE);
	return rc;
}

在 phy_attach_direct 函數中,當一個phy device 未匹配到 phy driver 時,默認使用 genphy driver。

int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
		      u32 flags, phy_interface_t interface)
{
	//...
	/* 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;
	}
	//...
}

調用鏈如下:

.ndo_open->stmmac_open->stmmac_init_phy -> of_phy_connect ->phy_connect_direct->phy_attach_direct

phy driver 實例

static struct phy_driver genphy_driver = {
	.phy_id		= 0xffffffff,
	.phy_id_mask	= 0xffffffff,
	.name		= "Generic PHY",
	.soft_reset	= genphy_no_soft_reset,
	.config_init	= genphy_config_init,
	.features	= PHY_GBIT_FEATURES | SUPPORTED_MII |
			  SUPPORTED_AUI | SUPPORTED_FIBRE |
			  SUPPORTED_BNC,
	.config_aneg	= genphy_config_aneg,
	.aneg_done	= genphy_aneg_done,
	.read_status	= genphy_read_status,
	.suspend	= genphy_suspend,
	.resume		= genphy_resume,
	.set_loopback   = genphy_loopback,
};

其中比較關鍵的一點是 .read_status = genphy_read_status ,用於獲取phy 的連接狀態,speed ,全雙工狀態等。

read_status 在 phy_state_machine 中被調用,phy_state_machine 是個狀態機,每間隔1s 執行一次。當phy 的狀態發生改變時,如協商速率從100M 變爲了 1000M ,這時會調用 phy_link_change ->phydev->adjust_link 來調整 MAC 端的速率

標準phy 與非標準phy

兩者區別在於寄存器定義,IEEE802.3 定義了地址爲0-15這16個寄存器的功能,而有的廠家製造的PHY 寄存器定義不一樣,所以就有了標準PHY 與非標準PHY .

寄存器定義符合IEEE802.3 標準的,可以直接使用 genphy_driver ,而非標準的需要單獨實現。

小結

  • phy 驅動的核心功能是提供phy初始化接口,自動協商接口,以及狀態讀取接口(linkup,協商速率,等)
  • 標準phy 可以使用 genphy_driver , 非標的要單獨實現

switch 驅動

switch與cpu 的硬件連接

                                            ------------------
                                            |  CPU           |
                                            |   -----------  |
                                            |   | MAC/ephy   |  
                                            |___|__||_____|__|
                                                   ||                                           
                                            RGMII/ ||  RXP/TXP
                                            MII    ||
                                                   ||
   ------------------------------------------------||---------- 
   |   Switch                                      ||        |
   |                                               ||        |
   |                                               ||        |
   |   |-----|   |-----|   |-----|   |-----|    |-----|      |
   ____|_____|___|_____|___|_____|___|_____|____|_____|______|
         PHY1      PHY2      PHY3      PHY4      PHY/MAC

switch 一般包含多個PHY,同樣會有提供MDIO ,I2C 接口給CPU 進行寄存器讀寫。其寄存器會比單純的PHY寄存器複雜。所以switch 驅動一般不註冊爲phy驅動,而是註冊爲平臺驅動。

MAC-MAC 模式下的 fixed-link

當cpu 與swicth 使用MAC-MAC 方式連接時,內核仍會運行phy 狀態機去獲取phy 的連接狀態連接速率等,這時就要告訴內核我是固定連接的,內核給了個虛擬MDIO接口位於drivers\net\phy\fixed_phy.c ,主要提供假的 mdio_read 函數,返回固定信息給內核。

int swphy_read_reg(int reg, const struct fixed_phy_status *state)
{
	...
    bmcr  |= speed[speed_index].bmcr  & duplex[duplex_index].bmcr;
    lpa   |= speed[speed_index].lpa   & duplex[duplex_index].lpa;
    lpagb |= speed[speed_index].lpagb & duplex[duplex_index].lpagb;	
    ...
	switch (reg) {
	case MII_BMCR:
		return bmcr;
	case MII_BMSR:
		return bmsr;	
	case MII_LPA:
		return lpa;
	case MII_STAT1000:
		return lpagb;	
	default:
		return 0xffff;
	}
}

需要在 dts 中添加如下內容:

Examples:

ethernet@0 {
	...
	fixed-link {
	      speed = <1000>;
	      full-duplex;
	};
	...
};

代碼中 of_phy_is_fixed_link 當節點中包含fixed-link 返回true.

if (!plat->phy_node && of_phy_is_fixed_link(np)) {
		if ((of_phy_register_fixed_link(np) < 0))    // 這裏註冊一個使用虛擬mdio接口的phy device
			return -ENODEV;

		dev_dbg(dev, "Found fixed-link subnode\n");
		plat->phy_node = of_node_get(np);
		mdio = false;
	}

MAC-MAC 方式下,phy driver 使用 genphy_driver ,phy device 的 mido 是假的mdio 。

OpenWrt swconfig 框架

OpenWrt 中實現了 swconfig 工具用於配置 switch ,swconfig 主要分爲兩部分

  • 應用層實現

  • 內核層實現

    swconfig: 
    ------------------------------------
    應用層
             
               |  | (Generic Netlink)
    -----------|--|---------------------
    內核層      
    		swconfig core
    		---------------------------
           驅動層           swicth_dev
    ------------------------------------
    
    

swconfig 應用層實現

swconfig 源代碼可以在 build_dir/target_xxx/swconfig 下找到。主要是使用 genetlink 接口與內核進行通信。

使用方法參考:https://openwrt.org/docs/techref/swconfig

  • swconfig list
    
  • swconfig dev switch0 show
    
  • Show current configuration

    swconfig dev rtl8366rb show
    

    and you will obtain:

    VLAN 1:
            info: VLAN 1: Ports: '12345t', members=003e, untag=001e, fid=0
            fid: 0
            ports: 1 2 3 4 5t
    VLAN 2:
            info: VLAN 2: Ports: '05t', members=0021, untag=0001, fid=0
            fid: 0
            ports: 0 5t
    

swconfig 內核層實現

  1. core 部分

代碼位於linux-3.18.21/drivers/net/phy/swconfig.c ,這是OpenWrt 打補丁後生成的代碼。

static struct genl_ops swconfig_ops[] = {
  {
  	.cmd = SWITCH_CMD_LIST_GLOBAL,
  	.doit = swconfig_list_attrs,
  	.policy = switch_policy,
  },	
  ...
};


static struct genl_family switch_fam = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0)
  .id = GENL_ID_GENERATE,
#endif
  .name = "switch",
  .hdrsize = 0,
  .version = 1,
  .maxattr = SWITCH_ATTR_MAX,
  .module = THIS_MODULE,
  .ops = swconfig_ops,
  .n_ops = ARRAY_SIZE(swconfig_ops),
};

static int __init swconfig_init(void)
{
  INIT_LIST_HEAD(&swdevs);

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0)
  return genl_register_family_with_ops(&switch_fam, swconfig_ops);
#else
  return genl_register_family(&switch_fam);
#endif
}

核心代碼是使用 genl_register_family 註冊了一個 struct genl_family switch_fam,switch_fam.ops = swconfig_ops, swconfig_ops 中註冊了相應命令集合及其處理函數。

此外對外導出register_switch ,unregister_switch 接口,用於switch 驅動實現自己的接口函數。

int register_switch(struct switch_dev *dev, struct net_device *netdev)
{
    ...
}
EXPORT_SYMBOL_GPL(register_switch);
EXPORT_SYMBOL_GPL(unregister_switch);

2)swicth_dev 部分

以 drivers\net\phy\rtl8367b.c 爲例:

static struct platform_driver rtl8367b_driver = {
	.driver = {
		.name		= RTL8367B_DRIVER_NAME,
		.owner		= THIS_MODULE,
#ifdef CONFIG_OF
		.of_match_table = of_match_ptr(rtl8367b_match),
#endif
	},
	.probe		= rtl8367b_probe,
	.remove		= rtl8367b_remove,
	.shutdown	= rtl8367b_shutdown,
};

它將switch 驅動實現爲 platform_driver , 在 probe 函數中,最終會調用 register_switch

static const struct switch_dev_ops rtl8367b_sw_ops = {
	.attr_global = {
		.attr = rtl8367b_globals,
		.n_attr = ARRAY_SIZE(rtl8367b_globals),
	},
	.attr_port = {
		.attr = rtl8367b_port,
		.n_attr = ARRAY_SIZE(rtl8367b_port),
	},
	.attr_vlan = {
		.attr = rtl8367b_vlan,
		.n_attr = ARRAY_SIZE(rtl8367b_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 = rtl8367b_sw_get_port_link,
	.get_port_stats = rtl8367b_sw_get_port_stats,
};

{
	struct switch_dev *dev = &smi->sw_dev;
	int err;

	dev->name = "RTL8367B";
	dev->cpu_port = RTL8367B_CPU_PORT_NUM;
	dev->ports = RTL8367B_NUM_PORTS;
	dev->vlans = RTL8367B_NUM_VIDS;
	dev->ops = &rtl8367b_sw_ops;
	dev->alias = dev_name(smi->parent);

	err = register_switch(dev, NULL);
	if (err)
		dev_err(smi->parent, "switch registration failed\n");

	return err;
}

可以看出爲了實現swconfig 功能,swicth 驅動要做的事情就是實現 struct switch_dev_ops 結構體 ,實現以下接口。

  • .get_vlan_ports ,
  • .set_vlan_ports ,
  • .get_port_pvid ,
  • .set_port_pvid ,
  • .reset_switch ,
  • .get_port_link
  • get_port_stats ,

Linux dsa 框架

什麼是dsa

dsa 全稱是 Distributed Switch Architecture ,即分佈式交換機架構。其介紹可見:Documentation\networking\dsa\dsa.txt

是linux 內核自帶的交換機子系統。設計目的是使交換機可以通過工具bridge, iproute2, ifconfig 直接進行配置/查詢。

具體實現,有待研究。

Introduction

This document describes the Distributed Switch Architecture (DSA) subsystem
design principles, limitations, interactions with other subsystems, and how to
develop drivers for this subsystem as well as a TODO for developers interested
in joining the effort.

Design principles

The Distributed Switch Architecture is a subsystem which was primarily designed
to support Marvell Ethernet switches (MV88E6xxx, a.k.a Linkstreet product line)
using Linux, but has since evolved to support other vendors as well.

小結

  • MAC-MAC 模式下一般要使用 fixed-link
  • switch 驅動一般註冊爲平臺驅動
  • switch 驅動核心功能是實現switch 的配置,vlan ,port 等
  • swconfig 框架和 dsa 框架都是爲了方便應用層配置switch
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章