網絡卡驅動: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。以後我們說的裝置指的就是一個功能。
注意PCI registers總是little-endian。下面是一些主要的registers:
- vendorID
16位,裝置廠家的標識 - deviceID
16位,由裝置廠家提供的裝置標識 - class
每個裝置都屬於一種class。這是一個24位的值,高8位表示一個group,如:ethernet 和 token ring 都屬於 network group - subsystem vendorID
對裝置進一步標識。
這裡的網絡卡是一個PCI裝置,作為一個驅動,首先要向核心申明自己都支援什麼裝置型別:
[ drivers/net/ethernet/realtek/8139cp.c ]
由於是PCI裝置,網絡卡驅動當然要作為PCI驅動註冊到核心中。/* 驅動支援的硬體裝置列表 */ 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); // 在核心中註冊此列表,當核心在訪問一個裝置時,就可以通過這個列表找到相關的驅動
[ 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