Linux网卡驱动

时间:2024-01-18 14:01:20

<网络知识>

a:网络模型
              OSI模型               TCP模型
Linux网卡驱动
虽然OSI模型看着挺完美的,但是过于复杂,这样就会导致不实用,在Linux系统中用的是TCP模型。每一层都是一种协议,系统对要发送的数据进行层层数据封装,就像洋葱层层刨去还原数据。
<Linux网络驱动特点>
a:对于网络接口的常用文件操作(读,写等)是没有意义,在Linux系统中,网络驱动是唯一一个无法体现UNIX/linux

的一切皆是文件的思想,因为网络设备咩有对应的设备文件。并且网络接口函数都存在于自己的名字空间中(设备结构体中,有相应的指向ops的指针)。但是注意,当应用程序使用套接字的时候,任然使用"read""write"的调用但是这些调用作用于软件对象上,他们与网络接口完全不同。

<网络数据发送过程>
a:示意图如图所示Linux网卡驱动
(1)从应用层 write()->sock_write() (net/socket.c)->inet_write() (net/inet/af_inet.c)->tcp_write() (net/inet/tcp.c)真正处理。前两个如前所说只是作一些检查。在传输层进行处理所原因在于:数据的封装只有在传输层才行。
(2)tcp_write()函数完成数据的封装,将用户缓冲区赋值到内核缓冲区,封装到sk_buff结构。如果网络拥塞,则暂时缓存到 write_queue队列中,稍后发送;否则则可以不经过write_queue直接发送出去。传输层协议调用ip_queue_xmit()函数将数 据包发送到网络层进行处理。
(3)ip_queue_xmit()函数

对数据帧进行完善,调用dev_queue_xmit()函数将数据包送往链路层进行处理。同时将此数据包缓存到sock_send队列,保证可靠传输,
此时,数据包已经从write_queue中删除。write_queue:从用户层结束新的数据包,没有用ip_queue_xmit传输出去。而
send_queue则调用了ip_queue_xmit传输。ip_queue_xmit()直接调用dev_queue_xmit()函数进行发送。

(4)dev_queue_xmit()完成本层处理后,调用设备结构device的hard_start_xmit函数指针指向的具体硬件发送函数。对于
NE系列网络设备的ei_start_xmit()函数,其首先将数据从内核赋值到网卡设备硬件缓冲区,操作具体的硬件寄存器,最终完成发送。
<网卡驱动中的一些重要数据结构>
a:net_device{}中的部分重要数据
stuct net_device
{
//全局信息
char name[IFNAMSIZ];
/*设备名子. 如果名子由驱动设置, 包含一个 %d 格式串,register_netdev用一个数替换它来形成一个唯一的名子; 分配的编号从0 开始.*/
unsigned long state;
/*设备状态. 这个成员包括几个标志. 驱动正常情况下不直接操作这些标 志;相反, 提供了一套实用函数. 这些函数在我们进入驱动操作后马上讨论这些函数.*/
struct net_device *next; 
/*全局列表中指向下一个设备的指针. 这个成员驱动不能动*/
int (*init)(struct net_device *dev);
/*一个初始化函数. 如果设置了这个指针, 这个函数被 register_netdev调用来完成对 net_device结构的初始化. 大部分现代的网络驱动不再使用这个函数; 相反, 初始化在注册接口前进行.*/
//硬件信息
unsigned long rmem_end;
unsigned long rmem_start;
unsigned long mem_end;
unsigned long mem_start;
/*设备内存信息.
这些成员持有设备使用的共享内存的开始和结束地址. 如果设备有不同的接收和发送内存, mem 成员由发送内存使用, rmem 成员接收内存使用.
rmem 成员在驱动之外从不被引用. 惯例上, 设置 end 成员, 所以 end - start 是可用的板上内存的数量.*/
unsigned long base_addr;
/*网络接口的
I/O 基地址. 这个成员, 如同前面的, 由驱动在设备探测时赋值. ifconfig 目可用来显示或修改当前值. base_addr
可以当系统启动时在内核命令行中显式赋值( 通过 netdev=参数), 或者在模块加载时.这个成员, 象上面描述过的内存成员,
内核不使用它们.*/
unsigned char irq;
/*安排的中断号. 当接口被列出时 ifconfig 打印出 dev->irq 的值. 这个值常常在启动或者加载时间设置并且在后来由 ifconfig 打印.*/
unsigned char if_port;
/*在多端口设备中使用的端口. 例如, 这个成员用在同时支持同轴线(IF_PORT_10BASE2)和双绞线(IF_PORT_100BSAET)以太网连接. 完整的已知端口类型设置定义在 <linux/netdevie.h>.*/
unsigned char dma;
/*设备分配的 DMA 通道. 这个成员只在某些外设总线时有意义, 例如 ISA.它不在设备驱动自身以外使用, 只是为了信息目的( 在 ifconfig ) 中.*/
//接口信息
/*有关接口的大部分信息由
ether_setup 函数正确设置(或者任何其他对给定硬件类型适合的设置函数). 以太网卡可以依赖这个通用的函数设置大部分这些成员,但是
flags 和 dev_addr 成员是特定设备的, 必须在初始化时间明确指定.一些非以太网接口可以使用类似 ether_setup
的帮助函数.*/
void ltalk_setup(struct net_device *dev);
/*设置一个 LocalTalk 设备的成员*/
void fc_setup(struct net_device *dev);
/*初始化光通道设备的成员*/
void fddi_setup(struct net_device *dev);
/*配置一个光纤分布数据接口 (FDDI) 网络的接口*/
void hippi_setup(struct net_device *dev);
/*预备给一个高性能并行接口 (HIPPI) 的高速互连驱动的成员*/
void tr_setup(struct net_device *dev);
/*处理令牌环网络接口的设置大部分设备会归于这些类别中的一类. 如果你的是全新和不同的, 但是, 你需要手工赋值下面的成员*/
unsigned short hard_header_len;
/*硬件头部长度, 就是, 被发送报文前面在 IP 头之前的字节数, 或者别的协议信息. 对于以太网接口 hard_header_len 值是 14 (ETH_HLEN).
unsigned mtu;
/*最大传输单元 (MTU). 这个成员是网络层用作驱动报文传输. 以太网有一个 1500 字节的 MTU (ETH_DATA_LEN). 这个值可用 ifconfig 改变.*/
unsigned long tx_queue_len;
/*设备发送队列中可以排队的最大帧数. 这个值由 ether_setup 设置为1000, 但是你可以改它. 例如, plip 使用 10 来避免浪费系统内存( 相比真实以太网接口, plip 有一个低些的吞吐量).*/
unsigned short type;
/*接口的硬件类型. 这个 type 成员由 ARP 用来决定接口支持什么样的硬件地址. 对以太网接口正确的值是 ARPHRD_ETHER, 这是由 ether_setup设置的值. 可认识的类型定义于 <linux/if_arp.h>.*/
unsigned char addr_len;
unsigned char broadcast[MAX_ADDR_LEN];
unsigned char dev_addr[MAX_ADDR_LEN];
/*硬件
(MAC) 地址长度和设备硬件地址. 以太网地址长度是 6 个字节( 我们指的是接口板的硬件 ID ), 广播地址由 6 个 0xff
字节组成;ether_setup 安排成正确的值. 设备地址, 另外, 必须以特定于设备的方式从接口板读出, 驱动应当将它拷贝到
dev_addr. 硬件地址用来产生正确的以太网头, 在报文传递给驱动发送之前. snull 设备不使用物理接口,它创造自己的硬件接口.*/
unsigned short flags;
int features;
/*接口标志(下面详述)这个
flags 成员是一个位掩码, 包括下面的位值. IFF_ 前缀代表 "interfaceflags". 有些标志由内核管理,
有些由接口在初始化时设置来表明接口的能力和其他特性. 有效的标志, 对应于
<linux/if.h>(详情见/linux内核驱动/网络接口标志*/
//设备方法
如同在字符和块驱动的一样,
每个网络设备声明能操作它的函数. 本节列出能够对网络接口进行的操作. 有些操作可以留作 NULL, 别的常常是不被触动的, 因为
ether_setup 给它们安排了合适的方法.网络接口的设备方法可分为 2 组: 基本的和可选的. 基本方法包括那些必需的
能够使用接口的; 可选的方法实现更多高级的不是严格要求的功能.*/
基本方法:
int (*open)(struct net_device *dev);
/*打开接口. 任何时候 ifconfig 激活它, 接口被打开. open 方法应当注册它需要的任何系统资源( I/O 口, IRQ, DMA, 等等), 打开硬件, 进行任何别的你的设备要求的设置.*/
int (*stop)(struct net_device *dev);
/*停止接口. 接口停止当它被关闭. 这个函数应当恢复在打开时进行的操作.*/
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
/*起始报文的发送的方法. 完整的报文(协议头和所有)包含在一个 socket缓存区( sk_buff ) 结构. socket 缓存在本章后面介绍.*/
int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr,void *saddr, unsigned len);
/*用之前取到的源和目的硬件地址来建立硬件头的函数(在hard_start_xmit
前调用). 它的工作是将作为参数传给它的信息组织成一个合适的特定于设备的硬件头. eth_header 是以太网类型接口的缺省函数,
ether_setup 针对性地对这个成员赋值.*/
int (*rebuild_header)(struct sk_buff *skb);
/*用来在 ARP 解析完成后但是在报文发送前重建硬件头的函数. 以太网设备使用的缺省的函数使用 ARP 支持代码来填充报文缺失的信息.*/
void (*tx_timeout)(struct net_device *dev);
/*由网络代码在一个报文发送没有在一个合理的时间内完成时调用的方法,可能是丢失一个中断或者接口被锁住. 它应当处理这个问题并恢复报文发送.*/
struct net_device_stats *(*get_stats)(struct net_device *dev);
/*任何时候当一个应用程序需要获取接口的统计信息, 调用这个方法. 例如,当 ifconfig 或者 netstat -i 运行时. snull 的一个例子实现在"统计信息"一节中介绍.*/
int (*set_config)(struct net_device *dev, struct ifmap *map);
/*改变接口配置. 这个方法是配置驱动的入口点. 设备的 I/O 地址和中断号可以在运行时使用 set_config 来改变. 这种能力可由系统管理员在接口没有探测到时使用. 现代硬件正常的驱动一般不需要实现这个方法.*/
可选操作:
int weight;
int (*poll)(struct net_device *dev; int *quota);
/*由适应 NAPI 的驱动提供的方法, 用来在查询模式下操作接口, 中断关闭着. NAPI ( 以及 weight 成员) 在"接收中断缓解"一节中涉及.*/
void (*poll_controller)(struct net_device *dev);
/*在中断关闭的情况下, 要求驱动检查接口上的事件的函数. 它用于特殊的内核中的网络任务, 例如远程控制台和使用网络的内核调试.*/
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
/*处理特定于接口的 ioctl 命令. (这些命令的实现在"定制 ioclt 命令"一节中描述)相应的 net_device 结构中的成员可留为 NULL, 如果接口不需要任何特定于接口的命令.*/
void (*set_multicast_list)(struct net_device *dev);
/*当设备的组播列表改变和当标志改变时调用的方法. */
int (*set_mac_address)(struct net_device *dev, void *addr);
/*如果接口支持改变它的硬件地址的能力,
可以实现这个函数. 很多接口根本不支持这个能力. 其他的使用缺省的 eth_mac_adr
实现(在deivers/net/net_init.c). eth_mac_addr 只拷贝新地址到dev->dev_addr,
只在接口没有运行时作这件事. 使用 eth_mac_addr 的驱动应当在它们的 open 方法中自 dev->dev_addr
里设置硬件 MAC 地址.*/
int (*change_mtu)(struct net_device *dev, int new_mtu);
/*当接口的最大传输单元 (MTU) 改变时动作的函数. 如果用户改变 MTU 时驱动需要做一些特殊的事情, 它应当声明它的自己的函数; 否则, 缺省的会将事情做对. snull 有对这个函数的一个模板, 如果你有兴趣.*/
int (*header_cache) (struct neighbour *neigh, struct hh_cache *hh);
/*header_cache 被调用来填充 hh_cache 结构, 使用一个 ARP 请求的结果.几乎全部类似以太网的驱动可以使用缺省的 eth_header_cache 实现.*/
int (*header_cache_update) (struct hh_cache *hh, struct net_device *dev, unsigned char *haddr);
/*在响应一个变化中, 更新 hh_cache 结构中的目的地址的方法. 以太网设备使用 eth_header_cache_update.*/
int (*hard_header_parse) (struct sk_buff *skb, unsigned char *haddr);
/*hard_header_parse 方法从包含在 skb 中的报文中抽取源地址, 拷贝到haddr 的缓存区. 函数的返回值是地址的长度. 以太网设备通常使用eth_header_parse.*/
公用成员
/*结构 net_device 剩下的数据成员由接口使用来持有有用的状态信息. 有些是ifconfig 和 netstat 用来提供给用户关于当前配置的信息. 因此, 接口应当给这些成员赋值:*/
unsigned long trans_start;
unsigned long last_rx;
/*保存一个 jiffy 值的成员. 驱动负责分别更新这些值, 当开始发送和收到一个报文时. trans_start 值被网络子系统用来探测发送器加锁.last_rx 目前没有用到, 但是驱动应当尽量维护这个成员以备将来使用.*/
int watchdog_timeo;
/*网络层认为一个传送超时发生前应当过去的最小时间(按 jiffy 计算),调用驱动的 tx_timeout 函数.*/
void *priv;
/*filp->private_data 的对等者. 在现代的驱动里, 这个成员由alloc_netdev 设置, 不应当直接存取; 使用 netdev_priv 代替.*/
struct dev_mc_list *mc_list;
int mc_count;
/*处理组播发送的成员. mc_count 是 mc_list 中的项数目. 更多细节见"组播"一节.*/
spinlock_t xmit_lock;
int xmit_lock_owner;
/*xmit_lock
用来避免对驱动的 hard_start_xmit 函数多个同时调用.xmit_lock_owner 是已获得 xmit_lock 的 CPU
号. 驱动应当不改变这些成员的值.结构 net_device 中有其他的成员, 但是网络驱动用不着它们.*/  
}  

b:struct sk_buff{}

Linux系统中每个网络数据包都由一个套接字缓冲结构struct sk_buff 描述,即一个sk_buff结构就是一个网络包指向sk_buff指针通常被称作skb

struct sk_buff {

struct sk_buff*next;

struct sk_buff*prev;

ktime_ttstamp;

struct sock*sk;

struct net_device*dev;

charcb[48] __aligned(8);

unsigned long_skb_refdst;

#ifdef CONFIG_XFRM

structsec_path*sp;

#endif

unsigned intlen,

data_len;

__u16mac_len,

hdr_len;

union {

__wsumcsum;

struct {

__u16csum_start;

__u16csum_offset;

};

};

__u32priority;

kmemcheck_bitfield_begin(flags1);

__u8local_df:1,

cloned:1,

ip_summed:2,

nohdr:1,

nfctinfo:3;

__u8pkt_type:3,

fclone:2,

ipvs_property:1,

peeked:1,

nf_trace:1;

kmemcheck_bitfield_end(flags1);

__be16protocol;

void(*destructor)(struct sk_buff *skb);

#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)

struct nf_conntrack*nfct;

#endif

#ifdef NET_SKBUFF_NF_DEFRAG_NEEDED

struct sk_buff*nfct_reasm;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

struct nf_bridge_info*nf_bridge;

#endif

intskb_iif;

#ifdef CONFIG_NET_SCHED

__u16tc_index;/* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

__u16tc_verd;/* traffic control verdict */

#endif

#endif

__u32rxhash;

kmemcheck_bitfield_begin(flags2);

__u16queue_mapping:16;

#ifdef CONFIG_IPV6_NDISC_NODETYPE

__u8ndisc_nodetype:2;

#endif

__u8ooo_okay:1;

kmemcheck_bitfield_end(flags2);

#ifdef CONFIG_NET_DMA

dma_cookie_tdma_cookie;

#endif

#ifdef CONFIG_NETWORK_SECMARK

__u32secmark;

#endif

union {

__u32mark;

__u32dropcount;

};

__u16vlan_tci;

sk_buff_data_ttransport_header;

sk_buff_data_tnetwork_header;

sk_buff_data_tmac_header;

sk_buff_data_ttail;

sk_buff_data_tend;

unsigned char*head,*data;

unsigned inttruesize;

atomic_tusers;

};

head:用户指向数据包的开始

data:用于指向数据包载荷的开始

tail:用户指向数据包载荷的结尾

end:用于指向数据包的结尾

len:用于记录数据包包含的数据量

<部分重要函数>

(1)分配net_device

结构//调用函数alloc_etherdev()/alloc_etherdev()

(2)释放net_device

free_netdev()

(3)向linux系统注册网卡驱动

register_netdev()

(4)向Linux系统注销网卡驱动

unregister_netdev()

(5)为sk_buff分配内存,并为其关联数据到数据包负载缓冲区

dev_alloc_skb()

(6)释放sk_buff缓冲区
dev_kfree_skb()
(7)为数据包缓冲区和有效负载开头之间增加填充
skb_reserve() 
<网卡驱动实例>
typedef struct board_info {

 void __iomem *io_addr;/* 寄存器 I/O 基地址*/
void __iomem *io_data;/* 数据 I/O 基地址 */
u16 irq; /* IRQ */ u16 tx_pkt_cnt;
...
} board_info_t; static int dm9000_ioctl(struct net_device *dev, struct ifreq *req, int cmd){...} 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_eeprom_len = dm9000_get_eeprom_len,
.get_eeprom = dm9000_get_eeprom,
.set_eeprom = dm9000_set_eeprom,
}; /* 设置 DM9000 多播地址 */
static void dm9000_hash_table(struct net_device *dev) /* 看门狗超时,网络层将调用该函数 */
static void dm9000_timeout(struct net_device *dev)
{
...
netif_stop_queue(dev);
netif_wake_queue(dev);
...
} static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
...
/* 将发送数据包移至 DM9000 的 TX RAM */
writeb(DM9000_MWCMD, db->io_addr); (db->outblk)(db->io_data, skb->data, skb->len);
dev->stats.tx_bytes += skb->len;
...
} /* 数据发送完成 */
static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{
...
netif_wake_queue(dev);
} /* 接收数据并传递给上层 */
static void dm9000_rx(struct net_device *dev)
{
...
netif_rx(skb);
dev->stats.rx_packets++;
...
} static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
...
return IRQ_HANDLED;
} /* 打开网卡接口 */
static int dm9000_open(struct net_device *dev)
{
..
netif_start_queue(dev);
...
return ;
}
/* 从 phyxcer 读一个 word */
static int dm9000_phy_read(struct net_device *dev, int phy_reg_unused, int reg){...}; /* 向 phyxcer 写一个 word */
static void dm9000_phy_write(struct net_device *dev,int phyaddr_unused, int reg,int value)
{...} static int __devinit dm9000_probe(struct platform_device *pdev)
{
... /* Init network device */
ndev = alloc_etherdev(sizeof(struct board_info)); SET_NETDEV_DEV(ndev, &pdev->dev); ether_setup(ndev);
ndev->open = &dm9000_open;
ndev->hard_start_xmit = &dm9000_start_xmit;
ndev->tx_timeout = &dm9000_timeout;
ndev->stop = &dm9000_stop;
ndev->set_multicast_list = &dm9000_hash_table;
ndev->ethtool_ops = &dm9000_ethtool_ops;
ndev->do_ioctl = &dm9000_ioctl; #ifdef CONFIG_NET_POLL_CONTROLLER
ndev->poll_controller = &dm9000_poll_controller;
#endif db->msg_enable = NETIF_MSG_LINK;
...
db->mii.mdio_read = dm9000_phy_read;
db->mii.mdio_write = dm9000_phy_write; platform_set_drvdata(pdev, ndev);
ret = register_netdev(ndev); ...
} static int __devexit dm9000_drv_remove(struct platform_device *pdev)
{
struct net_device *ndev = platform_get_drvdata(pdev); platform_set_drvdata(pdev, NULL); unregister_netdev(ndev);
free_netdev(ndev); /* free device structure */ return ;
} static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
},
.probe = dm9000_probe,
.remove = __devexit_p(dm9000_drv_remove),
}; static int __init dm9000_init(void)
{
return platform_driver_register(&dm9000_driver);
} static void __exit dm9000_cleanup(void)
{
platform_driver_unregister(&dm9000_driver);
} module_init(dm9000_init);
module_exit(dm9000_cleanup); static struct resource ldd6410_dm9000_resource[] = {
[] = {
.start = 0x18000000,
.end = 0x18000000 + ,
.flags = IORESOURCE_MEM
},
[] = {
.start = 0x18000000 + 0x4,
.end = 0x18000000 + 0x7,
.flags = IORESOURCE_MEM
},
[] = {
.start = IRQ_EINT(),
.end = IRQ_EINT(),
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
} }; static struct dm9000_plat_data ldd6410_dm9000__platdata = {
.flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO__EEPROM,
.dev_addr = { 0x0, 0x16, 0xd4, 0x9f, 0xed, 0xa4 },
}; static struct platform__device ldd6410_dm9000 = {
.name = "dm9000",
.id = ,
.num_resources = ARRAY_SIZE(ldd6410_dm9000_resource),
.resource = ldd6410_dm9000_resource,
.dev = {
.platform_data = &ldd6410_dm9000_platdata,
}
};

<网络知识>

a:网络模型
              OSI模型               TCP模型
Linux网卡驱动
虽然OSI模型看着挺完美的,但是过于复杂,这样就会导致不实用,在Linux系统中用的是TCP模型。每一层都是一种协议,系统对要发送的数据进行层层数据封装,就像洋葱层层刨去还原数据。
<Linux网络驱动特点>
a:对于网络接口的常用文件操作(读,写等)是没有意义,在Linux系统中,网络驱动是唯一一个无法体现UNIX/linux

的一切皆是文件的思想,因为网络设备咩有对应的设备文件。并且网络接口函数都存在于自己的名字空间中(设备结构体中,有相应的指向ops的指针)。但是注意,当应用程序使用套接字的时候,任然使用"read""write"的调用但是这些调用作用于软件对象上,他们与网络接口完全不同。

<网络数据发送过程>
a:示意图如图所示Linux网卡驱动
(1)从应用层 write()->sock_write() (net/socket.c)->inet_write() (net/inet/af_inet.c)->tcp_write() (net/inet/tcp.c)真正处理。前两个如前所说只是作一些检查。在传输层进行处理所原因在于:数据的封装只有在传输层才行。
(2)tcp_write()函数完成数据的封装,将用户缓冲区赋值到内核缓冲区,封装到sk_buff结构。如果网络拥塞,则暂时缓存到 write_queue队列中,稍后发送;否则则可以不经过write_queue直接发送出去。传输层协议调用ip_queue_xmit()函数将数 据包发送到网络层进行处理。
(3)ip_queue_xmit()函数

对数据帧进行完善,调用dev_queue_xmit()函数将数据包送往链路层进行处理。同时将此数据包缓存到sock_send队列,保证可靠传输,
此时,数据包已经从write_queue中删除。write_queue:从用户层结束新的数据包,没有用ip_queue_xmit传输出去。而
send_queue则调用了ip_queue_xmit传输。ip_queue_xmit()直接调用dev_queue_xmit()函数进行发送。

(4)dev_queue_xmit()完成本层处理后,调用设备结构device的hard_start_xmit函数指针指向的具体硬件发送函数。对于
NE系列网络设备的ei_start_xmit()函数,其首先将数据从内核赋值到网卡设备硬件缓冲区,操作具体的硬件寄存器,最终完成发送。
<网卡驱动中的一些重要数据结构>
a:net_device{}中的部分重要数据
stuct net_device
{
//全局信息
char name[IFNAMSIZ];
/*设备名子. 如果名子由驱动设置, 包含一个 %d 格式串,register_netdev用一个数替换它来形成一个唯一的名子; 分配的编号从0 开始.*/
unsigned long state;
/*设备状态. 这个成员包括几个标志. 驱动正常情况下不直接操作这些标 志;相反, 提供了一套实用函数. 这些函数在我们进入驱动操作后马上讨论这些函数.*/
struct net_device *next; 
/*全局列表中指向下一个设备的指针. 这个成员驱动不能动*/
int (*init)(struct net_device *dev);
/*一个初始化函数. 如果设置了这个指针, 这个函数被 register_netdev调用来完成对 net_device结构的初始化. 大部分现代的网络驱动不再使用这个函数; 相反, 初始化在注册接口前进行.*/
//硬件信息
unsigned long rmem_end;
unsigned long rmem_start;
unsigned long mem_end;
unsigned long mem_start;
/*设备内存信息.
这些成员持有设备使用的共享内存的开始和结束地址. 如果设备有不同的接收和发送内存, mem 成员由发送内存使用, rmem 成员接收内存使用.
rmem 成员在驱动之外从不被引用. 惯例上, 设置 end 成员, 所以 end - start 是可用的板上内存的数量.*/
unsigned long base_addr;
/*网络接口的
I/O 基地址. 这个成员, 如同前面的, 由驱动在设备探测时赋值. ifconfig 目可用来显示或修改当前值. base_addr
可以当系统启动时在内核命令行中显式赋值( 通过 netdev=参数), 或者在模块加载时.这个成员, 象上面描述过的内存成员,
内核不使用它们.*/
unsigned char irq;
/*安排的中断号. 当接口被列出时 ifconfig 打印出 dev->irq 的值. 这个值常常在启动或者加载时间设置并且在后来由 ifconfig 打印.*/
unsigned char if_port;
/*在多端口设备中使用的端口. 例如, 这个成员用在同时支持同轴线(IF_PORT_10BASE2)和双绞线(IF_PORT_100BSAET)以太网连接. 完整的已知端口类型设置定义在 <linux/netdevie.h>.*/
unsigned char dma;
/*设备分配的 DMA 通道. 这个成员只在某些外设总线时有意义, 例如 ISA.它不在设备驱动自身以外使用, 只是为了信息目的( 在 ifconfig ) 中.*/
//接口信息
/*有关接口的大部分信息由
ether_setup 函数正确设置(或者任何其他对给定硬件类型适合的设置函数). 以太网卡可以依赖这个通用的函数设置大部分这些成员,但是
flags 和 dev_addr 成员是特定设备的, 必须在初始化时间明确指定.一些非以太网接口可以使用类似 ether_setup
的帮助函数.*/
void ltalk_setup(struct net_device *dev);
/*设置一个 LocalTalk 设备的成员*/
void fc_setup(struct net_device *dev);
/*初始化光通道设备的成员*/
void fddi_setup(struct net_device *dev);
/*配置一个光纤分布数据接口 (FDDI) 网络的接口*/
void hippi_setup(struct net_device *dev);
/*预备给一个高性能并行接口 (HIPPI) 的高速互连驱动的成员*/
void tr_setup(struct net_device *dev);
/*处理令牌环网络接口的设置大部分设备会归于这些类别中的一类. 如果你的是全新和不同的, 但是, 你需要手工赋值下面的成员*/
unsigned short hard_header_len;
/*硬件头部长度, 就是, 被发送报文前面在 IP 头之前的字节数, 或者别的协议信息. 对于以太网接口 hard_header_len 值是 14 (ETH_HLEN).
unsigned mtu;
/*最大传输单元 (MTU). 这个成员是网络层用作驱动报文传输. 以太网有一个 1500 字节的 MTU (ETH_DATA_LEN). 这个值可用 ifconfig 改变.*/
unsigned long tx_queue_len;
/*设备发送队列中可以排队的最大帧数. 这个值由 ether_setup 设置为1000, 但是你可以改它. 例如, plip 使用 10 来避免浪费系统内存( 相比真实以太网接口, plip 有一个低些的吞吐量).*/
unsigned short type;
/*接口的硬件类型. 这个 type 成员由 ARP 用来决定接口支持什么样的硬件地址. 对以太网接口正确的值是 ARPHRD_ETHER, 这是由 ether_setup设置的值. 可认识的类型定义于 <linux/if_arp.h>.*/
unsigned char addr_len;
unsigned char broadcast[MAX_ADDR_LEN];
unsigned char dev_addr[MAX_ADDR_LEN];
/*硬件
(MAC) 地址长度和设备硬件地址. 以太网地址长度是 6 个字节( 我们指的是接口板的硬件 ID ), 广播地址由 6 个 0xff
字节组成;ether_setup 安排成正确的值. 设备地址, 另外, 必须以特定于设备的方式从接口板读出, 驱动应当将它拷贝到
dev_addr. 硬件地址用来产生正确的以太网头, 在报文传递给驱动发送之前. snull 设备不使用物理接口,它创造自己的硬件接口.*/
unsigned short flags;
int features;
/*接口标志(下面详述)这个
flags 成员是一个位掩码, 包括下面的位值. IFF_ 前缀代表 "interfaceflags". 有些标志由内核管理,
有些由接口在初始化时设置来表明接口的能力和其他特性. 有效的标志, 对应于
<linux/if.h>(详情见/linux内核驱动/网络接口标志*/
//设备方法
如同在字符和块驱动的一样,
每个网络设备声明能操作它的函数. 本节列出能够对网络接口进行的操作. 有些操作可以留作 NULL, 别的常常是不被触动的, 因为
ether_setup 给它们安排了合适的方法.网络接口的设备方法可分为 2 组: 基本的和可选的. 基本方法包括那些必需的
能够使用接口的; 可选的方法实现更多高级的不是严格要求的功能.*/
基本方法:
int (*open)(struct net_device *dev);
/*打开接口. 任何时候 ifconfig 激活它, 接口被打开. open 方法应当注册它需要的任何系统资源( I/O 口, IRQ, DMA, 等等), 打开硬件, 进行任何别的你的设备要求的设置.*/
int (*stop)(struct net_device *dev);
/*停止接口. 接口停止当它被关闭. 这个函数应当恢复在打开时进行的操作.*/
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
/*起始报文的发送的方法. 完整的报文(协议头和所有)包含在一个 socket缓存区( sk_buff ) 结构. socket 缓存在本章后面介绍.*/
int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr,void *saddr, unsigned len);
/*用之前取到的源和目的硬件地址来建立硬件头的函数(在hard_start_xmit
前调用). 它的工作是将作为参数传给它的信息组织成一个合适的特定于设备的硬件头. eth_header 是以太网类型接口的缺省函数,
ether_setup 针对性地对这个成员赋值.*/
int (*rebuild_header)(struct sk_buff *skb);
/*用来在 ARP 解析完成后但是在报文发送前重建硬件头的函数. 以太网设备使用的缺省的函数使用 ARP 支持代码来填充报文缺失的信息.*/
void (*tx_timeout)(struct net_device *dev);
/*由网络代码在一个报文发送没有在一个合理的时间内完成时调用的方法,可能是丢失一个中断或者接口被锁住. 它应当处理这个问题并恢复报文发送.*/
struct net_device_stats *(*get_stats)(struct net_device *dev);
/*任何时候当一个应用程序需要获取接口的统计信息, 调用这个方法. 例如,当 ifconfig 或者 netstat -i 运行时. snull 的一个例子实现在"统计信息"一节中介绍.*/
int (*set_config)(struct net_device *dev, struct ifmap *map);
/*改变接口配置. 这个方法是配置驱动的入口点. 设备的 I/O 地址和中断号可以在运行时使用 set_config 来改变. 这种能力可由系统管理员在接口没有探测到时使用. 现代硬件正常的驱动一般不需要实现这个方法.*/
可选操作:
int weight;
int (*poll)(struct net_device *dev; int *quota);
/*由适应 NAPI 的驱动提供的方法, 用来在查询模式下操作接口, 中断关闭着. NAPI ( 以及 weight 成员) 在"接收中断缓解"一节中涉及.*/
void (*poll_controller)(struct net_device *dev);
/*在中断关闭的情况下, 要求驱动检查接口上的事件的函数. 它用于特殊的内核中的网络任务, 例如远程控制台和使用网络的内核调试.*/
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
/*处理特定于接口的 ioctl 命令. (这些命令的实现在"定制 ioclt 命令"一节中描述)相应的 net_device 结构中的成员可留为 NULL, 如果接口不需要任何特定于接口的命令.*/
void (*set_multicast_list)(struct net_device *dev);
/*当设备的组播列表改变和当标志改变时调用的方法. */
int (*set_mac_address)(struct net_device *dev, void *addr);
/*如果接口支持改变它的硬件地址的能力,
可以实现这个函数. 很多接口根本不支持这个能力. 其他的使用缺省的 eth_mac_adr
实现(在deivers/net/net_init.c). eth_mac_addr 只拷贝新地址到dev->dev_addr,
只在接口没有运行时作这件事. 使用 eth_mac_addr 的驱动应当在它们的 open 方法中自 dev->dev_addr
里设置硬件 MAC 地址.*/
int (*change_mtu)(struct net_device *dev, int new_mtu);
/*当接口的最大传输单元 (MTU) 改变时动作的函数. 如果用户改变 MTU 时驱动需要做一些特殊的事情, 它应当声明它的自己的函数; 否则, 缺省的会将事情做对. snull 有对这个函数的一个模板, 如果你有兴趣.*/
int (*header_cache) (struct neighbour *neigh, struct hh_cache *hh);
/*header_cache 被调用来填充 hh_cache 结构, 使用一个 ARP 请求的结果.几乎全部类似以太网的驱动可以使用缺省的 eth_header_cache 实现.*/
int (*header_cache_update) (struct hh_cache *hh, struct net_device *dev, unsigned char *haddr);
/*在响应一个变化中, 更新 hh_cache 结构中的目的地址的方法. 以太网设备使用 eth_header_cache_update.*/
int (*hard_header_parse) (struct sk_buff *skb, unsigned char *haddr);
/*hard_header_parse 方法从包含在 skb 中的报文中抽取源地址, 拷贝到haddr 的缓存区. 函数的返回值是地址的长度. 以太网设备通常使用eth_header_parse.*/
公用成员
/*结构 net_device 剩下的数据成员由接口使用来持有有用的状态信息. 有些是ifconfig 和 netstat 用来提供给用户关于当前配置的信息. 因此, 接口应当给这些成员赋值:*/
unsigned long trans_start;
unsigned long last_rx;
/*保存一个 jiffy 值的成员. 驱动负责分别更新这些值, 当开始发送和收到一个报文时. trans_start 值被网络子系统用来探测发送器加锁.last_rx 目前没有用到, 但是驱动应当尽量维护这个成员以备将来使用.*/
int watchdog_timeo;
/*网络层认为一个传送超时发生前应当过去的最小时间(按 jiffy 计算),调用驱动的 tx_timeout 函数.*/
void *priv;
/*filp->private_data 的对等者. 在现代的驱动里, 这个成员由alloc_netdev 设置, 不应当直接存取; 使用 netdev_priv 代替.*/
struct dev_mc_list *mc_list;
int mc_count;
/*处理组播发送的成员. mc_count 是 mc_list 中的项数目. 更多细节见"组播"一节.*/
spinlock_t xmit_lock;
int xmit_lock_owner;
/*xmit_lock
用来避免对驱动的 hard_start_xmit 函数多个同时调用.xmit_lock_owner 是已获得 xmit_lock 的 CPU
号. 驱动应当不改变这些成员的值.结构 net_device 中有其他的成员, 但是网络驱动用不着它们.*/  
}  

b:struct sk_buff{}

Linux系统中每个网络数据包都由一个套接字缓冲结构struct sk_buff 描述,即一个sk_buff结构就是一个网络包指向sk_buff指针通常被称作skb

struct sk_buff {

struct sk_buff*next;

struct sk_buff*prev;

ktime_ttstamp;

struct sock*sk;

struct net_device*dev;

charcb[48] __aligned(8);

unsigned long_skb_refdst;

#ifdef CONFIG_XFRM

structsec_path*sp;

#endif

unsigned intlen,

data_len;

__u16mac_len,

hdr_len;

union {

__wsumcsum;

struct {

__u16csum_start;

__u16csum_offset;

};

};

__u32priority;

kmemcheck_bitfield_begin(flags1);

__u8local_df:1,

cloned:1,

ip_summed:2,

nohdr:1,

nfctinfo:3;

__u8pkt_type:3,

fclone:2,

ipvs_property:1,

peeked:1,

nf_trace:1;

kmemcheck_bitfield_end(flags1);

__be16protocol;

void(*destructor)(struct sk_buff *skb);

#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)

struct nf_conntrack*nfct;

#endif

#ifdef NET_SKBUFF_NF_DEFRAG_NEEDED

struct sk_buff*nfct_reasm;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

struct nf_bridge_info*nf_bridge;

#endif

intskb_iif;

#ifdef CONFIG_NET_SCHED

__u16tc_index;/* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

__u16tc_verd;/* traffic control verdict */

#endif

#endif

__u32rxhash;

kmemcheck_bitfield_begin(flags2);

__u16queue_mapping:16;

#ifdef CONFIG_IPV6_NDISC_NODETYPE

__u8ndisc_nodetype:2;

#endif

__u8ooo_okay:1;

kmemcheck_bitfield_end(flags2);

#ifdef CONFIG_NET_DMA

dma_cookie_tdma_cookie;

#endif

#ifdef CONFIG_NETWORK_SECMARK

__u32secmark;

#endif

union {

__u32mark;

__u32dropcount;

};

__u16vlan_tci;

sk_buff_data_ttransport_header;

sk_buff_data_tnetwork_header;

sk_buff_data_tmac_header;

sk_buff_data_ttail;

sk_buff_data_tend;

unsigned char*head,*data;

unsigned inttruesize;

atomic_tusers;

};

head:用户指向数据包的开始

data:用于指向数据包载荷的开始

tail:用户指向数据包载荷的结尾

end:用于指向数据包的结尾

len:用于记录数据包包含的数据量

<部分重要函数>

(1)分配net_device

结构//调用函数alloc_etherdev()/alloc_etherdev()

(2)释放net_device

free_netdev()

(3)向linux系统注册网卡驱动

register_netdev()

(4)向Linux系统注销网卡驱动

unregister_netdev()

(5)为sk_buff分配内存,并为其关联数据到数据包负载缓冲区

dev_alloc_skb()

(6)释放sk_buff缓冲区
dev_kfree_skb()
(7)为数据包缓冲区和有效负载开头之间增加填充
skb_reserve() 
<网卡驱动实例>

2 typedef struct board_info {
3
4 void __iomem *io_addr;/* 寄存器 I/O 基地址*/
5 void __iomem *io_data;/* 数据 I/O 基地址 */
6 u16 irq; /* IRQ */
7
8 u16 tx_pkt_cnt;
9 ...
10 } board_info_t;
11
12 static int dm9000_ioctl(struct net_device *dev, struct ifreq *req, int cmd){...}
13
14 static const struct ethtool_ops dm9000_ethtool_ops = {
15 .get_drvinfo = dm9000_get_drvinfo,
16 .get_settings = dm9000_get_settings,
17 .set_settings = dm9000_set_settings,
18 .get_msglevel = dm9000_get_msglevel,
19 .set_msglevel = dm9000_set_msglevel,
20 .nway_reset = dm9000_nway_reset,
21 .get_link = dm9000_get_link,
22 .get_eeprom_len = dm9000_get_eeprom_len,
23 .get_eeprom = dm9000_get_eeprom,
24 .set_eeprom = dm9000_set_eeprom,
25 };
26
27 /* 设置 DM9000 多播地址 */
28 static void dm9000_hash_table(struct net_device *dev)
29
30 /* 看门狗超时,网络层将调用该函数 */
31 static void dm9000_timeout(struct net_device *dev)
32 {
33 ...
34 netif_stop_queue(dev);
35 netif_wake_queue(dev);
36 ...
37 }
38
39 static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
40 {
41 ...
42 /* 将发送数据包移至 DM9000 的 TX RAM */
43 writeb(DM9000_MWCMD, db->io_addr);
44

45 (db->outblk)(db->io_data, skb->data, skb->len);
46 dev->stats.tx_bytes += skb->len;
47 ...
48 }
49
50 /* 数据发送完成 */
51 static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
52 {
53 ...
54 netif_wake_queue(dev);
55 }
56
57 /* 接收数据并传递给上层 */
58 static void dm9000_rx(struct net_device *dev)
60 {
61 ...
62 netif_rx(skb);
63 dev->stats.rx_packets++;
64 ...
65 }
66
67 static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
68 {
69 ...
70 return IRQ_HANDLED;
71 }
72
73 /* 打开网卡接口 */
74 static int dm9000_open(struct net_device *dev)
75 {
76 ..
77 netif_start_queue(dev);
78 ...
79 return 0;
80 }
81 /* 从 phyxcer 读一个 word */
82 static int dm9000_phy_read(struct net_device *dev, int phy_reg_unused, int reg){...};
83 /* 向 phyxcer 写一个 word */
84 static void dm9000_phy_write(struct net_device *dev,int phyaddr_unused, int reg,int value)
{...}
87
88 static int __devinit dm9000_probe(struct platform_device *pdev)
90 {
91 ...
92
93 /* Init network device */
94 ndev = alloc_etherdev(sizeof(struct board_info));
95
96 SET_NETDEV_DEV(ndev, &pdev->dev);
97
98 ether_setup(ndev);
100 ndev->open = &dm9000_open;
101 ndev->hard_start_xmit = &dm9000_start_xmit;
102 ndev->tx_timeout = &dm9000_timeout;
103 ndev->stop = &dm9000_stop;
104 ndev->set_multicast_list = &dm9000_hash_table;
105 ndev->ethtool_ops = &dm9000_ethtool_ops;
106 ndev->do_ioctl = &dm9000_ioctl;
107
108 #ifdef CONFIG_NET_POLL_CONTROLLER
109 ndev->poll_controller = &dm9000_poll_controller;
110 #endif
111
112 db->msg_enable = NETIF_MSG_LINK;
113 ...
114 db->mii.mdio_read = dm9000_phy_read;
115 db->mii.mdio_write = dm9000_phy_write;
116
117 platform_set_drvdata(pdev, ndev);
118 ret = register_netdev(ndev);
119
120 ...
121 }
122
123 static int __devexit dm9000_drv_remove(struct platform_device *pdev)
125 {
126 struct net_device *ndev = platform_get_drvdata(pdev);
127
128 platform_set_drvdata(pdev, NULL);
129
130 unregister_netdev(ndev);
131 free_netdev(ndev); /* free device structure */
132
133 return 0;
134 }
135
136 static struct platform_driver dm9000_driver = {
137 .driver = {
138 .name = "dm9000",
139 .owner = THIS_MODULE,
140 },
141 .probe = dm9000_probe,
142 .remove = __devexit_p(dm9000_drv_remove),
143 };
144
145 static int __init dm9000_init(void)
146 {
147 return platform_driver_register(&dm9000_driver);
148 }
149
150 static void __exit dm9000_cleanup(void)
151 {
152 platform_driver_unregister(&dm9000_driver);
153 }
154
155 module_init(dm9000_init);
156 module_exit(dm9000_cleanup);
1 static struct resource ldd6410_dm9000_resource[] = {
2 [0] = {
3 .start = 0x18000000,
4 .end = 0x18000000 + 3,
5 .flags = IORESOURCE_MEM
6 },
7 [1] = {
8 .start = 0x18000000 + 0x4,
9 .end = 0x18000000 + 0x7,
10 .flags = IORESOURCE_MEM
11 },
12 [2] = {
13 .start = IRQ_EINT(7),
14 .end = IRQ_EINT(7),
15 .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
16 }
17
18 };
19
20 static struct dm9000_plat_data ldd6410_dm9000__platdata = {
21 .flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO__EEPROM,
22 .dev_addr = { 0x0, 0x16, 0xd4, 0x9f, 0xed, 0xa4 },
23 };
24
25 static struct platform__device ldd6410_dm9000 = {
26 .name = "dm9000",
27 .id = 0,
28 .num_resources = ARRAY_SIZE(ldd6410_dm9000_resource),
29 .resource = ldd6410_dm9000_resource,
30 .dev = {
31 .platform_data = &ldd6410_dm9000_platdata,
32 }
33 };