Linux驱动开发16 网络设备驱动框架

嵌入式下的网络硬件接口

        首先,嵌入式网络硬件分为两部分: 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,找到如下所示内容:
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值