STMMAC驱动
Posted 四季帆
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STMMAC驱动相关的知识,希望对你有一定的参考价值。
1. 注册平台驱动
1.1 设备树配置
ethqos_hw: qcom,ethernet@20000
compatible = "qcom,stmmac-ethqos";
//以下属性都会在stmmac_probe_config_dt函数中进行解析
snps,pbl = <32>;
rx-fifo-depth = <16384>;
tx-fifo-depth = <20480>;
snps,mtl-rx-config = <&mtl_rx_setup>;
snps,mtl-tx-config = <&mtl_tx_setup>;
snps,reset = <&tlmm 79 GPIO_ACTIVE_HIGH>;
qcom,phy-intr-redirect = <&tlmm 124 GPIO_ACTIVE_LOW>;
gdsc_emac-supply = <&emac_gdsc>;
snps,reset-delays-us = <0 11000 70000>;
phy-mode = "rgmii"; //指定interface类型(比如rgmii或者rmii等)
phy-handle = <&phy1>;
mdio //描述MDIO控制器驱动节点
#address-cells = <1>;
#size-cells = <0>;
compatible = "snps,dwmac-mdio";
phy1: ethernet-phy@3 //描述控制器下挂PHY设备的节点
compatible = "ethernet-phy-id002b.0b21", "ethernet-phy-ieee802.3-c22";
reg = <3>;
;
;
;
1.2 device和driver匹配
static const struct of_device_id qcom_ethqos_match[] =
.compatible = "qcom,qcs404-ethqos", .data = &emac_v2_3_0_por,
.compatible = "qcom,sdxprairie-ethqos", .data = &emac_v2_3_2_por,
.compatible = "qcom,stmmac-ethqos", ,
.compatible = "qcom,emac-smmu-embedded", ,
;
static struct platform_driver qcom_ethqos_driver =
.probe = qcom_ethqos_probe,
.remove = qcom_ethqos_remove,
.driver =
.name = DRV_NAME,
.pm = &qcom_ethqos_pm_ops,
.of_match_table = of_match_ptr(qcom_ethqos_match),
,
;
static int __init qcom_ethqos_init_module(void)
ret = platform_driver_register(&qcom_ethqos_driver);
return ret;
static int qcom_ethqos_probe(struct platform_device *pdev)
return _qcom_ethqos_probe(pdev);
static int _qcom_ethqos_probe(void *arg)
plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac); /* 解析设备树中网卡节点中的各种属性配置,比如phy-handle,max-speed等,同时初始化一些私有变量 */
ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); /* 这个最后的probe函数是最关键的,其中调用了每个网卡驱动必会调用的alloc_etherdev创建net_device,之后一路初始化硬件、配置phy、最终调用register_netdev完成网卡驱动的注册 */
2. 解析设备树中ethernet节点的各种属性
struct plat_stmmacenet_data *
stmmac_probe_config_dt(struct platform_device *pdev, const char **mac)
struct device_node *np = pdev->dev.of_node;
struct plat_stmmacenet_data *plat; /* 用来存储ethernet device的所有配置信息 */
struct stmmac_dma_cfg *dma_cfg;
int rc;
plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL);
if (!plat)
return ERR_PTR(-ENOMEM);
*mac = of_get_mac_address(np); /* 从dts中解析mac-address属性,获得网卡的mac地址;mac-address属性的值,可以是hardcode在dts中,也可以从bootloader中传递过来 */
if (IS_ERR(*mac))
if (PTR_ERR(*mac) == -EPROBE_DEFER)
return ERR_CAST(*mac);
*mac = NULL;
plat->phy_interface = of_get_phy_mode(np); /* 一般是从dts中解析phy-mode属性,获得网卡和phy芯片之间通信的总线协议,一般是MII或者RMGII,在linux中有一张phy_modes的表格,枚举了所有可能的通信协议 */
if (plat->phy_interface < 0)
return ERR_PTR(plat->phy_interface);
plat->interface = stmmac_of_get_mac_mode(np); /* 从dts中解析mac-mode属性,如果网卡和phy之间有协议转换设备(比如从RGMII转换到GMII),才会有该属性的配置 */
if (plat->interface < 0)
plat->interface = plat->phy_interface;
/* Some wrapper drivers still rely on phy_node. Let's save it while
* they are not converted to phylink. */
plat->phy_node = of_parse_phandle(np, "phy-handle", 0); /*从dts中找到本网卡关联的phy节点*/
/* PHYLINK automatically parses the phy-handle property */
plat->phylink_node = np;
/* Get max speed of operation from device tree */
if (of_property_read_u32(np, "max-speed", &plat->max_speed))/*max-speed这个参数比较关键,dts配置的值会写到网卡的寄存器中,控制网卡的最高带宽 */
plat->max_speed = -1;
plat->bus_id = of_alias_get_id(np, "ethernet"); /*如果有多个ethernet,且在dts中配置有别名(alias),这里获得本ethernet节点在alias中定义的id号,方便与其他ethernet硬件做区分*/
if (plat->bus_id < 0)
plat->bus_id = 0;
/* Default to phy auto-detection */
plat->phy_addr = -1;
/* Default to get clk_csr from stmmac_clk_crs_set(),
* or get clk_csr from device tree.
*/
plat->clk_csr = -1;
of_property_read_u32(np, "clk_csr", &plat->clk_csr); /* csr是Control and Status Register的缩写,暂不清楚这个独立时钟的用途是什么 */
/* "snps,phy-addr" is not a standard property. Mark it as deprecated
* and warn of its use. Remove this when phy node support is added.
*/
if (of_property_read_u32(np, "snps,phy-addr", &plat->phy_addr) == 0) /*过时*/
dev_warn(&pdev->dev, "snps,phy-addr property is deprecated\\n");
/* To Configure PHY by using all device-tree supported properties */
rc = stmmac_dt_phy(plat, np, &pdev->dev); /*解析dts,如果ethernet节点中包含mdio子节点,则创建stmmac_mdio_bus_data结构体变量;mdio是以太网mac芯片与phy芯片交互的总线协议*/
if (rc)
return ERR_PTR(rc);
of_property_read_u32(np, "tx-fifo-depth", &plat->tx_fifo_size); /*获得mac控制器tx fifo的深度,单位是字节;snps,dwc-qos-ethernet-4.10支持256 byte, 512 byte, 1 KB, 2 KB, 4 KB, 8 KB, 16 KB, 32 KB, 64 KB, 128 KB, or 256 KB*/
of_property_read_u32(np, "rx-fifo-depth", &plat->rx_fifo_size); /*获得mac控制器rx fifo的深度,单位是字节;snps,dwc-qos-ethernet-4.10支持256 byte, 512 byte, 1 KB, 2 KB, 4 KB, 8 KB, 16 KB, 32 KB, 64 KB, 128 KB, or 256 KB*/
plat->force_sf_dma_mode =
of_property_read_bool(np, "snps,force_sf_dma_mode"); /* 从dts中获取属性值,以确定是否在tx和rx过程中强制使用 dma store and forward 模式;不使能该模式时,默认使用threshold模式 */
plat->en_tx_lpi_clockgating =
of_property_read_bool(np, "snps,en-tx-lpi-clockgating"); /*从dts中获取属性值,以确定是否在tx低功耗模式下使能gating mac tx clock;我理解该feature是指,当tx fifo无数据可发时,动态关闭tx的clock时钟,从而减低dynamic功耗*/
/* Set the maxmtu to a default of JUMBO_LEN in case the
* parameter is not present in the device tree.
*/
plat->maxmtu = JUMBO_LEN; /* mac控制器支持的最大MTU(网络上传送的最大数据包),这里设置的“巨包”size是9000 */
......../*省去部分不关注的内容*/
dma_cfg = devm_kzalloc(&pdev->dev, sizeof(*dma_cfg),
GFP_KERNEL); /*申请dma_cfg结构体内存,开始配置mac控制器的dma*/
if (!dma_cfg)
stmmac_remove_config_dt(pdev, plat);
return ERR_PTR(-ENOMEM);
plat->dma_cfg = dma_cfg;
of_property_read_u32(np, "snps,pbl", &dma_cfg->pbl); /*从dts中获取属性值,pbl全称是 programmable burst length, 可以是2、4或8 */
if (!dma_cfg->pbl)
dma_cfg->pbl = DEFAULT_DMA_PBL; /*如果dts中没配置,就用默认值,为8*/
of_property_read_u32(np, "snps,txpbl", &dma_cfg->txpbl); /*tx pbl,配置了该值DMA tx会使用该值而不是pbl*/
of_property_read_u32(np, "snps,rxpbl", &dma_cfg->rxpbl);/* rx pbl, 配置了该值DMA rx会使用该值而不是pbl */
dma_cfg->pblx8 = !of_property_read_bool(np, "snps,no-pbl-x8");
dma_cfg->aal = of_property_read_bool(np, "snps,aal"); /* 配置该值,表示地址对齐 */
dma_cfg->fixed_burst = of_property_read_bool(np, "snps,fixed-burst");/* 配置该值,表示DMA传输为固定burst传输 */
dma_cfg->mixed_burst = of_property_read_bool(np, "snps,mixed-burst");/* 配置该值,表示DMA传输为混合burst传输 */
plat->force_thresh_dma_mode = of_property_read_bool(np, "snps,force_thresh_dma_mode"); /* 配置该值,DMA使用threshold模式 */
if (plat->force_thresh_dma_mode)
plat->force_sf_dma_mode = 0; /* 若DMA使用threshold模式,则禁用store and fore模式 */
dev_warn(&pdev->dev,
"force_sf_dma_mode is ignored if force_thresh_dma_mode is set.\\n");
of_property_read_u32(np, "snps,ps-speed", &plat->mac_port_sel_speed); /*暂不理解,文档描述如下:Port selection speed that can be passed to the core when PCS is supported. For example, this is used in case of SGMII and MAC2MAC connection.*/
plat->axi = stmmac_axi_setup(pdev); /*根据dts属性,配置mac控制器axi寄存器*/
rc = stmmac_mtl_setup(pdev, plat); /* 根据dts属性,配置tx/rx queue相关寄存器,这里比较关键,后面详述 */
if (rc)
stmmac_remove_config_dt(pdev, plat);
return ERR_PTR(rc);
........
plat->pclk = devm_clk_get(&pdev->dev, "pclk"); /* 从dts中获得mac控制器的pclk */
if (IS_ERR(plat->pclk))
if (PTR_ERR(plat->pclk) == -EPROBE_DEFER)
goto error_pclk_get;
plat->pclk = NULL;
clk_prepare_enable(plat->pclk); /* 使能pclk */
........
plat->stmmac_rst = devm_reset_control_get(&pdev->dev,
STMMAC_RESOURCE_NAME);/* 从dts中获得mac控制器reset信号线的信息,以便之后做mac控制器所有寄存器的reset */
if (IS_ERR(plat->stmmac_rst))
if (PTR_ERR(plat->stmmac_rst) == -EPROBE_DEFER)
goto error_hw_init;
dev_info(&pdev->dev, "no reset control found\\n");
plat->stmmac_rst = NULL;
return plat;
error_hw_init:
clk_disable_unprepare(plat->pclk);
error_pclk_get:
clk_disable_unprepare(plat->stmmac_clk);
return ERR_PTR(-EPROBE_DEFER);
static int stmmac_mtl_setup(struct platform_device *pdev,
struct plat_stmmacenet_data *plat)
struct device_node *q_node;
struct device_node *rx_node;
struct device_node *tx_node;
u8 queue = 0;
int ret = 0;
/* For backwards-compatibility with device trees that don't have any
* snps,mtl-rx-config or snps,mtl-tx-config properties, we fall back
* to one RX and TX queues each.
*/
plat->rx_queues_to_use = 1;
plat->tx_queues_to_use = 1;/* 如果dts中没有配置queue的信息,默认tx和rx queue都使用1个*/
/* First Queue must always be in DCB mode. As MTL_QUEUE_DCB = 1 we need
* to always set this, otherwise Queue will be classified as AVB
* (because MTL_QUEUE_AVB = 0).
*/
plat->rx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB;
plat->tx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB;/* 配置第一个queue的模式为DCB ;queue的模式有两种,分别是DCB和AVB */
/*DCB模式 vs AVB模式:DCB(Data Center Bridge)模式常用于高吞吐场景, AVB(Audio Video Bridge)模式常用于局域网(LAN)中对时间敏感(time-sensitive)的业务。*/
rx_node = of_parse_phandle(pdev->dev.of_node, "snps,mtl-rx-config", 0);
if (!rx_node)
return ret;
tx_node = of_parse_phandle(pdev->dev.of_node, "snps,mtl-tx-config", 0);/* ethernet节点中需要有mtl-rx-config和mtl-tx-config两个节点,没有的话以下的配置也没必要展开了 */
if (!tx_node)
of_node_put(rx_node);
return ret;
/* Processing RX queues common config */
if (of_property_read_u32(rx_node, "snps,rx-queues-to-use",
&plat->rx_queues_to_use))/* 解析配置了多少个rx queue,如果没写,那就默认是1 */
plat->rx_queues_to_use = 1;
if (of_property_read_bool(rx_node, "snps,rx-sched-sp"))
plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP;
else if (of_property_read_bool(rx_node, "snps,rx-sched-wsp"))
plat->rx_sched_algorithm = MTL_RX_ALGORITHM_WSP;
else
plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP;/* 配置rx queue们采用什么调度算法 */
/* Processing individual RX queue config */
for_each_child_of_node(rx_node, q_node) /* 遍历所有rx queue做配置*/
if (queue >= plat->rx_queues_to_use)
break;
if (of_property_read_bool(q_node, "snps,dcb-algorithm"))
plat->rx_queues_cfg[queue].mode_to_use = MTL_QUEUE_DCB;/* 配置每个 rx queue使用哪种算法 */
else if (of_property_read_bool(q_node, "snps,avb-algorithm"))
plat->rx_queues_cfg[queue].mode_to_use = MTL_QUEUE_AVB;
else
plat->rx_queues_cfg[queue].mode_to_use = MTL_QUEUE_DCB;
queue++;
/* Processing TX queues common config */
if (of_property_read_u32(tx_node, "snps,tx-queues-to-use",
&plat->tx_queues_to_use))
plat->tx_queues_to_use = 1; /* 解析配置了多少个tx queue,如果没写,那就默认是1 */
if (of_property_read_bool(tx_node, "snps,tx-sched-wrr"))
plat->tx_sched_algorithm = MTL_TX_ALGORITHM_WRR;
else if (of_property_read_bool(tx_node, "snps,tx-sched-wfq"))
plat->tx_sched_algorithm = MTL_TX_ALGORITHM_WFQ;
else if (of_property_read_bool(tx_node, "snps,tx-sched-dwrr"))
plat->tx_sched_algorithm = MTL_TX_ALGORITHM_DWRR;
else if (of_property_read_bool(tx_node, "snps,tx-sched-sp"))
plat->tx_sched_algorithm = MTL_TX_ALGORITHM_SP;
else
plat->tx_sched_algorithm = MTL_TX_ALGORITHM_SP; /* 配置tx queue们采用什么调度算法 */
queue = 0;
/* Processing individual TX queue config */
for_each_child_of_node(tx_node, q_node) /* 遍历所有tx queue做配置*/
if (queue >= plat->tx_queues_to_use)
break;
if (of_property_read_u32(q_node, "snps,weight",
&plat->tx_queues_cfg[queue].weight))
plat->tx_queues_cfg[queue].weight = 0x10 + queue;
/* 配置每个 tx queue使用哪种算法 */
if (of_property_read_bool(q_node, "snps,dcb-algorithm"))
plat->tx_queues_cfg[queue].mode_to_use = MTL_QUEUE_DCB;
else if (of_property_read_bool(q_node,
"snps,avb-algorithm"))
plat->tx_queues_cfg[queue].mode_to_use = MTL_QUEUE_AVB;
queue++;
return ret;
3. register_netdev前的准备工作
int stmmac_dvr_probe(struct device *device,
struct plat_stmmacenet_data *plat_dat,
struct stmmac_resources *res)
struct net_device *ndev = NULL;
struct stmmac_priv *priv;
u32 queue, rxq, maxq;
int i, ret = 0;
/* 申请以太网卡驱动关键的net_device结构体和私有结构体stmmac_priv的内存;net_device指代一个标准的以太网卡,这里之所以说是标准,是因为各家网卡驱动都会有自己特殊的结构体用来管理一些特有的属性,比如stmmac网卡驱动的特殊结构体是stmmac_priv,标准的网卡实例net_device是它的一个成员;该函数同时也为收发queue申请内存 */
ndev = devm_alloc_etherdev_mqs(device, sizeof(struct stmmac_priv),
MTL_MAX_TX_QUEUES, MTL_MAX_RX_QUEUES);
if (!ndev)
return -ENOMEM;
SET_NETDEV_DEV(ndev, device);/*设置sysfs中设备的父子关系,net_device结构体的parent是它对应的device结构体*/
priv = netdev_priv(ndev);/* devm_alloc_etherdev_mqs申请内存时,net_device和stmmac_priv之间有一个偏移关系,这里可以直接通过net_device的内存地址找到stmmac_priv */
priv->device = device;
priv->dev = ndev;
stmmac_set_ethtool_ops(ndev);/*为用户态工具ethtool访问网卡interface时设置相应的回调函数*/
priv->pause = pause;
priv->plat = plat_dat;
priv->ioaddr = res->addr;
priv->dev->base_addr = (unsigned long)res->addr;
priv->dev->irq = res->irq;
priv->wol_irq = res->wol_irq;
priv->lpi_irq = res->lpi_irq;
if (!IS_ERR_OR_NULL(res->mac))
memcpy(priv->dev->dev_addr, res->mac, ETH_ALEN);
dev_set_drvdata(device, priv->dev);
/* Verify driver arguments */
stmmac_verify_args();
/* Allocate workqueue */
priv->wq = create_singlethread_workqueue("stmmac_wq");
if (!priv->wq)
dev_err(priv->device, "failed to create workqueue\\n");
return -ENOMEM;
/*这里创建了初始化了一个workqueue,workqueue中只有一个work是stmamac_service_task,这是用来在网卡interface出错无法恢复时的一个重新初始化手段,后文详述*/
INIT_WORK(&priv->service_task, stmmac_service_task);/*--->*/
/* Override with kernel parameters if supplied XXX CRS XXX
* this needs to have multiple instances
*/
if ((phyaddr >= 0) && (phyaddr <= 31))
priv->plat->phy_addr = phyaddr;
if (priv->plat->stmmac_rst) /*如果网卡配置了reset引脚,通过先assert(复位)再deassert(解复位)的方式的方式初始化网卡的寄存器状态 */
ret = reset_control_assert(priv->plat->stmmac_rst);
reset_control_deassert(priv->plat->stmmac_rst);
/* Some reset controllers have only reset callback instead of
* assert + deassert callbacks pair.
*/
if (ret == -ENOTSUPP)
reset_control_reset(priv->plat->stmmac_rst);
/* Init MAC and get the capabilities */
ret = stmmac_hw_init(priv);/* 初始化MAC寄存器,同时根据MAC支持的特性做结构体配置 */
if (ret)
goto error_hw_init;
stmmac_check_ether_addr(priv);/* 检查网卡MAC地址是否有效,如果是无效地址,则为网卡生成一个随机MAC地址 */
/* Configure real RX and TX queues */
netif_set_real_num_rx_queues(ndev, priv->plat->rx_queues_to_use);
netif_set_real_num_tx_queues(ndev, priv->plat->tx_queues_to_use);/*根据配置了多少tx/rx queue初始化结构体*/
ndev->netdev_ops = &stmmac_netdev_ops;/* 这里非常关键,为netdev_ops回调赋值,这里面包括了interface up/down时实际执行的函数,以及其他网卡interface操作时的重要回调,比如change mtu、ioctl等 */
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);/*为interface tx设置watchdog,如果超时则通过stmmac_service_task重启interface*/
/* MTU range: 46 - hw-specific max */
ndev->min_mtu = ETH_ZLEN - ETH_HLEN;/*设置mtu的最大/最小值*/
if (priv->plat->has_xgmac)
ndev->max_mtu = XGMAC_JUMBO_LEN;
else if ((priv->plat->enh_desc) || (priv->synopsys_id >= DWMAC_CORE_4_00))
ndev->max_mtu = JUMBO_LEN;
else
ndev->max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN);
/* Will not overwrite ndev->max_mtu if plat->maxmtu > ndev->max_mtu
* as well as plat->maxmtu < ndev->min_mtu which is a invalid range.
*/
if ((priv->plat->maxmtu < ndev->max_mtu) &&
(priv->plat->maxmtu >= ndev->min_mtu))
ndev->max_mtu = priv->plat->maxmtu;
else if (priv->plat->maxmtu < ndev->min_mtu)
dev_warn(priv->device,
"%s: warning: maxmtu having invalid value (%d)\\n",
__func__, priv->plat->maxmtu);
if (flow_ctrl)
priv->flow_ctrl = FLOW_AUTO; /* RX/TX pause on */
/* Setup channels NAPI */
maxq = max(priv->plat->rx_queues_to_use, priv->plat->tx_queues_to_use);
for (queue = 0; queue < maxq; queue++)
struct stmmac_channel *ch = &priv->channel[queue];
ch->priv_data = priv;
ch->index = queue;
if (queue < priv->plat->rx_queues_to_use)
netif_napi_add(ndev, &ch->rx_napi, stmmac_napi_poll_rx,
NAPI_POLL_WEIGHT);/*为每条rx queue设置napi回调*/
if (queue < priv->plat->tx_queues_to_use)
netif_tx_napi_add(ndev, &ch->tx_napi,
stmmac_napi_poll_tx,
NAPI_POLL_WEIGHT);/*为每条tx queue设置napi回调*/
if (priv->hw->pcs != STMMAC_PCS_RGMII &&
priv->hw->pcs != STMMAC_PCS_TBI &&
priv->hw->pcs != STMMAC_PCS_RTBI)
/* MDIO bus Registration */
ret = stmmac_mdio_register(ndev);/*注册mdio相关结构体,以使MAC能与PHY通信--->*/
if (ret < 0)
dev_err(priv->device,
"%s: MDIO bus (id: %d) registration failed",
__func__, priv->plat->bus_id);
goto error_mdio_register;
ret = stmmac_phy_setup(priv); /*注册phy实体-->*/
if (ret)
netdev_err(ndev, "failed to setup phy (%d)\\n", ret);
goto error_phy_setup;
ret = register_netdev(ndev);/*注册mac实体*/
if (ret)
dev_err(priv->device, "%s: ERROR %i registering the device\\n",
__func__, ret);
goto error_netdev_register;
return ret;
stmmac_dvr_probe
stmmac_phy_setup
phylink_create
phylink_parse_mode
//判断设备树中是否有配置fixed-link或者managed节点,如果有则设置为MLO_AN_FIXED模式或者MLO_AN_INBAND模式,如果两者都没设置则返回0====》代表MLO_AN_PHY模式
4. 控制器平台驱动的probe函数
/****************************************************************************************
直接调用CPU的MDIO控制器的方式的MDIO控制器驱动(probe函数中涉及PHY设备的创建和注册)
****************************************************************************************/
# linux-4.9.225\\drivers\\net\\ethernet\\freescale\\fsl_pq_mdio.c
stmmac_dvr_probe
|
|--- stmmac_mdio_register(ndev)
|--- struct mii_bus *new_bus
|--- new_bus = mdiobus_alloc_size(sizeof(*priv)) //分配结构体
|--- new_bus->name = "stmmac"
|--- new_bus->read = &stmmac_mdio_read //总线的读接口
|--- new_bus->write = &stmmac_mdio_write; //总线的写接口
|
|--- of_mdiobus_register(new_bus, np)//注册mii_bus设备,并通过设备树中控制器的子节点创建PHY设备
| |--- mdio->phy_mask = ~0 //屏蔽所有PHY,防止自动探测。相反,设备树中列出的phy将在总线注册后填充
| |--- mdio->dev.of_node = np
| |--- mdiobus_register(mdio) //@注意@ 注册MDIO总线设备(注意是总线设备不是总线,因为总线也是一种设备。mdio_bus是在其他地方注册的,后面会讲到)
| | |--- __mdiobus_register(bus, THIS_MODULE)
| | | |--- bus->owner = owner
| | | |--- bus->dev.parent = bus->parent
| | | |--- bus->dev.class = &mdio_bus_class
| | | |--- bus->dev.groups = NULL
| | | |--- dev_set_name(&bus->dev, "%s", bus->id) //设置总线设备的名称
| | | |--- device_register(&bus->dev) //注册总线设备
| |
| |--- for_each_available_child_of_node(np, child) //遍历这个平台设备的子节点并为每个phy注册一个phy_device
| |--- addr = of_mdio_parse_addr(&mdio->dev, child) //从子节点的"reg"属性中获得PHY设备的地址
| | |--- of_property_read_u32(np, "reg", &addr)
| |--- if (addr < 0) //如果未获得子节点的"reg"属性,则在后面再启用扫描可能存在的PHY的,然后注册
| | |--- scanphys = true
| | |--- continue
| |
| |--- of_mdiobus_register_phy(mdio, child, addr) //创建并注册PHY设备
| | |--- is_c45 = of_device_is_compatible(child,"ethernet-phy-ieee802.3-c45") //判断设备树中的PHY的属性是否指定45号条款
| | |
| | |--- if (!is_c45 && !of_get_phy_id(child, &phy_id)) //如果设备树中的PHY的属性未指定45号条款 且通过"ethernet-phy-id%4x.%4x"属性指定PHY的ID
| | | |---phy_device_create(mdio, addr, phy_id, 0, NULL)
| | |---else //我这里采用的是else分支
| | | |---phy = get_phy_device(mdio, addr, is_c45) //在@bus上的@addr处读取PHY的ID寄存器,然后分配并返回表示它的phy_device
| | | |--- get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids) //通过mdio得到PHY的ID
| | | |--- phy_device_create(bus, addr, phy_id, is_c45, &c45_ids) //创建PHY设备
| | | |--- struct phy_device *dev
| | | |--- struct mdio_device *mdiodev
| | | |--- dev = kzalloc(sizeof(*dev), GFP_KERNEL)
| | | |--- mdiodev = &dev->mdio //mdiodev是最新的内核引入,较老的版本没有这个结构
| | | |--- mdiodev->dev.release = phy_device_release
| | | |--- mdiodev->dev.parent = &bus->dev
| | | |--- mdiodev->dev.bus = &mdio_bus_type //PHY设备和驱动都会挂在mdio_bus下,匹配时会调用对应的match函数 ---|
| | | |--- mdiodev->bus = bus |
| | | |--- mdiodev->pm_ops = MDIO_BUS_PHY_PM_OPS |
| | | |--- mdiodev->bus_match = phy_bus_match //真正实现PHY设备和驱动匹配的函数<--------------------------------|
| | | |--- mdiodev->addr = addr
| | | |--- mdiodev->flags = MDIO_DEVICE_FLAG_PHY
| | | |--- mdiodev->device_free = phy_mdio_device_free
| | | |--- diodev->device_remove = phy_mdio_device_remove
| | | |--- dev->speed = SPEED_UNKNOWN
| | | |--- dev->duplex = DUPLEX_UNKNOWN
| | | |--- 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 //指示PHY设备和驱动程序尚未准备就绪,在PHY驱动的probe函数中会更改为READY
| | | |--- INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine) //PHY的状态机(核心WORK)
| | | |--- INIT_WORK(&dev->phy_queue, phy_change) //由phy_interrupt / timer调度以处理PHY状态的更改
| | | |--- request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id))//加载内核模块(这里没有细致研究过)
| | | |--- device_initialize(&mdiodev->dev) //设备模型中的一些设备,主要是kset、kobject、ktype的设置
| | |
| | |--- irq_of_parse_and_map(child, 0) //将中断解析并映射到linux virq空间(未深入研究)
| | |--- if (of_property_read_bool(child, "broken-turn-around"))//MDIO总线中的TA(Turnaround time)
| | | |--- mdio->phy_ignore_ta_mask |= 1 << addr
| | |
| | |--- of_node_get(child)//将OF节点与设备结构相关联,以便以后查找
| | |--- phy->mdio.dev.of_node = child
| | |
| | |--- phy_device_register(phy)//注册PHY设备
| | | |--- mdiobus_register_device(&phydev->mdio) //注册到mdiodev->bus,其实笔者认为这是一个虚拟的注册,仅仅是根据PHY的地址在mdiodev->bus->mdio_map数组对应位置填充这个mdiodev
| | | | |--- mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev // 方便通过mdiodev->bus统一管理和查找,以及关联bus的读写函数,方便PHY的功能配置
| | | |
| | | |--- device_add(&phydev->mdio.dev)//注册到linux设备模型框架中
| |
| |--- if (!scanphys) //如果从子节点的"reg"属性中获得PHY设备的地址,scanphys=false,这里就直接返回了,因为不需要再扫描了
| | |--- return 0
| |
/******************************************************************************************************************
一般来说只要设备树种指定了PHY设备的"reg"属性,后面的流程可以自动忽略
******************************************************************************************************************
| |--- for_each_available_child_of_node(np, child) //自动扫描具有空"reg"属性的PHY
| |--- if (of_find_property(child, "reg", NULL)) //跳过具有reg属性集的PHY
| | |--- continue
| |
| |--- for (addr = 0; addr < PHY_MAX_ADDR; addr++) //循环遍历扫描
| |--- if (mdiobus_is_registered_device(mdio, addr)) //跳过已注册的PHY
| | |--- continue
| |
| |--- dev_info(&mdio->dev, "scan phy %s at address %i\\n", child->name, addr) //打印扫描的PHY,建议开发人员设置"reg"属性
| |
| |--- if (of_mdiobus_child_is_phy(child))
| |--- of_mdiobus_register_phy(mdio, child, addr) //注册PHY设备
|
******************************************************************************************************************/
|--- ret = stmmac_phy_setup(priv); /*注册phy实体*/
|--- ret = register_netdev(ndev);/*注册mac实体*/
5. PHY寄存器的读写
在PHY设备的注册中(读PHY ID)、PHY的初始化、自协商、中断、状态、能力获取等流程中经常可以看到phy_read和phy_write两个函数(下一节要讲的PHY驱动),这两个函数的实现就依赖于控制器设备mii_bus的读写。
PHY寄存器的读写函数phy_read和phy_write定义在linux-4.9.225\\include\\linux\\phy.h中,如下:
static inline int phy_read(struct phy_device *phydev, u32 regnum)
return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, regnum);
static inline int phy_write(struct phy_device *phydev, u32 regnum, u16 val)
return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, regnum, val);
其中mdiobus_read和mdiobus_write定义在linux-4.9.225\\drivers\\net\\phy\\mdio_bus.c中,如下:
/**
* mdiobus_read - Convenience function for reading a given MII mgmt register
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to read
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
int retval;
BUG_ON(in_interrupt());
mutex_lock(&bus->mdio_lock);
retval = bus->read(bus, addr, regnum);
mutex_unlock(&bus->mdio_lock);
return retval;
/**
* mdiobus_write - Convenience function for writing a given MII mgmt register
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to write
* @val: value to write to @regnum
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
int err;
BUG_ON(in_interrupt());
mutex_lock(&bus->mdio_lock);
err = bus->write(bus, addr, regnum, val);
mutex_unlock(&bus->mdio_lock);
return err;
可以清楚的看到bus->read和bus->write读写接口在这里得到调用。
6. mdiodev的注册和获取
stmmac_dvr_probe
stmmac_mdio_register
of_mdiobus_register
mdiobus_register
__mdiobus_register
mdiobus_scan
phy_device_register(phy)
mdiobus_register_device((&phydev->mdio))
mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev
for_each_available_child_of_node(np, child) //遍历这个平台设备的子节点并为每个phy注册一个phy_device
addr = of_mdio_parse_addr(&mdio->dev, child) //从子节点的"reg"属性中获得PHY设备的地址
mdiobus_get_phy
bus->mdio_map[addr]
container_of(mdiodev, struct phy_device, mdio) //获取phy_device
以上是关于STMMAC驱动的主要内容,如果未能解决你的问题,请参考以下文章
RK3399驱动开发 | 10 - RK3399以太网gmac调试
RK3399驱动开发 | 10 - RK3399以太网gmac调试
i.MX6ULL驱动开发 | 33 - NXP原厂网络设备驱动浅读(LAN8720 PHY)
RK3288+RTL8201F-VB-CG网卡,在android5.1的3.10kernel上怎么配置?