前面学习了下Linux下的网络设备驱动程序的框架inux 驱动框架---net驱动框架,感觉知道了一个机器的大致结构还是不太清楚具体的细节处是怎么处理的,所以今天就来以dm9000这个网上教程最多的驱动实例来详细看一下Linux网络设备驱动的开发更细节的内容。这个驱动比较综合先是以platform总线为基础,中间有用到互斥,延迟任务等细节的知识所以需要先简单了解一下用到的框架的基本原理。

  因为是基于platform总线的所以肯定是分设备和驱动两部分这里直接上代码了,然后后面在详细阐述工作过程。先看设备的相关代码,其次是关于platform的驱动框架的内容这里详细说。

设备代码。

static struct resource smdkv210_dm9000_resources[] = {
[0] = DEFINE_RES_MEM(S5PV210_PA_SROM_BANK1, 1),
[1] = DEFINE_RES_MEM(S5PV210_PA_SROM_BANK1 + 4, 4),
[2] = DEFINE_RES_NAMED(IRQ_EINT(7), 1, NULL, IORESOURCE_IRQ \
| IORESOURCE_IRQ_HIGHLEVEL),
}; static struct dm9000_plat_data smdkv210_dm9000_platdata = {
.flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM,
.dev_addr = { 0x08, 0x90, 0x00, 0xa0, 0x02, 0x10 },
}; static struct platform_device smdkv210_dm9000 = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(smdkv210_dm9000_resources),
.resource = smdkv210_dm9000_resources,
.dev = {
.platform_data = &smdkv210_dm9000_platdata,
},
};

驱动部分

static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
.pm = &dm9000_drv_pm_ops,
.of_match_table = of_match_ptr(dm9000_of_matches),
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,
};

这里把网络设备驱动的配置过程放到了总线的probe接口中来实现,本质上没有区别。

/*
* Search DM9000 board, allocate space and register it
*/
static int
dm9000_probe(struct platform_device *pdev)
{
struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);
struct board_info *db; /* Point a board information structure */
struct net_device *ndev;
const unsigned char *mac_src;
int ret = 0;
int iosize;
int i;
u32 id_val; if (!pdata) {
pdata = dm9000_parse_dt(&pdev->dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} /* Init network device */
ndev = alloc_etherdev(sizeof(struct board_info));
if (!ndev)
return -ENOMEM; SET_NETDEV_DEV(ndev, &pdev->dev); dev_dbg(&pdev->dev, "dm9000_probe()\n"); /* setup board info structure */
db = netdev_priv(ndev); db->dev = &pdev->dev;
db->ndev = ndev; spin_lock_init(&db->lock);
mutex_init(&db->addr_lock); INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work); db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (db->addr_res == NULL || db->data_res == NULL ||
db->irq_res == NULL) {
dev_err(db->dev, "insufficient resources\n");
ret = -ENOENT;
goto out;
} db->irq_wake = platform_get_irq(pdev, 1);
if (db->irq_wake >= 0) {
dev_dbg(db->dev, "wakeup irq %d\n", db->irq_wake); ret = request_irq(db->irq_wake, dm9000_wol_interrupt,
IRQF_SHARED, dev_name(db->dev), ndev);
if (ret) {
dev_err(db->dev, "cannot get wakeup irq (%d)\n", ret);
} else { /* test to see if irq is really wakeup capable */
ret = irq_set_irq_wake(db->irq_wake, 1);
if (ret) {
dev_err(db->dev, "irq %d cannot set wakeup (%d)\n",
db->irq_wake, ret);
ret = 0;
} else {
irq_set_irq_wake(db->irq_wake, 0);
db->wake_supported = 1;
}
}
} iosize = resource_size(db->addr_res);
db->addr_req = request_mem_region(db->addr_res->start, iosize,
pdev->name); if (db->addr_req == NULL) {
dev_err(db->dev, "cannot claim address reg area\n");
ret = -EIO;
goto out;
} db->io_addr = ioremap(db->addr_res->start, iosize); if (db->io_addr == NULL) {
dev_err(db->dev, "failed to ioremap address reg\n");
ret = -EINVAL;
goto out;
} iosize = resource_size(db->data_res);
db->data_req = request_mem_region(db->data_res->start, iosize,
pdev->name); if (db->data_req == NULL) {
dev_err(db->dev, "cannot claim data reg area\n");
ret = -EIO;
goto out;
} db->io_data = ioremap(db->data_res->start, iosize); if (db->io_data == NULL) {
dev_err(db->dev, "failed to ioremap data reg\n");
ret = -EINVAL;
goto out;
} /* fill in parameters for net-dev structure */
ndev->base_addr = (unsigned long)db->io_addr;
ndev->irq = db->irq_res->start; /* ensure at least we have a default set of IO routines */
dm9000_set_io(db, iosize); /* check to see if anything is being over-ridden */
if (pdata != NULL) {
/* check to see if the driver wants to over-ride the
* default IO width */ if (pdata->flags & DM9000_PLATF_8BITONLY)
dm9000_set_io(db, 1); if (pdata->flags & DM9000_PLATF_16BITONLY)
dm9000_set_io(db, 2); if (pdata->flags & DM9000_PLATF_32BITONLY)
dm9000_set_io(db, 4); /* check to see if there are any IO routine
* over-rides */ if (pdata->inblk != NULL)
db->inblk = pdata->inblk; if (pdata->outblk != NULL)
db->outblk = pdata->outblk; if (pdata->dumpblk != NULL)
db->dumpblk = pdata->dumpblk; db->flags = pdata->flags;
} #ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
db->flags |= DM9000_PLATF_SIMPLE_PHY;
#endif dm9000_reset(db); /* try multiple times, DM9000 sometimes gets the read wrong */
for (i = 0; i < 8; i++) {
id_val = ior(db, DM9000_VIDL);
id_val |= (u32)ior(db, DM9000_VIDH) << 8;
id_val |= (u32)ior(db, DM9000_PIDL) << 16;
id_val |= (u32)ior(db, DM9000_PIDH) << 24; if (id_val == DM9000_ID)
break;
dev_err(db->dev, "read wrong id 0x%08x\n", id_val);
} if (id_val != DM9000_ID) {
dev_err(db->dev, "wrong id: 0x%08x\n", id_val);
ret = -ENODEV;
goto out;
} /* Identify what type of DM9000 we are working on */ id_val = ior(db, DM9000_CHIPR);
dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val); switch (id_val) {
case CHIPR_DM9000A:
db->type = TYPE_DM9000A;
break;
case CHIPR_DM9000B:
db->type = TYPE_DM9000B;
break;
default:
dev_dbg(db->dev, "ID %02x => defaulting to DM9000E\n", id_val);
db->type = TYPE_DM9000E;
} /* dm9000a/b are capable of hardware checksum offload */
if (db->type == TYPE_DM9000A || db->type == TYPE_DM9000B) {
ndev->hw_features = NETIF_F_RXCSUM | NETIF_F_IP_CSUM;
ndev->features |= ndev->hw_features;
} /* from this point we assume that we have found a DM9000 */ /* driver system function */
ether_setup(ndev); ndev->netdev_ops = &dm9000_netdev_ops;
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
ndev->ethtool_ops = &dm9000_ethtool_ops; db->msg_enable = NETIF_MSG_LINK;
db->mii.phy_id_mask = 0x1f;
db->mii.reg_num_mask = 0x1f;
db->mii.force_media = 0;
db->mii.full_duplex = 0;
db->mii.dev = ndev;
db->mii.mdio_read = dm9000_phy_read;
db->mii.mdio_write = dm9000_phy_write; mac_src = "eeprom"; /* try reading the node address from the attached EEPROM */
for (i = 0; i < 6; i += 2)
dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i); if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
mac_src = "platform data";
memcpy(ndev->dev_addr, pdata->dev_addr, ETH_ALEN);
} if (!is_valid_ether_addr(ndev->dev_addr)) {
/* try reading from mac */ mac_src = "chip";
for (i = 0; i < 6; i++)
ndev->dev_addr[i] = ior(db, i+DM9000_PAR);
} if (!is_valid_ether_addr(ndev->dev_addr)) {
dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
"set using ifconfig\n", ndev->name); eth_hw_addr_random(ndev);
mac_src = "random";
} platform_set_drvdata(pdev, ndev);
ret = register_netdev(ndev); if (ret == 0)
printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n",
ndev->name, dm9000_type_to_char(db->type),
db->io_addr, db->io_data, ndev->irq,
ndev->dev_addr, mac_src);
return 0; out:
dev_err(db->dev, "not found (%d).\n", ret); dm9000_release_board(pdev, db);
free_netdev(ndev); return ret;
}

处理过程大致是

1、申请一个网络设备内存空间+驱动私有数据结构空间,存放顺序是net_device+priv_data。
2、初始化锁和delay_work等私有数据结构成员。
3、DM9000的驱动支持唤醒,所以中间这一段是唤醒中断的设置。
4、内核地址空间申请这里涉及request_mem_region和ioremap的区别的辨析。简单可以理解request_mem_region只是向内核通知这一点地址空间已经被占用但为建立MMU表项。
5、前面声明完地址使用后对特定地址进行MMU映射map包括控制地址和数据地址两部分。
6、调用dm9000_ser_io()初始化私有数据的读写接口。这里实际是执行的它dm9000_set_io(db, 2);最终实际绑定的函数接口和具体的SOC对网卡的接口方式有关。三星这里采用
的是将网卡作为类ROM处理所以读写就是如同读rom。写实际是iowrite16_rep()对应读取就是ioread16_rep();处理之外IO实现还可以有设备数据提供。
7、在此之前还没有对dm9000 网卡进行任何设置,接下来才开始配置网卡芯片。
8、复位网卡芯片dm9000_reset()。通过操作dm9000的控制寄存器实现。寄存器写入操作就是先写入寄存器地址在写入值。复位了两次
9、读取dm9000的芯片ID号,读取的过程很简单直接写入读写地址然后读数据寄存器。识别dm9000网卡类型保存在db->type。因为这个驱动支持多种dm9000类型的网卡。
10、如果是DM9000A和DM9000B网卡他们支持新的特性所以要在网络设备的特性标识中增加新的特性支持标志。
11、调用ether_setup初始化网络设备。
12、接下来就是绑定网络设备的操作接口;
       ndev->netdev_ops = &dm9000_netdev_ops;//网络设备功能层实现的内容具体后面分析
       ndev->watchdog_timeo = msecs_to_jiffies(watchdog);//超时管理的吧
        ndev->ethtool_ops = &dm9000_ethtool_ops;//网络工具功能接口
13、接下来的就是phy接口的配置和接口函数绑定了,dm9000还支持mii接口phy管理。
14、设置网卡MAC地址,这里有多种机制,平台数据,网卡自己的eeprom和随机。
15、最后就是注册网卡设备到系统了,并用私有数据替换设备platform_data。

这个过程只是网络设备接口层的注册的基本流程,具体的网络设备接口层下的功能层代码在第12步时后完成绑定,现在来看下具体的实现。

网络设备功能层

分析网络设备接口层的实现,DM9000的网卡实现了一下这些接口一个个来看都是怎么样的实现机制。

static const struct net_device_ops dm9000_netdev_ops = {
.ndo_open = dm9000_open,
.ndo_stop = dm9000_stop,
.ndo_start_xmit = dm9000_start_xmit,
.ndo_tx_timeout = dm9000_timeout,
.ndo_set_rx_mode = dm9000_hash_table,
.ndo_do_ioctl = dm9000_ioctl,
.ndo_change_mtu = eth_change_mtu,
.ndo_set_features = dm9000_set_features,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = dm9000_poll_controller,
#endif
};

dm9000_open
1、状态检查和初始化所需变量初始化然后初始化网卡dm9000_init_dm9000。就是一大堆dm9000网卡的设置看明白应该需要参考datasheet。需要知道的是他配置了发送完成中断和数据接受完成中断。
2、中断申请并绑定中毒action然后使能网卡中断。这里的中断函数回头再来细看。
3、调用netif_start_queue 激活发送queue,并启动delay_work工作,工作函数在probe中指定了。

dm9000_stop
1、停止delay_work
2、netif_stop_queue();
3、netif_carrier_off();
4、释放中断、关闭网卡。

dm9000_start_xmit()需要互斥
1、直接拷贝数局到DM9000的RAM中
2、统计信息更新
3、如果是网卡发送的第一个包则可以直接发送数据,否则将数据包排队。
4、释放上层传来的sk_buff,因为网卡驱动已经记录了必要信息。

dm9000_timeout
当消息发送超时时会被调用
1、读取寄存器并保存
2、调用netif_stop_queue(),重新初始化网卡并开启中断。
3、重启发送netif_wake_queue(dev);恢复寄存器。
未发现明显重发机制。

dm9000_hash_table
1、

dm9000_ioctl
1、

dm9000_set_features
1、修改网络设备特性标志。
2、修改硬件及寄存器配置。

eth_change_mtu
1、很简单直接修改net_device->mtu 成员

eth_validate_addr
1、按MAC地址的规则检查MAC地址信息返回非法与否。

eth_mac_addr
1、检查是否支持修改,检查新地址是否合法。
2、执行修改硬件相关,并拷贝到网络设备结构体。

dm9000_ethtool_ops 接口

static const struct ethtool_ops dm9000_ethtool_ops = {
.get_drvinfo = dm9000_get_drvinfo,
.get_settings = dm9000_get_settings,
.set_settings = dm9000_set_settings,
.get_msglevel = dm9000_get_msglevel,
.set_msglevel = dm9000_set_msglevel,
.nway_reset = dm9000_nway_reset,
.get_link = dm9000_get_link,
.get_wol = dm9000_get_wol,
.set_wol = dm9000_set_wol,
.get_eeprom_len = dm9000_get_eeprom_len,
.get_eeprom = dm9000_get_eeprom,
.set_eeprom = dm9000_set_eeprom,
};

这个接口比较简单基本就是对网络设备和驱动的属性信息的修改和获取。最后再来看看中断中的执行流程。

网卡中断

static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
board_info_t *db = netdev_priv(dev);
int int_status;
unsigned long flags;
u8 reg_save; dm9000_dbg(db, 3, "entering %s\n", __func__); /* A real interrupt coming */ /* holders of db->lock must always block IRQs */
spin_lock_irqsave(&db->lock, flags); /* Save previous register address */
reg_save = readb(db->io_addr); dm9000_mask_interrupts(db);
/* Got DM9000 interrupt status */
int_status = ior(db, DM9000_ISR); /* Got ISR */
iow(db, DM9000_ISR, int_status); /* Clear ISR status */ if (netif_msg_intr(db))
dev_dbg(db->dev, "interrupt status %02x\n", int_status); /* Received the coming packet */
if (int_status & ISR_PRS)
dm9000_rx(dev); /* Trnasmit Interrupt check */
if (int_status & ISR_PTS)
dm9000_tx_done(dev, db); if (db->type != TYPE_DM9000E) {
if (int_status & ISR_LNKCHNG) {
/* fire a link-change request */
schedule_delayed_work(&db->phy_poll, 1);
}
} dm9000_unmask_interrupts(db);
/* Restore previous register address */
writeb(reg_save, db->io_addr); spin_unlock_irqrestore(&db->lock, flags); return IRQ_HANDLED;
}

基本流程就是关中断、获取锁,然后读取寄存器当前状态并保存,清除网卡中断标志。
如果是接收到数据则执行dm9000_rx,如果是发送数据的ack则需要执行 dm9000_tx_done 等。
然后开中断、恢复寄存器。其中重点看看dm9000_rx和dm9000_tx_done。
dm9000_rx
1、读取网卡状态寄存器判断数据是否已经可以读取或是否出错。
2、从网卡RAM读取数据到内存。
3、包合法性检查,同时维护网络设备统计信息。
4、数据包合法,申请sk_buff 开始打包成sockt数据包,这里需要循环读直到数据全部被读取
5、检查是接收中断直接透穿到上层或者调用dm9000_rx在其中调用netif_rx(skb);向上层通报。反之当是发送完成中断则执行dm9000_tx_done。

重点看一下dm9000_tx_done

static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{
int tx_status = ior(db, DM9000_NSR); /* Got TX status */ if (tx_status & (NSR_TX2END | NSR_TX1END)) {
/* One packet sent complete */
db->tx_pkt_cnt--;
dev->stats.tx_packets++; if (netif_msg_tx_done(db))
dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status); /* Queue packet check & send */
if (db->tx_pkt_cnt > 0)
dm9000_send_packet(dev, db->queue_ip_summed, ======》此处联系前面发送过程中如果当前包还未发送完成时的处理就可以明白
db->queue_pkt_len);
netif_wake_queue(dev);
}
}

基本的执行流程就是:‘

1、获取网卡发送状态信息
2、根据状态前一个发送给完毕则开始发送下一个包,同时更新之前的的包的状态。

这里回头在看看发送时干了什么才能让发送连续起来,先不考虑超时问题。这个成员会因为新增需要发送的数据包而tx_pkt_cnt++,而在成功发送后tx_pkt_cnt--,所以地下这部分时发送的代码节选

if (db->tx_pkt_cnt == 1) {
dm9000_send_packet(dev, skb->ip_summed, skb->len);
} else {
/* Second packet */
db->queue_pkt_len = skb->len;
db->queue_ip_summed = skb->ip_summed;
netif_stop_queue(dev);
}

如果之前的数据已经发送完了,则不用关直接调用网卡的数据包发送;反之如果之前的数据还为发送完成则将当前数据包暂时挂起,此时对于网卡来说已经有两包数据需要发送了此时注意netif_stop_queue(),这个调用会阻止上层继续调用设备接口层的数据包发送接口所以上层会暂时向设备接口层请求数据发送。此时再到中断中的dm9000_tx_done中来db->tx_pkt_cnt--;之后再调用网卡的数据包发送接口将数据发送到网卡,之后netif_wake_queue()再次唤醒发送队列,此时上层就有可以调用设备接口层的dm9000_start_xmit()函数发送数据了。

Linux 驱动框架---dm9000分析的更多相关文章

  1. Linux 驱动框架---net驱动框架

    这一篇主要是学习网络设备驱动框架性的东西具体的实例分析可以参考Linux 驱动框架---dm9000分析 .Linux 对于网络设备的驱动的定义分了四层分别是网络接口层对上是IP,ARP等网络协议,因 ...

  2. Linux 驱动框架---i2c驱动框架

    i2c驱动在Linux通过一个周的学习后发现i2c总线的驱动框架还是和Linux整体的驱动框架是相同的,思想并不特殊比较复杂的内容如i2c核心的内容都是内核驱动框架实现完成的,今天我们暂时只分析驱动开 ...

  3. Linux 驱动框架---input子系统

    input 子系统也是作为内核的一个字符设备模块存在的,所以他也是字符设备自然也会有字符设备的文件接口.input子系统的注册过程主要分为两步,先注册了一个input class然后再注册一个字符设备 ...

  4. Linux 驱动框架---input子系统框架

    前面从具体(Linux 驱动框架---input子系统)的工作过程学习了Linux的input子系统相关的架构知识,但是前面的学习比较实际缺少总结,所以今天就来总结一下输入子系统的架构分层,站到远处来 ...

  5. Linux驱动框架之framebuffer驱动框架

    1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲 ...

  6. Linux驱动框架之misc类设备驱动框架

    1.何为misc设备 (1)misc中文名就是杂项设备\杂散设备,因为现在的硬件设备多种多样,有好些设备不好对他们进行一个单独的分类,所以就将这些设备全部归属于 杂散设备,也就是misc设备,例如像a ...

  7. spi驱动框架全面分析,从master驱动到设备驱动

    内核版本:linux2.6.32.2  硬件资源:s3c2440 参考:  韦东山SPI视频教程 内容概括:     1.I2C 驱动框架回顾     2.SPI 框架简单介绍     3.maste ...

  8. Linux 驱动框架---linux 设备

    Linux 设备 Linux驱动中的三大主要基础成员主要是设备,总线和驱动.今天先来从设备开始分析先把设备相关的数据结构放到这里方便后面看到来查,其中有些进行了简单的注释. struct device ...

  9. Linux 驱动框架---platform驱动框架

    Linux系统的驱动框架主要就是三个主要部分组成,驱动.总线.设备.现在常见的嵌入式SOC已经不是单纯的CPU的概念了,它们都会在片上集成很多外设电路,这些外设都挂接在SOC内部的总线上,不同与IIC ...

随机推荐

  1. 关于springboot2.X使用外部tomcat服务器进行部署的操作详细步骤

    1.修改pom.xml文件(4个地方) ①<packaging>war</packaging>将其中的jar该为war ②<dependency> <grou ...

  2. Linux安装Oracle数据库SQLPlus客户端

    安装 RPM包下载地址:https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html ...

  3. 夯实基础系列一:Java 基础总结

    前言 大学期间接触 Java 的时间也不短了,不论学习还是实习,都让我发觉基础的重要性.互联网发展太快了,各种框架各种技术更新迭代的速度非常快,可能你刚好掌握了一门技术的应用,它却已经走在淘汰的边缘了 ...

  4. GStreamer各个包构建

    GStreamer按功能.维护的标准化程度.依赖库的版权差异等分了若干个包(package),如 gstreamer, gst-plugins-base, gst-plugins-good, gst- ...

  5. code-server Command ' ' not found

    由于通过一些特殊的方式登录linux用户后,全局变量不会自动加载,需要在 vscode 的 bash terminal手动读取 输入 source /etc/profile 或者vim ~/.bash ...

  6. 等待 Redis 应答 Redis pipeline It's not just a matter of RTT

    小结: 1.When pipelining is used, many commands are usually read with a single read() system call, and ...

  7. 获取当前文件路径 import 原理 一般把模块组成的集合称为包(package)

    获取当前文件路径 testpath.py import sysprint(sys.path) [root@d mapReduceLog]# python testpath.py['/data/mapR ...

  8. 【Redis 分布式锁】(1)一把简单的“锁”

    原文链接:https://www.changxuan.top/?p=1230 在单体架构向分布式集群架构演进的过程中,项目中必不可少的一个功能组件就是分布式锁.在开发团队有技术积累的情况下,做为团队的 ...

  9. Spark程序使用Scala进行单元测试

    Spark程序使用Scala进行单元测试 1.Rdd测试 2.无返回值方法测试 3.测试私有方法 原文作者:大葱拌豆腐 原文地址:Spark程序进行单元测试-使用scala 1.Rdd测试 spark ...

  10. linux系统资源限制———ulimit命令

    简介 Linux ulimit命令用于控制shell程序的资源. ulimit为shell内建指令,可用来控制shell执行程序的资源 推荐:https://blog.csdn.net/skiwnc/ ...