天天看點

Linux stmac網卡代碼分析----probeprobe

probe

主要分析一下驅動的主要架構,必要地方細緻分析下

檔案位置:

  • drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c

    從檔案的最後看起:

    module_pci_driver(stmmac_pci_driver);

stmmac_pci_driver結構體如下,裡面包含了id_table、probe、remove。id_table裡包含了支援裝置的vender id、device id,當掃描到支援的pcie裝置時(把venderid 和device id 和table裡的id對比)就調用probe函數

static struct pci_driver stmmac_pci_driver = { 
    .name = STMMAC_RESOURCE_NAME,
    .id_table = stmmac_id_table,
    .probe = stmmac_pci_probe,
    .remove = stmmac_pci_remove,
    .driver         = { 
        .pm     = &stmmac_pm_ops,
    },  
};
           

以下是stmmac_pci_probe函數的一些主要調用

Linux stmac網卡代碼分析----probeprobe

主要執行了以下操作

  1. 為plat_stmmacenet_data配置設定了空間
  2. 填充plat_stmmacenet_data
  3. 使能pci
  4. 填充plat_stmmacenet_data中一些網卡參數資訊
  5. 擷取pci的base address
  6. 調用stmmac_dvr_probe

接下來就進入到stmmac_dvr_probe函數,在此函數中主要完成net_device結構體的配置設定,和填充,填充中最重要的是設定了net_device結構體中的netdev_ops,最後使用register_netdev(ndev)注冊到核心

stmmac_dvr_probe

Linux stmac網卡代碼分析----probeprobe
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, maxq;
	int ret = 0;

	ndev = alloc_etherdev_mqs(sizeof(struct stmmac_priv),
				  MTL_MAX_TX_QUEUES,
				  MTL_MAX_RX_QUEUES);

	SET_NETDEV_DEV(ndev, device);
    
	/*對priv的填充*/
	priv = netdev_priv(ndev);
	priv->device = device;
	priv->dev = ndev;
	//對ethtool的支援
	stmmac_set_ethtool_ops(ndev);
	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 (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");
		ret = -ENOMEM;
		goto error_wq;
	}

	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;

	/* Init MAC and get the capabilities */
	ret = stmmac_hw_init(priv);
	if (ret)
		goto error_hw_init;

	/* 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);
	
    //設定網卡的ops
	ndev->netdev_ops = &stmmac_netdev_ops;

	ndev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
			    NETIF_F_RXCSUM;

	ret = stmmac_tc_init(priv, priv);
	if (!ret) {
		ndev->hw_features |= NETIF_F_HW_TC;
	}

	if ((priv->plat->tso_en) && (priv->dma_cap.tsoen)) {
		ndev->hw_features |= NETIF_F_TSO | NETIF_F_TSO6;
		priv->tso = true;
		dev_info(priv->device, "TSO feature enabled\n");
	}
	ndev->features |= ndev->hw_features | NETIF_F_HIGHDMA;
	ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
#ifdef STMMAC_VLAN_TAG_USED
	/* Both mac100 and gmac support receive VLAN tag detection */
	ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX;
#endif
priv->msg_enable = netif_msg_init(debug, default_msg_level);

	/* MTU range: 46 - hw-specific max */
	ndev->min_mtu = ETH_ZLEN - ETH_HLEN;
	if ((priv->plat->enh_desc) || (priv->synopsys_id >= DWMAC_CORE_4_00))
		ndev->max_mtu = JUMBO_LEN;
	else if (priv->plat->has_xgmac)
		ndev->max_mtu = XGMAC_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)
			ch->has_rx = true;
		if (queue < priv->plat->tx_queues_to_use)
			ch->has_tx = true;

		netif_napi_add(ndev, &ch->napi, stmmac_napi_poll,
			       NAPI_POLL_WEIGHT);
	}

	mutex_init(&priv->lock);

    /*如果設定了plat->clk_csr那麼CSR clock在運作中将會是固定不變的,
      如果沒有設定,那麼click會根據csr實際的時鐘輸入動态的設定*/
	if (!priv->plat->clk_csr)
		stmmac_clk_csr_set(priv);
	else
		priv->clk_csr = priv->plat->clk_csr;
    
	//檢查是否支援Physical Coding Sublayer
	stmmac_check_pcs_mode(priv);

	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);
		if (ret < 0) {
			dev_err(priv->device,
				"%s: MDIO bus (id: %d) registration failed",
				__func__, priv->plat->bus_id);
			goto error_mdio_register;
		}
	}

	ret = register_netdev(ndev);
    
	return ret;
error_netdev_register:
	if (priv->hw->pcs != STMMAC_PCS_RGMII &&
	    priv->hw->pcs != STMMAC_PCS_TBI &&
	    priv->hw->pcs != STMMAC_PCS_RTBI)
		stmmac_mdio_unregister(ndev);
error_mdio_register:
	for (queue = 0; queue < maxq; queue++) {
		struct stmmac_channel *ch = &priv->channel[queue];
		netif_napi_del(&ch->napi);
	}
error_hw_init:
	destroy_workqueue(priv->wq);
error_wq:
	free_netdev(ndev);
	return ret;
}
EXPORT_SYMBOL_GPL(stmmac_dvr_probe);
           

下面看看stmmac_hw_init(priv),這裡有硬體相關的設定,特别是設定了操作硬體的ops,函數中主要調用了stmmac_hwif_init函數。

int stmmac_hwif_init(struct stmmac_priv *priv)
{
	bool needs_xgmac = priv->plat->has_xgmac;
	bool needs_gmac4 = priv->plat->has_gmac4;
	bool needs_gmac = priv->plat->has_gmac;
	const struct stmmac_hwif_entry *entry;
	struct mac_device_info *mac;
	bool needs_setup = true;
	int i, ret;
	u32 id;

	if (needs_gmac) {
		id = stmmac_get_id(priv, GMAC_VERSION);
	} else if (needs_gmac4 || needs_xgmac) {
		id = stmmac_get_id(priv, GMAC4_VERSION);
	} else {
		id = 0;
	}

	/* Save ID for later use */
	priv->synopsys_id = id;

	/* Lets assume some safe values first */
	priv->ptpaddr = priv->ioaddr +
		(needs_gmac4 ? PTP_GMAC4_OFFSET : PTP_GMAC3_X_OFFSET);
	priv->mmcaddr = priv->ioaddr +
		(needs_gmac4 ? MMC_GMAC4_OFFSET : MMC_GMAC3_X_OFFSET);

	/* Check for HW specific setup first */
	if (priv->plat->setup) {
		mac = priv->plat->setup(priv);
		needs_setup = false;
	} else {
		mac = devm_kzalloc(priv->device, sizeof(*mac), GFP_KERNEL);
	}

	if (!mac)
		return -ENOMEM;

	/* 為網卡設定硬體操作相關的回調函數*/
	for (i = ARRAY_SIZE(stmmac_hw) - 1; i >= 0; i--) {
		/*在stmmac_hw數組中選取一個entry*/
        entry = &stmmac_hw[i];
	
        /*進行篩選*/
		if (needs_gmac ^ entry->gmac)
			continue;
		if (needs_gmac4 ^ entry->gmac4)
			continue;
		if (needs_xgmac ^ entry->xgmac)
			continue;
		/* Use synopsys_id var because some setups can override this */
		if (priv->synopsys_id < entry->min_id)
			continue;

		/* 到這裡表示已經選到合适的entry,将它儲存到mac結構體 */
		mac->desc = mac->desc ? : entry->desc;
		mac->dma = mac->dma ? : entry->dma;
		mac->mac = mac->mac ? : entry->mac;
		mac->ptp = mac->ptp ? : entry->hwtimestamp;
		mac->mode = mac->mode ? : entry->mode;
		mac->tc = mac->tc ? : entry->tc;
		
        /*把mac挂接到priv->hw*/
		priv->hw = mac;
		priv->ptpaddr = priv->ioaddr + entry->regs.ptp_off;
		priv->mmcaddr = priv->ioaddr + entry->regs.mmc_off;

		/* Save quirks, if needed for posterior use */
		priv->hwif_quirks = entry->quirks;
		return 0;
	}

	dev_err(priv->device, "Failed to find HW IF (id=0x%x, gmac=%d/%d)\n",
			id, needs_gmac, needs_gmac4);
	return -EINVAL;
}
           

以下是對上述代碼用到的資料結構的一個整理,以便于把握整體脈絡

Linux stmac網卡代碼分析----probeprobe

繼續閱讀