1. 程式人生 > >網絡卡驅動:8139Cp

網絡卡驅動:8139Cp

PCI匯流排概念:

PCI匯流排不只是一種排線方式,它還定義了一整套計算機不同部分互動的規則。PCI裝置在啟動時自動被配置,相關驅動必須讀取這些配置資訊來完成初始化。

linux實現了PCI domains。一個domain 支援 256 buses。每個bus最多支援32個裝置,一個裝置可以是一個多功能線路板(如:包含一個音效卡和一個CD-ROM),此多功能板最多支援8個功能。每個功能由一個16位的地址進行標識(對應於pci_dev)。可用lspci命令檢視,輸出為bus:device; bus:device.function; domain:bus:device.function。以後我們說的裝置指的就是一個功能。

和裝置相關的有三個地址空間:memory locations,I/O ports,configuration registers。前兩個被一個PCI bus上的所有裝置共享。訪問前兩個地址空間可以用一般的函式,如inb,readb等,訪問configuration registers提供了專門的函式。在初始化時,不同裝置的I/O space 被對映到了不同的地址空間,這一資訊可以訪問configuration registers得到。

注意PCI registers總是little-endian。下面是一些主要的registers:

  1. vendorID
    16位,裝置廠家的標識
  2. deviceID 
    16位,由裝置廠家提供的裝置標識
  3. class
      每個裝置都屬於一種class。這是一個24位的值,高8位表示一個group,如:ethernet 和 token ring 都屬於 network group
  4. subsystem vendorID
    對裝置進一步標識。

這裡的網絡卡是一個PCI裝置,作為一個驅動,首先要向核心申明自己都支援什麼裝置型別:

[ drivers/net/ethernet/realtek/8139cp.c ]

/* 驅動支援的硬體裝置列表
 */
static DEFINE_PCI_DEVICE_TABLE(cp_pci_tbl) = {
	{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK,	PCI_DEVICE_ID_REALTEK_8139), },
	{ PCI_DEVICE(PCI_VENDOR_ID_TTTECH,	PCI_DEVICE_ID_TTTECH_MC322), },
	{ },
};
MODULE_DEVICE_TABLE(pci, cp_pci_tbl); // 在核心中註冊此列表,當核心在訪問一個裝置時,就可以通過這個列表找到相關的驅動
由於是PCI裝置,網絡卡驅動當然要作為PCI驅動註冊到核心中。

[ drivers/net/ethernet/realtek/8139cp.c ]

/* 這個網絡卡驅動首先是一個PCI驅動,此結構用來把PCI驅動註冊到核心中
 */
static struct pci_driver cp_driver = {
	.name         = DRV_NAME,	// 模組名稱
	.id_table     = cp_pci_tbl,	// 此驅動支援的硬體裝置列表
	.probe        =	cp_init_one,    // 此驅動被載入時的初始化函式
	.remove       = cp_remove_one,	// 此驅動被解除安裝時呼叫的函式
/* 如果支援電源管理
 */
#ifdef CONFIG_PM
	.resume       = cp_resume,	// 啟用時呼叫的函式
	.suspend      = cp_suspend,	// 掛起時呼叫的函式
#endif
};

/* 註冊cp_driver到核心中
 */
static int __init cp_init (void)
{
#ifdef MODULE
	pr_info("%s", version);
#endif
	return pci_register_driver(&cp_driver);
}

/* 從核心中登出cp_driver
 */
static void __exit cp_exit (void)
{
	pci_unregister_driver (&cp_driver);
}

module_init(cp_init); // 核心啟動或是模組載入時呼叫 cp_init
module_exit(cp_exit); // 核心或模組解除安裝時呼叫 cp_exit
驅動模組在使用之前必須初始化:
[ drivers/net/ethernet/realtek/8139cp.c ]
/* 驅動被載入時的初始化函式
 * pdev : PCI硬體裝置,這裡是網絡卡
 * ent  : 驅動支援的硬體列表
 */
static int cp_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)
{
	struct net_device *dev;	// 網路裝置
	struct cp_private *cp;	// 驅動的私有資料
	int rc;
	void __iomem *regs;	// PCI對映到實體記憶體的起始地址對應的核心的虛地址
	resource_size_t pciaddr;	// PCI對映到實體記憶體的起始地址
	unsigned int addr_len, i, pci_using_dac;

#ifndef MODULE
	static int version_printed;
	if (version_printed++ == 0)
		pr_info("%s", version);
#endif

	/* pdev->vendor是16位的裝置廠家的標識
	 * pdev->device是16位的由裝置廠家提供的裝置標識
	 * pdev->revision是版本號
	 * 這些值都是核心從PCI裝置的暫存器中讀取出來的。
	 */
	if (pdev->vendor == PCI_VENDOR_ID_REALTEK &&
	    pdev->device == PCI_DEVICE_ID_REALTEK_8139 && pdev->revision < 0x20) {
		dev_info(&pdev->dev,
			 "This (id %04x:%04x rev %02x) is not an 8139C+ compatible chip, use 8139too\n",
			 pdev->vendor, pdev->device, pdev->revision);
		return -ENODEV;
	}

	/* 分配網路裝置,大小為驅動的私有資料的大小
	 */
	dev = alloc_etherdev(sizeof(struct cp_private));
	if (!dev)
		return -ENOMEM;
	/* 把新建的net_device物件加到樹中
	 * 核心中所有的裝置和驅動都被組織在一棵樹中
	 */
	SET_NETDEV_DEV(dev, &pdev->dev);

	cp = netdev_priv(dev);	// 私有資料
	cp->pdev = pdev;
	cp->dev = dev;
	cp->msg_enable = (debug < 0 ? CP_DEF_MSG_ENABLE : debug);
	spin_lock_init (&cp->lock);
	cp->mii_if.dev = dev;
	cp->mii_if.mdio_read = mdio_read;
	cp->mii_if.mdio_write = mdio_write;
	cp->mii_if.phy_id = CP_INTERNAL_PHY;
	cp->mii_if.phy_id_mask = 0x1f;
	cp->mii_if.reg_num_mask = 0x1f;
	cp_set_rxbufsize(cp);	// 設定緩衝區大小,將cp->rx_buf_sz設為PKT_BUF_SZ(1536位元組)

	rc = pci_enable_device(pdev);	// 啟用裝置
	if (rc)
		goto err_out_free;

	/* 設定memory-write-invalidate
	 * 就是當把一大段資料(大於一個cache line)寫入到PCI裝置時,如果此時cache為髒(cache中的資料還沒有寫入PCI裝置),
	 * 就直接把資料寫入到PCI裝置,同時將cache中的髒標記去掉。
	 */
	rc = pci_set_mwi(pdev);
	if (rc)
		goto err_out_disable;

	/* 通知核心裝置對應的記憶體資源和IO己經被佔用,其它的PCI裝置不要再使用這一區域了
	 */
	rc = pci_request_regions(pdev, DRV_NAME);
	if (rc)
		goto err_out_mwi;

	pciaddr = pci_resource_start(pdev, 1);	// 得到PCI對映到實體記憶體的起始地址
	if (!pciaddr) {
		rc = -EIO;
		dev_err(&pdev->dev, "no MMIO resource\n");
		goto err_out_res;
	}
	if (pci_resource_len(pdev, 1) < CP_REGS_SIZE) {	// 得到PCI對映到實體記憶體的長度,長度不小於(0xff + 1)
		rc = -EIO;
		dev_err(&pdev->dev, "MMIO resource (%llx) too small\n",
		       (unsigned long long)pci_resource_len(pdev, 1));
		goto err_out_res;
	}

	/* Configure DMA attributes. */
	if ((sizeof(dma_addr_t) > 4) &&	// DMA地址是64位的
	    !pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)) &&
	    !pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) {
		pci_using_dac = 1;
	} else {
		pci_using_dac = 0;

		rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
		if (rc) {
			dev_err(&pdev->dev,
				"No usable DMA configuration, aborting\n");
			goto err_out_res;
		}
		rc = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
		if (rc) {
			dev_err(&pdev->dev,
				"No usable consistent DMA configuration, aborting\n");
			goto err_out_res;
		}
	}

	// 設定C+ mode 命令
	cp->cpcmd = (pci_using_dac ? PCIDAC : 0) |	// DMA地址是否為64位
		    PCIMulRW | RxChkSum | CpRxOn | CpTxOn;	// Enable PCI read/write multiple 
								// Rx checksum offload enable 
								// Rx mode enable 
								// Tx mode enable

	dev->features |= NETIF_F_RXCSUM; 	/* Receive checksumming offload */
	dev->hw_features |= NETIF_F_RXCSUM;	/* Receive checksumming offload */

	regs = ioremap(pciaddr, CP_REGS_SIZE);	// 實體地址對映到核心的虛地址空間
	if (!regs) {
		rc = -EIO;
		dev_err(&pdev->dev, "Cannot map PCI MMIO (%[email protected]%Lx)\n",
			(unsigned long long)pci_resource_len(pdev, 1),
		       (unsigned long long)pciaddr);
		goto err_out_res;
	}
	cp->regs = regs;

	cp_stop_hw(cp);	// 初始化硬體暫存器

	/* read MAC address from EEPROM 
	 * 從EEPROM中讀取MAC地址
	 */
	addr_len = read_eeprom (regs, 0, 8) == 0x8129 ? 8 : 6;
	/* 讀取3次,每次2位元組,把6位元組的MAC地址讀到了 dev->dev_addr中
	 */
	for (i = 0; i < 3; i++)
		((__le16 *) (dev->dev_addr))[i] =
		    cpu_to_le16(read_eeprom (regs, i + 7, addr_len));

	dev->netdev_ops = &cp_netdev_ops;	// 網路裝置的各項操作介面
	/* 當支援NAPI時,執行poll操作的函式為cp_rx_poll
	 * 將cp->napi掛載到了dev上
	 */
	netif_napi_add(dev, &cp->napi, cp_rx_poll, 16);	
	dev->ethtool_ops = &cp_ethtool_ops;
	dev->watchdog_timeo = TX_TIMEOUT;	// 傳送超時:(6*HZ)

	/* Transmit VLAN CTAG HW acceleration 
	 * Receive VLAN CTAG HW acceleration 
	 */
	dev->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX;

	if (pci_using_dac)	// 64位
		dev->features |= NETIF_F_HIGHDMA; /* Can DMA to high memory. */

	/* disabled by default until verified */
	/* Scatter/gather IO.  
	 * Can checksum TCP/UDP over IPv4.
	 * ... TCPv4 segmentation 
	 * Transmit VLAN CTAG HW acceleration 
	 * Receive VLAN CTAG HW acceleration 
	 */
	dev->hw_features |= NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |
		NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX;
	/* Scatter/gather IO.  
	 * Can checksum TCP/UDP over IPv4.
	 * ... TCPv4 segmentation 
	 * Can DMA to high memory. 
	 */
	dev->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |
		NETIF_F_HIGHDMA;

	rc = register_netdev(dev);	// 核心註冊net_device物件
	if (rc)
		goto err_out_iomap;

	netdev_info(dev, "RTL-8139C+ at 0x%p, %pM, IRQ %d\n",
		    regs, dev->dev_addr, pdev->irq);

	pci_set_drvdata(pdev, dev);	// PCI裝置與dev關聯

	/* enable busmastering and memory-write-invalidate 
	 * 開啟DMA
	 */
	pci_set_master(pdev);

	/* 如果網絡卡支援wake-on-LAN,進行一些設定
	 * wadk-on-LAN是指一種電源管理功能。主機睡眠時,當網路有一些活動,就會把主機喚醒。
	 */
	if (cp->wol_enabled)
		cp_set_d3_state (cp); /* Put the board into D3cold state and wait for WakeUp signal */

	return 0;

err_out_iomap:
	iounmap(regs);
err_out_res:
	pci_release_regions(pdev);
err_out_mwi:
	pci_clear_mwi(pdev);
err_out_disable:
	pci_disable_device(pdev);
err_out_free:
	free_netdev(dev);
	return rc;
}
下面是一些在初始化函式中要呼叫的函式:

分配網路裝置net_device

[ include/linux/etherdevice.h ]

#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
#define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv, count, count)
[ net/ethernet/eth.c ]
/**
 * alloc_etherdev_mqs - Allocates and sets up an Ethernet device
 * @sizeof_priv: Size of additional driver-private structure to be allocated
 *	for this Ethernet device
 * @txqs: The number of TX queues this device has.
 * @rxqs: The number of RX queues this device has.
 *
 * Fill in the fields of the device structure with Ethernet-generic
 * values. Basically does everything except registering the device.
 *
 * Constructs a new net device, complete with a private data area of
 * size (sizeof_priv).  A 32-byte (not bit) alignment is enforced for
 * this private data area.
 */

struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs,
				      unsigned int rxqs)
{
	return alloc_netdev_mqs(sizeof_priv, "eth%d", ether_setup, txqs, rxqs);
}
EXPORT_SYMBOL(alloc_etherdev_mqs);
[ net/core/dev.c ]
/**
 *	alloc_netdev_mqs - allocate network device
 *	@sizeof_priv:	size of private data to allocate space for
 *	@name:		device name format string
 *	@setup:		callback to initialize device
 *	@txqs:		the number of TX subqueues to allocate
 *	@rxqs:		the number of RX subqueues to allocate
 *
 *	Allocates a struct net_device with private data area for driver use
 *	and performs basic initialization.  Also allocates subqueue structs
 *	for each queue on the device.
 */
struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
		void (*setup)(struct net_device *),
		unsigned int txqs, unsigned int rxqs)
{
	struct net_device *dev;	// 網路裝置
	size_t alloc_size;	// 實際分配的大小
	struct net_device *p;	// 指向分配的記憶體

	BUG_ON(strlen(name) >= sizeof(dev->name));

	/* 傳輸佇列大小不能小於1
	 */
	if (txqs < 1) {
		pr_err("alloc_netdev: Unable to allocate device with zero queues\n");
		return NULL;
	}

/* 是否啟用sysfs虛擬檔案系統。sysfs以更整齊更直觀的方式向用戶展示了核心的各種引數
 * /proc將會向sysfs遷移
 */
#ifdef CONFIG_SYSFS
	/* 接收佇列大小不能小於1
	 */
	if (rxqs < 1) {
		pr_err("alloc_netdev: Unable to allocate device with zero RX queues\n");
		return NULL;
	}
#endif

	/* 記憶體大小為net_device結構大小加上傳遞進來的值的大小
	 * 要32位元組對齊,對齊的公式為  ( xx + 31  ) & ( ~31 )
	 */
	alloc_size = sizeof(struct net_device);
	if (sizeof_priv) {
		/* ensure 32-byte alignment of private area */
		alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
		alloc_size += sizeof_priv;
	}
	/* ensure 32-byte alignment of whole construct */
	alloc_size += NETDEV_ALIGN - 1;

	// 分配記憶體
	p = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT);
	if (!p)
		p = vzalloc(alloc_size);
	if (!p)
		return NULL;

	dev = PTR_ALIGN(p, NETDEV_ALIGN);	// 網路裝置的起始地址要32位元組對齊
	dev->padded = (char *)dev - (char *)p;	// 儲存網路裝置的起始地址與所分配記憶體的起始地址之間的間距

	dev->pcpu_refcnt = alloc_percpu(int);	// 引用計數,是PRE_CPU變數
	if (!dev->pcpu_refcnt)
		goto free_dev;

	if (dev_addr_init(dev))	// 插入一個空的地址到dev->dev_addrs中,並將此空地址賦給dev->dev_addr 
		goto free_pcpu;

	dev_mc_init(dev);	// 初始化多播地址列表為空
	dev_uc_init(dev);	// 初始化單播地址列表為空

	dev_net_set(dev, &init_net);	// 當支援使用者處定義網路空間時使用,略過

	dev->gso_max_size = GSO_MAX_SIZE;	// 65536
	dev->gso_max_segs = GSO_MAX_SEGS;	// 65535

	INIT_LIST_HEAD(&dev->napi_list);
	INIT_LIST_HEAD(&dev->unreg_list);
	INIT_LIST_HEAD(&dev->close_list);
	INIT_LIST_HEAD(&dev->link_watch_list);
	INIT_LIST_HEAD(&dev->adj_list.upper);
	INIT_LIST_HEAD(&dev->adj_list.lower);
	INIT_LIST_HEAD(&dev->all_adj_list.upper);
	INIT_LIST_HEAD(&dev->all_adj_list.lower);
	dev->priv_flags = IFF_XMIT_DST_RELEASE;	// 設定標記位:在進行dev_hard_start_xmit時可以釋放skb->dst
	/* setup為引數,乙太網呼叫ether_setup
	 * dev->priv_flags設定支援在傳輸過程中支援共享skb
	 */
	setup(dev);	

	dev->num_tx_queues = txqs;	// txqs = 1
	dev->real_num_tx_queues = txqs;	// txqs = 1
	if (netif_alloc_netdev_queues(dev))	// 分配1個netdev_queue物件,並把它賦值給dev->_tx
		goto free_all;

/* 是否啟用sysfs虛擬檔案系統。sysfs以更整齊更直觀的方式向用戶展示了核心的各種引數
 * /proc將會向sysfs遷移
 */
#ifdef CONFIG_SYSFS
	dev->num_rx_queues = rxqs;	// txqs = 1
	dev->real_num_rx_queues = rxqs;	// txqs = 1
	if (netif_alloc_rx_queues(dev))	// 分配1個netdev_rx_queue 物件,並把它賦值給dev->_rx
		goto free_all;
#endif

	strcpy(dev->name, name);	// 設定dev->name為傳進來的引數name
	dev->group = INIT_NETDEV_GROUP;	// 設定dev->group為0
	if (!dev->ethtool_ops)
		dev->ethtool_ops = &default_ethtool_ops;
	return dev;

free_all:
	free_netdev(dev);
	return NULL;

free_pcpu:
	free_percpu(dev->pcpu_refcnt);
	netif_free_tx_queues(dev);
#ifdef CONFIG_SYSFS
	kfree(dev->_rx);
#endif

free_dev:
	netdev_freemem(dev);
	return NULL;
}
EXPORT_SYMBOL(alloc_netdev_mqs);

[ drivers/net/ethernet/realtek/8139cp.c ]
// 設定緩衝的大小
static inline void cp_set_rxbufsize (struct cp_private *cp)
{
	unsigned int mtu = cp->dev->mtu;	// 前面設為了1500位元組(ETH_DATA_LEN)

	if (mtu > ETH_DATA_LEN)
		/* MTU + ethernet header + FCS + optional VLAN tag */
		cp->rx_buf_sz = mtu + ETH_HLEN + 8;
	else
		cp->rx_buf_sz = PKT_BUF_SZ;	// 設為PKT_BUF_SZ(1536位元組)
}

[ drivers/net/ethernet/realtek/8139cp.c ]
// 初始化硬體暫存器
static void cp_stop_hw (struct cp_private *cp)
{
	cpw16(IntrStatus, ~(cpr16(IntrStatus)));	// 讀取中斷狀態,取反,寫入
	cpw16_f(IntrMask, 0);	// 關中斷
	cpw8(Cmd, 0);	// 命令暫存器設為0
	cpw16_f(CpCmd, 0);	// C+命令暫存器設為0
	cpw16_f(IntrStatus, ~(cpr16(IntrStatus)));	// 恢復原來的中斷狀態

	cp->rx_tail = 0;
	cp->tx_head = cp->tx_tail = 0;

	netdev_reset_queue(cp->dev);
}

[ drivers/net/ethernet/realtek/8139cp.c ]
/* Put the board into D3cold state and wait for WakeUp signal */
static void cp_set_d3_state (struct cp_private *cp)
{
	pci_enable_wake(cp->pdev, PCI_D0, 1); /* Enable PME# generation */
	pci_set_power_state (cp->pdev, PCI_D3hot);	// 設定電源狀態
}

初始化完成後,當開始使用時,要呼叫cp_open( 如當執行命令 ifconfig 設定IP地址時 ),此函式也會完成一些初始化的工作:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 當執行命令 ifconfig 設定IP地址時,會呼叫此函式
static int cp_open (struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);
	const int irq = cp->pdev->irq;	// PCI裝置中斷號
	int rc;

	netif_dbg(cp, ifup, dev, "enabling interface\n");

	rc = cp_alloc_rings(cp);	// 分配發送和接收資料的緩衝
	if (rc)
		return rc;

	napi_enable(&cp->napi);	// 啟用NAPI(清除掉NAPI_STATE_SCHED標記)

	cp_init_hw(cp);	// 設定裝置暫存器,以便接收進來的資料直接放入緩衝中

	rc = request_irq(irq, cp_interrupt, IRQF_SHARED, dev->name, dev);	// 註冊中斷,中斷到來時執行cp_interrupt函式
	if (rc)
		goto err_out_hw;

	cp_enable_irq(cp);	// 啟用中斷

	/* 通知核心網路斷開
	 * 這裡不一定是真的斷開。這裡只是通知核心鏈路層的狀態改變
	 * 一般當網路配置改變後,可通過這種方式通知核心
	 */
	netif_carrier_off(dev);	
	mii_check_media(&cp->mii_if, netif_msg_link(cp), true);	// 檢測MII介面
	netif_start_queue(dev);	// 告訴核心可以傳送資料了

	return 0;

err_out_hw:
	napi_disable(&cp->napi);
	cp_stop_hw(cp);
	cp_free_rings(cp);
	return rc;
}
其中呼叫的函式如下:
[ drivers/net/ethernet/realtek/8139cp.c ]
// 分配緩衝區
static int cp_alloc_rings (struct cp_private *cp)
{
	struct device *d = &cp->pdev->dev;
	void *mem;
	int rc;

	/* 分配記憶體,cp->ring_dma是實體地址,mem是對應的虛地址
	 * 大小為 sizeof(struct cp_desc) * 64 + sizeof(struct cp_desc) * 64 + 64 
	 * 其中64個cp_desc用作接收緩衝描述符,64個cp_desc用作傳送緩衝描述符,還有64位元組DMA的狀態
	 */
	mem = dma_alloc_coherent(d, CP_RING_BYTES, &cp->ring_dma, GFP_KERNEL);
	if (!mem)
		return -ENOMEM;

	/* 設定接收緩衝描述符和傳送緩衝描述符的地址,它們是在同一塊記憶體塊中
	 */
	cp->rx_ring = mem;
	cp->tx_ring = &cp->rx_ring[CP_RX_RING_SIZE];

	rc = cp_init_rings(cp);	// 對分配的記憶體進行初始化
	if (rc < 0)
		dma_free_coherent(d, CP_RING_BYTES, cp->rx_ring, cp->ring_dma);

	return rc;
}

static int cp_init_rings (struct cp_private *cp)
{
    /* 接收緩衝描述符表清0
     * 接收緩衝描述符表中最後一個地址設定終止標記 (1 << 30),此標記位元組序為little-endian
     * 這裡沒有對傳送緩衝描述符表進行初始化,因為當傳送資料時,自然會進行填充
     */
    memset(cp->tx_ring, 0, sizeof(struct cp_desc) * CP_TX_RING_SIZE);
    cp->tx_ring[CP_TX_RING_SIZE - 1].opts1 = cpu_to_le32(RingEnd);    

    cp_init_rings_index(cp);

    return cp_refill_rx (cp);    // 對接收緩衝描述符表中的每個物件初始化
}

static void cp_init_rings_index (struct cp_private *cp)
{
    cp->rx_tail = 0;
    cp->tx_head = cp->tx_tail = 0;
}

// 對接收緩衝描述符表中的每個物件初始化
static int cp_refill_rx(struct cp_private *cp)
{
    struct net_device *dev = cp->dev;
    unsigned i;

    for (i = 0; i < CP_RX_RING_SIZE; i++) {    // 迴圈對接收緩衝描述符表
        struct sk_buff *skb;
        dma_addr_t mapping;

        // 分配一個sk_buff,其緩衝大小為1536位元組,大於乙太網的MTU
        skb = netdev_alloc_skb_ip_align(dev, cp->rx_buf_sz);    
          if (!skb)
            goto err_out;

        /* 得到skb->data(skb對應的緩衝)的實體地址
         * 從PCI裝置讀取資料時,要用到實體地址
         */
        mapping = dma_map_single(&cp->pdev->dev, skb->data,
                     cp->rx_buf_sz, PCI_DMA_FROMDEVICE);
        if (dma_mapping_error(&cp->pdev->dev, mapping)) {
            kfree_skb(skb);
            goto err_out;
        }
        cp->rx_skb[i] = skb;    // 接收緩衝

        /* 設定cp_desc 
         * cp->rx_ring[i]與 cp->rx_skb[i] 一一對應
         */
        cp->rx_ring[i].opts2 = 0;
        cp->rx_ring[i].addr = cpu_to_le64(mapping);    // 緩衝實體地址
        /* 設定cp_desc.opts1
         * 緩衝的大小 | 此描述符被NIC使用 
         * 最後一個描述符加上一個終止標記
         */
        if (i == (CP_RX_RING_SIZE - 1))    // 接收緩衝地址列表的最後一項
            cp->rx_ring[i].opts1 =
                cpu_to_le32(DescOwn | RingEnd | cp->rx_buf_sz);    // (1 << 31) | (1 << 30) | 1536
        else
            cp->rx_ring[i].opts1 =
                cpu_to_le32(DescOwn | cp->rx_buf_sz);    // (1 << 31) | (1 << 30) | 1536
    }

    return 0;

err_out:
    cp_clean_rings(cp);
    return -ENOMEM;
}     

[ drivers/net/ethernet/realtek/8139cp.c ]
// 設定裝置暫存器,以便接收進來的資料直接放入緩衝中
static void cp_init_hw (struct cp_private *cp)
{
	struct net_device *dev = cp->dev;

	cp_reset_hw(cp);	// 對裝置進行reset

	/* Cfg9436 EEPROM control register 
	 * Unlock ConfigX/MII register access 
	 */
	cpw8_f (Cfg9346, Cfg9346_Unlock);

	/* Restore our idea of the MAC address. 
	 * 前面從EEPROM中讀入了MAC地址,現在將它們寫到暫存器中
	 */
	cpw32_f (MAC0 + 0, le32_to_cpu (*(__le32 *) (dev->dev_addr + 0)));
	cpw32_f (MAC0 + 4, le32_to_cpu (*(__le32 *) (dev->dev_addr + 4)));

	/* 當硬體接收到資料,會把資料寫到接收緩衝
	 * 傳送時,從傳送緩衝中取資料
	 */
	cp_start_hw(cp);	
	/* 設定傳送資料的閥值
	 * 在硬體晶片上有一個2K位元組的緩衝區,資料先放到此緩衝區,當達到一個閥值時,開始傳送
	 */
	cpw8(TxThresh, 0x06); /* XXX convert magic num to a constant */

	__cp_set_rx_mode(dev);	// 設定多播地址屬性
	cpw32_f (TxConfig, IFG | (TX_DMA_BURST << TxDMAShift));  /* Tx configuration */

	/* Software marker, driver is loaded 
	 * Enable various PM features of chip 
	 */
	cpw8(Config1, cpr8(Config1) | DriverLoaded | PMEnable);
	/* Disable Wake-on-LAN. Can be turned on with ETHTOOL_SWOL */
	cpw8(Config3, PARMEnable);	// Enable auto-loading of PHY parms 
	cp->wol_enabled = 0;

	cpw8(Config5, cpr8(Config5) & PMEStatus);   /* PME status can be reset by PCI RST# */

	cpw16(MultiIntr, 0);  /* Multiple interrupt select */

	/* Cfg9436 EEPROM control register 
	 * Lock ConfigX/MII register access 
	 */
	cpw8_f(Cfg9346, Cfg9346_Lock);
}

// reset硬體
static void cp_reset_hw (struct cp_private *cp)
{
    unsigned work = 1000;

    /* 對設定的命令暫存器寫入reset命令
     * 當完成reset後,此標記會自動清除
     */
    cpw8(Cmd, CmdReset);

    /* 迴圈1000次,不停讀取這一標記
     * 每次迴圈都睡眠10ms,在睡眠期間是不能被中斷的。
     */
    while (work--) {
        if (!(cpr8(Cmd) & CmdReset))
            return;

        schedule_timeout_uninterruptible(10);
    }

    netdev_err(cp->dev, "hardware reset timeout\n");
}

/* 當硬體接收到資料,會把資料寫到接收緩衝
 * 傳送時,從傳送緩衝中取資料
 */
static inline void cp_start_hw (struct cp_private *cp)
{
    dma_addr_t ring_dma;

    /* 設定C+ mode 命令
     * 前面被設定為: 
     * 如果DMA地址為64位設定 PCI Dual Address Cycle (64-bit PCI)
     * Enable PCI read/write multiple 
     * Rx checksum offload enable 
     * Rx mode enable 
     * Tx mode enable
     */
    cpw16(CpCmd, cp->cpcmd);

    /*
     * These (at least TxRingAddr) need to be configured after the
     * corresponding bits in CpCmd are enabled. Datasheet v1.6 §6.33
     * (C+ Command Register) recommends that these and more be configured
     * *after* the [RT]xEnable bits in CpCmd are set. And on some hardware
     * it's been observed that the TxRingAddr is actually reset to garbage
     * when C+ mode Tx is enabled in CpCmd.
     * 將64位傳送資料緩衝的地址設為0
     */
    cpw32_f(HiTxRingAddr, 0);
    cpw32_f(HiTxRingAddr + 4, 0);

    ring_dma = cp->ring_dma;    // 接收緩衝描述符表的實體地址
    /* 接收緩衝描述符表的物理起始寫入暫存器
     * 當有資料到達時,資料會自動存入相應的記憶體地址
     */
    cpw32_f(RxRingAddr, ring_dma & 0xffffffff);
    cpw32_f(RxRingAddr + 4, (ring_dma >> 16) >> 16);

    ring_dma += sizeof(struct cp_desc) * CP_RX_RING_SIZE;    // 傳送緩衝描述符表的實體地址
    /* 傳送緩衝描述符表的物理起始寫入暫存器
     */
    cpw32_f(TxRingAddr, ring_dma & 0xffffffff);
    cpw32_f(TxRingAddr + 4, (ring_dma >> 16) >> 16);

    /*
     * Strictly speaking, the datasheet says this should be enabled
     * *before* setting the descriptor addresses. But what, then, would
     * prevent it from doing DMA to random unconfigured addresses?
     * This variant appears to work fine.
     * 告訴裝置可以接收和傳送資料了
     */
    cpw8(Cmd, RxOn | TxOn);

    netdev_reset_queue(cp->dev);
}

/* Set or clear the multicast filter for this adaptor.
   This routine is not state sensitive and need not be SMP locked. 
 設定多播地址屬性
*/
static void __cp_set_rx_mode (struct net_device *dev)
{
    struct cp_private *cp = netdev_priv(dev);
    u32 mc_filter[2];    /* Multicast hash filter */
    int rx_mode;

    /* Note: do not reorder, GCC is clever about common statements. */
    if (dev->flags & IFF_PROMISC) {    // 網路裝置設定為接收所有的包
        /* Unconditionally log net taps. 
         * Accept broadcast packets 
         * Accept multicast packets 
         * Accept pkts with our MAC as dest 
         * Accept all pkts w/ physical dest 
         */
        rx_mode =
            AcceptBroadcast | AcceptMulticast | AcceptMyPhys |
            AcceptAllPhys;
        mc_filter[1] = mc_filter[0] = 0xffffffff;
    } else if ((netdev_mc_count(dev) > multicast_filter_limit) ||    // 網路裝置的多播地址數量(dev->mc)大於32
           (dev->flags & IFF_ALLMULTI)) {            // 或者網路裝置設定為接收所有多播地址的包
        /* Too many to filter perfectly -- accept all multicasts. 
         * Accept broadcast packets 
         * Accept multicast packets 
         * Accept pkts with our MAC as dest 
         */
        rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
        mc_filter[1] = mc_filter[0] = 0xffffffff;
    } else {
        struct netdev_hw_addr *ha;
        /* Accept broadcast packets 
         * Accept pkts with our MAC as dest 
         */
        rx_mode = AcceptBroadcast | AcceptMyPhys;
        mc_filter[1] = mc_filter[0] = 0;
        netdev_for_each_mc_addr(ha, dev) {    // 對每一個多播地址
            int bit_nr = ether_crc(ETH_ALEN, ha->addr) >> 26;

            mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
            rx_mode |= AcceptMulticast;    // Accept multicast packets 
        }
    }

    /* We can safely update without stopping the chip. */
    cp->rx_config = cp_rx_config | rx_mode;
    cpw32_f(RxConfig, cp->rx_config);  /* Rx configuration */

    /* Multicast filter. */
    cpw32_f (MAR0 + 0, mc_filter[0]);
    cpw32_f (MAR0 + 4, mc_filter[1]);
}

// 開啟中斷
static void cp_enable_irq(struct cp_private *cp)
{
    cpw16_f(IntrMask, cp_intr_mask);
}

// 釋放緩衝空間
static void cp_free_rings (struct cp_private *cp)
{
    cp_clean_rings(cp);
    dma_free_coherent(&cp->pdev->dev, CP_RING_BYTES, cp->rx_ring,    // 釋放緩衝描述符空間
              cp->ring_dma);
    cp->rx_ring = NULL;
    cp->tx_ring = NULL;
}
關閉網路時,呼叫cp_close

[ drivers/net/ethernet/realtek/8139cp.c ]

// 關閉網路
static int cp_close (struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);	// 私有資料
	unsigned long flags;

	napi_disable(&cp->napi);	// 關閉NAPI

	netif_dbg(cp, ifdown, dev, "disabling interface\n");

	spin_lock_irqsave(&cp->lock, flags);

	netif_stop_queue(dev);	// 告訴核心不要傳送資料了
	/* 通知核心網路斷開
	 * 這裡不一定是真的斷開。這裡只是通知核心鏈路層的狀態改變
	 * 一般當網路配置改變後,可通過這種方式通知核心
	 */
	netif_carrier_off(dev);

	cp_stop_hw(cp);	// 對硬體暫存器進行讀寫

	spin_unlock_irqrestore(&cp->lock, flags);

	free_irq(cp->pdev->irq, dev);	// 釋放中斷

	cp_free_rings(cp);	// 清除緩衝
	return 0;
}
傳送資料時呼叫的函式:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 傳送資料
static netdev_tx_t cp_start_xmit (struct sk_buff *skb,
					struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);	// 私有資料
	unsigned entry;
	u32 eor, flags;
	unsigned long intr_flags;
	__le32 opts2;
	int mss = 0;

	spin_lock_irqsave(&cp->lock, intr_flags);

	/* This is a hard error, log it. 
	 * 傳送緩衝不夠
	 */
	if (TX_BUFFS_AVAIL(cp) <= (skb_shinfo(skb)->nr_frags + 1)) {
		netif_stop_queue(dev);
		spin_unlock_irqrestore(&cp->lock, intr_flags);
		netdev_err(dev, "BUG! Tx Ring full when queue awake!\n");
		return NETDEV_TX_BUSY;
	}

	entry = cp->tx_head;	// 傳送緩衝開始位置
	eor = (entry == (CP_TX_RING_SIZE - 1)) ? RingEnd : 0;	// 是否為傳送緩衝最後的位置
	mss = skb_shinfo(skb)->gso_size;	// GSO分片長度

	opts2 = cpu_to_le32(cp_tx_vlan_tag(skb));  /* VLAN tag */

	if (skb_shinfo(skb)->nr_frags == 0) {	// 沒有分片的情況
		struct cp_desc *txd = &cp->tx_ring[entry];	// 傳送描述符
		u32 len;
		dma_addr_t mapping;

		len = skb->len;	// 資料長度
		mapping = dma_map_single(&cp->pdev->dev, skb->data, len, PCI_DMA_TODEVICE);	// 得到資料的實體地址
		if (dma_mapping_error(&cp->pdev->dev, mapping))
			goto out_dma_error;

		txd->opts2 = opts2;	// 設定描述符標記(VLAN tag)
		txd->addr = cpu_to_le64(mapping);	// 設定描述符實體地址
		wmb();	// 保證語句之前的寫操作一定在語句之後的寫操作之前,這是為了防止CPU為提升效率打亂寫操作的順序

		/* 傳送緩衝最後的位置
		 * 長度 
		 * Descriptor is owned by NIC 
		 * First segment of a packet 
		 * Final segment of a packet 
		 */
		flags = eor | len | DescOwn | FirstFrag | LastFrag;

		if (mss)	// 支援GSO
			flags |= LargeSend | ((mss & MSSMask) << MSSShift);  /* TCP Large Send Offload (TSO) | GSO分片大小 */
		else if (skb->ip_summed == CHECKSUM_PARTIAL) {	// IP區域性校驗
			const struct iphdr *ip = ip_hdr(skb);	// IP頭部
			if (ip->protocol == IPPROTO_TCP)	// 上層協議是TCP
				flags |= IPCS | TCPCS;	// Calculate IP checksum | Calculate TCP/IP checksum 
			else if (ip->protocol == IPPROTO_UDP)	// 上層協議是UDP
				flags |= IPCS | UDPCS;	// Calculate IP checksum | Calculate UDP/IP checksum 
			else
				WARN_ON(1);	/* we need a WARN() */
		}

		txd->opts1 = cpu_to_le32(flags);	// 設定描述符標記(VLAN tag),注意位元組序
		wmb();	// 保證語句之前的寫操作一定在語句之後的寫操作之前,這是為了防止CPU為提升效率打亂寫操作的順序

		cp->tx_skb[entry] = skb;	// 設定傳送描述符對應的緩衝
		entry = NEXT_TX(entry);	// 下一個描述符位置
	} else {	// 有分片
		struct cp_desc *txd;
		u32 first_len, first_eor;
		dma_addr_t first_mapping;
		int frag, first_entry = entry;	// 第一個描述符位置
		const struct iphdr *ip = ip_hdr(skb);	// IP頭部

		/* We must give this initial chunk to the device last.
		 * Otherwise we could race with the device.
		 */
		first_eor = eor;	// 第一個描述符是否為傳送緩衝最後的位置
		first_len = skb_headlen(skb);	// 主緩衝的大小(資料總大小-分片資料的大小)
		first_mapping = dma_map_single(&cp->pdev->dev, skb->data,	// 主緩衝資料的實體地址
					       first_len, PCI_DMA_TODEVICE);
		if (dma_mapping_error(&cp->pdev->dev, first_mapping))
			goto out_dma_error;

		cp->tx_skb[entry] = skb;	// 設定傳送描述符對應的緩衝
		entry = NEXT_TX(entry);		// 下一個描述符位置

		for (frag = 0; frag < skb_shinfo(skb)->nr_frags; frag++) {	// 對所有分片迴圈,把每一個分片都放入傳送緩衝
			const skb_frag_t *this_frag = &skb_shinfo(skb)->frags[frag];	// 分片描述符
			u32 len;
			u32 ctrl;
			dma_addr_t mapping;

			len = skb_frag_size(this_frag);	// 分片大小
			mapping = dma_map_single(&cp->pdev->dev,	// 分片資料的實體地址
						 skb_frag_address(this_frag),
						 len, PCI_DMA_TODEVICE);
			if (dma_mapping_error(&cp->pdev->dev, mapping)) {
				unwind_tx_frag_mapping(cp, skb, first_entry, entry);
				goto out_dma_error;
			}

			eor = (entry == (CP_TX_RING_SIZE - 1)) ? RingEnd : 0; 	// 是否為傳送緩衝最後的位置

			/* 傳送緩衝最後的位置
			 * 長度 
			 * Descriptor is owned by NIC 
			 */
			ctrl = eor | len | DescOwn;

			if (mss)	// 支援GSO
				ctrl |= LargeSend | ((mss & MSSMask) << MSSShift);	/* TCP Large Send Offload (TSO) | GSO分片大小 */
			else if (skb->ip_summed == CHECKSUM_PARTIAL) {	// IP區域性校驗
				if (ip->protocol == IPPROTO_TCP)	// 上層協議是TCP
					ctrl |= IPCS | TCPCS;	// Calculate IP checksum | Calculate TCP/IP checksum 
				else if (ip->protocol == IPPROTO_UDP)	// 上層協議是UDP
					ctrl |= IPCS | UDPCS;	// Calculate IP checksum | Calculate UDP/IP checksum 
				else
					BUG();
			}

			if (frag == skb_shinfo(skb)->nr_frags - 1)	// 最後一個分片
				ctrl |= LastFrag; 	/* Final segment of a packet */

			txd = &cp->tx_ring[entry];	// 傳送緩衝描述符
			txd->opts2 = opts2;	// 設定傳送緩衝描述符標記(VLAN tag)
			txd->addr = cpu_to_le64(mapping); // 設定傳送緩衝描述符實體地址
			wmb();	// 保證語句之前的寫操作一定在語句之後的寫操作之前,這是為了防止CPU為提升效率打亂寫操作的順序

			txd->opts1 = cpu_to_le32(ctrl);	// 設定傳送緩衝描述符標記(VLAN tag)
			wmb();	// 保證語句之前的寫操作一定在語句之後的寫操作之前,這是為了防止CPU為提升效率打亂寫操作的順序

			cp->tx_skb[entry] = skb;	// 設定傳送描述符對應的緩衝
			entry = NEXT_TX(entry);	// 下一個描述符位置
		}// for

		txd = &cp->tx_ring[first_entry];	// 主緩衝對應的傳送緩衝描述符
		txd->opts2 = opts2;	// 設定傳送緩衝描述符標記(VLAN tag)
		txd->addr = cpu_to_le64(first_mapping);	// 設定傳送緩衝描述符實體地址
		wmb();	// 保證語句之前的寫操作一定在語句之後的寫操作之前,這是為了防止CPU為提升效率打亂寫操作的順序

		if (skb->ip_summed == CHECKSUM_PARTIAL) {	// IP區域性校驗
			if (ip->protocol == IPPROTO_TCP)	// 上層協議是TCP
				/* 傳送緩衝最後的位置
				 * 長度 
				 * First segment of a packet 
				 * Descriptor is owned by NIC 
				 * Calculate IP checksum 
				 * Calculate TCP/IP checksum 
				 */
				txd->opts1 = cpu_to_le32(first_eor | first_len |
						FirstFrag | DescOwn |
						IPCS | TCPCS);
			else if (ip->protocol == IPPROTO_UDP)	// 上層協議是UDP
				/* 傳送緩衝最後的位置
				 * 長度 
				 * First segment of a packet 
				 * Descriptor is owned by NIC 
				 * Calculate IP checksum 
				 * Calculate UDP/IP checksum 
				 */
				txd->opts1 = cpu_to_le32(first_eor | first_len |
							 FirstFrag | DescOwn |
							 IPCS | UDPCS);
			else
				BUG();
		} else
			/* 傳送緩衝最後的位置
			 * 長度 
			 * First segment of a packet 
			 * Descriptor is owned by NIC 
			 */
			txd->opts1 = cpu_to_le32(first_eor | first_len |
					FirstFrag | DescOwn);
		wmb();	// 保證語句之前的寫操作一定在語句之後的寫操作之前,這是為了防止CPU為提升效率打亂寫操作的順序
	} // if
	cp->tx_head = entry;	// 當前的緩衝區頭部位置

	netdev_sent_queue(dev, skb->len);	// 通知核心傳送了多少資料
	netif_dbg(cp, tx_queued, cp->dev, "tx queued, slot %d, skblen %d\n",
		  entry, skb->len);
	if (TX_BUFFS_AVAIL(cp) <= (MAX_SKB_FRAGS + 1))	// 有效傳送緩衝少於17個,停止傳送
		netif_stop_queue(dev);	// 傳送佇列設定__QUEUE_STATE_DRV_XOFF,告訴上層協議停止傳送

out_unlock:
	spin_unlock_irqrestore(&cp->lock, intr_flags);

	/* Tell chip to check Tx descriptors for work 
	 * One or more normal Tx packets to send 
	 */
	cpw8(TxPoll, NormalTxPoll);  

	return NETDEV_TX_OK;
out_dma_error:
	kfree_skb(skb);
	cp->dev->stats.tx_dropped++;
	goto out_unlock;
}

傳送超時呼叫的函式:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 傳送超時
static void cp_tx_timeout(struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);	// 私有資料
	unsigned long flags;
	int rc;

	netdev_warn(dev, "Transmit timeout, status %2x %4x %4x %4x\n",
		    cpr8(Cmd), cpr16(CpCmd),
		    cpr16(IntrStatus), cpr16(IntrMask));

	spin_lock_irqsave(&cp->lock, flags);

	cp_stop_hw(cp);	// 對硬體暫存器進行讀寫
	cp_clean_rings(cp);	// 清除緩衝
	rc = cp_init_rings(cp);	// 緩衝進行初始化
	/* 當硬體接收到資料,會把資料寫到接收緩衝
	 * 傳送時,從傳送緩衝中取資料
	 */
	cp_start_hw(cp);
	cp_enable_irq(cp);	// 開中斷

	netif_wake_queue(dev);	// 通知核心可能傳送資料了

	spin_unlock_irqrestore(&cp->lock, flags);
}

 /* VLAN tag */
static inline u32 cp_tx_vlan_tag(struct sk_buff *skb)
{
    return vlan_tx_tag_present(skb) ?
        TxVlanTag | swab16(vlan_tx_tag_get(skb)) : 0x00;
}

// 將分片的實體地址取消對映
static void unwind_tx_frag_mapping(struct cp_private *cp, struct sk_buff *skb,
                   int first, int entry_last)
{
    int frag, index;
    struct cp_desc *txd;
    skb_frag_t *this_frag;
    for (frag = 0; frag+first < entry_last; frag++) {    // 對所有分片迴圈
        index = first+frag;
        cp->tx_skb[index] = NULL;
        txd = &cp->tx_ring[index];    // 對應的傳送緩衝描述符
        this_frag = &skb_shinfo(skb)->frags[frag];
        dma_unmap_single(&cp->pdev->dev, le64_to_cpu(txd->addr),    // 取消對映
                 skb_frag_size(this_frag), PCI_DMA_TODEVICE);
    }
}
接收資料,當有資料到達時,會產生中斷,在中斷函式處理接收資料:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 中斷函式
static irqreturn_t cp_interrupt (int irq, void *dev_instance)
{
	struct net_device *dev = dev_instance;
	struct cp_private *cp;
	int handled = 0;
	u16 status;

	if (unlikely(dev == NULL))
		return IRQ_NONE;
	cp = netdev_priv(dev);	// 私有資料

	spin_lock(&cp->lock);

	status = cpr16(IntrStatus);	// 得到中斷狀態
	if (!status || (status == 0xFFFF))
		goto out_unlock;

	handled = 1;

	netif_dbg(cp, intr, dev, "intr, status %04x cmd %02x cpcmd %04x\n",
		  status, cpr8(Cmd), cpr16(CpCmd));

	/* Rx error 
	 * No Rx descriptors available 
	 * Rx FIFO Overflow 
	 * Rx packet received 
	 * 將上面的標記清除
	 */
	cpw16(IntrStatus, status & ~cp_rx_intr_mask);

	/* close possible race's with dev_close */
	if (unlikely(!netif_running(dev))) {
		cpw16(IntrMask, 0);
		goto out_unlock;
	}

	/* Rx packet received 
	 * Rx error 
	 * No Rx descriptors available 
	 * Rx FIFO Overflow 
	 * 接收到了資料
	 */
	if (status & (RxOK | RxErr | RxEmpty | RxFIFOOvr))
		if (napi_schedule_prep(&cp->napi)) {	// 如果支援NAPI且NAPI可以執行
			cpw16_f(IntrMask, cp_norx_intr_mask);	// 關閉接收中斷,保留髮送中斷
			/* 將cp->napi加入到softnet_data中(softnet_data是一個PRE_CPU變數)
			 * 啟用軟中斷,在軟中斷註冊的輪詢函式(cp->napi己繫結函式cp_rx_poll)中完成網路資料包的接收操作
			 */
			__napi_schedule(&cp->napi);	
		}

	/* Tx packet sent 
	 * Tx error 
	 * No Tx descriptors available 
	 * Software-requested interrupt 
	 * 為上面狀態時,說時傳輸完成或出錯,呼叫函式cp_tx進行一些清理工作
	*/
	if (status & (TxOK | TxErr | TxEmpty | SWInt))
		cp_tx(cp);
	/* Packet underrun, or link change 
	 * 當網線拔插時會產生LinkChg中斷
	 */
	if (status & LinkChg)
		mii_check_media(&cp->mii_if, netif_msg_link(cp), false);	// 檢測MII介面


	if (status & PciErr) {	// PCI 裝置出錯
		u16 pci_status;

		pci_read_config_word(cp->pdev, PCI_STATUS, &pci_status);
		pci_write_config_word(cp->pdev, PCI_STATUS, pci_status);
		netdev_err(dev, "PCI bus error, status=%04x, PCI status=%04x\n",
			   status, pci_status);

		/* TODO: reset hardware */
	}

out_unlock:
	spin_unlock(&cp->lock);

	return IRQ_RETVAL(handled);
}
在中斷函式中呼叫的函式:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 傳輸完成後的清理工作
static void cp_tx (struct cp_private *cp)
{
	unsigned tx_head = cp->tx_head;	// 傳送緩衝描述符表的開頭位置
	unsigned tx_tail = cp->tx_tail;	// 傳送緩衝描述符表的結束位置
	unsigned bytes_compl = 0, pkts_compl = 0;

	while (tx_tail != tx_head) {
		struct cp_desc *txd = cp->tx_ring + tx_tail;	// 從傳送緩衝描述符表的末尾開始
		struct sk_buff *skb;
		u32 status;

		rmb();	// 保證此語句前面和後面語句的執行順序(禁止編譯器進行優化)
		status = le32_to_cpu(txd->opts1);
		if (status & DescOwn)	/* Descriptor is owned by NIC */
			break;

		skb = cp->tx_skb[tx_tail];	// 得到對應的緩衝
		BUG_ON(!skb);

		// 取消描述符的地址對映
		dma_unmap_single(&cp->pdev->dev, le64_to_cpu(txd->addr),	// 記憶體實體地址
				 le32_to_cpu(txd->opts1) & 0xffff,	// 記憶體大小
				 PCI_DMA_TODEVICE);

		if (status & LastFrag) {	/* Final segment of a packet */
			/* Tx error summary 
			 * Tx FIFO underrun 
			 * 出錯,統計出錯數量
			 */
			if (status & (TxError | TxFIFOUnder)) {
				netif_dbg(cp, tx_err, cp->dev,
					  "tx err, status 0x%x\n", status);
				cp->dev->stats.tx_errors++;	// 傳送錯誤總數
				if (status & TxOWC)
					cp->dev->stats.tx_window_errors++; 	/* Tx Out-of-window collision */
				if (status & TxMaxCol)
					cp->dev->stats.tx_aborted_errors++; 	/* Tx aborted due to excessive collisions */
				if (status & TxLinkFail)
					cp->dev->stats.tx_carrier_errors++; 	/* Link failed during Tx of packet */
				if (status & TxFIFOUnder)
					cp->dev->stats.tx_fifo_errors++; 	/* Tx FIFO underrun */
			} else {
				cp->dev->stats.collisions +=
					((status >> TxColCntShift) & TxColCntMask);	// 衝突數量
				cp->dev->stats.tx_packets++;	// 傳送總數量
				cp->dev->stats.tx_bytes += skb->len;	// 傳送總大小
				netif_dbg(cp, tx_done, cp->dev,
					  "tx done, slot %d\n", tx_tail);
			}
			bytes_compl += skb->len;	// 所有緩衝中的資料大小
			pkts_compl++;	// 緩衝的數量
			dev_kfree_skb_irq(skb);	// 釋放緩衝
		}

		cp->tx_skb[tx_tail] = NULL;	// 緩衝設為空

		tx_tail = NEXT_TX(tx_tail);	// 下一個描述符
	}

	cp->tx_tail = tx_tail;	// 記錄當前的描述符尾部

	netdev_completed_queue(cp->dev, pkts_compl, bytes_compl);	// 通知核心傳送的資料包個數和資料大小
	/* 如果可用描述符空間大於 16 + 1,重新開始傳輸
	 */
	if (TX_BUFFS_AVAIL(cp) > (MAX_SKB_FRAGS + 1))	
		netif_wake_queue(cp->dev);	// 通知核心可能傳送資料了
}
在接收資料時為提高效能可使用NAPI。原理就是當硬體中斷產生後,通過一個輪詢函式(通過軟中斷實現)不停讀取資料,在這個過程中網絡卡的中斷被關閉。其中的輪詢函式在初始化時被指定為cp_rx_poll

[ drivers/net/ethernet/realtek/8139cp.c ]

// NAPI時進行讀取資料的操作
static int cp_rx_poll(struct napi_struct *napi, int budget)
{
	struct cp_private *cp = container_of(napi, struct cp_private, napi);	// 私有資料
	struct net_device *dev = cp->dev;	// 網路裝置
	unsigned int rx_tail = cp->rx_tail;	// 讀取描述符的尾部
	int rx; 	// 接收到的包的數量

rx_status_loop:
	rx = 0;
	/* 中斷狀態為
	 * Rx packet received 
	 * Rx error 
	 * No Rx descriptors available 
	 * Rx FIFO Overflow 
	 */
	cpw16(IntrStatus, cp_rx_intr_mask);

	/* 如果有資料到達,會自動放入緩衝中
	 * 下面就是從緩衝中讀取資料
	 */
	while (1) {
		u32 status, len;
		dma_addr_t mapping, new_mapping;
		struct sk_buff *skb, *new_skb;
		struct cp_desc *desc;
		const unsigned buflen = cp->rx_buf_sz;	// 1536位元組

		skb = cp->rx_skb[rx_tail];	// 得到緩衝
		BUG_ON(!skb);

		desc = &cp->rx_ring[rx_tail];	// 得到描述符
		status = le32_to_cpu(desc->opts1);	// 描述符狀態
		if (status & DescOwn)	/* Descriptor is owned by NIC */
			break;

		len = (status & 0x1fff) - 4;	// 緩衝中的資料長度
		mapping = le64_to_cpu(desc->addr);	// 緩衝對應的實體地址

		/* First segment of a packet 
		 * Final segment of a packet 
		 * 這兩種情況記錄一些出錯資訊然後直接丟棄
		 */
		if ((status & (FirstFrag | LastFrag)) != (FirstFrag | LastFrag)) {
			/* we don't support incoming fragmented frames.
			 * instead, we attempt to ensure that the
			 * pre-allocated RX skbs are properly sized such
			 * that RX fragments are never encountered
			 */
			cp_rx_err_acct(cp, rx_tail, status, len);
			dev->stats.rx_dropped++;
			cp->cp_stats.rx_frags++;
			goto rx_next;
		}

		/* Rx error summary 
		 * Rx error, FIFO overflowed, pkt bad 
		 * 這兩種情況記錄一些出錯資訊然後直接丟棄
		 */
		if (status & (RxError | RxErrFIFO)) {
			cp_rx_err_acct(cp, rx_tail, status, len);
			goto rx_next;
		}

		netif_dbg(cp, rx_status, dev, "rx slot %d status 0x%x len %d\n",
			  rx_tail, status, len);

		/* 分配一個新的記憶體緩衝塊
		 * 原來的緩衝要從緩衝列表中摘除向上層傳遞
		 * 這個新的記憶體用來替換原來的空間
		 */
		new_skb = netdev_alloc_skb_ip_align(dev, buflen);
		if (!new_skb) {
			dev->stats.rx_dropped++;
			goto rx_next;
		}

		/* 得到new_skb->data(new_skb對應的緩衝)的實體地址
		 * 從PCI裝置讀取資料時,要用到實體地址
		 */
		new_mapping = dma_map_single(&cp->pdev->dev, new_skb->data, buflen,
					 PCI_DMA_FROMDEVICE);
		if (dma_mapping_error(&cp->pdev->dev, new_mapping)) {
			dev->stats.rx_dropped++;
			kfree_skb(new_skb);
			goto rx_next;
		}

		// 取消原描述符的地址對映
		dma_unmap_single(&cp->pdev->dev, mapping,
				 buflen, PCI_DMA_FROMDEVICE);

		/* Handle checksum offloading for incoming packets. */
		if (cp_rx_csum_ok(status))
			skb->ip_summed = CHECKSUM_UNNECESSARY;
		else
			skb_checksum_none_assert(skb);

		skb_put(skb, len);	// 調整skb的資料大小

		cp->rx_skb[rx_tail] = new_skb; // 將原緩衝(skb)的位置用新的替換

		cp_rx_skb(cp, skb, desc);	// 將skb交給上層協議
		rx++;	// 接收到的包的數量
		mapping = new_mapping;	// 新緩衝的實體地址

rx_next:
		/* 設定新緩衝的屬性
		 */
		cp->rx_ring[rx_tail].opts2 = 0;
		cp->rx_ring[rx_tail].addr = cpu_to_le64(mapping);	// 緩衝實體地址
		/* 設定cp_desc.opts1
		 * 緩衝的大小 | 此描述符被NIC使用 
		 * 最後一個描述符加上一個終止標記
		 */
		if (rx_tail == (CP_RX_RING_SIZE - 1))	// 接收緩衝地址列表的最後一項
			desc->opts1 = cpu_to_le32(DescOwn | RingEnd | cp->rx_buf_sz);	// (1 << 31) | (1 << 30) | 1536
		else
			desc->opts1 = cpu_to_le32(DescOwn | cp->rx_buf_sz);	// (1 << 31) | (1 << 30) | 1536
		rx_tail = NEXT_RX(rx_tail);	// 下一個描述符

		if (rx >= budget)	// 如果接收的資料包超過指定的值(引數),結束此次讀取
			break;
	} // while

	cp->rx_tail = rx_tail;	// 當前描述符表的末尾

	/* if we did not reach work limit, then we're done with
	 * this round of polling
	 */
	if (rx < budget) {
		unsigned long flags;

		/* Rx packet received 
		 * Rx error 
		 * No Rx descriptors available 
		 * Rx FIFO Overflow 
		 * 如果中斷狀態為上面的值,回到函式開始處
		 */
		if (cpr16(IntrStatus) & cp_rx_intr_mask)
			goto rx_status_loop;

		napi_gro_flush(napi, false);
		spin_lock_irqsave(&cp->lock, flags);
		__napi_complete(napi);
		cpw16_f(IntrMask, cp_intr_mask);	// 開啟接收和傳送中斷
		spin_unlock_irqrestore(&cp->lock, flags);
	}

	return rx;
}
在輪詢函式中呼叫的函式:
[ drivers/net/ethernet/realtek/8139cp.c ]
// 接收時的錯誤統計
static void cp_rx_err_acct (struct cp_private *cp, unsigned rx_tail,
			    u32 status, u32 len)
{
	netif_dbg(cp, rx_err, cp->dev, "rx err, slot %d status 0x%x len %d\n",
		  rx_tail, status, len);
	cp->dev->stats.rx_errors++;	// 總的錯誤數
	if (status & RxErrFrame)
		cp->dev->stats.rx_frame_errors++;  /* Rx frame alignment error */
	if (status & RxErrCRC)
		cp->dev->stats.rx_crc_errors++; 	/* Rx CRC error */
	if ((status & RxErrRunt) || (status & RxErrLong))  
		cp->dev->stats.rx_length_errors++;	/* Rx error, packet > 4096 bytes or packet < 64 bytes */
	if ((status & (FirstFrag | LastFrag)) != (FirstFrag | LastFrag))  /* First segment of a packet */ /* Final segment of a packet */
		cp->dev->stats.rx_length_errors++;
	if (status & RxErrFIFO)
		cp->dev->stats.rx_fifo_errors++;  /* Rx error, FIFO overflowed, pkt bad */
}

 // 檢驗和是否正確
static inline unsigned int cp_rx_csum_ok (u32 status)
{
    unsigned int protocol = (status >> 16) & 0x3;

    /* 協議是TCP,TCP的檢驗和無錯或協議是UDP且UDP的檢驗和無錯
     */
    if (((protocol == RxProtoTCP) && !(status & TCPFail)) ||
        ((protocol == RxProtoUDP) && !(status & UDPFail)))
        return 1;
    else
        return 0;
}

// 將skb交給上層協議
static inline void cp_rx_skb (struct cp_private *cp, struct sk_buff *skb,
                  struct cp_desc *desc)
{
    u32 opts2 = le32_to_cpu(desc->opts2);    /* Rx VLAN tag available */

    /* skb->data指向了乙太網頭部後面,也就是IP頭部的位置
     * 返回資料包的乙太網協議
     */
    skb->protocol = eth_type_trans (skb, cp->dev);

    cp->dev->stats.rx_packets++;    // 收到的包的數量
    cp->dev->stats.rx_bytes += skb->len;    // 收到的包的大小

    if (opts2 & RxVlanTagged)  /* Rx VLAN tag available */
        __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), swab16(opts2 & 0xffff)); /* 802.1Q VLAN Extended Header  */

    napi_gro_receive(&cp->napi, skb);    // GRO機制,將小的資料包儘可能合併成大的資料包,然後提交到上層協議
}
當支援電源管理時,掛起和繼續時呼叫的函式:

[ drivers/net/ethernet/realtek/8139cp.c ]

#ifdef CONFIG_PM
static int cp_suspend (struct pci_dev *pdev, pm_message_t state)
{
	struct net_device *dev = pci_get_drvdata(pdev);	// 網路裝置
	struct cp_private *cp = netdev_priv(dev);	// 私有資料
	unsigned long flags;

	if (!netif_running(dev))	// 裝置是否被喚醒(dev->state 是否設了__LINK_STATE_START標記)
		return 0;

	netif_device_detach (dev);	// 清除裝置狀態(dev->state)的__LINK_STATE_PRESENT標記,禁止傳送佇列
	netif_stop_queue (dev);	// 告訴核心不要傳送資料了

	spin_lock_irqsave (&cp->lock, flags);

	/* Disable Rx and Tx */
	cpw16 (IntrMask, 0);	// 關中斷
	cpw8  (Cmd, cpr8 (Cmd) & (~RxOn | ~TxOn));	// 禁止傳送和接收資料

	spin_unlock_irqrestore (&cp->lock, flags);

	pci_save_state(pdev);	// 儲存PCI狀態
	pci_enable_wake(pdev, pci_choose_state(pdev, state), cp->wol_enabled);	// 根據是否支援Wake-on-LAN禁止或允許PME#
	pci_set_power_state(pdev, pci_choose_state(pdev, state));	// 設定電源狀態

	return 0;
}

// 繼續執行
static int cp_resume (struct pci_dev *pdev)
{
	struct net_device *dev = pci_get_drvdata (pdev);	// 網路裝置
	struct cp_private *cp = netdev_priv(dev);	// 私有資料
	unsigned long flags;

	if (!netif_running(dev))	// 裝置是否被喚醒(dev->state 是否設了__LINK_STATE_START標記)
		return 0;

	netif_device_attach (dev);	// 設定裝置狀態(dev->state)的__LINK_STATE_PRESENT標記,並喚醒傳送佇列

	pci_set_power_state(pdev, PCI_D0);	// 設定電源狀態為PCI_D0	
	pci_restore_state(pdev);	// 恢復PCI裝置儲存的狀態
	pci_enable_wake(pdev, PCI_D0, 0);	// 禁止PME#的產生

	/* FIXME: sh*t may happen if the Rx ring buffer is depleted */
	cp_init_rings_index (cp);
	cp_init_hw (cp); 	// 設定裝置暫存器,以便接收進來的資料直接放入緩衝中
	cp_enable_irq(cp); 	// 開啟中斷
	netif_start_queue (dev); // 告訴核心可以傳送資料了

	spin_lock_irqsave (&cp->lock, flags);

	mii_check_media(&cp->mii_if, netif_msg_link(cp), false);	// 檢測MII介面

	spin_unlock_irqrestore (&cp->lock, flags);

	return 0;
}
#endif /* CONFIG_PM */


其它的一些函式

[ drivers/net/ethernet/realtek/8139cp.c ]

#ifdef CONFIG_NET_POLL_CONTROLLER
/*
 * Polling receive - used by netconsole and other diagnostic tools
 * to allow network i/o with interrupts disabled.
 */
static void cp_poll_controller(struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);	// 私有資料
	const int irq = cp->pdev->irq;	// 中斷號

	disable_irq(irq);	// 關閉中斷
	cp_interrupt(irq, dev);	// 呼叫中斷函式
	enable_irq(irq);	// 開啟中斷
}
#endif

[ drivers/net/ethernet/realtek/8139cp.c ]
// 設定多播地址屬性
static void cp_set_rx_mode (struct net_device *dev)
{
	unsigned long flags;
	struct cp_private *cp = netdev_priv(dev); // 私有資料

	spin_lock_irqsave (&cp->lock, flags);
	__cp_set_rx_mode(dev);
	spin_unlock_irqrestore (&cp->lock, flags);
}

[ drivers/net/ethernet/realtek/8139cp.c ]
// 這裡只是得到接收被忽略的資料個數
static void __cp_get_stats(struct cp_private *cp)
{
	/* only lower 24 bits valid; write any value to clear */
	cp->dev->stats.rx_missed_errors += (cpr32 (RxMissed) & 0xffffff);
	cpw32 (RxMissed, 0); 	/* 24 bits valid, write clears */
}

// 這裡只是得到接收被忽略的資料個數
static struct net_device_stats *cp_get_stats(struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned long flags;

	/* The chip only need report frame silently dropped. */
	spin_lock_irqsave(&cp->lock, flags);
 	if (netif_running(dev) && netif_device_present(dev))
 		__cp_get_stats(cp);
	spin_unlock_irqrestore(&cp->lock, flags);

	return &dev->stats;
}

[ drivers/net/ethernet/realtek/8139cp.c ]
// 改變MTU
static int cp_change_mtu(struct net_device *dev, int new_mtu)
{
	struct cp_private *cp = netdev_priv(dev);	// 私有資料

	/* check for invalid MTU, according to hardware limits 
	 * 60 - 4096
	 */
	if (new_mtu < CP_MIN_MTU || new_mtu > CP_MAX_MTU)
		return -EINVAL;

	/* if network interface not up, no need for complexity */
	if (!netif_running(dev)) {
		dev->mtu = new_mtu;
		cp_set_rxbufsize(cp);	/* set new rx buf size */
		return 0;
	}

	/* network IS up, close it, reset MTU, and come up again. */
	cp_close(dev);
	dev->mtu = new_mtu;
	cp_set_rxbufsize(cp);
	return cp_open(dev);
}

[ drivers/net/ethernet/realtek/8139cp.c ]
/* Set the ethtool Wake-on-LAN settings */
static int netdev_set_wol (struct cp_private *cp,
			   const struct ethtool_wolinfo *wol)
{
	u8 options;

	/* Wake up when the cable connection is re-established 
	 * Wake up when receives a Magic Packet 
	 */
	options = cpr8 (Config3) & ~(LinkUp | MagicPacket);
	/* If WOL is being disabled, no need for complexity */
	if (wol->wolopts) {
		if (wol->wolopts & WAKE_PHY)	options |= LinkUp; 	// Wake up when the cable connection is re-established 
		if (wol->wolopts & WAKE_MAGIC)	options |= MagicPacket; // Wake up when receives a Magic Packet 
	}

	cpw8 (Cfg9346, Cfg9346_Unlock);
	cpw8 (Config3, options);	// 寫到Config3的位置
	cpw8 (Cfg9346, Cfg9346_Lock);

	options = 0; /* Paranoia setting */
	/* Accept Unicast wakeup frame 
	 * Accept Multicast wakeup frame 
	 * Accept Broadcast wakeup frame 
	 * 先去掉以上標記
	 */
	options = cpr8 (Config5) & ~(UWF | MWF | BWF);
	/* If WOL is being disabled, no need for complexity */
	if (wol->wolopts) {
		if (wol->wolopts & WAKE_UCAST)  options |= UWF; 	// Accept Unicast wakeup frame 
		if (wol->wolopts & WAKE_BCAST)	options |= BWF; 	// Accept Broadcast wakeup frame 
		if (wol->wolopts & WAKE_MCAST)	options |= MWF; 	// Accept Multicast wakeup frame 
	}

	cpw8 (Config5, options);

	cp->wol_enabled = (wol->wolopts) ? 1 : 0;

	return 0;
}

 /* Get the ethtool Wake-on-LAN settings */
static void netdev_get_wol (struct cp_private *cp,
                 struct ethtool_wolinfo *wol)
{
    u8 options;

    wol->wolopts   = 0; /* Start from scratch */
    wol->supported = WAKE_PHY   | WAKE_BCAST | WAKE_MAGIC |
                 WAKE_MCAST | WAKE_UCAST;
    /* We don't need to go on if WOL is disabled */
    if (!cp->wol_enabled) return;

    options        = cpr8 (Config3);
    if (options & LinkUp)        wol->wolopts |= WAKE_PHY;    // Wake up when the cable connection is re-established 
    if (options & MagicPacket)   wol->wolopts |= WAKE_MAGIC;    // Wake up when receives a Magic Packet 

    options        = 0; /* Paranoia setting */
    options        = cpr8 (Config5);
    if (options & UWF)           wol->wolopts |= WAKE_UCAST;    // Accept Unicast wakeup frame 
    if (options & BWF)           wol->wolopts |= WAKE_BCAST;    // Accept Broadcast wakeup frame 
    if (options & MWF)           wol->wolopts |= WAKE_MCAST;    // Accept Multicast wakeup frame 
}
[ drivers/net/ethernet/realtek/8139cp.c ]
// 驅動資訊
static void cp_get_drvinfo (struct net_device *dev, struct ethtool_drvinfo *info)
{
	struct cp_private *cp = netdev_priv(dev);	// 私有資料

	strlcpy(info->driver, DRV_NAME, sizeof(info->driver));	// "8139cp"
	strlcpy(info->version, DRV_VERSION, sizeof(info->version));	// "1.3"
	strlcpy(info->bus_info, pci_name(cp->pdev), sizeof(info->bus_info));
}

static void cp_get_ringparam(struct net_device *dev,
				struct ethtool_ringparam *ring)
{
	ring->rx_max_pending = CP_RX_RING_SIZE;	// 64
	ring->tx_max_pending = CP_TX_RING_SIZE;	// 64
	ring->rx_pending = CP_RX_RING_SIZE;	// 64
	ring->tx_pending = CP_TX_RING_SIZE;	// 64
}

static int cp_get_regs_len(struct net_device *dev)
{
	return CP_REGS_SIZE;	// (0xff + 1)
}

static int cp_get_sset_count (struct net_device *dev, int sset)
{
	switch (sset) {
	case ETH_SS_STATS:
		return CP_NUM_STATS;	// 14
	default:
		return -EOPNOTSUPP;
	}
}

[ drivers/net/ethernet/realtek/8139cp.c ]
// 設定MAC地址
static int cp_set_mac_address(struct net_device *dev, void *p)
{
	struct cp_private *cp = netdev_priv(dev);// 私有資料
	struct sockaddr *addr = p;

	/* 地址不是00:00:00:00:00:00, 也不是FF:FF:FF:FF:FF:FF
	 */
	if (!is_valid_ether_addr(addr->sa_data))
		return -EADDRNOTAVAIL;

	memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);	// 拷貝地址

	spin_lock_irq(&cp->lock);

	cpw8_f(Cfg9346, Cfg9346_Unlock);
	cpw32_f(MAC0 + 0, le32_to_cpu (*(__le32 *) (dev->dev_addr + 0)));	// 寫到暫存器中
	cpw32_f(MAC0 + 4, le32_to_cpu (*(__le32 *) (dev->dev_addr + 4)));
	cpw8_f(Cfg9346, Cfg9346_Lock);

	spin_unlock_irq(&cp->lock);

	return 0;
}


相關推薦

驅動8139Cp

PCI匯流排概念: PCI匯流排不只是一種排線方式,它還定義了一整套計算機不同部分互動的規則。PCI裝置在啟動時自動被配置,相關驅動必須讀取這些配置資訊來完成初始化。 linux實現了PCI domains。一個domain 支援 256 buses。每個bus最多支援3

嵌入式Linux——驅動(1)驅動框架介紹

宣告:文字是看完韋東山老師的視訊和看了一些文章後,所寫的總結。我會盡力將自己所瞭解的知識寫出來,但由於自己感覺並沒有學的很好,所以文中可能有錯的地方敬請指出,謝謝。         在介紹本文之前,我想先對前面的知識做一下總結,我們知道Linux系統的裝置分為字元裝置(ch

Ubuntu14.04 無線驅動安裝

由於新安裝的14.04是 沒有無線網絡卡驅動的,這裡需要自己安裝網絡卡驅動。 (第一步) 我們需要在win10下看到網絡卡型號 (第二步) 升級ubuntu核心(uname -sr可以看現在的核心版本) 在 http://kernel.ubuntu.com/~kernel

DPDK驅動載入、繫結和解綁

Igb_uio程式碼相關的可以分為三個部分:igb_uio核心驅動,核心uio框架,uio使用者態部分。 載入igb_uio模組與繫結dpdk網絡卡 a)載入dpdk驅動需要先載入uio:modprobe uio b)載入dpdk驅動的方法:/sbin/insmod  ig

ThinkPad E480安裝ubuntu後沒有無線驅動,找不到wifi的解決方案

先安裝的ubuntu版本是ubuntu-16.04.3-desktop-amd64.iso 安裝後通過cat /proc/version或者uname -rs檢視linunx核心版本,其linux核心版本為4.10。 網上查到linux4.15及以上的linux核心才能支援此筆記本的無線網絡

安裝完Ubuntu系統之後無法連線無線,有無線卻找不到驅動

今天給我的Mac裝完Ubuntu16.04之後,發現登入系統之後無法連線WIFI無線網路,筆記本是自帶無線網絡卡的,所以這個出現問題的原因只有一個,那就是沒有安裝好網絡卡驅動,而且Ubuntu自帶的相容網絡卡驅動並沒有起到作用。這個時候我查閱了一些網路上的相關資料,自己總結了一個解決辦法,相

Jetson tk1 安裝 Intel 7260ac 無線驅動

首先,利用Jetpack將Jetson TK1升級到最新的L4T (version 21.3 +) 如果工作環境能提供有線網路,請將網線插到開發板,在開發板L4T的terminal輸入以下指令來下載並安裝驅動: sudo apt-get install git git clon

centos7.5安裝無線驅動

本文主要參考: https://blog.csdn.net/yanshaoshuai/article/details/81148664 http://elrepo.org/tiki/wl-kmod 1. 概要 本篇部落格主要記錄在 centos7.5 環境下安裝 BCM

【Ubuntu16.04】安裝無線驅動

1、參考連結:https://blog.csdn.net/weijia_kmy/article/details/51304518 昨天下載了ubuntu16.0.4,安裝後發現沒有wifi可連線,於是上網查了一個晚上,都沒有可行的辦法。無奈今早就又下載ubuntu15.10安裝,發現可以連wifi

l(轉)Linux DM9000驅動程式完全分析

[置頂] Linux DM9000網絡卡驅動程式完全分析 分類: Linux裝置驅動程式第三版學習筆記 2011-02-26 16:11 3513人閱讀 評論(34) 收藏 舉報 說明1:本文分析基於核心原始碼版本為linux-2

LINUX核心升級 - 更新驅動

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

故障彈出介面eth0: 錯誤沒有找到合適的裝置沒有找到可用於連結System eth0 的

今天在使用putty連線linux6.0系統遠端的時候,重啟網絡卡的時候發現此報錯: 報錯截圖如下: 排錯步驟如下: 1:檢視系統是否識別相應網絡卡(發現沒有eth0網絡卡存在): 根據顯示資訊我們可以初步判定系統網絡卡資訊有問題: 2:檢視當前MAC地址相關資訊:

VirtualBox配置實現主機-虛擬機器互Ping、聯網

1. 設定virtualbox網絡卡 查詢主機連線方式,也就是檢視使用網線還是wifi連線(主要是檢視描述名稱): 使用cmd視窗: ipconfig -all 橋接網絡卡-主機-虛擬機器通訊 通過上圖博主是使用網線連線,所以下圖選擇相應的介面名稱:

Centos7.4.1708 安裝usb無限驅動

今天總結一下前幾天折騰的usb無線網絡卡驅動。 一、確定網絡卡驅動 實驗機器核心版本資訊如下: [[email protected] ~]# uname -a Linux localhost.localdomain 3.10.0-693.21.1.el7.x86_64 #1

驅動的移植

需要用到的檔案在 "u-boot-samsung-dev\board\samsung\smdkc110" 目錄下 第一步:  找到   smdkc110.c  下的 static void dm9000_pre_init 函式 在"S5PV210_UM

初識Linux 驅動移植 之 dm9621驅動移植

概述 將kernel移植到開發板並能正常載入和啟動核心後,發現網絡卡並沒有工作,因此將網絡卡作為第一個移植的實踐。這篇文章用於記錄移植dm9621網絡卡過程中遇到的問題以及如何定位問題並嘗試解決。 配置核心 在找到dm9621網絡卡驅動的原始碼後,需要將其新增

驅動收發包過程

網絡卡 網絡卡工作在物理層和資料鏈路層,主要由PHY/MAC晶片、Tx/Rx FIFO、DMA等組成,其中網線通過變壓器接PHY晶片、PHY晶片通過MII接MAC晶片、MAC晶片接PCI匯流排 PHY晶片主要負責:CSMA/CD、模數轉換、編解碼、串並轉換 MAC晶片主要

新裝linux系統沒有驅動的解決方法

最近公司伺服器剛裝完centos6.0系統,發現只有一個lo網絡卡,沒有eth0也沒有ifcfg-eth0檔案,可以初步說明沒有網絡卡驅動 1.首先下載一個centos6.0的網絡卡驅動(舊版本的網絡卡驅動網上很難找,本人網上沒找到,最後通過客服人員才獲得,已經

Linux 驅動sk_buff核心原始碼隨筆

          這幾天在除錯有關網絡卡驅動的東西,有很多地方不清楚。而且網絡卡驅動主要有兩個很重要的結構體:struct net_device 和struct sk_buff。 驅動都是圍繞這兩個東西進行操作的,為了搞清楚該如何按協議棧處理資料包,週末閒來無事就看看核

重灌完centos6.5後沒有驅動的解決方法

進入到系統,輸入ifconfig命令,發現只有lo,沒有eth0資訊,在/etc/sysconfig/network-scripts/目錄下面也沒有ifcfg-eth0檔案 然後百度了很多文章,找到一個不錯的方法,在此做個筆記 解決方法: 1、檢視網絡卡型號:lsp