/*****************************网络设备驱动***********************************/
/*
*基础知识:
*(1)linux内核中的网络子系统被设计成完全与协议无关,该思想应用于网络协议(IP、IPX及其他协议)
* 和硬件协议中(以太网、令牌环等),内核与网络驱动程序之的交互,可能每次处理的是一个网络数据包;
* 协议隐藏在驱动程序之后,同时物理传输又被隐藏在协议之后。
*
*(2)linux网络接口没有/dev目录下的设备文件,网络接口存在于它们自己的名字空间,并导出一系列不同的操作。
*
*(3)linux网络划分为4层,依次是网络协议接口层、网络设备接口层、设备驱动功能层和网络设备与媒介层:
*
* 网络协议接口层:它向网络层协议提供统一的数据包收发接口,不论上层协议为ARP还是IP,
* 都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据
*
* 网络设备接口层:它向协议接口提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器
*
* 设备驱动功能层:它是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序
*
* 网络设备与媒介层:它完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被驱动功能层的函数物理上驱动
*
*------------------------------------------------------------------------
* 数据包发送 数据包接收 网络协议接口层
* dev_queue_xmit() netif_rx()
*------------------------------------------------------------------------
* ↓ ↑ 网络设备接口层
* struct net_device
*------------------------------------------------------------------------
* ↓ ↑ 设备驱动功能层
* 数据包发送 中断处理
* hard_start_xmit() (数据包接收)
*------------------------------------------------------------------------
* ↓ ↑ 网络设备与媒介层
* 网 络 物 理 设 备 媒 介
*------------------------------------------------------------------------
*
*(4)在网络世界中使用术语“octet”指一组8个的数据位,它是能为网络设备和协议所能理解的最小单位。
*
*(5)“协议头”是在数据包中的一系列字节,它将通过网络子系统的不同层,当一个应用程序通过TCP套接字发送一块数据时,
* 网络子系统将把数据块分割成若干数据包,并在数据包开头加入用来描述数据流类型的TCP协议头。下层协议将
* 在TCP协议头前接着添加IP协议头,用来指定数据包达到目的地址的路径,*如果数据包通过类以太网媒介,将继续在数据包前加入
* 以太网头,其包含的信息将由硬件来解释,网络驱动程序不必关心高层协议的协议头。
*
*(6)对网络接口来说,没有和主设备号及次设备号等价的东西,所以网络驱动程序不必请求这种设备号,相反,
* 驱动程序对每个新检测到得接口,向全局网络设备链表中插入一个数据结构,在设计具体的网络设备驱动程序时,
* 我们需要完成的主要工作是编写设备驱动功能层的相关函数来填充net_device数据结构的内容并将net_device注册入内核。
*/
--------------------------------struct net_devic----------------------------------------------
# include <linux/netdevice.h>
struct net_device //用来描述网络接口,该结构包含了一个kobject和引用计数,并且通过sysfs导出信息
{
/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* the interface.
*/
char name[IFNAMSIZ];
//(全局信息)网络设备名称,如果被驱动程序设置的名称中包含%d格式化字符串,register_netdev将使用一个数字替换它,
// 使之成为唯一的名字,分配的编号从0开始
/* device name hash chain */
struct hlist_node name_hlist;
/* snmp alias */
char *ifalias;
/*
* I/O specific fields
* FIXME: Merge these and struct ifmap into one
*/
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^硬件信息^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
unsigned long mem_end; /* shared mem end */
//设备内存信息,保存了设备使用的共享内存的结束地址
unsigned long mem_start; /* shared mem start */
//设备内存信息,保存了设备使用的共享内存的起始地址,根据约定,end成员的设置要保证end-start等于可用的板卡内存量
unsigned long base_addr; /* device I/O address */
//网络接口的基地址(网络设备I/O基地址),这个成员和前述成员类似,要在设备探测阶段赋值,
//ifconfig命令可显示或修改当前值,和前面的内存成员类似,内核不会使用该成员
unsigned int irq; /* device IRQ number */
//被赋予的中断号(设备使用的中断号),在列出接口时,ifconfig命令将打印dev->irq的值,
//这个值通常在引导或装载阶段设置,其后可利用ifconfig修改
/*
* Some hardware also needs these fields, but they are not
* part of the usual set specified in Space.c.
*/
unsigned char if_port; /* Selectable AUI, TP,..*/
//指定在多端口设备上使用哪个端口(比如:如果设备同时支持同轴电缆(IF_PORT_10BASE2)和双绞线(IF_PORT_10BASET)以太网连接时,
//可使用该成员。完整的已知端口类型在<linux/netdevice.h>/struct net_device_stats中定义为IF_PORT_UNKNOWN = 0,IF_PORT_10BASE2,
//IF_PORT_10BASET,IF_PORT_AUI,IF_PORT_100BASET,IF_PORT_100BASETX,IF_PORT_100BASEFX
unsigned char dma; /* DMA channel */
//为设备分配的DMA通道,该成员只对某些外设总线有用,比如ISA。除了用于显示信息(ifconfig命令)之外,不会在驱动程序之外使用这个成员
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
unsigned long state;
//设备状态,这个成员包含若干标志,驱动程序通常无需直接操作这些标志,相反内核提供了一组工具函数
struct list_head dev_list;
struct list_head napi_list;
/* Net device features */
unsigned long features;
//接口标志,其取值为下面的以NETIF_F开头的宏,该成员用于告诉内核设备是如何处理传出的数据,对于接收到的数据不使用它,因此需要单独设置它
/* 驱动程序设置net_device结构中的功能成员,以告诉内核该接口硬件的特殊功能 */
#define NETIF_F_SG 1 /* Scatter/gather IO. */
#define NETIF_F_FRAGLIST 64 /* Scatter/gather IO. */
//包括NETIF_F_SG在内,这两个标志控制了分散/聚集I/O的使用,如果一个数据包被分成了多个独立的内存段,而接口又能传输这样的数据包
//NETIF_F_FRAGLIST表明接口能够处理那些分成块的数据包,在2.6内核只有回环设备有此功能
#define NETIF_F_IP_CSUM 2 /* Can checksum TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM 4 /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM 8 /* Can checksum all the packets. */
//这些标志告诉内核,不要对通过接口传出系统的部分或者全部的数据包使用校验,如果接口仅仅能够校验IP数据包,则设置NETIF_F_IP_CSUM。
//如果该接口不需要校验,则设置NETIF_F_NO_CSUM,回环设备设置该标志
//如果硬件自己进行校验的话,则设置NETIF_F_HW_CSUM
#define NETIF_F_IPV6_CSUM 16 /* Can checksum TCP/UDP over IPV6 */
#define NETIF_F_HIGHDMA 32 /* Can DMA to high memory. */
//如果设备可以在高端内存使用DMA,设置该标志,如果不设置该标志,所有为驱动程序提供的数据包缓冲区将在低端内存中分配
#define NETIF_F_HW_VLAN_TX 128 /* Transmit VLAN hw acceleration */
#define NETIF_F_HW_VLAN_RX 256 /* Receive VLAN hw acceleration */
#define NETIF_F_HW_VLAN_FILTER 512 /* Receive filtering on VLAN */
#define NETIF_F_VLAN_CHALLENGED 1024 /* Device cannot handle VLAN packets */
//这些选项表示硬件支持802.1q VLAN数据包,对VLAN的支持已经超过了本章范围,如果设置不支持VLAN,设置NETIF_F_VLAN_CHALLENGED标志
#define NETIF_F_GSO 2048 /* Enable software GSO. */
//如果设备能够执行TCP分割卸载,则设置该标志,TSO是一个新的特性
#define NETIF_F_LLTX 4096 /* LockLess TX - deprecated. Please */
/* do not use LLTX in new drivers */
#define NETIF_F_NETNS_LOCAL 8192 /* Does not change network namespaces */
#define NETIF_F_GRO 16384 /* Generic receive offload */
#define NETIF_F_LRO 32768 /* large receive offload */
#define NETIF_F_FCOE_CRC (1 << 24) /* FCoE CRC32 */
/* Segmentation offload features */
#define NETIF_F_GSO_SHIFT 16
#define NETIF_F_GSO_MASK 0x00ff0000
#define NETIF_F_TSO (SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
#define NETIF_F_UFO (SKB_GSO_UDP << NETIF_F_GSO_SHIFT)
#define NETIF_F_GSO_ROBUST (SKB_GSO_DODGY << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO_ECN (SKB_GSO_TCP_ECN << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO6 (SKB_GSO_TCPV6 << NETIF_F_GSO_SHIFT)
#define NETIF_F_FSO (SKB_GSO_FCOE << NETIF_F_GSO_SHIFT)
/* List of features with software fallbacks. */
#define NETIF_F_GSO_SOFTWARE (NETIF_F_TSO | NETIF_F_TSO_ECN | NETIF_F_TSO6)
#define NETIF_F_GEN_CSUM (NETIF_F_NO_CSUM | NETIF_F_HW_CSUM)
#define NETIF_F_V4_CSUM (NETIF_F_GEN_CSUM | NETIF_F_IP_CSUM)
#define NETIF_F_V6_CSUM (NETIF_F_GEN_CSUM | NETIF_F_IPV6_CSUM)
#define NETIF_F_ALL_CSUM (NETIF_F_V4_CSUM | NETIF_F_V6_CSUM)
/*
* If one device supports one of these features, then enable them
* for all in netdev_increment_features.
*/
#define NETIF_F_ONE_FOR_ALL (NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_ROBUST | \
NETIF_F_SG | NETIF_F_HIGHDMA | \
NETIF_F_FRAGLIST)
/* Interface index. Unique device identifier */
int ifindex;
int iflink;
struct net_device_stats stats;
//它保存接口统计信息
#ifdef CONFIG_WIRELESS_EXT
/* List of functions to handle Wireless Extensions (instead of ioctl).
* See <net/iw_handler.h> for details. Jean II */
const struct iw_handler_def * wireless_handlers;
/* Instance data managed by the core of Wireless Extensions. */
struct iw_public_data * wireless_data;
#endif
/* Management operations */
const struct net_device_ops *netdev_ops;
const struct ethtool_ops *ethtool_ops;
//它包含了ethtool支持的全部24个函数,如果驱动程序使用了MII层,可以使用mii_ethtool_gset和mii_ethtool_settings函数
//为了使ethtool能与设备配合工作,必须在net_device结构中设置指向ethtool_ops的结构指针,因此可以使用宏SET_ETHTOOL_OPS完成这一任务
/* Hardware header description */
const struct header_ops *header_ops;
unsigned int flags; /* interface flags (a la BSD) */
//网络接口标志,其包含以下值:
//IFF_UP:对驱动程序,该标志只读,当接口被激活并可以开始传输数据包时,内核设置该标志(在程序改变IFF_UP时,会调用open或stop设备函数,
//IFF_UP或其他任意标志被修改时,set_multicast_list函数将被调用,如果驱动程序需要在标志被修改时执行一些动作,则
//必须在set_multicast_list中完成这些动作)
//IFF_BROADCAST:该标志(为网络代码所维护)说明接口允许广播,以太网是可广播的
//IFF_DEBUG:表示调试模式,该标志可用来控制用于调试目的的大量printk调用
//IFF_LOOPBACK:该标志只能对回环设备进行设置,内核检查该标志以判断接口是否为回环设备,而不是将lo作为特殊的接口名称进行判断
//IFF_POINTOPOINT:该标志表明连接到点对点链路,这个标志由驱动程序设置,有时也由ifconfig设置,比如:plip和PPP驱动程序将设置该标志
//IFF_NOTRAILERS:linux不使用该标志,只是为了和BSD兼容
//IFF_RUNNING:该标志表示接口以及启动并且正在运行,该标志主要用于BSD兼容性,内核很少使用该标志,大多数网络驱动不需要关心该标志
//IFF_NOARP:该标志表明接口不能执行ARP,ARP是以太网的底层协议,它的作用是将IP地址转换为以太网的MAC(介质访问控制)地址
//IFF_PROMISC:设置该标志(由网络代码完成)将激活混杂模式。默认情况下,以太网接口使用一个硬件过滤器来确保它只接收广播数据包
//以及直接发送到接口硬件地址的数据包,像tcpdump这样的数据包侦听器会在接口上设置混杂模式,以便检索到通过传输介质
//的所有数据包
//IFF_MULTICAST:该标志由驱动程序设置,表示该接口能够进行组播发送,ether_setup默认设置 IFF_MULTICAST,因此驱动程序不支持组播
// 就必须在初始化时清除该标志
//IFF_ALLMULTI:该标志告诉接收所有的组播数据包,仅仅在IFF_MULTICAST被设置的情况下,内核在主机执行组播路由是设置该标志,
// IFF_ALLMULTI对接口来讲是只读的
//IFF_MASTER:该标志由负载均衡代码使用,接口驱动程序无需了解该标志
//FF_SLAVE: 该标志由负载均衡代码使用,接口驱动程序无需了解该标志
//IFF_PORTSEL:
//FF_AUTOMEDIA:以上两个标志表明设备能够在多种介质类型之间切换,在实际使用中,这两个标志都不会被内核使用
//IFF_DYNAMIC:该标志由驱动程序设置,表示接口地址可改变,现在内核不使用该标志
unsigned short gflags;
unsigned short priv_flags; /* Like 'flags' but invisible to userspace. */
unsigned short padded; /* How much padding added by alloc_netdev() */
unsigned char operstate; /* RFC2863 operstate */
unsigned char link_mode; /* mapping policy to operstate */
unsigned mtu; /* interface MTU value */
//最大传输单元(MTU),网络层使用该成员驱动数据包的传输,以太网的MTU是1500个octet(字节)即ETH_DATA_LEN
unsigned short type; /* interface hardware type */
//接口硬件类型,ARP使用type成员判断接口所支持的硬件地址类型,以太网接口的正确值是ARPHRD_ETHER,这也是初始化函数ether_setup
//所设置的值,可识别的值的类型在<linux/if_arp.h>中定义
unsigned short hard_header_len; /* hardware hdr length */
//(接口信息)硬件头的长度即数据包中位于IP头,或者其他协议信息之前的字节数目,对于以太网接口,hard_header_len=14=ETH_HLEN
/* extra head- and tailroom the hardware may need, but not in all cases
* can this be guaranteed, especially tailroom. Some cases also use
* LL_MAX_HEADER instead to allocate the skb.
*/
unsigned short needed_headroom;
unsigned short needed_tailroom;
struct net_device *master; /* Pointer to master device of a group,
* which this device is member of.
*/
/* Interface address info. */
unsigned char perm_addr[MAX_ADDR_LEN]; /* permanent hw address */
unsigned char addr_len; /* hardware address length */
//硬件(MAC)地址长度,以太网地址长度是6个octet(即接口板卡的硬件ID),ether_setup会进行正确的设置
unsigned short dev_id; /* for shared network cards */
spinlock_t addr_list_lock;
struct dev_addr_list *uc_list; /* Secondary unicast mac addresses */
int uc_count; /* Number of installed ucasts */
int uc_promisc;
struct dev_addr_list *mc_list; /* Multicast mac addresses */
//组播MAC地址
int mc_count; /* Number of installed mcasts */
//mc_list和mc_count用来处理组播传输,mc_count是mc_list所包含的项的数目
unsigned int promiscuity;
unsigned int allmulti;
/* Protocol specific pointers */
#ifdef CONFIG_NET_DSA
void *dsa_ptr; /* dsa specific data */
#endif
void *atalk_ptr; /* AppleTalk link */
void *ip_ptr; /* IPv4 specific data */
void *dn_ptr; /* DECnet specific data */
void *ip6_ptr; /* IPv6 specific data */
void *ec_ptr; /* Econet specific data */
void *ax25_ptr; /* AX.25 specific data */
struct wireless_dev *ieee80211_ptr; /* IEEE 802.11 specific data,
assign before registering */
/*
* Cache line mostly used on receive path (including eth_type_trans())
*/
unsigned long last_rx; /* Time of last Rx */
//保存一个jiffies值,驱动程序接收到数据包时负责更新这个值,last_rx当前未使用,但驱动应该维护这个成员,以便将来使用
/* Interface address info used in eth_type_trans() */
unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address, (before bcast
//存放设备的硬件地址(MAC,6个字节),设备地址必须从接口卡中以设备特有的方式读取,并且驱动程序要负责将该地址赋值到dev_addr,
//在数据包交给驱动程序之前,要利用硬件地址生成正确的以太网数据包头
because most packets are unicast) */
unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */
//存放广播地址,由6个0xff octet组成,ether_setup会进行正确的设置
struct netdev_queue rx_queue;
struct netdev_queue *_tx ____cacheline_aligned_in_smp;
/* Number of TX queues allocated at alloc_netdev_mq() time */
unsigned int num_tx_queues;
/* Number of TX queues currently active in device */
unsigned int real_num_tx_queues;
unsigned long tx_queue_len; /* Max frames per queue allowed */
//可在设备的传输队列中排队的最大帧数目,ether_setup将该成员设置为1000,但也可以修改它,
spinlock_t tx_global_lock;
/*
* One part is mostly used on xmit path (device)
*/
/* These may be needed for future network-power-down code. */
unsigned long trans_start; /* Time (in jiffies) of last Tx */
//记录最后的数据包开始发送时的时间戳,保存一个jiffies值,驱动程序在数据包传输开始时负责更新这个值,网络子系统使用它的值监测传输器是否被锁定
int watchdog_timeo; /* used by dev_watchdog() */
//记录最后一次接收到数据包时的时间戳,在网络层确定传输已经超时,并且调用驱动程序的rx_timeout函数之前的最小时间(jiffies为单位)
struct timer_list watchdog_timer;
/* Number of references to this device */
atomic_t refcnt ____cacheline_aligned_in_smp;
/* delayed register/unregister */
struct list_head todo_list;
/* device index hash chain */
struct hlist_node index_hlist;
struct net_device *link_watch_next;
//指向全局链表下一个设备的指针,驱动程序不应该修改该成员
/* register/unregister state machine */
enum { NETREG_UNINITIALIZED=0,
NETREG_REGISTERED, /* completed register_netdevice */
NETREG_UNREGISTERING, /* called unregister_netdevice */
NETREG_UNREGISTERED, /* completed unregister todo */
NETREG_RELEASED, /* called free_netdev */
NETREG_DUMMY, /* dummy device for NAPI poll */
} reg_state;
/* Called from unregister, can be used to call free_netdev */
void (*destructor)(struct net_device *dev);
#ifdef CONFIG_NETPOLL
struct netpoll_info *npinfo;
#endif
#ifdef CONFIG_NET_NS
/* Network namespace this network device is inside */
struct net *nd_net;
#endif
/* mid-layer private */
void *ml_priv;
//指向私有数据,在现代驱动程序中该成员由alloc_netdev设置,并且不能被直接访问,如要访问,需要使用netdev_priv函数
/* bridge stuff */
struct net_bridge_port *br_port;
/* macvlan */
struct macvlan_port *macvlan_port;
/* GARP */
struct garp_port *garp_port;
/* class/net/name entry */
struct device dev;
/* space for optional statistics and wireless sysfs groups */
struct attribute_group *sysfs_groups[3];
/* rtnetlink link ops */
const struct rtnl_link_ops *rtnl_link_ops;
/* VLAN feature mask */
unsigned long vlan_features;
/* for setting kernel sock attribute on TCP connection setup */
#define GSO_MAX_SIZE 65536
unsigned int gso_max_size;
#ifdef CONFIG_DCB
/* Data Center Bridging netlink ops */
struct dcbnl_rtnl_ops *dcbnl_ops;
#endif
#if defined(CONFIG_FCOE) || defined(CONFIG_FCOE_MODULE)
/* max exchange id for FCoE LRO by ddp */
unsigned int fcoe_ddp_xid;
#endif
#ifdef CONFIG_COMPAT_NET_DEV_OPS
/* 每个网络设备都要声明作用其上的函数,本节将给出可在网络接口上执行的操作,某些操作可保留为NULL,其他一些无需修改,因为ether_setup将赋予适当的方法 */
struct {
int (*init)(struct net_device *dev);
//(全局信息)初始化函数接口,如果这个指针被设置了,则register_netdev将调用该函数完成对net_device结构的初始化,大多数现代驱动程序
//不再使用这个函数了,相反,它们是在注册接口前完成初始化工作的
void (*uninit)(struct net_device *dev);
//不初始化函数接口
int (*open)(struct net_device *dev);
//打开函数接口,在ifconfig激活接口时,接口将被打开,open函数应该注册所有的系统资源(I/O端口、IRQ、DMA等等),打开硬件,并对设备执行其他所需要的位置
//成功返回0,失败返回负值
int (*stop)(struct net_device *dev);
//停止函数接口,当接口终止时应该被停止,在该函数中执行的操作与打开时执行的相反
//成功返回0,失败返回负值
int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev);
//该方法初始化数据包的传输,完整的数据包(协议头和数据)包含在一个套接字缓冲区(sk_buff)结构中(该函数会启动数据包的发送,
//当系统调用hard_start_xmit函数时,需要向其传入一个sk_buff结构体指针,以使得驱动程序能够获取从上层传递下来的数据包)
u16 (*select_queue)(struct net_device *dev,struct sk_buff *skb);
void (*change_rx_flags)(struct net_device *dev,int flags);
void (*set_rx_mode)(struct net_device *dev);
void (*set_multicast_list)(struct net_device *dev);
//当设备组播列表发生改变,或设备标志发生改变时,将调用该方法
int (*set_mac_address)(struct net_device *dev,void *addr);
//如果接口支持硬件地址的改变,则实现该方法,许多接口根本不支持这种功能,其他接口使用默认的eth_mac_addr实现,eth_mac_addr仅仅将新地址
//复制到dev->dev_addr中,而且只能在接口不工作时进行设置,使用eth_mac_addr的驱动程序应该在open函数中,用dev->dev_addr设置硬件的MAC地址
int (*validate_addr)(struct net_device *dev);
int (*do_ioctl)(struct net_device *dev,struct ifreq *ifr, int cmd);
//执行接口特有的ioctl命令,ifr指向内核空间的地址,其中保存有用户传递结构的副本,在do_ioctl返回时,该结构将复制回用户空间,这样
//驱动程序可使用私有命令来接收和返回数据
int (*set_config)(struct net_device *dev,struct ifmap *map);
//改变接口配置,该函数是配置驱动程序的入口点,利用该函数,可在运行中改变设备的I/O地址和中断号,在探测不到接口时,系统管理员可使用该函数
//现代硬件的驱动程序通常不需要实现该方法
int (*change_mtu)(struct net_device *dev, int new_mtu);
//在接口MTU(最大传输单元)改变时,该函数负责采取相应的动作,如果驱动程序在用户改变MTU时需要完成某些特定工作,则应该声明自己的函数,否
//则默认的函数可正确实现相关处理
int (*neigh_setup)(struct net_device *dev,struct neigh_parms *);
void (*tx_timeout) (struct net_device *dev);
//如果数据包的传输在合理的时间段内失败(超时),则假定丢失了中断或接口被锁住,这时网络代码将调用tx_timeout,它负责解决问题并重新开始数据包的传输
struct net_device_stats* (*get_stats)(struct net_device *dev);
//获取网络设备状态,当应用程序需要获得接口的统计信息时,将调用该函数,例如:在运行ifconfig或netstat -i命令将利用该方法
void (*vlan_rx_register)(struct net_device *dev,struct vlan_group *grp);
void (*vlan_rx_add_vid)(struct net_device *dev,unsigned short vid);
void (*vlan_rx_kill_vid)(struct net_device *dev,unsigned short vid);
#ifdef CONFIG_NET_POLL_CONTROLLER
void (*poll_controller)(struct net_device *dev);
//该函数在禁止中断的情况下要求驱动程序在接口检查事件,它被用于特定的内核网络服务中,比如远程控制台和内核网络调试(轮询方式时需使用,
//调用netif_rx_schedule函数,它负责在后来的某个时间点调用poll函数)
#endif
};
#endif
};
--------------------------------------------------------------------------------------------------------------
/*
* 对于具体的设备xxx,驱动工程师应该编写设备驱动功能层的函数,这些函数形如:xxx_open、xxx_stop、xxx_tx、xxx_get_stats、
* xxx_tx_timeout等函数,由于网络数据包的接收可由中断引发,所以可能包含xxx_interrupt
*和xxx_rx函数,前者完成中断类型判断等基本工作,后者则需要完成数据包的生成和递交上层等复杂工作。对于特定的设备,
*我们还可以定义其相关私有数据和操作,并封装为一个私有信息结构体xxx_private,让其指针被赋值给
*net_device的priv成员,xxx_private结构体中包含设备特殊的属性和操作、自旋所与信号量、定时器以及统计信息等,由工程师定义。
*
*
*1:分配和释放上述结构体:
*/
struct net_device *alloc_netdev(int sizeof_priv,const char *mask,void (*setup)(struct net_device *));
//分配net_device结构(函数本身会分配并且最后返回指向该结构的指针)
//1:sizeof_priv为驱动程序私有数据的大小,这个成员和net_device结构一同分配给网络设备
//2:mask是接口的名字,其在用户空间可见,这个名字可以使用%d的格式,内核将用下一个
// 可用的接口号代替%d(比如:eth%d)
//3:setup是一个初始化函数,用来设置net_device结构剩余的部分。
//4:必须要检查函数的返回值以确定分配工作成功完成
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
//该宏使用eth%d的形式指定分配给网络设备的名字,它提供了自己的初始化函数(ether_setup),用正确的值为以太网
//设备设置net_device中的许多成员,驱动程序只是在成功分配“私有数据”区后,直接做一些必须的初始化工作
void free_netdev(struct net_device *dev); //释放
--------------------------------------------------------------------------------------------------------------
/*
*2:初始化:
* net_device结构总是被聚集在一起的,不能像处理file_operations等结构那样在编译时进行初始化,
*在调用register_netdev前必须完成初始化,net_device结构既大又复杂,但幸运的是
*内核ether_setup函数中为这个结构设置了许多默认值(可以调用该函数来初始化其中大部分成员,
*然后自己初始化剩余的部分)。以太网可利用这个通用函数设置大部分成员,但flags和dev_addr成员是设备
*特有的,因此必须在初始化期间显式赋值。某些非以太网接口也可使用类似ther_setup这样的辅助函数
*/
void (*setup)(struct net_device *dev); //大部分接口相关的信息可由该函数正确设置
void ltalk_setup(struct net_device *dev); //设置LocalTalk设备的函数
void fc_setup(struct net_device *dev); //初始化光纤通道设备
void fddi_setup(struct net_device *dev); //配置光纤分布式数据网络接口
void hippi_setup(struct net_device *dev); //初始化高性能并行接口
void tr_setup(struct net_device *dev); //处理令牌环网络接口的设置函数
/*
*大多数设备都属于这些类中的一种,如果设备是全新的类,就需要手工初始化一下成员:hard_header_len、mtu、tx_queue_len、type、addr_len、broadcast[MAX_ADDR_LEN]、broadcast[MAX_ADDR_LEN]、flags、features
*
*设备初始化工作:
*1:进行硬件上的准备工作,检查网络设备是否存在,如果存在,则检测设备所使用的硬件资源,
硬件检测出了相应的资源,需要根据检测结果填充net_device结构体成员和私有数据(一:探测设备是否存在,可先假设存在设备xxx,
* 访问该设备,如果设备的表现与预期的一致,就确定设备存在,否则设备不存在;二:探测设备的具体硬件配置;
三:申请设备所需要的硬件资源,如用request_region()函数进行I/O端口的申请等)
*2:进行软件接口上准备,分配net_device结构体并对其数据和函数指针成员赋值
*3:获取设备私有数据指针并初始化其各成员,如果私有信息中包括自旋锁或信号量等并发或同步机制,则需要对其初始化
*4:参考dm9000.c中的dm9000_probe函数
*/
-------------------------------------------------------------------------------------------------------------
/*
*3:注册:
*/
int register_netdev(struct net_device *dev);
//返回值为非0则发生错误,需要注意的是注册之后,就可以调用驱动程序操作设备了,因此必须在初始化一切事情后再注册
void unregister_netdev(struct net_device *dev); //注销
void *netdev_priv(const struct net_device *dev); //获取设备的私有数据指针
-------------------------------------------------------------------------------------------------------------
/*
*4:网络设备的打开和关闭:
*/
int (*open)(struct net_device *dev);
int (*stop)(struct net_device *dev);
/*
* 驱动程序可在装载阶段或内核引导阶段探测接口,但是在接口能够发送数据包之前,内核必须打开接口并且赋予其地址,
* 内核可在响应ifconfig命令时打开或关闭一个接口。在使用ifconfig向接口赋予地址时要执行两个任务:
* 首先,它通过ioctl(SIOCSIFADDR)赋予地址,然后通过ioctl(SIOCSIFFLAGS)设置dev->flag中的IFF_UP标志以打开接口。
*
* 对设备而言,无需对ioctl(SIOCSIFADDR)做任何事情,内核不会调用任何驱动程序函数,
* 也就是说,该任务由内核来执行,与设备无关。而后一个命令ioctl(SIOCSIFFLAGS)会调用open函数。
*在接口关闭时ifconfig使用ioctl(SIOCSIFFLAGS)来清除IFF_UP标志,然后调用stop函数。
*
*1打开函数的工作::
*
*1:使能设备使用的硬件资源,申请I/O区域、中断、和DMA通道,在接口能够和外界通讯之前,要将硬件地址(MAC)从硬件设备复制到dev->dev_addr。
*2:一旦数据准备开始发送数据后,open中还应该启动接口的传输队列(允许接口接收传输数据包)
*: void netif_start_queue(struct net_device *dev);
//启动接口的传输队列(允许接口接收传输数据包)(告诉上层协议层往下层传送数据包)
*
*在stop函数中,完成与open函数相反的操作::
*1:停止传输数据包:
* void netif_stop_queue(struct net_device *dev);
//调用它来停止传输数据包,在接口关闭时必须调用该函数,但该函数也可以用来临时停止传输(此时网卡还在发送数据)(告诉上层协议层不要再往下层传送数据包)
*2:释放设备所使用的I/O区域、中断、和DMA通道
*/
------------------------------------------------------------------------------------------------------------
/*
*5:数据包的发送:
*/
int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev);
/*
* 无论何时内核要传输数据包,它都会调用驱动程序的struct net_device结构体成员hard_start_transmit函数
将数据放在外发队列。内核处理的每个数据包位于一个套接字缓冲区结构(sk_buff)中,这个结构的名称来自于表示网络连接
的unix抽象即套接字,尽管接口无需处理套接字,但每个网络数据包属于更高网络层的某个套接字,而且所有套接字的输入/输
出缓冲区都是sk_buff结构形成的链表,同一个sk_buff结构还用在主机网络数据以及所有的linux网络子系统,但是对接口而
言,套接字缓冲区仅仅是一个数据包而已。指向sk_buff的指针通常称为skb。传递给hard_start_transmit的套接字缓冲区
(sk_buff)包含了物理数据包(以它在介质上的格式),并拥有完整的传输层数据包头,接口无需修改要传输的数据,
skb->data指向要传输的数据包,而skb->len是以字节为单位的数据包的长度,如果驱动程序能够处理scatter/gather I/O,
形式将变得有些复杂。每个接口的传输函数hard_start_transmit都必须根据其驱动的特有硬件实现这段代码。
如果hard_start_transmit执行成功应该返回0,此时负责传输数据包的驱动程序要尽量保证传输的正常进行,而且
完毕后必须释放skb,返回一个非0值表示此次传输失败,内核将会重试传输,在这种情况下,驱动程序
应该在解决掉导致失败的原因前停止队列
*控制并发传输:
* hard_start_transmit函数可通过一个自旋锁获得并发调用时的保护。但是在该函数函数后,有可能再次被调用,
当软件指示硬件开始传输数据包后,该函数返回,但是硬件传输可能尚未结束。另一方便,实际的硬件接口却是异步传输数据包的,
而且可用来保存外发数据包的存储空间有限,在内存耗尽时,驱动程序需要告诉网络系统在硬件能够接受数据之前,不能启动其他
的数据包传输,调用netif_stop_queue可完成这一通知,前面在停止队列时介绍过这个函数。在驱动程序停止队列之后,它必须
在将来的某个时刻,当它能够再次接受数据包的传输时,重新启动该队列,为此可调用下面函数:
*/
void netif_wake_queue(struct net_device *dev);
//唤醒传输队列,这个函数除了通知网络系统可再次开始传输数据包以外,和netif_start_queue函数一样
/*
* 许多现代的网络接口在传输多个数据包时,维护一个内部的队列,这样可以获得最好的网络性能。这种设备的网络
驱动程序在任意时刻都可支持多个外发传输数据包,但不管设备是否支持多个外发传输数据包,设备内存都会
*被填满,一旦设备内存填充到容不下最大可能的数据包的时候,驱动程序应该停止队列,直到空间可用为止
*
*如果想从其他地方而不是hard_start_transmit函数中禁止数据包的发送,则可调用下面的函数:
*/
void netif_tx_disable(struct net_device *dev);
//该函数的行为与netif_stop_queue类似,但它还确保了在返回时,在其他的cpu上没有运行hard_start_transmit函数
/*
*数据包的发送工作:
*1:网络驱动程序从上层协议传递过来的sk_buff参数获得数据包的有效数据和长度,将有效数据放入缓冲区
*
*2:对于以太网,如果有效数据的长度小于以太网冲突检测所要求数据帧的最小长度ETN_ZLEN,则该临时缓冲区末尾填充0
*
*3:设置硬件的寄存器,驱使网络设备进行数据发送操作
*
*4:当发送队列为满或其他原因来不及发送当前上层传下来的包,则调用netif_stop_queue函数阻止上层继续向网络驱动
传递数据包,当忙于发送的数据包被发送完成后,在发送完成中断处理函数中,应该调用netif_wake_queue
* 唤醒被阻塞的上层协议以启动它继续向网络设备驱动发送数据包。
*/
-------------------------------------------------------------------------------------------------------------
/*
*6:传输超时:
*/
void (*tx_timeout) (struct net_device *dev);
/*
* 大部分处理实际硬件的驱动程序必须能够应付硬件偶尔不能正确的响应的问题,接口也许会忘记它在做什么,
或者系统有可能丢失中断,许多驱动程序利用定时器处理这类问题,如果某个操作在定时器到期时还未完成,则认
为出现了问题,从本质上讲,网络系统是通过大量定时器控制的多个状态机的复杂组合。网络代码把检测超时作为
其常用操作之一,因此网络驱动程序无需自己检测这种问题,相反驱动程序只需要设置一个超时周期,并在
net_device结构的watchdog_timeo成员中设置,这个周期以jiffies为单位,对通常的传输延迟来说应该是足
够长了的,如果当前的系统时间超过设备的trans_start时间至少一个超时周期,网络层将最终调用驱动程序的
tx_timeout函数,这个函数的任务是完成解决超时问题而需要的任何工作,并确保正在的任何传输能够结束,驱动
程序丢失网络代码提交的套接字缓冲区.当传输超时发生时,驱动程序必须在接口统计信息中标记该错误,并要将设备
重置为一个合理的状态,以便传输新的数据包(包括填补丢失的中断、调用netif_wake_queue重新启动传输队列)
*/
-------------------------------------------------------------------------------------------------------------
/*
*7:数据包的接收:
*/
void xxx_rx(struct net_device *dev)//接收数据包处理函数原型,此函数被中断处理函数调用
/*
* 从网络上接收数据要比传输数据复杂一点,这是因为必须在原子上下文分配一个sk_buff并传递给上层处理,
网络驱动程序实现了两种模式接收数据包:中断驱动方式和轮询方式,大多数驱动程序实现了中断驱动技术,
一些宽带适配器的驱动程序也实现了轮询技术,接收处理函数xxx_rx()在硬件接收到数据包之后(数据包已经在计
算机内存中了),被中断处理函数调用(网卡芯片在接收到数据后会产生中断),接收处理函数xxx_rx()接收一个
指向数据的指针,以及数据包的长度,另外它还负责将数据包已经其他附加信息发送到上层的网络代码。
*xxx_rx()接收函数的设计步骤:
*1:判断中断类型,是发送完成中断还是接收中断。
*2:如果是接收中断则调用xxx_rx,在xxx_rx函数中分配一个保存数据包的缓冲区(skb_buff)和数据缓冲区,可用下面这个函数:*/
struct sk_buff *dev_alloc_skb(unsigned int length);
//length为数据长度,该函数以原子的优先权调用Kmalloc,因此可在中断期间安全使用,必须检查返回值
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
//此函数在接收函数中可能要用到,该函数刷新缓冲区内的数据末尾指针,并且返回新创建数据区的指针
/*
*3:读取接收到的数据到数据缓冲区
*
*4:在能够处理数据包之前,网络层必须知道数据包的一些信息,为此必须在将缓冲区传递到上层之前,对struct net_device
结构和skb的成员protocol正确赋值,可使用下面函数查找正确值来给protocol赋值:*/
__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev);
/*
*5:指定如何校验skb->ip_summed,其取值有: CHECKSUM_HW:设备已经在硬件层求得了校验和CHECKSUM_NONE:校验和还未被验证,
而且该任务必须由系统软件完成,对新分配的缓冲区,这是默认策略
* CHECKSUM_UNNECESSARY:不进行任何校验和的计算
*6:驱动程序更新其统计计数器,以记录已接收到每个数据包,统计结构包含若干成员,最重要的有rx_packets、rx_bytes、
tx_packets、tx_bytes ,其中包含了已接收和已发送的数据包个数以及已传输的总量,
* 详情见后面的统计信息章节
*7:最后调用下面函数来将套接字缓冲区传递给上层软件处理:*/
int netif_rx(struct sk_buff *skb);
//它返回0(NET_RX_SUCCESS)表示数据包已经被成功接收,其他值表示失败(NET_RX_CN_LOW、NET_RX_CN_MOD、NET_RX_CN_HIGH由
低到高表示网络子系统的拥堵状况,NET_RX_DROP表示对数据包的丢失),大多数驱动忽略该函数的返回值
---------------------------------------------------------------------------------------------------------------
/*
*8:中断处理例程:
* 大多数硬件接口通过中断处理例程来控制,接口在两种可能的事件下中断处理器:新数据包到达或者外发数据包的传输已经完成。
网络接口还能产生中断以通知错误的产生、连接状态等情况,通常中断通过检查物理设备中的状态寄存器,以区分新数据包到达中断和
数据传输完毕中断。
*
*网络中断处理程序的设计步骤:
*1:通过读取网络设备确定是新数据包到达中断还是数据传输完毕中断
*2:若是新数据包到达中断则调用接收程序
*3:若是数据传输完毕中断则调用数据传输完毕处理程序(包更新统计信息、释放skb_buff)*/
dev_kfree_skb(struct sk_buff *skb);
//释放skb_buff,如果知道代码不在中断上下文中运行则调用该函数
void dev_kfree_skb_irq(struct sk_buff *skb);
//如果要在中断处理例程中释放skb_buff,则使用该函数,此时使用该函数能优化性能
void dev_kfree_skb_any(struct sk_buff *skb);
//释放skb_buff,如果相关代码既可以在中断上下文中运行,也能在非中断上下文中运行,使用该函数
---------------------------------------------------------------------------------------------------------------
/*
*9:链路状态的改变
* 网络连接要和本地系统之外的外界打交道,因此经常会受到外部事件的影响,而这些事件又可能是
瞬时发生的。网络子系统需要了解网络链路是否正常工作,因而提供了驱动程序可利用的几个函数(大多数涉及实际的物理连接的
*网络技术提供载波状态信息,载波的存在意味着硬件功能是正常的)
*/
int netif_carrier_ok(const struct net_device *dev);
//该函数用来检测当前的载波状态(和设备结构中反映的状态一样)
void netif_carrier_off(struct net_device *dev);
//如果驱动程序检测出设备上不存在载波,则应该调用该函数通知内核这一情况,某些驱动程序在发生重要的配置变化时
//(比如介质类型)也会调用该函数,一旦适配器完成了本身的重置,就会检测到新的载波,而数据可以重新开始
void netif_carrier_on(struct net_device *dev);
//当载波再次出现时,应调用该函数
---------------------------------------------------------------------------------------------------------------
/*
*10:套接字缓冲区(struct sk_buff)
* 套接字缓冲区用于在linux网络子系统中的各层之间传递数据,当发送数据包时,linux内核的网络处理模块必须建立
一个包含要传输的数据包的sk_buff,然后将sk_buff递交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备
发送,同样,当网络设备从网络媒介接收数据包后,它必须将接收到的数据转换为sk_buff数据结构并传递上层,
各层剥去相应的协议头直至交给用户
*/
----------------------------struct sk_buff ------------------------------
#include <linux/skbbuff.h>
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
struct sock *sk;
ktime_t tstamp;
struct net_device *dev; //接收和发送该缓冲区的设备
union {
struct dst_entry *dst;
struct rtable *rtable;
};
#ifdef CONFIG_XFRM
struct sec_path *sp;
#endif
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[48];
unsigned int len, //在数据包中全部数据的长度
data_len; //分割存储的数据片段的长度
__u16 mac_len,
hdr_len;
union {
__wsum csum;
struct {
__u16 csum_start;
__u16 csum_offset;
};
};
__u32 priority;
__u8 local_df:1,
cloned:1,
ip_summed:2,
//对数据包的校验策略,该成员由驱动程序对传入数据包进行设置
nohdr:1,
nfctinfo:3;
__u8 pkt_type:3,
//在发送过程中使用的数据包类型。驱动程序负责将其设置为:PACKET_HOST(该数据包是给我的)、PACKET_OTHERHOST
(该数据包不是给我的)、PACKET_BROADCAST(广播)、PACKET_MULTICAST(组播),以太网驱动程序不必显示修改pkt_type,
因为eth_type_trans会完成这个工作
fclone:2,
ipvs_property:1,
peeked:1,
nf_trace:1;
__be16 protocol;
void (*destructor)(struct sk_buff *skb);
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
struct nf_conntrack *nfct;
struct sk_buff *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge;
#endif
int iif;
__u16 queue_mapping;
#ifdef CONFIG_NET_SCHED
__u16 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u16 tc_verd; /* traffic control verdict */
#endif
#endif
#ifdef CONFIG_IPV6_NDISC_NODETYPE
__u8 ndisc_nodetype:2;
#endif
#if defined(CONFIG_MAC80211) || defined(CONFIG_MAC80211_MODULE)
__u8 do_not_encrypt:1;
__u8 requeue:1;
#endif
/* 0/13/14 bit hole */
#ifdef CONFIG_NET_DMA
dma_cookie_t dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
__u32 secmark;
#endif
__u32 mark;
__u16 vlan_tci;
sk_buff_data_t transport_header; //指向传输层报文头的指针
sk_buff_data_t network_header; //指向网络层报文头的指针
sk_buff_data_t mac_header; //指向链路层报文头的指针
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail; //为其尾部位置,skb->tail - skb->end=当前已使用的数据空间
sk_buff_data_t end; //指向tail可达到的最大地址
unsigned char *head, //为整个缓冲区的头指针,指向已分配的开头,skb->end - skb->head=可用缓冲区空间
*data; //为有效数据的头指针,
unsigned int truesize;
atomic_t users;
};
-----------------------------------------------------------------------------
//操作套接字缓冲区的函数:
//分配:
struct sk_buff *alloc_skb(unsigned int size,gfp_t priority);
//该函数分配一个缓冲区并初始化skb->data和skb->tail为skb->head
struct sk_buff *dev_alloc_skb(unsigned int length);
//该函数以GFP_ATOMIC优先级调用alloc_skb,并在skb->head和skb->data 之间保留一些空间,
//网络层使用这一数据空间进行优化,驱动程序不应该访问这个空间
//释放:
void kfree_skb(struct sk_buff *skb); //该函数由内核内部调用,驱动程序应该使用一种dev_kfree_skb形式的函数
dev_kfree_skb(struct sk_buff *skb); //释放skb_buff,如果知道代码不在中断上下文中运行则调用该函数
void dev_kfree_skb_irq(struct sk_buff *skb); //如果要在中断处理例程中释放skb_buff,则使用该函数,此时使用该函数能优化性能
void dev_kfree_skb_any(struct sk_buff *skb);
//释放skb_buff,如果相关代码既可以在中断上下文中运行,也能在非中断上下文中运行,使用该函数
//更新sk_buff结构中的tail和len成员,可用这些函数在缓冲区尾部添加数据。每个函数的返回值是skb->tail的先前值
//(换句话说,它指向刚刚建立的数据空间),驱动程序可以使用该返回值并通过调用
//memcopy(skb_put(...),data,len)来拷贝数据:
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
//该函数会检查放入缓冲区的数据,获取skb中数据的指针
unsigned char *__skb_put(struct sk_buff *skb, unsigned int len);
//该函数不会检查放入缓冲区的数据,获取skb中数据的指针
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
//该函数减少skb->data,并增加skb->len,除了数据添加在数据包的头部而不是尾部之外,其功能类似skb_put,
返回值执行刚刚创建的数据空间,在传输数据包之前,可使用该函数添加硬件头,该函数会检查可用空间
unsigned char *__skb_push(struct sk_buff *skb, unsigned int len);
//该函数减少skb->data,并增加skb->len,除了数据添加在数据包的头部而不是尾部之外,其功能类似skb_put,
返回值执行刚刚创建的数据空间,在传输数据包之前,可使用该函数添加硬件头,该函数不会检查可用空间
int skb_tailroom(const struct sk_buff *skb);
//该函数返回缓冲区中可用空间的大小,如果驱动程序在缓冲区中放入多于其能容纳的数据,则系统会出现panic
unsigned int skb_headroom(const struct sk_buff *skb);
//返回data之前可用的空间的数量
void skb_reserve(struct sk_buff *skb, int len);
//这个函数增加data和tail,该函数可在填充缓冲区之前保留报文头空间,大多数以太网接口在数据包之前保留2个字节,
这样IP头可在14字节的以太网头之后,在16字节边界上对齐
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len);
//从数据包头中删除数据,驱动程序无需使用这个函数
int skb_is_nonlinear(const struct sk_buff *skb);
//如果为使用分散/聚集I/O将skb分解为多个数据片段,则返回真值
unsigned int skb_headlen(const struct sk_buff *skb);
//返回skb第一个段的长度(该部分指向了skb->data)
//如果要在内核中直接访问非线性skb中的数据片段,这些函数负责映射和解除映射(由于使用了原子的kmap,因此不能同时映射多个数据片段):
void *kmap_skb_frag(const skb_frag_t *frag);
void kunmap_skb_frag(void *vaddr);
------------------------------------------------------------------------------------------------------------------------
/*
*11:参数设置与统计信息
*
*struct net_device的下面的成员完成这些任务
*/
int (*set_mac_address)(struct net_device *dev,void *addr);
int (*set_config)(struct net_device *dev,struct ifmap *map);
struct net_device_stats* (*get_stats)(struct net_device *dev);
struct net_device_stats
{
unsigned long rx_packets; /* total packets received */ //成功接收数据包的数量
unsigned long tx_packets; /* total packets transmitted */ //成功发送数据包的数量
unsigned long rx_bytes; /* total bytes received */ //接口接收的字节总量
unsigned long tx_bytes; /* total bytes transmitted */ //接口发送的字节总量
unsigned long rx_errors; /* bad packets received */ //错误接收的个数
unsigned long tx_errors; /* packet transmit problems */ //错误发送的个数
unsigned long rx_dropped; /* no space in linux buffers */ //在接收过程中丢弃的数据包数目
unsigned long tx_dropped; /* no space available in linux */ //在发送过程中丢弃的数据包的数目
unsigned long multicast; /* multicast packets received */ //接收到的组播数据包个数
unsigned long collisions; //因介质拥堵而导致的冲突个数
/* detailed rx_errors: */
unsigned long rx_length_errors;
unsigned long rx_over_errors; /* receiver ring buff overflow */
unsigned long rx_crc_errors; /* recved pkt with crc error */
unsigned long rx_frame_errors; /* recv'd frame alignment error */
unsigned long rx_fifo_errors; /* recv'r fifo overrun */
unsigned long rx_missed_errors; /* receiver missed packet */
/* detailed tx_errors */
unsigned long tx_aborted_errors;
unsigned long tx_carrier_errors;
unsigned long tx_fifo_errors;
unsigned long tx_heartbeat_errors;
unsigned long tx_window_errors;
/* for cslip etc */
unsigned long rx_compressed;
unsigned long tx_compressed;
};
int netif_running(const struct net_device *dev);
//判断设备是否正在运行,忙则返回非0,否则返回0,设备忙时是不允许设置MAC地址
/*
*当用户调用ioctl函数时,若命令为SIOCSIFMAP(控制台ifconfig就会引发这一调用),系统会调用set_config函数,
系统会向set_config函数传入struct ifmap结构体,该结构体中主要包含用户
*欲设置的设备要使用的I/O地址、中断等信息。
*
*net_device_stats统计信息的修改应该在设备驱动程序的与发送和接收相关的具体函数中完成,这些函数包括中断处理程序、
数据包发送函数、数据包发送超时函数和数据包接收相关函数等
*/
------------------------------------------------------------------------------------------------------------------
/*
*12::组播
*
*组播数据包是期望由多于一个主机、但不是所有主机接收的网络数据包,这一功能通过赋予针对一组主机的特殊硬件地址来完成,
发送到其中一个特殊地址的数据包,应该由该组中的所有主机接收到,对以太网而言,组播地址在目标地址的第一个字节的最低位
设置为1,而所有设备网卡将自己的硬件地址的相应位清零。
*
*对组播数据包的支持由如下几项组成:一个设备函数、一个数据结构以及若干设备标志位:
*/
void (*set_multicast_list)(struct net_device *dev);
//当设备组播列表发生改变,或设备标志发生改变时,将调用该方法,该设备还在dev->flags被修改时调用
//相关数据(struct net_device *dev):
struct dev_mc_list *dev->mc_list; //这是与设备关联的所有组播地址形成的一个链表
/*
struct dev_addr_list
{
struct dev_addr_list *next; //列表中的下一个地址
u8 da_addr[MAX_ADDR_LEN]; //硬件地址
u8 da_addrlen; //地址长度
u8 da_synced; //
int da_users; //用户的数量
int da_gusers; //组的数量
};
*/
int dev->mc_count; //链表的节点数目
unsigned int dev->flags;
//IFF_MULTICAST 除非驱动程序在dev->flags中设置这个标志,接口是不会请求处理组播数据包的。
虽然如此,但当dev->flags发生变化时,内核会调用驱动程序的 set_multicast_list
//IFF_ALLMULTI 这个标志由网络软件在dev->flags中设置,用来告诉驱动程序检索来自网络的所有组播数据
//IFF_PROMISC 当接口被设置为混杂模式时,在dev->flags中设置该标志,不管dev->mc_list中含有哪些主机地址,接口都应该接收所有的数据包
------------------------------------------------------------------------------------------------------------------
/*
*13:对介质无关接口的支持(Media Independent Interface ,MII):
* 它描述了以太网收发器是如何与网络控制器连接的,在市场上销售的大量产品都有这样的接口,如果要为MII控制器写一个驱动程序,
*需要仔细学习内核中通用MII支持层,为了使用通用MII层,需要包含头文件<linux/mii.h>
*无论是否使用全双工模式,都需要用收发器的物理ID填写mii_if_info结构,为使用mii_if_info结构,
*需要使用下面两个函数来实现与特定MII接口通信:
*/
int (*mdio_read)(struct net_device *dev, int phy_id, int location);
void (*mdio_write)(struct net_device *dev, int phy_id, int location, int val);
/*
*通用MII代码还提供了一套用于查询和修改收发器操作模式的函数,其中的驱动函数都被设计成与ethtool工具配合使用,
*参考<linux/mii.h>和drivers/net/mii.c以了解实现细节。
*
*ethtool是为系统管理员提供的用于控制网络接口的工具,只有当驱动程序支持ethtool时,使用ethtool才能控制包括速度、
*介质类型、双工操作、DMA设置、硬件校验、LAN唤醒操作在内的许多接口函数,
*
*可从网站http://sf.net/projects/gkernel/下载ethtool。
*
*在<linux/ethtool.h>中含有为支持ethtool的相关申明,其核心是一个ethtool_ops类型结构,它包含了ethtool支持的全部24个函数,
*如果驱动程序使用了MII层,可以使用mii_ethtool_gset和mii_ethtool_settings函数
*来填充struct net_device的成员get_settings和set_settings函数。
*
*为了使ethtool能与设备配合工作,必须在net_device结构中设置指向ethtool_ops的结构指针,因此可以使用宏SET_ETHTOOL_OPS
(在<linux/netdevice.h>中定义)完成这一任务,请注意即使以及关闭了接口,ethtool函数依然能够被调用。
*
*-------------------------------------------------------------------------
*网卡驱动程序设计可参考的程序:loopback.c、plip.c、e100.c
*/
/*************************end************************/
注:以上选自韦东山群答疑助手:沈朝平《Linux驱动程序学习笔记》!非常感谢!