1、为了更好低学习linux的网络驱动架构,本文选择分析linux kernel下的有线网卡驱动enc28j60来学习网络驱动架构。
enc28j60是一个10/100Mb的有线网卡,适用于嵌入式设备上进行网络通信,使用SPI接口与CPU进行通信。该驱动已经集成在linux kernel源码中,路径是driver/net/ethernet/microchip/enc28j60.c
2、驱动入口
既然是使用SPI接口与CPU进行通信,那么想当然地一定用了SPI Driver的架构,入口和出口如下:
static const struct of_device_id enc28j60_dt_ids[] = {
{ .compatible = "microchip,enc28j60" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, enc28j60_dt_ids);
static struct spi_driver enc28j60_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = enc28j60_dt_ids,
},
.probe = enc28j60_probe,
.remove = enc28j60_remove,
};
static int __init enc28j60_init(void)
{
msec20_to_jiffies = msecs_to_jiffies(20);
return spi_register_driver(&enc28j60_driver);
}
module_init(enc28j60_init);
static void __exit enc28j60_exit(void)
{
spi_unregister_driver(&enc28j60_driver);
}
使用spi_register_driver和spi_unregister_driver分别向内核注册和注销驱动的作用。
使用MODULE_DEVICE_TABLE对外输出device_id,以供其他模块依赖加载。
在driver和device匹配(match)后,会调用spi_driver中的probe函数,我们在probe函数中进一步进行初始化。
2、probe初始化
probe函数详情如下:
static int enc28j60_probe(struct spi_device *spi)
{
struct net_device *dev;
struct enc28j60_net *priv;
const void *macaddr;
int ret = 0;
if (netif_msg_drv(&debug))
dev_info(&spi->dev, DRV_NAME " Ethernet driver %s loaded\n",
DRV_VERSION);
dev = alloc_etherdev(sizeof(struct enc28j60_net));
if (!dev) {
ret = -ENOMEM;
goto error_alloc;
}
priv = netdev_priv(dev);
priv->netdev = dev; /* priv to netdev reference */
priv->spi = spi; /* priv to spi reference */
priv->msg_enable = netif_msg_init(debug.msg_enable,
ENC28J60_MSG_DEFAULT);
mutex_init(&priv->lock);
INIT_WORK(&priv->tx_work, enc28j60_tx_work_handler);
INIT_WORK(&priv->setrx_work, enc28j60_setrx_work_handler);
INIT_WORK(&priv->irq_work, enc28j60_irq_work_handler);
INIT_WORK(&priv->restart_work, enc28j60_restart_work_handler);
spi_set_drvdata(spi, priv); /* spi to priv reference */
SET_NETDEV_DEV(dev, &spi->dev);
if (!enc28j60_chipset_init(dev)) {
if (netif_msg_probe(priv))
dev_info(&spi->dev, DRV_NAME " chip not found\n");
ret = -EIO;
goto error_irq;
}
macaddr = of_get_mac_address(spi->dev.of_node);
if (macaddr)
ether_addr_copy(dev->dev_addr, macaddr);
else
eth_hw_addr_random(dev);
enc28j60_set_hw_macaddr(dev);
/* Board setup must set the relevant edge trigger type;
* level triggers won't currently work.
*/
ret = request_irq(spi->irq, enc28j60_irq, 0, DRV_NAME, priv);
if (ret < 0) {
if (netif_msg_probe(priv))
dev_err(&spi->dev, DRV_NAME ": request irq %d failed "
"(ret = %d)\n", spi->irq, ret);
goto error_irq;
}
dev->if_port = IF_PORT_10BASET;
dev->irq = spi->irq;
dev->netdev_ops = &enc28j60_netdev_ops;
dev->watchdog_timeo = TX_TIMEOUT;
dev->ethtool_ops = &enc28j60_ethtool_ops;
enc28j60_lowpower(priv, true);
ret = register_netdev(dev);
if (ret) {
if (netif_msg_probe(priv))
dev_err(&spi->dev, "register netdev " DRV_NAME
" failed (ret = %d)\n", ret);
goto error_register;
}
dev_info(&dev->dev, DRV_NAME " driver registered\n");
return 0;
error_register:
free_irq(spi->irq, priv);
error_irq:
free_netdev(dev);
error_alloc:
return ret;
}
dev = alloc_etherdev(sizeof(struct enc28j60_net));
在这里动态地申请net_device
结构体,同时申请了大小为sizeof(struct enc28j60_net)
的私有空间,其内存分布如下:
内存低地址 | 内存高地址 |
---|---|
net_device | enc28j60_net |
priv = netdev_priv(dev);
通过netdev_priv
获取到私有空间的地址,即struct enc28j60_net这个结构体的地址。
priv->netdev = dev; /* priv to netdev reference */
priv->spi = spi; /* priv to spi reference */
priv->msg_enable = netif_msg_init(debug.msg_enable,
ENC28J60_MSG_DEFAULT);
mutex_init(&priv->lock);
INIT_WORK(&priv->tx_work, enc28j60_tx_work_handler);
INIT_WORK(&priv->setrx_work, enc28j60_setrx_work_handler);
INIT_WORK(&priv->irq_work, enc28j60_irq_work_handler);
INIT_WORK(&priv->restart_work, enc28j60_restart_work_handler);
在这里对申请的enc28j60_net结构体成员进行初始化。
spi_set_drvdata(spi, priv); /* spi to priv reference */
SET_NETDEV_DEV(dev, &spi->dev);
spi_set_drvdata
将struct enc28j60_net
这个结构体与spi_device关联在一起
if (!enc28j60_chipset_init(dev)) {
if (netif_msg_probe(priv))
dev_info(&spi->dev, DRV_NAME " chip not found\n");
ret = -EIO;
goto error_irq;
}
这里对硬件进行初始化,设置寄存器
macaddr = of_get_mac_address(spi->dev.of_node);
if (macaddr)
ether_addr_copy(dev->dev_addr, macaddr);
else
eth_hw_addr_random(dev);
enc28j60_set_hw_macaddr(dev);
上述代码进行mac设置,如果设备树里面指定了mac地址,则使用设备树里的,否则随机生成。
ret = request_irq(spi->irq, enc28j60_irq, 0, DRV_NAME, priv);
if (ret < 0) {
if (netif_msg_probe(priv))
dev_err(&spi->dev, DRV_NAME ": request irq %d failed "
"(ret = %d)\n", spi->irq, ret);
goto error_irq;
}
在这里申请中断,并指定中断处理函数。
static const struct net_device_ops enc28j60_netdev_ops = {
.ndo_open = enc28j60_net_open,
.ndo_stop = enc28j60_net_close,
.ndo_start_xmit = enc28j60_send_packet,
.ndo_set_rx_mode = enc28j60_set_multicast_list,
.ndo_set_mac_address = enc28j60_set_mac_address,
.ndo_tx_timeout = enc28j60_tx_timeout,
.ndo_validate_addr = eth_validate_addr,
};
static const struct ethtool_ops enc28j60_ethtool_ops = {
.get_drvinfo = enc28j60_get_drvinfo,
.get_msglevel = enc28j60_get_msglevel,
.set_msglevel = enc28j60_set_msglevel,
.get_link_ksettings = enc28j60_get_link_ksettings,
.set_link_ksettings = enc28j60_set_link_ksettings,
};
dev->if_port = IF_PORT_10BASET;
dev->irq = spi->irq;
dev->netdev_ops = &enc28j60_netdev_ops;
dev->watchdog_timeo = TX_TIMEOUT;
dev->ethtool_ops = &enc28j60_ethtool_ops;
enc28j60_lowpower(priv, true);
ret = register_netdev(dev);
在这里填充net_device结构体,设置该网络设备的netdev_ops
和ethtool_ops
。其中netdev_ops
负责该设备的打开、关闭、发送等有关功能性的函数接口;ethtool_ops
负责一些负责性的函数接口,比如获取连接速度、MAC地址等。
至此网卡初始化完毕,上层函数发包使用网络设备对应的ndo_start_xmit进行发包,收包由中断处理函数唤醒收包队列进行收包。
中断处理如下
static irqreturn_t enc28j60_irq(int irq, void *dev_id)
{
struct enc28j60_net *priv = dev_id;
schedule_work(&priv->irq_work);
return IRQ_HANDLED;
}
static void enc28j60_irq_work_handler(struct work_struct *work)
{
struct enc28j60_net *priv =
container_of(work, struct enc28j60_net, irq_work);
struct net_device *ndev = priv->netdev;
int intflags, loop;
if (netif_msg_intr(priv))
printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __func__);
/* disable further interrupts */
locked_reg_bfclr(priv, EIE, EIE_INTIE);
do {
loop = 0;
intflags = locked_regb_read(priv, EIR);
/* DMA interrupt handler (not currently used) */
if ((intflags & EIR_DMAIF) != 0) {
loop++;
if (netif_msg_intr(priv))
printk(KERN_DEBUG DRV_NAME
": intDMA(%d)\n", loop);
locked_reg_bfclr(priv, EIR, EIR_DMAIF);
}
/* LINK changed handler */
if ((intflags & EIR_LINKIF) != 0) {
loop++;
if (netif_msg_intr(priv))
printk(KERN_DEBUG DRV_NAME
": intLINK(%d)\n", loop);
enc28j60_check_link_status(ndev);
/* read PHIR to clear the flag */
enc28j60_phy_read(priv, PHIR);
}
/* TX complete handler */
if (((intflags & EIR_TXIF) != 0) &&
((intflags & EIR_TXERIF) == 0)) {
bool err = false;
loop++;
if (netif_msg_intr(priv))
printk(KERN_DEBUG DRV_NAME
": intTX(%d)\n", loop);
priv->tx_retry_count = 0;
if (locked_regb_read(priv, ESTAT) & ESTAT_TXABRT) {
if (netif_msg_tx_err(priv))
dev_err(&ndev->dev,
"Tx Error (aborted)\n");
err = true;
}
if (netif_msg_tx_done(priv)) {
u8 tsv[TSV_SIZE];
enc28j60_read_tsv(priv, tsv);
enc28j60_dump_tsv(priv, "Tx Done", tsv);
}
enc28j60_tx_clear(ndev, err);
locked_reg_bfclr(priv, EIR, EIR_TXIF);
}
/* TX Error handler */
if ((intflags & EIR_TXERIF) != 0) {
u8 tsv[TSV_SIZE];
loop++;
if (netif_msg_intr(priv))
printk(KERN_DEBUG DRV_NAME
": intTXErr(%d)\n", loop);
locked_reg_bfclr(priv, ECON1, ECON1_TXRTS);
enc28j60_read_tsv(priv, tsv);
if (netif_msg_tx_err(priv))
enc28j60_dump_tsv(priv, "Tx Error", tsv);
/* Reset TX logic */
mutex_lock(&priv->lock);
nolock_reg_bfset(priv, ECON1, ECON1_TXRST);
nolock_reg_bfclr(priv, ECON1, ECON1_TXRST);
nolock_txfifo_init(priv, TXSTART_INIT, TXEND_INIT);
mutex_unlock(&priv->lock);
/* Transmit Late collision check for retransmit */
if (TSV_GETBIT(tsv, TSV_TXLATECOLLISION)) {
if (netif_msg_tx_err(priv))
printk(KERN_DEBUG DRV_NAME
": LateCollision TXErr (%d)\n",
priv->tx_retry_count);
if (priv->tx_retry_count++ < MAX_TX_RETRYCOUNT)
locked_reg_bfset(priv, ECON1,
ECON1_TXRTS);
else
enc28j60_tx_clear(ndev, true);
} else
enc28j60_tx_clear(ndev, true);
locked_reg_bfclr(priv, EIR, EIR_TXERIF | EIR_TXIF);
}
/* RX Error handler */
if ((intflags & EIR_RXERIF) != 0) {
loop++;
if (netif_msg_intr(priv))
printk(KERN_DEBUG DRV_NAME
": intRXErr(%d)\n", loop);
/* Check free FIFO space to flag RX overrun */
if (enc28j60_get_free_rxfifo(priv) <= 0) {
if (netif_msg_rx_err(priv))
printk(KERN_DEBUG DRV_NAME
": RX Overrun\n");
ndev->stats.rx_dropped++;
}
locked_reg_bfclr(priv, EIR, EIR_RXERIF);
}
/* RX handler */
if (enc28j60_rx_interrupt(ndev))
loop++;
} while (loop);
/* re-enable interrupts */
locked_reg_bfset(priv, EIE, EIE_INTIE);
if (netif_msg_intr(priv))
printk(KERN_DEBUG DRV_NAME ": %s() exit\n", __func__);
}
中断处理函数enc28j60_irq_work_handler
很简单,就是直接调度一个work,而该work在probe初始化的时候,已经与enc28j60_irq_work_handler
关联。
INIT_WORK(&priv->irq_work, enc28j60_irq_work_handler);