嵌入式下的网络硬件接口
首先,嵌入式网络硬件分为两部分:
MAC 和 PHY
,大家都是通过看数据手册来判断一款 SOC
是否支持网络,如果一款芯片数据手册说自己支持网络,一般都是说的
这款 SOC 内置 MAC
,
MAC
类似
I2C
控制器、
SPI
控制器一样的外设。但是光有
MAC还不能直接驱动网络,还需要
另外一个芯片:PHY
,因此对于内置
MAC
的
SOC
,其外部必须搭配一个 PHY
芯片。
如果不内置MAC 则
SOC
内部没有网络
MAC
外设
没有内部 MAC
,那么可以找个
外置的 MAC 芯片
啊,不过一般这种外置的网络芯片都是
MAC+PHY 一体的
。比如三星
linux
开发板里面用的最多的
DM9000
,因为三星的芯片基本没有内部 MAC(
比如
S3C2440
、
S5PV210
,
4412
等
)
,所以三星的开发板都是通过外置的 DM9000
来完成有线网络功能
W5500。这个一般用于单片机领域
,单片机通过
SPI
接口与
W5500
进行通信,由于W5500 内置了硬件
TCP/IP
协议栈,因此单片机就不需要移植负责的软件协议栈
外置MAC芯片的缺点就是网络效率不高,因为一般芯片内置的 MAC
会有网络加速引擎,比如网络专用
DMA
,网络处理效率会很高。而且此类芯片网速都不快,基本就是
10/100M
。
SOC
内部集成网络
MAC
外设
一般常见的通用 SOC
都会集成网络
MAC
外设,比如
STM32F4/F7/H7
系列、
NXP
的
I.MX 系列,内部集成网络 MAC
的优点如下:
①、内部 MAC
外设会有专用的加速模块,比如专用的
DMA
,加速网速数据的处理。
②、网速快,可以支持 10/100/1000M
网速。
③、外接 PHY
可选择性多,成本低。
内部的 MAC
外设会通过
MII 或者 RMII 接口
来连接外部的
PHY
芯片,
MII/RMII
接口用来传输网络数据。另外主控需要配置或读取 PHY
芯片,也就是读写
PHY
的内部寄存器,所以还需要一个控制接口,叫做 MIDO
,
MDIO
很类似
IIC
,也是两根线,一根数据线叫做
MDIO
,一根时钟线叫做 MDC
。
I.MX6ULL 就有两个
10M/100M
的网络
MAC
外设,正点原子
ALPHA
开发板板载了两颗 PHY
芯片,型号为
LAN8720
。
MII/RMII 接口
MII
接口
MII 全称是
Media Independent Interface
,直译过来就是介质独立接口,它是
IEEE-802.3
定 义的以太网标准接口,MII
接口用于以太网
MAC
连接
PHY
芯片,连接示意图如图
69.1.2.1
所示
RMII
接口
RMII 全称是
Reduced Media Independent Interface
,翻译过来就是精简的介质独立接口,也 就是 MII
接口的精简版本。
RMII
接口只需要
7
根数据线,相比
MII
直接减少了
9
根,极大的方便了板子布线,RMII
接口连接
PHY
芯片的示意图如图
69.1.2.2
所示:
MDIO 接口
MDIO 全称是
Management Data Input/Output
,直译过来就是管理数据输入输出接口,是一 个
简单的两线串行接口
,
一根 MDIO 数据线,一根 MDC 时钟线
。驱动程序可以通过
MDIO
和 MDC 这两根线访问
PHY
芯片的任意一个寄存器。
MDIO
接口支持多达
32
个
PHY
。同一时刻内只能对一个 PHY
进行操作,那么如何区分这
32
个
PHY
芯片呢?和
IIC
一样,使用器件地址即可。同一 MDIO
接口下的所有
PHY
芯片,
其器件地址不能冲突,必须保证唯一
,具体器件地址值要查阅相应的 PHY
数据手册。因此,MAC
和外部
PHY
芯片进行连接的时候主要是
MII/RMII
和
MDIO
接口,另外可能还需要复位、中断等其他引脚。
RJ45 接口
网络设备是通过网线连接起来的,插入网线的叫做 RJ45
座
RJ45 座子上一般有两个灯,一个黄色
(
橙色
)
,一个绿色,绿色亮的话表示网络连接正常,黄色闪烁的话说明当前正在进行网络通信。这两个灯由 PHY 芯片控制,
PHY
芯片会有两个引脚来连接 RJ45
座上的这两个灯。内部
MAC+
外部
PHY+RJ45
座
(
内置网络变压器
)
就组成了一个完整的嵌入式网络接口硬件
PHY 芯片详解
PHY 是
IEEE 802.3
规定的一个标准模块,前面说了,
SOC
可以对
PHY
进行配置或者读取 PHY 相关状态,这个就需要
PHY
内部寄存器去实现了。
PHY
芯片寄存器地址空间为
5
位,地址 0~31
共
32
个寄存器,
IEEE
定义了
0~15
这
16
个寄存器的功能,
16~31
这
16
个寄存器由厂商自行实现。也就是说不管你用的哪个厂家的 PHY
芯片,其中
0~15
这
16
个寄存器是一模一样的。仅靠这 16
个寄存器是完全可以驱动起
PHY
芯片的,至少能保证基本的网络数据通信,
因此 Linux 内核有通用 PHY 驱动
PHY的前16个寄存器详解
Linux 内核网络驱动框架
net_device 结构体(注册与注销)
Linux 内核使用
net_device 结构体表示一个具体的网络设备,net_device 结构体定义在 include/linux/netdevice.h 中,net_device 是一个庞大的结构体
struct net_device {
char name[IFNAMSIZ];
struct hlist_node name_hlist;
char *ifalias;
/*
* I/O specific fields
* FIXME: Merge these and struct ifmap into one
*/
unsigned long mem_end;
unsigned long mem_start;
unsigned long base_addr;
int irq;
atomic_t carrier_changes;
/*
* Some hardware also needs these fields (state,dev_list,
* napi_list,unreg_list,close_list) but they are not
* part of the usual set specified in Space.c.
*/
unsigned long state;
struct list_head dev_list;
struct list_head napi_list;
struct list_head unreg_list;
struct list_head close_list;
struct list_head ptype_all;
struct list_head ptype_specific;
struct {
struct list_head upper;
struct list_head lower;
} adj_list;
struct {
struct list_head upper;
struct list_head lower;
} all_adj_list;
netdev_features_t features;
netdev_features_t hw_features;
netdev_features_t wanted_features;
netdev_features_t vlan_features;
netdev_features_t hw_enc_features;
netdev_features_t mpls_features;
int ifindex;
int group;
struct net_device_stats stats;
atomic_long_t rx_dropped;
atomic_long_t tx_dropped;
#ifdef CONFIG_WIRELESS_EXT
const struct iw_handler_def * wireless_handlers;
struct iw_public_data * wireless_data;
#endif
const struct net_device_ops *netdev_ops;
const struct ethtool_ops *ethtool_ops;
#ifdef CONFIG_NET_SWITCHDEV
const struct swdev_ops *swdev_ops;
#endif
const struct header_ops *header_ops;
unsigned int flags;
unsigned int priv_flags;
unsigned short gflags;
unsigned short padded;
unsigned char operstate;
unsigned char link_mode;
unsigned char if_port;
unsigned char dma;
unsigned int mtu;
unsigned short type;
unsigned short hard_header_len;
unsigned short needed_headroom;
unsigned short needed_tailroom;
/* Interface address info. */
unsigned char perm_addr[MAX_ADDR_LEN];
unsigned char addr_assign_type;
unsigned char addr_len;
unsigned short neigh_priv_len;
unsigned short dev_id;
unsigned short dev_port;
spinlock_t addr_list_lock;
unsigned char name_assign_type;
bool uc_promisc;
struct netdev_hw_addr_list uc;
struct netdev_hw_addr_list mc;
struct netdev_hw_addr_list dev_addrs;
#ifdef CONFIG_SYSFS
struct kset *queues_kset;
#endif
unsigned int promiscuity;
unsigned int allmulti;
/* Protocol specific pointers */
#if IS_ENABLED(CONFIG_VLAN_8021Q)
struct vlan_info __rcu *vlan_info;
#endif
#if IS_ENABLED(CONFIG_NET_DSA)
struct dsa_switch_tree *dsa_ptr;
#endif
#if IS_ENABLED(CONFIG_TIPC)
struct tipc_bearer __rcu *tipc_ptr;
#endif
void *atalk_ptr;
struct in_device __rcu *ip_ptr;
struct dn_dev __rcu *dn_ptr;
struct inet6_dev __rcu *ip6_ptr;
void *ax25_ptr;
struct wireless_dev *ieee80211_ptr;
struct wpan_dev *ieee802154_ptr;
#if IS_ENABLED(CONFIG_MPLS_ROUTING)
struct mpls_dev __rcu *mpls_ptr;
#endif
/*
* Cache lines mostly used on receive path (including eth_type_trans())
*/
unsigned long last_rx;
/* Interface address info used in eth_type_trans() */
unsigned char *dev_addr;
#ifdef CONFIG_SYSFS
struct netdev_rx_queue *_rx;
unsigned int num_rx_queues;
unsigned int real_num_rx_queues;
#endif
unsigned long gro_flush_timeout;
rx_handler_func_t __rcu *rx_handler;
void __rcu *rx_handler_data;
struct netdev_queue __rcu *ingress_queue;
unsigned char broadcast[MAX_ADDR_LEN];
#ifdef CONFIG_RFS_ACCEL
struct cpu_rmap *rx_cpu_rmap;
#endif
struct hlist_node index_hlist;
/*
* Cache lines mostly used on transmit path
*/
struct netdev_queue *_tx ____cacheline_aligned_in_smp;
unsigned int num_tx_queues;
unsigned int real_num_tx_queues;
struct Qdisc *qdisc;
unsigned long tx_queue_len;
spinlock_t tx_global_lock;
int watchdog_timeo;
#ifdef CONFIG_XPS
struct xps_dev_maps __rcu *xps_maps;
#endif
/* These may be needed for future network-power-down code. */
/*
* trans_start here is expensive for high speed devices on SMP,
* please use netdev_queue->trans_start instead.
*/
unsigned long trans_start;
struct timer_list watchdog_timer;
int __percpu *pcpu_refcnt;
struct list_head todo_list;
struct list_head link_watch_list;
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:8;
bool dismantle;
enum {
RTNL_LINK_INITIALIZED,
RTNL_LINK_INITIALIZING,
} rtnl_link_state:16;
void (*destructor)(struct net_device *dev);
#ifdef CONFIG_NETPOLL
struct netpoll_info __rcu *npinfo;
#endif
possible_net_t nd_net;
/* mid-layer private */
union {
void *ml_priv;
struct pcpu_lstats __percpu *lstats;
struct pcpu_sw_netstats __percpu *tstats;
struct pcpu_dstats __percpu *dstats;
struct pcpu_vstats __percpu *vstats;
};
struct garp_port __rcu *garp_port;
struct mrp_port __rcu *mrp_port;
struct device dev;
const struct attribute_group *sysfs_groups[4];
const struct attribute_group *sysfs_rx_queue_group;
const struct rtnl_link_ops *rtnl_link_ops;
/* for setting kernel sock attribute on TCP connection setup */
#define GSO_MAX_SIZE 65536
unsigned int gso_max_size;
#define GSO_MAX_SEGS 65535
u16 gso_max_segs;
u16 gso_min_segs;
#ifdef CONFIG_DCB
const struct dcbnl_rtnl_ops *dcbnl_ops;
#endif
u8 num_tc;
struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE];
u8 prio_tc_map[TC_BITMASK + 1];
#if IS_ENABLED(CONFIG_FCOE)
unsigned int fcoe_ddp_xid;
#endif
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
struct netprio_map __rcu *priomap;
#endif
struct phy_device *phydev;
struct lock_class_key *qdisc_tx_busylock;
};
1
、申请
net_device
编写网络驱动的时候首先要申请 net_device
,使用
alloc_netdev
函数来申请
net_device
,这
是一个宏,宏定义如下
2
、删除
net_device
当我们注销网络驱动的时候需要释放掉前面已经申请到的 net_device
,释放函数为free_netdev,函数原型如下:
void free_netdev(struct net_device *dev)
3
、注册
net_device
net_device 申请并初始化完成以后就需要向内核注册
net_device
,要用到函数
register_netdev
, 函数原型如下:
int register_netdev(struct net_device *dev)
4
、注销
net_device
既然有注册,那么必然有注销,注销 net_device
使用函数
unregister_netdev
,函数原型如下:
void unregister_netdev(struct net_device *dev)
net_device_ops 结构体(网络设备操作集)
net_device 有个非常重要的成员变量:
netdev_ops
,为
net_device_ops
结构体指针类型,这就是网络设备的操作集。net_device_ops
结构体定义在
include/linux/netdevice.h
文件中
struct net_device_ops {
int (*ndo_init)(struct net_device *dev);
void (*ndo_uninit)(struct net_device *dev);
int (*ndo_open)(struct net_device *dev);
int (*ndo_stop)(struct net_device *dev);
netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb,
struct net_device *dev);
u16 (*ndo_select_queue)(struct net_device *dev,
struct sk_buff *skb,
void *accel_priv,
select_queue_fallback_t fallback);
void (*ndo_change_rx_flags)(struct net_device *dev,
int flags);
void (*ndo_set_rx_mode)(struct net_device *dev);
int (*ndo_set_mac_address)(struct net_device *dev,
void *addr);
int (*ndo_validate_addr)(struct net_device *dev);
int (*ndo_do_ioctl)(struct net_device *dev,
struct ifreq *ifr, int cmd);
int (*ndo_set_config)(struct net_device *dev,
struct ifmap *map);
int (*ndo_change_mtu)(struct net_device *dev,
int new_mtu);
int (*ndo_neigh_setup)(struct net_device *dev,
struct neigh_parms *);
void (*ndo_tx_timeout) (struct net_device *dev);
struct rtnl_link_stats64* (*ndo_get_stats64)(struct net_device *dev,
struct rtnl_link_stats64 *storage);
struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);
int (*ndo_vlan_rx_add_vid)(struct net_device *dev,
__be16 proto, u16 vid);
int (*ndo_vlan_rx_kill_vid)(struct net_device *dev,
__be16 proto, u16 vid);
#ifdef CONFIG_NET_POLL_CONTROLLER
void (*ndo_poll_controller)(struct net_device *dev);
int (*ndo_netpoll_setup)(struct net_device *dev,
struct netpoll_info *info);
void (*ndo_netpoll_cleanup)(struct net_device *dev);
#endif
#ifdef CONFIG_NET_RX_BUSY_POLL
int (*ndo_busy_poll)(struct napi_struct *dev);
#endif
int (*ndo_set_vf_mac)(struct net_device *dev,
int queue, u8 *mac);
int (*ndo_set_vf_vlan)(struct net_device *dev,
int queue, u16 vlan, u8 qos);
int (*ndo_set_vf_rate)(struct net_device *dev,
int vf, int min_tx_rate,
int max_tx_rate);
int (*ndo_set_vf_spoofchk)(struct net_device *dev,
int vf, bool setting);
int (*ndo_get_vf_config)(struct net_device *dev,
int vf,
struct ifla_vf_info *ivf);
int (*ndo_set_vf_link_state)(struct net_device *dev,
int vf, int link_state);
int (*ndo_set_vf_port)(struct net_device *dev,
int vf,
struct nlattr *port[]);
int (*ndo_get_vf_port)(struct net_device *dev,
int vf, struct sk_buff *skb);
int (*ndo_set_vf_rss_query_en)(
struct net_device *dev,
int vf, bool setting);
int (*ndo_setup_tc)(struct net_device *dev, u8 tc);
#if IS_ENABLED(CONFIG_FCOE)
int (*ndo_fcoe_enable)(struct net_device *dev);
int (*ndo_fcoe_disable)(struct net_device *dev);
int (*ndo_fcoe_ddp_setup)(struct net_device *dev,
u16 xid,
struct scatterlist *sgl,
unsigned int sgc);
int (*ndo_fcoe_ddp_done)(struct net_device *dev,
u16 xid);
int (*ndo_fcoe_ddp_target)(struct net_device *dev,
u16 xid,
struct scatterlist *sgl,
unsigned int sgc);
int (*ndo_fcoe_get_hbainfo)(struct net_device *dev,
struct netdev_fcoe_hbainfo *hbainfo);
#endif
#if IS_ENABLED(CONFIG_LIBFCOE)
#define NETDEV_FCOE_WWNN 0
#define NETDEV_FCOE_WWPN 1
int (*ndo_fcoe_get_wwn)(struct net_device *dev,
u64 *wwn, int type);
#endif
#ifdef CONFIG_RFS_ACCEL
int (*ndo_rx_flow_steer)(struct net_device *dev,
const struct sk_buff *skb,
u16 rxq_index,
u32 flow_id);
#endif
int (*ndo_add_slave)(struct net_device *dev,
struct net_device *slave_dev);
int (*ndo_del_slave)(struct net_device *dev,
struct net_device *slave_dev);
netdev_features_t (*ndo_fix_features)(struct net_device *dev,
netdev_features_t features);
int (*ndo_set_features)(struct net_device *dev,
netdev_features_t features);
int (*ndo_neigh_construct)(struct neighbour *n);
void (*ndo_neigh_destroy)(struct neighbour *n);
int (*ndo_fdb_add)(struct ndmsg *ndm,
struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr,
u16 vid,
u16 flags);
int (*ndo_fdb_del)(struct ndmsg *ndm,
struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr,
u16 vid);
int (*ndo_fdb_dump)(struct sk_buff *skb,
struct netlink_callback *cb,
struct net_device *dev,
struct net_device *filter_dev,
int idx);
int (*ndo_bridge_setlink)(struct net_device *dev,
struct nlmsghdr *nlh,
u16 flags);
int (*ndo_bridge_getlink)(struct sk_buff *skb,
u32 pid, u32 seq,
struct net_device *dev,
u32 filter_mask,
int nlflags);
int (*ndo_bridge_dellink)(struct net_device *dev,
struct nlmsghdr *nlh,
u16 flags);
int (*ndo_change_carrier)(struct net_device *dev,
bool new_carrier);
int (*ndo_get_phys_port_id)(struct net_device *dev,
struct netdev_phys_item_id *ppid);
int (*ndo_get_phys_port_name)(struct net_device *dev,
char *name, size_t len);
void (*ndo_add_vxlan_port)(struct net_device *dev,
sa_family_t sa_family,
__be16 port);
void (*ndo_del_vxlan_port)(struct net_device *dev,
sa_family_t sa_family,
__be16 port);
void* (*ndo_dfwd_add_station)(struct net_device *pdev,
struct net_device *dev);
void (*ndo_dfwd_del_station)(struct net_device *pdev,
void *priv);
netdev_tx_t (*ndo_dfwd_start_xmit) (struct sk_buff *skb,
struct net_device *dev,
void *priv);
int (*ndo_get_lock_subclass)(struct net_device *dev);
netdev_features_t (*ndo_features_check) (struct sk_buff *skb,
struct net_device *dev,
netdev_features_t features);
int (*ndo_set_tx_maxrate)(struct net_device *dev,
int queue_index,
u32 maxrate);
int (*ndo_get_iflink)(const struct net_device *dev);
};
第 2 行: ndo_init 函数,当第一次注册网络设备的时候此函数会执行,设备可以在此函数中做一些需要退后初始化的内容,不过一般驱动中不使用此函数,虚拟网络设备可能会使用。第 3 行: ndo_uninit 函数,卸载网络设备的时候此函数会执行。第 4 行: ndo_open 函数,打开网络设备的时候此函数会执行,网络驱动程序需要实现此函数, 非常重要 !以 NXP 的 I.MX 系列 SOC 网络驱动为例,会在此函数中做如下工作:·使能网络外设时钟。·申请网络所使用的环形缓冲区。·初始化 MAC 外设。·绑定接口对应的 PHY 。·如果使用 NAPI 的话要使能 NAPI 模块,通过 napi_enable 函数来使能。·开启 PHY 。·调用 netif_tx_start_all_queues 来使能传输队列,也可能调用 netif_start_queue 函数。·……第 5 行: ndo_stop 函数,关闭网络设备的时候此函数会执行,网络驱动程序也需要实现此函数。以 NXP 的 I.MX 系列 SOC 网络驱动为例,会在此函数中做如下工作:·停止 PHY 。·停止 NAPI 功能。·停止发送功能。·关闭 MAC 。·断开 PHY 连接。·关闭网络时钟。·释放数据缓冲区。·……第 6 行: ndo_start_xmit 函数,当需要发送数据的时候此函数就会执行,此函数有一个参数为 sk_buff 结构体指针, sk_buff 结构体在 Linux 的网络驱动中非常重要, sk_buff 保存了上层传递给网络驱动层的数据。也就是说,要发送出去的数据都存在了 sk_buff 中,关于 sk_buff 稍后会做详细的讲解。如果发送成功的话此函数返回 NETDEV_TX_OK ,如果发送失败了就返回NETDEV_TX_BUSY,如果发送失败了我们就需要停止队列。第 8 行: ndo_select_queue 函数,当设备支持多传输队列的时候选择使用哪个队列第 14 行: ndo_set_rx_mode 函数,此函数用于改变地址过滤列表,根据 net_device 的 flags 成员变量来设置 SOC 的网络外设寄存器。比如 flags 可能为 IFF_PROMISC 、IFF_ALLMULTI 或 IFF_MULTICAST,分别表示混杂模式、单播模式或多播模式。第 15 行: ndo_set_mac_address 函数,此函数用于修改网卡的 MAC 地址,设置 net_device的 dev_addr 成员变量,并且将 MAC 地址写入到网络外设的硬件寄存器中。第 17 行: ndo_validate_addr 函数,验证 MAC 地址是否合法,也即是验证 net_device 的 dev_addr 中的 MAC 地址是否合法,直接调用 is_valid_ether_addr 函数。第 18 行: ndo_do_ioctl 函数,用户程序调用 ioctl 的时候此函数就会执行,比如 PHY 芯片相关的命令操作,一般会直接调用 phy_mii_ioctl 函数。第 22 行: ndo_change_mtu 函数,更改 MTU 大小。第 26 行: ndo_tx_timeout 函数,当发送超时的时候产生会执行,一般都是网络出问题了导致发送超时。一般可能会重启 MAC 和 PHY ,重新开始数据发送等。第 37 行: ndo_poll_controller 函数,使用查询方式来处理网卡数据的收发。第 104 行: ndo_set_features 函数,修改 net_device 的 features 属性,设置相应的硬件属性。
sk_buff 结构体(管理数据包)
我们重点来看一下 sk_buff
这个结构体,
sk_buff
是
Linux
网络重要的数据结构,用于管理
接收或发送数据包,
sk_buff
结构体定义在
include/linux/skbuff.h
中
网络是分层的,对于应用层而言不用关系具体的底层是如何工作的,只需要按照协议将要发送或接收的数据打包好即可。打包好以后都通过
dev_queue_xmit 函数
将数据发送出去,接收数据的话使用
netif_rx 函数
即可,我们依次来看一下这两个函数
1
、
dev_queue_xmit
函数
此函数用于将网络数据发送出去,函数定义在 include/linux/netdevice.h
中,函数原型如下:
static inline int dev_queue_xmit(struct sk_buff *skb)
2
、
netif_rx
函数
上层接收数据的话使用 netif_rx
函数,但是最原始的网络数据一般是通过轮询、中断或
NAPI
的方式来接收。
netif_rx
函数定义在
net/core/dev.c
中,函数原型如下:
int netif_rx(struct sk_buff *skb)
针对 sk_buff 内核提供了一系列的操作与管理函数,我们简单看一些常见的 API 函数:
1
、分配
sk_buff
要使用 sk_buff
必须先分配,首先来看一下
alloc_skb
这个函数,此函数定义在
include/linux/skbuff.h
中,函数原型如下:
static inline struct sk_buff *alloc_skb(unsigned int size, gfp_t priority)
在网络设备驱动中常常使用 netdev_alloc_skb
来为某个设备申请一个用于接收的
skb_buff
,
此函数也定义在
include/linux/skbuff.h
中,函数原型如下:
(用这个)
static inline struct sk_buff *netdev_alloc_skb(struct net_device *dev, unsigned int length)
2
、释放
sk_buff
当使用完成以后就要释放掉 sk_buff
,释放函数可以使用
kfree_skb
,函数定义在
include/linux/skbuff.c
中,函数原型如下:
void kfree_skb(struct sk_buff *skb)
对于网络设备而言最好使用如下所示释放函数:
(用这个)
void dev_kfree_skb (struct sk_buff *skb)
函数只要一个参数
skb
,就是要释放的
sk_buff
。
3
、
skb_put
、
skb_push
、
sbk_pull
和
skb_reserve 这四个函数用于变更 sk_buff
,
skb_put 函数,此函数用于在尾部扩展
skb_buff 的数据区,也就将 skb_buff
的
tail
后移
n
个字节,从而
导致 skb_buff 的 len 增加 n 个字节
,原型如下:
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
skb_push 函数用于在
头部扩展 skb_buff 的数据区
,函数原型如下所示:
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
sbk_pull 函数用于从
sk_buff 的数据区起始位置删除数据
,函数原型如下所示:
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
sbk_reserve 函数用于
调整缓冲区的头部大小
,方法很简单讲
skb_buff
的
data
和
tail
同时后
移
n
个字节即可,函数原型如下所示:
static inline void skb_reserve(struct sk_buff *skb, int len)
网络 NAPI 处理机制 与 napi_struct 实例
Linux 里面的网络数据接收也轮询和中断两种,
1)中断的好处就是响应快,数据量小的时候处理及时,速度快,但是一旦当数据量大,而且都是短帧的时候会导致中断频繁发生,消耗大量的 CPU 处理时间在中断自身处理上。
2)轮询恰好相反,响应没有中断及时,但是在处理大量数据的时候不需要消耗过多的 CPU 处理时间
Linux 在这两个处理方式的基础上提出了另外一种网络数据接收的处理方法:NAPI(New API)
,
NAPI
是一种高效的网络处理技术。 NAPI 的核心思想就是不全部采用中断来读取网络数据,而是
采用中断来唤醒数据接收服务程序
,
在接收服务程序中采用 POLL 的方法来轮询处理数据
。这种方法的好处就是可以提高短数据包的接收效率,减少中断处理的时间。
讲解一下如何在驱动中使用 NAPI
, Linux 内核使用结构体
napi_struct
表示
NAPI
,在使用
NAPI
之前要先初始化一个
napi_struct 实例
。
1
、初始化
NAPI
首先要初始化一个
napi_struct
实例,使用
netif_napi_add 函数
,此函数定义在
net/core/dev.c
中,函数原型如下:
void netif_napi_add(struct net_device *dev, struct napi_struct *napi, int (*poll)(struct napi_struct *, int), int weight)
2
、删除
NAPI
如果要删除
NAPI
,使用
netif_napi_del
函数即可,函数原型如下:
void netif_napi_del(struct napi_struct *napi)
3
、使能
NAPI
初始化完
NAPI
以后,必须使能才能使用,使用函数
napi_enable
,函数原型如下:
inline void napi_enable(struct napi_struct *n)
4
、关闭
NAPI
关闭
NAPI
使用
napi_disable
函数即可,函数原型如下:
void napi_disable(struct napi_struct *n)
5
、检查
NAPI
是否可以进行调度
使用
napi_schedule_prep
函数检查
NAPI
是否可以进行调度,函数原型如下:
inline bool napi_schedule_prep(struct napi_struct *n)
6
、
NAPI
调度
如果可以调度的话就进行调度,使用
__napi_schedule
函数完成
NAPI
调度,函数原型如下:
void __napi_schedule(struct napi_struct *n)
7
、
NAPI
处理完成
NAPI
处理完成以后需要调用
napi_complete
函数来标记
NAPI
处理完成,函数原型如下:
inline void napi_complete(struct napi_struct *n)
实验
不需要写代码 真正如果需要写网络驱动框架去看懂NXP官方的即可
首 先 肯 定 是 设 备 树 , NXP
的
I.MX
系 列
SOC
网 络 绑 定 文 档 为
Documentation/devicetree/bindings/net/fsl-fec.txt
,此绑定文档描述了
I.MX
系列
SOC
网络设备树 节点的要求。
打开
imx6ull.dtsi
,找到如下
I.MX6ULL
的两个网络外设节点
示例代码
69.4.1.1
是
NXP
官方编写的,我们不需要去修改,但是示例代码 69.4.1.1
是不能工作的,还需要根据实际情况添加或修改一些属性。打开 imx6ull-alientek-emmc.dts
,找到如下内容:
首先来看一下
I.MX6ULL
的网络控制器部分驱动,从示例代码
69.4.1.1
中可以看出, compatible 属性有两个值
“fsl,imx6ul-fec”
和
“fsl,imx6q-fec”
,通过在
linux
内核源码中搜索这两个字符串即可找到对应的驱动文件,驱动文件为 drivers/net/ethernet/freescale/fec_main.c
,打开fec_main.c,找到如下所示内容: