23.从0学ARM-网卡DM9000及uboot协议栈详解

第二十三章

一、网卡

1. 概念

网卡是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件。由于其拥有MAC地址,因此属于OSI模型的第2层。它使得用户可以通过电缆或无线相互连接。

每一个网卡都有一个被称为MAC地址的独一无二的48位串行号,它被写在卡上的一块ROM中。在网络上的每一个计算机都必须拥有一个独一无二的MAC地址。没有任何两块被生产出来的网卡拥有同样的地址。这是因为电气电子工程师协会(IEEE)负责为网络接口控制器(网卡)销售商分配唯一的MAC地址。

网卡上面装有处理器和存储器(包括RAM和ROM)。网卡和局域网之间的通信是通过电缆或双绞线以串行传输方式进行的。而网卡和计算机之间的通信则是通过计算机主板上的I/O总线以并行传输方式进行。

因此,网卡的一个重要功能就是要进行串行/并行转换。由于网络上的数据率和计算机总线上的数据率并不相同,因此在网卡中必须装有对数据进行缓存的存储芯片。

网卡以前是作为扩展卡插到计算机总线上的,但是由于其价格低廉而且以太网标准普遍存在,大部分新的计算机都在主板上集成了网络接口。

这些主板或是在主板芯片中集成了以太网的功能,或是使用一块通过PCI (或者更新的PCI-Express总线)连接到主板上的廉价网卡。

除非需要多接口或者使用其它种类的网络,否则不再需要一块独立的网卡。甚至更新的主板可能含有内置的双网络(以太网)接口。

2. 主要功能

1、数据的封装与解封
发送时将上一层传递来的数据加上首部和尾部,成为以太网的帧。接收时将以太网的帧剥去首部和尾部,然后送交上一层

2、链路管理
主要是通过CSMA/CD(Carrier Sense Multiple Access with Collision Detection ,带冲突检测的载波监听多路访问)协议来实现

3、数据编码与译码
即曼彻斯特编码与译码。其中曼彻斯特码,又称数字双向码、分相码或相位编码(PE),是一种常用的二元码线路编码方式之一,被物理层使用来编码一个同步位流的时钟和数据。在通信技术中,用来表示所要发送比特 流中的数据与定时信号所结合起来的代码。 常用在以太网通信,列车总线控制,工业总线等领域。

3. 分类

  1. 按总线接口类型分
    按网卡的总线接口类型来分一般可分bai为ISA接口网卡、PCI接口网卡以及在服务器上使用的PCI-X总线接口类型的网卡,笔记本电脑所使用的网卡是PCMCIA接口类型的。
  • (1)ISA总线网卡
  • (2)PCI总线网卡
  • (3)PCI-X总线网卡
  • (4)PCMCIA总线网卡
  • (5)USB总线接口网卡
  1. 按网络接口划分
    除了可以按网卡的总线接口类型划分外,我们还可以按网卡的网络接口类型来划分。网卡最终是要与网络进行连接,所以也就必须有一个接口使网线通过它与其它计算机网络设备连接起来。不同的网络接口适用于不同的网络类型,目前常见的接口主要有以太网的RJ-45接口、细同轴电缆的BNC接口和粗同轴电AUI接口、FDDI接口、ATM接口等。而且有的网卡为了适用于更广泛的应用环境,提供了两种或多种类型的接口,如有的网卡会同时提供RJ-45、BNC接口或AUI接口。
  • (1)RJ-45接口网卡
  • (2)BNC接口网卡
  • (3)AUI接口网卡
  • (4)FDDI接口网卡
  • (5)ATM接口网卡
  1. 按带宽划分
    随着网络技术的发展,网络带宽也在不断提高,但是不同带宽的网卡所应用的环境也有所不同,目前主流的网卡主要有10Mbps网卡、100Mbps以太网卡、10Mbps/100Mbps自适应网卡、1000Mbps千兆以太网卡四种。
  • (1)10Mbps网卡
  • (2)100Mbps网卡
  • (3)10Mbps/100Mbps网卡
  • (4)1000Mbps以太网卡

二、DM9000A

DM9000芯片是DAVICOM公司生产,DM9000A 是一款完全集成的、性价比高、引脚数少、带有通用处理器接口的单芯片快速以太网控制器。

一个 10/100M PHY 和 4K 双字的 SRAM 。它是出于低功耗和高性能目的设计的,其 IO 端口支持 3.3V 与 5V 容限值。

DM9000A 为适应各种处理器,提供了 8 位、16 位数据接口访问内部存储器。

DM9000A物理协议层接口完全支持使用 10MBps 下 3 类、4 类、5 类非屏蔽双绞线和 100MBps 下 5类非屏蔽双绞线。这是完全遵照 IEEE 802.3u 标准。

它的自动协商功能将自动完成 DM9000AE配置以使其发挥出最佳性能。

它还支持 IEEE 802.3x 全双工流量控制。

1. 模块图

23.从0学ARM-网卡DM9000及uboot协议栈详解_第1张图片

EEPROM Interface接口用于存放mac地址,Internal SRAM用于存放收发数据,MII部分把MAC部分与PHY部分连接起来通信,AUTO-MDIX用于自适应10/100M网络,在物理层上,MAC在PHY之下。

2. 引脚分析

23.从0学ARM-网卡DM9000及uboot协议栈详解_第2张图片
23.从0学ARM-网卡DM9000及uboot协议栈详解_第3张图片

23.从0学ARM-网卡DM9000及uboot协议栈详解_第4张图片(#:表示低电平有效)

开发板FS4412的网卡DM9000A连接到了SROM控制器,下面我们分析数据线、地址线和信号线连接

1) SD0~15

SD0~15: 16位数据线连接到引脚BUF_B_Xm0DATA[0:15],由CMD引脚决定访问类型。
23.从0学ARM-网卡DM9000及uboot协议栈详解_第5张图片
23.从0学ARM-网卡DM9000及uboot协议栈详解_第6张图片

可见数据和地址线都连接到了SOC的XM0上。

23.从0学ARM-网卡DM9000及uboot协议栈详解_第7张图片数据线和信号线对应的SROMC的引脚如上图。

2) CMD

 dm9000       外围电路             转换电路         soc 
  CMD--------BUF_B_Xm0ADDR2--------Xm0ADDR2-----Xm0ADDR2

如下图所示:
CMD: 命令线,当CMD为高,表示SD 传输的是数据,CMD为低表示传输的是地址,接在exynos4412的BUF_B_Xm0ADDR2上,可见CMD复用了地址线Xm0ADDR2引脚。
23.从0学ARM-网卡DM9000及uboot协议栈详解_第8张图片

3) IOR#、IOW#

 dm9000       外围电路         转换电路    soc 
  IOR--------BUF_Xm0OEn--------Xm0OEn-----Xm0OEn
  IOW--------BUF_Xm0WEn--------Xm0WEn-----Xm0WEn  

23.从0学ARM-网卡DM9000及uboot协议栈详解_第9张图片

4) CS#

 dm9000       外围电路         转换电路    soc 
  CS--------BUF_Xm0cs1--------Xm0cs1-----Xm0CSn1

23.从0学ARM-网卡DM9000及uboot协议栈详解_第10张图片
CS#:片选,放在exynos4412的Bank1的片选上面,内存基地址是0x05000000。

我们的DM9000A是放在exynos4412的Bank1(0X05000000)的片选上面。

而DM9000的CMD引脚接在Bank1的LADDR2上面

读写DM9000A的地址
CMD拉低, 此时向0X05000000地址上读写的数据便是DM9000A的内部寄存器地址

读写DM9000A的数据
CMD拉高,此时向0X05000000+4地址上读写的数据便是DM9000A的数据

设置exynos4412的bank1的硬件位宽,时序,因为不同的硬件,涉及的数据收发都不同。

5) INT#

中断线DM9000_IRQ通过U8转接到引脚XEINT6
23.从0学ARM-网卡DM9000及uboot协议栈详解_第11张图片GPX0_6
由上图可知中断引脚INT,接在exynos4412的GPX0_6脚上。
uboot中的DM9000A的驱动没有用到中断。

3. 复用GPIO引脚

XM0引脚复用了GPIO引脚,所以需要初始化对应的GPIO引脚来使能SROMC。

1) GPY0CON
23.从0学ARM-网卡DM9000及uboot协议栈详解_第12张图片
2) GPY1CON
23.从0学ARM-网卡DM9000及uboot协议栈详解_第13张图片3)GPY3CON
23.从0学ARM-网卡DM9000及uboot协议栈详解_第14张图片

4) GPY5CON
23.从0学ARM-网卡DM9000及uboot协议栈详解_第15张图片

5) GPY6CON
23.从0学ARM-网卡DM9000及uboot协议栈详解_第16张图片

三、SROM 控制器

1. 概念

SROM是高速存储器,Cache技术就是通过在DROM和CPU之间插入一小块SROM来减小CPU和存储之间的速度差异的。

EXYNOS 4412包含了SROM控制器,特性如下:

  • 外部 8/16位 NOR Flash/PROM/ SRAM memory.
  • 4组内存,每块内存最多16 MB

首先我们要初始化 exynos4412的 SROM 控制器,设置总线宽度和相关时序。

针对 SROM 控制器的每一个 bank 只有2 个寄存器: SROM_BW 和 SROM_BC

2. SROM_BW

在 SROM_BW 寄存器中,我们只关心与 bank1 相关的域。
23.从0学ARM-网卡DM9000及uboot协议栈详解_第17张图片

上面分析过,
DM9000A 的 16 根数据线全部接在 exynos 4412的数据线上,所以 DataWidth1 设置为 1;
DM9000A 的地址是按字节存取的,所以 AddrMode1 设置为 1;
通过查看原理图,没有使用 Xm0WAITn和 Xm0BEn 引脚;
所以 WaitEnable1 和 ByteEnable1 均设置为 0。

SROM_BW[7:4]=0x3

3. SROM_BC1

SROM 控制器读时序和 DM9000A 的读时序主要通过SROM_BCn控制寄存器设置。

设置这些时序之前,首先来看DM9000A芯片手册时序图和exynos4412的时序图
23.从0学ARM-网卡DM9000及uboot协议栈详解_第18张图片23.从0学ARM-网卡DM9000及uboot协议栈详解_第19张图片
23.从0学ARM-网卡DM9000及uboot协议栈详解_第20张图片
详尽时序分析:,内存控制器使用HCLK作为时钟,在HCLK为100MHz时,1个clock大约为10ns。
信号值的设定如下:

信号 含义 最低时间(ns)
Tacs 地址发出后等多长时间发片选, DM9000AE 中 CS 和 CMD(地址)同时发出,所以 Tacs最低为0ns 0
Tcos 发出片选信号后等多长时间发出读使能信号(nOW、 IOR),在 DM9000A 的时序图上对应 T1,最小为 0 0
Tacc 读使能信号持续时间,access cycle ,读写使能后,多久才能访问数据,在 DM9000A 的时序图上对应 T2 10
Tcoh 当DM9000A的写信号取消后,数据线上的数据还需要至少3ns才消失(nOE读写取消后,片选需要维持多长时间)在 DM9000A 的时序图中对应 T4 3
Tcah 片选结束后,地址保存时间, DM9000A 中CS和cmd同时结束,所以 Tcah=0 0
Tacp 页模式,不管 0
PMC 页模式,不管 0

从DM9000A的读写时序图中可以看出,T2+T6实际上构成了DM9000A的一个访问周期,因此还需要满足:Tacs + Tcos + Tacc + Tcoh + Tcah>= T2+T6,最终使用下面的表达式来表达:
(Tacs >= 0 && Tacs <= 4) && (Tcos >= 0 && Tcos <= 4) && (Tacc >= 1 && Tacc <= 14 ) && (Tcoh >=1 && Tcoh <= 4 )

寄存器SROM_BCn (n = 0 to 3)定义如下:
23.从0学ARM-网卡DM9000及uboot协议栈详解_第21张图片23.从0学ARM-网卡DM9000及uboot协议栈详解_第22张图片

故设置参考值为:

#define DM9000_Tacs     (0x1)   // address set-up
#define DM9000_Tcos     (0x1)   // chip selection set-up
#define DM9000_Tacc     (0x5)   // access cycle
#define DM9000_Tcoh     (0x1)   // chip selection hold
#define DM9000_Tah      (0xC)   // address holding time
#define DM9000_Tacp     (0x9)   // page mode access cycle
#define DM9000_PMC      (0x1)   // normal(1data)page mode configuration

4. SROM初始化

u-boot 已经自带了 DM9000系列网卡的驱动,在 u-boot 源码中的 driver/net/dm9000x.c 的有一段说明:

       06/03/2008	Remy Bohmer <linux@bohmer.net>
			- Fixed the driver to work with DM9000A.
			  (check on ISR receive status bit before reading the
			  FIFO as described in DM9000 programming guide and
			  application notes)
			- Added autodetect of databus width.
			- Made debug code compile again.
			- Adapt eth_send such that it matches the DM9000*
			  application notes. Needed to make it work properly
			  for DM9000A.
			- Adapted reset procedure to match DM9000 application
			  notes (i.e. double reset)
			- some minor code cleanups
			These changes are tested with DM9000{
     A,EP,E} together
			with a 200MHz Atmel AT91SAM9261 core

可见,2008年Remy Bohmer已经为 DM9000A 添加了驱动,但是我们仍然需要针对板子做一些修改。

前一章我们针对参考的fs4412开发板移植了DM9000A的驱动,下面我们来详细分析DM9000A驱动程序。

分析驱动涉及到以下几个文件:

arch/arm/lib/board.c
board/samsung/origen/origen.c
drivers/net/Dm9000x.c
drivers/net/Dm9000x.h
include/config_cmd_default.h
include/configs/origen.h
include/net.h
net/eth.c

5. 宏定义

在include/configs/origen.h中需要定义DM9000A基地址和编译的宏。
其中最重要的几个宏如下:

名称 说明
CONFIG_DM9000_BASE DM9000A 的基地址 0x05000000
DM9000_IO DM9000A 的 INDEX 端口地址 CONFIG_DM9000_BASE
DM9000_DATA DM9000A 的 DATA 端口地址 (CONFIG_DM9000_BASE + 4)
CONFIG_DRIVER_DM9000 Makefile中用于控制dm9000驱动是否编译 1
CONFIG_DM9000_USE_16BIT DM9000A数据宽度
CONFIG_DM9000_NO_SROM 表示没有使用SROM 1

其中DM9000_DATA 定义为基地址+0x4,刚好把 Xm0ADDR2 拉高,即把 CMD 拉高。

查看文件drivers/net/Makefile:
CONFIG_DRIVER_DM9000从 Makefile 得知,要把 DM9000A 的驱动编译进 u-boot中,需要定义 CONFIG_DRIVER_DM9000 这个宏。

宏定义如下:

#ifdef CONFIG_CMD_NET
#define CONFIG_NET_MULTI
#define CONFIG_DRIVER_DM9000   			1
#define CONFIG_DM9000_BASE     			0x05000000 
#define DM9000_IO      					CONFIG_DM9000_BASE
#define DM9000_DATA    					(CONFIG_DM9000_BASE + 4)
#define CONFIG_DM9000_USE_16BIT
#define CONFIG_DM9000_NO_SROM  			1
#define CONFIG_ETHADDR 					11:22:33:44:55:66
#define CONFIG_IPADDR  					192.168.6.187
#define CONFIG_SERVERIP        			192.168.6.186
#define CONFIG_GATEWAYIP       			192.168.6.1
#define CONFIG_NETMASK 					255.255.255.0
#endif

除此以外我们还需要添加一些 u-boot 的命令,比如 ping 命令用来检查网络是否通畅,tftp用来下载文件。

uboot通过宏来控制是否编译这些命令,include/configs/origen.h定义了一些宏,但是有的是undefine,我们要打开它们。

/* Command definition*/
#include 

#define CONFIG_CMD_PING
#define CONFIG_CMD_ELF
#define CONFIG_CMD_DHCP
#define CONFIG_CMD_MMC
#define CONFIG_CMD_FAT
#define CONFIG_CMD_NET
#undef CONFIG_CMD_NFS
#define CONFIG_CMD_HELLO
#define CONFIG_CMD_LEDA

除此之外头文件: u-boot-2013.01/include/config_cmd_all.h 也列出了一些可用的命令。

#define CONFIG_CMD_BDI		/* bdinfo			*/
#define CONFIG_CMD_BOOTD	/* bootd			*/
#define CONFIG_CMD_CONSOLE	/* coninfo			*/
#define CONFIG_CMD_ECHO		/* echo arguments		*/
#define CONFIG_CMD_EDITENV	/* editenv			*/
#define CONFIG_CMD_FPGA		/* FPGA configuration Support	*/
#define CONFIG_CMD_IMI		/* iminfo			*/
#define CONFIG_CMD_ITEST	/* Integer (and string) test	*/
#ifndef CONFIG_SYS_NO_FLASH
#define CONFIG_CMD_FLASH	/* flinfo, erase, protect	*/
#define CONFIG_CMD_IMLS		/* List all found images	*/
#endif
#define CONFIG_CMD_LOADB	/* loadb			*/
#define CONFIG_CMD_LOADS	/* loads			*/
#define CONFIG_CMD_MEMORY	/* md mm nm mw cp cmp crc base loop mtest */
#define CONFIG_CMD_MISC		/* Misc functions like sleep etc*/
#define CONFIG_CMD_NET		/* bootp, tftpboot, rarpboot	*/
#define CONFIG_CMD_NFS		/* NFS support			*/
#define CONFIG_CMD_RUN		/* run command in env variable	*/
#define CONFIG_CMD_SAVEENV	/* saveenv			*/
#define CONFIG_CMD_SETGETDCR	/* DCR support on 4xx		*/
#define CONFIG_CMD_SOURCE	/* "source" command support	*/
#define CONFIG_CMD_XIMG		/* Load part of Multi Image	*/

6. 初始化srom

在arch/arm/lib/board.c的函数board_init_r中有如下代码:

void board_init_r(gd_t *id, ulong dest_addr)
{
     
	……
	board_init();	/* Setup chipselects */
	……
}

函数board_init()定义在board/samsung/origen/origen.c中,我们在该函数中添加了初始化srom代码:

int board_init(void)
{
     
	gpio1 = (struct exynos4_gpio_part1 *) EXYNOS4_GPIO_PART1_BASE;
	gpio2 = (struct exynos4_gpio_part2 *) EXYNOS4_GPIO_PART2_BASE;

	gd->bd->bi_boot_params = (PHYS_SDRAM_1 + 0x100UL);

#ifdef CONFIG_DRIVER_DM9000
	dm9000aep_pre_init();
#endif
	return 0;
}

函数dm9000aep_pre_init用来设置 SROM 控制器。

static void dm9000aep_pre_init(void)
{
     
	unsigned int tmp;
	unsigned char smc_bank_num = 1;
	unsigned int     smc_bw_conf=0;
	unsigned int     smc_bc_conf=0;

	/* gpio configuration */
	writel(0x00220020, 0x11000000 + 0x120);//GPY0CON
	writel(0x00002222, 0x11000000 + 0x140);//GPY1CON
	/* 16 Bit bus width */
	writel(0x22222222, 0x11000000 + 0x180);//GPY3CON
	writel(0x0000FFFF, 0x11000000 + 0x188);//GPY3PUD
	writel(0x22222222, 0x11000000 + 0x1C0);//GPY5CON
	writel(0x0000FFFF, 0x11000000 + 0x1C8);//GPY5PUD
	writel(0x22222222, 0x11000000 + 0x1E0);//GPY6CON
	writel(0x0000FFFF, 0x11000000 + 0x1E8);//GPY6PUD
	              
	smc_bw_conf &= ~(0xf<<4);
	smc_bw_conf |= (1<<7) | (1<<6) | (1<<5) | (1<<4);
	smc_bc_conf = ((DM9000_Tacs << 28)
			| (DM9000_Tcos << 24)
			| (DM9000_Tacc << 16)
			| (DM9000_Tcoh << 12)
			| (DM9000_Tah << 8)
			| (DM9000_Tacp << 4)
			| (DM9000_PMC));
	exynos_config_sromc(smc_bank_num,smc_bw_conf,smc_bc_conf);
}
/*
 *  exynos_config_sromc() - select the proper SROMC Bank and configure the
 *  band width control and bank control registers
 *  srom_bank    - SROM
 *  srom_bw_conf  - SMC Band witdh reg configuration value
 *  srom_bc_conf  - SMC Bank Control reg configuration value
 */

void exynos_config_sromc(u32 srom_bank, u32 srom_bw_conf, u32 srom_bc_conf)
{
     
	unsigned int tmp;
	struct exynos_sromc *srom = (struct exynos_sromc *)(EXYNOS4412_SROMC_BASE);

	/* Configure SMC_BW register to handle proper SROMC
	 * bank */
	tmp = srom->bw;
	tmp &= ~(0xF << (srom_bank * 4));
	tmp |= srom_bw_conf;
	srom->bw = tmp;

	/* Configure SMC_BC
	 * register */
	srom->bc[srom_bank] = srom_bc_conf;
}

四、DM9000A驱动分析

DM9000A所能支持的功能非常的多,驱动的实现相对比较复杂,搞清楚裸机的网卡驱动,我们再去学习Linux内核的DM9000驱动就相对容易一些。
本节将详细讲解DM9000A网卡的数据的收发操作的流程。

1. 相关结构体

struct board_info

/* Structure/enum declaration ------------------------------- */
typedef struct board_info {
     
	u32 runt_length_counter;	/* counter: RX length < 64byte */
	u32 long_length_counter;	/* counter: RX length > 1514byte */
	u32 reset_counter;	/* counter: RESET */
	u32 reset_tx_timeout;	/* RESET caused by TX Timeout */
	u32 reset_rx_status;	/* RESET caused by RX Statsus wrong */
	u16 tx_pkt_cnt;
	u16 queue_start_addr;
	u16 dbug_cnt;
	u8 phy_addr;
	u8 device_wait_reset;	/* device state */
	unsigned char srom[128];
	void (*outblk)(volatile void *data_ptr, int count);
	void (*inblk)(void *data_ptr, int count);
	void (*rx_status)(u16 *RxStatus, u16 *RxLen);
	struct eth_device netdev;
} board_info_t;

static board_info_t dm9000_info;

该结构体是用来维护DM9000系列网卡的结构体,所有和网卡DM9000A信息都保存到该结构体中。
struct eth_device
struct board_info中有一个重要的成员 netdev,该成员是uboot提供的标准的统一的网卡设备接口。

struct eth_device {
     
	char name[16];
	unsigned char enetaddr[6];
	int iobase;
	int state;

	int  (*init) (struct eth_device *, bd_t *);
	int  (*send) (struct eth_device *, void *packet, int length);
	int  (*recv) (struct eth_device *);
	void (*halt) (struct eth_device *);
#ifdef CONFIG_MCAST_TFTP
	int (*mcast) (struct eth_device *, u32 ip, u8 set);
#endif
	int  (*write_hwaddr) (struct eth_device *);
	struct eth_device *next;
	int index;
	void *priv;
};

该结构体维护了操作网卡的回调函数等信息,我们只需要把网口的收发数据操作封装到对应的回调函数中,然后注册到系统即可。

2. 网卡注册/注销

进入到arch/arm/lib/board.c 中的 board_init_r 函数:

665 #if defined(CONFIG_CMD_NET)
666     puts("Net:   ");
667     eth_initialize(gd->bd);
668 #if defined(CONFIG_RESET_PHY_R)
669     debug("Reset Ethernet PHY\n");
670     reset_phy();
671 #endif

如果定义了 CONFIG_CMD_NET,就调用 eth_initialize(gd->bd)进行网卡初始化。

这个宏在include/config_cmd_default.h 中定义,这个头文件又被单板配置文件 include/configs/origen.h 所包含。

eth_initialize 函数在 net/eth.c 中定义,下面是该函数部分代码:

308     /*
309      * If board-specific initialization exists, call it.
310      * If not, call a CPU-specific one
311      */
312     if (board_eth_init != __def_eth_init) {
     
313         if (board_eth_init(bis) < 0)
314             printf("Board Net Initialization Failed\n");
315     } else if (cpu_eth_init != __def_eth_init) {
     
316         if (cpu_eth_init(bis) < 0)
317             printf("CPU Net Initialization Failed\n");
318     } else
319         printf("Net Initialization Skipped\n");

这段代码功能是:如果定义了单板相关的初始化函数就调用它,否则调用 CPU 相关的初始化函数。

其中__def_eth_init 函数,同样在net/eth.c 中定义

105  * CPU and board-specific Ethernet initializations.  Aliased function
106  * signals caller to move on
107  */
108 static int __def_eth_init(bd_t *bis)
109 {
     
110     return -1;
111 }
112 int cpu_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));
113 int board_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));

这里用到了 gcc 的弱符号和别名属性。
如果我们没有定义自己的 board_eth_init 函数,则 board_eth_init 就和__def_eth_init 相同,调用 board_eth_init 就相当于调用__def_eth_init,现在就能明白上面的 if 判断语句了。

board_eth_init 在board/samsung/origen/origen.c 中定义

264 #ifdef CONFIG_CMD_NET
265 int board_eth_init(bd_t *bis)                                                  
266 {
           
267 
268     int rc = 0;
269 #ifdef CONFIG_DRIVER_DM9000
270     rc = dm9000_initialize(bis);                                            
271 #endif                                                                         
272     return rc;                                                              
273 }  
274 #endif

这里通过配置宏来决定调用哪个网卡初始化函数。

我们使用的是 DM9000A,我们先查看下 DM9000A 的驱动源文件drivers/net/DM9000x.c,初始化函数如下:

626 int dm9000_initialize(bd_t *bis)
627 {
     
628     struct eth_device *dev = &(dm9000_info.netdev);
629 
630     /* Load MAC address from EEPROM */
631     dm9000_get_enetaddr(dev);
632 
633     dev->init = dm9000_init;
634     dev->halt = dm9000_halt;
635     dev->send = dm9000_send;
636     dev->recv = dm9000_rx;
637     sprintf(dev->name, "dm9000");
638 
639     eth_register(dev);
640 
641     return 0;
642 } 

该函数就是 DM9000A 的初始化函数。
631行dm9000_get_enetaddr 从 EEPROM 加载MAC地址,

static void dm9000_get_enetaddr(struct eth_device *dev)
{
     
#if !defined(CONFIG_DM9000_NO_SROM)
	int i;
	for (i = 0; i < 3; i++)
		dm9000_read_srom_word(i, dev->enetaddr + (2 * i));
#endif
}

该函数根据宏CONFIG_DM9000_NO_SROM 来决定是否从EEPROM 加载MAC地址, 参考的板子上的 DM9000A 没有接 EEPROM,我们在 origen.h 中定义了这个宏,表示不从 EEPROM 加载 MAC地址。

633~636行是将网卡的初始化和收发数据的函数填充到dev中,用于注册到系统中:

639行,函数eth_register()的参数是dev,该变量地址其实是dm9000_info.netdev的地址。
dm9000_info定义在同一文件下:

108  static board_info_t dm9000_info;

函数eth_register()位于net/eth.c中;

  • 功能:用于注册网卡到系统中,如果之前网卡设备链表为空,则直接复制给全局指针变量eth_devices和eth_current ,如果不为空,则把当前网卡插入到链表eth_devices中。
int eth_register(struct eth_device *dev)
{
     
	struct eth_device *d;
	static int index;

	assert(strlen(dev->name) < sizeof(dev->name));

	if (!eth_devices) {
     //网卡设备链表为空
		eth_current = eth_devices = dev;
		eth_current_changed();
	} else {
     //找到表尾
		for (d = eth_devices; d->next != eth_devices; d = d->next)
			;
		d->next = dev;//插入表尾
	}

	dev->state = ETH_STATE_INIT;
	dev->next  = eth_devices;//新的设备指向网卡设备表头
	dev->index = index++;

	return 0;
}

其中

eth_devices:网卡设备的链表
eth_current: 用于保存当前使用的网卡

网卡注销
网卡注销函数eth_unregister()
该函数会将网卡节点dev从链表eth_devices中删除,并重新设置变量eth_current。

int eth_unregister(struct eth_device *dev)
{
     
	struct eth_device *cur;

	/* No device */
	if (!eth_devices)
		return -1;

	for (cur = eth_devices; cur->next != eth_devices && cur->next != dev;
	     cur = cur->next)
		;

	/* Device not found */
	if (cur->next != dev)
		return -1;

	cur->next = dev->next;

	if (eth_devices == dev)
		eth_devices = dev->next == eth_devices ? NULL : dev->next;

	if (eth_current == dev) {
     
		eth_current = eth_devices;
		eth_current_changed();
	}

	return 0;
}

3. 寄存器

DM9000A 拥有一系列的控制和状态寄存器,这些寄存器可以被处理器所访问,这些寄存器是按字节对齐的。

所有的 CSRs 在软件或者硬件复位后都将被置为默认值,除非他们被另外标识。

编号 寄存器 描述 偏移地址 复位后默认值
1 NCR 网络控制寄存器 00H 00H
2 NSR 网络状态寄存器 01H 00H
3 TCR 发送控制寄存器 02H 00H
4 TSR I 发送状态寄存器 1 03H 00H
5 TSR II 发送状态寄存器 2 04H 00H
6 RCR 接收控制寄存器 05H 00H
7 RSR 接收状态寄存器 06H 00H
8 ROCR 接收溢出计数寄存器 07H 00H
9 BPTR 背压阈值寄存器 08H 37H
10 FCTR 流控制阈值寄存器 09H 38H
11 FCR TX/RX 流控制寄存器 0AH 00H
12 EPCR EEPROM&PHY 控制寄存器 0BH 00H
13 EPAR EEPROM&PHY 地址寄存器 0CH 40H
14 EPDRL EEPROM&PHY 低字节数据寄存器 0DH XXH
15 EPDRH EEPROM&PHY 高字节数据寄存器 0EH XXH
16 WCR 唤醒控制寄存器 0FH 00H
17 PAR 物理地址寄存器 10H~15H 由 EEPROM决定
18 MAR 广播地址寄存器 16H~1DH XXH
19 GPCR 通用目的控制寄存器(8bit 模式) 1EH 01H
20 GPR 通用目的寄存器 1FH XXH
21 TRPAL TX SRAM 读指针地址低字节 22H 00H
22 TRPAH TX SRAM 读指针地址高字节 23H 00H
23 RWPAL RX SRAM 写指针地址低字节 24H 00H
24 RWPAH RX SRAM 写指针地址高字节 25H 0CH
25 VID 厂家 ID 28H~29H 0A46H
26 PID 产品 ID 2AH~2BH 9000H
27 CHIPR 芯片版本 2CH 18H
28 TCR2 发送控制寄存器 2 2DH 00H
29 OCR 操作控制寄存器 2EH 00H
30 SMCR 特殊模式控制寄存器 2FH 00H
31 ETXCSR 即将发送控制/状态寄存器 30H 00H
32 TCSCR 发送校验和控制寄存器 31H 00H
33 RCSCSR 接收校验和控制状态寄存器 32H 00H
34 MRCMDX 内存数据预取读命令寄存器(地址不加 1) F0H XXH
35 MRCMDX1 内存数据读命令寄存器(地址不加 1) F1H XXH
36 MRCMD 内存数据读命令寄存器(地址加 1) F2H XXH
37 MRRL 内存数据读地址寄存器低字节 F4H 00H
38 MRRH 内存数据读地址寄存器高字节 F5H 00H
39 MWCMDX 内存数据写命令寄存器(地址不加 1) F6H XXH
40 MWCMD 内存数据写命令寄存器(地址加 1) F8H XXH
41 MWRL 内存数据写地址寄存器低字节 FAH 00H
42 MWRH 内存数据写地址寄存器高字节 FBH 00H
43 TXPLL TX 数据包长度低字节寄存器 FCH XXH
44 TXPLH TX 数据包长度高字节寄存器 FDH XXH
45 ISR 中断状态寄存器 FEH 00H
46 IMR 中断屏蔽寄存器 FFH 00H

关于默认值的要点(Key to Default)
在下面寄存器描述中,默认栏采用如下形式:

<Reset Value>, <Access Type>

其中

1 该位设为逻辑 1
0 该位设为逻辑 0
X 没有默认值
P 电源复位恢复默认值
H 硬件复位恢复默认值
S 软件复位恢复默认值
E 从 EEPROM 得到默认值
T 从捆绑引脚(strap pin)得到默认值

:

RO = 只读
RW = 可读可写
R/C = 可读/擦除
RW/C1=可读可写/通过写1擦除
WO = 只写

保留位被隐藏且应写 0,在读访问时保留位没有定义。

如何读取 DM9000A 的寄存器 RSR?
假设要读取 DM9000A 的寄存器 RSR(RX Status Register),需要分 2 步:

  1. 向 INDEX 端口写入 RSR 寄存器的地址(0x06)
    条件: nGCS1 信号拉低、 Xm0WEn 信号拉低、 Xm0ADDR2 拉低, 或者说向下面的地址写数据 0x06
  2. 从 DATA 端口读取 RSR 寄存器的值
    条件: nGCS1 信号拉低、 Xm0OEn 信号拉低、 Xm0ADDR2 拉高, 或者说从下面的地址读数据

DM9000A的寄存器很多,但是我们并需要都掌握,我们只需要掌握其中几个最重要的寄存器的使用即可。

  1. 网络控制寄存器(NCR)
    23.从0学ARM-网卡DM9000及uboot协议栈详解_第23张图片
  2. 网络状态寄存器(NSR)
    23.从0学ARM-网卡DM9000及uboot协议栈详解_第24张图片
    23.从0学ARM-网卡DM9000及uboot协议栈详解_第25张图片
    23.从0学ARM-网卡DM9000及uboot协议栈详解_第26张图片23.从0学ARM-网卡DM9000及uboot协议栈详解_第27张图片

23.从0学ARM-网卡DM9000及uboot协议栈详解_第28张图片

23.从0学ARM-网卡DM9000及uboot协议栈详解_第29张图片

DAVICOM 指定配置和状态寄存器(DSCSR)
23.从0学ARM-网卡DM9000及uboot协议栈详解_第30张图片

4. 网卡的初始化

网卡的初始化函数入口位于文件net/eth.c下的函数eth_init():

404 int eth_init(bd_t *bis)
405 {
     
406     struct eth_device *old_current, *dev;
		……
425     old_current = eth_current;
426     do {
     
427         debug("Trying %s\n", eth_current->name);
428 
429         if (eth_current->init(eth_current, bis) >= 0) {
     
430             eth_current->state = ETH_STATE_ACTIVE;
431 
432             return 0;
433         }
434         debug("FAIL\n");
	……
440 }

429行即调用我们注册的dm9000A初始化函数,从这也可以看出,整个架构是把网卡的驱动独立分隔开,与硬件操作相关的代码由用户自己填充并注册到系统中即可,便于扩展。
进入dm9000_init():

290 static int dm9000_init(struct eth_device *dev, bd_t *bd)
291 {
     
292     int i, oft, lnk;
293     u8 io_mode;
294     struct board_info *db = &dm9000_info;
295 
296     DM9000_DBG("%s\n", __func__);
297 
298     /* RESET device */
299     dm9000_reset();
300 
301     if (dm9000_probe() < 0)
302         return -1;
303 
304     /* Auto-detect 8/16/32 bit mode, ISR Bit 6+7 indicate bus width */
305     io_mode = DM9000_ior(DM9000_ISR) >> 6;
306 
307     switch (io_mode) {
     
308     case 0x0:  /* 16-bit mode */
309         printf("DM9000: running in 16 bit mode\n");
310         db->outblk    = dm9000_outblk_16bit;
311         db->inblk     = dm9000_inblk_16bit;
312         db->rx_status = dm9000_rx_status_16bit;
313         break;
314     case 0x01:  /* 32-bit mode */
315         printf("DM9000: running in 32 bit mode\n");
316         db->outblk    = dm9000_outblk_32bit;
317         db->inblk     = dm9000_inblk_32bit;
318         db->rx_status = dm9000_rx_status_32bit;
319         break;
320     case 0x02: /* 8 bit mode */
321         printf("DM9000: running in 8 bit mode\n");
322         db->outblk    = dm9000_outblk_8bit;
323         db->inblk     = dm9000_inblk_8bit;
324         db->rx_status = dm9000_rx_status_8bit;
325         break;
326     default:
327         /* Assume 8 bit mode, will probably not work anyway */
328         printf("DM9000: Undefined IO-mode:0x%x\n", io_mode);
329         db->outblk    = dm9000_outblk_8bit;
330         db->inblk     = dm9000_inblk_8bit;
331         db->rx_status = dm9000_rx_status_8bit;
332         break;
333     } 
334 
335     /* Program operating register, only internal phy supported */
336     DM9000_iow(DM9000_NCR, 0x0);
337     /* TX Polling clear */
338     DM9000_iow(DM9000_TCR, 0);
339     /* Less 3Kb, 200us */
340     DM9000_iow(DM9000_BPTR, BPTR_BPHW(3) | BPTR_JPT_600US);
341     /* Flow Control : High/Low Water */
342     DM9000_iow(DM9000_FCTR, FCTR_HWOT(3) | FCTR_LWOT(8));
343     /* SH FIXME: This looks strange! Flow Control */
344     DM9000_iow(DM9000_FCR, 0x0);
345     /* Special Mode */
346     DM9000_iow(DM9000_SMCR, 0);
347     /* clear TX status */
348     DM9000_iow(DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
349     /* Clear interrupt status */
350     DM9000_iow(DM9000_ISR, ISR_ROOS | ISR_ROS | ISR_PTS | ISR_PRS);
351 
352     printf("MAC: %pM\n", dev->enetaddr);
353 
354     /* fill device MAC address registers */
355     for (i = 0, oft = DM9000_PAR; i < 6; i++, oft++)
356         DM9000_iow(oft, dev->enetaddr[i]);
357     for (i = 0, oft = 0x16; i < 8; i++, oft++)
358         DM9000_iow(oft, 0xff);
359 
360     /* read back mac, just to be sure */
361     for (i = 0, oft = 0x10; i < 6; i++, oft++)
362         DM9000_DBG("%02x:", DM9000_ior(oft));
363     DM9000_DBG("\n");
364 
365     /* Activate DM9000 */
366     /* RX enable */
367     DM9000_iow(DM9000_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);
368     /* Enable TX/RX interrupt mask */
369     DM9000_iow(DM9000_IMR, IMR_PAR); 
370 
371     i = 0;
372     while (!(dm9000_phy_read(1) & 0x20)) {
       /* autonegation complete bit */
373         udelay(1000);
374         i++;
375         if (i == 10000) {
     
376             printf("could not establish link\n");
377             return 0;
378         }
379     }
380 
381     /* see what we've got */
382     lnk = dm9000_phy_read(17) >> 12;
383     printf("operating at ");
384     switch (lnk) {
     
385     case 1:
386         printf("10M half duplex ");
387         break;
388     case 2:
389         printf("10M full duplex ");
390         break;
391     case 4:
392         printf("100M half duplex ");
393         break;
394     case 8:
395         printf("100M full duplex ");
396         break;
397     default:
398         printf("unknown: %d ", lnk);
399         break;
400     }
401     printf("mode\n");
402     return 0;
403 }

299行 函数DM9000_reset()是对dm9000A重置
301行 函数dm9000_probe()分别从寄存器VID、PID读取厂家ID、产品ID
305行 读取DM9000A的 ISR寄存器,根据bite[6:7]的值来决定最终从DM9000A中读取数位数,并将对应的函数设置到db->outblk和db->inblk这两个变量,最终上层服务想收发数据就通过这两个函数,对于16位模式,就分别赋值dm9000_outblk_16bit、dm9000_inblk_16bit;
db->rx_status该函数用于从DM9000A中读取网卡的状态信息和数据包的长度,对于16位模式会赋值为dm9000_rx_status_16bit
336~350行 对DM9000A进行初始化配置
355~358行 将mac地址写入到DM9000A的PAR寄存器
367行 使能数据接收
369行 使能SRAM的读/写指针在指针地址超过SRAM的大小时自动跳回起始位置
382行 读取phy寄存器DSCSR,打印当前网口的带宽

通过读 bit[15:12]来看经过自动协商后选择的是哪一种模式。
网卡自动协商完成后,结果将被写到该位。若该位为 1,意味着操作 1 模式是 100M 全双工模式。

5. 数据的发送

发送流程

  1. 清中断,ISR寄存器bit[1] = 1
  2. 发送写操作,操作MWCMD
  3. 通过DM9000_DATA写入数据
  4. 设置数据帧的长度 TXPLL、TXPLH
  5. 发送发送请求,TCR
  6. 等待数据发送完毕,轮训检查NSR
  7. 清中断,ISR寄存器bit[1] = 1

网卡数据的发送函数是dm9000_send()

405 /*
406   Hardware start transmission.
407   Send a packet to media from the upper layer.
408 */
409 static int dm9000_send(struct eth_device *netdev, void *packet, int length)
410 {
     
411     int tmo;
412     struct board_info *db = &dm9000_info;
413 
414     DM9000_DMP_PACKET(__func__ , packet, length);
415 
416     DM9000_iow(DM9000_ISR, IMR_PTM); /* Clear Tx bit in ISR */
417                                                                                                                                               
418     /* Move data to DM9000 TX RAM */
419     DM9000_outb(DM9000_MWCMD, DM9000_IO); /* Prepare for TX-data */
420 
421     /* push the data to the TX-fifo */
422     (db->outblk)(packet, length);
423 
424     /* Set TX length to DM9000 */
425     DM9000_iow(DM9000_TXPLL, length & 0xff);
426     DM9000_iow(DM9000_TXPLH, (length >> 8) & 0xff);
427 
428     /* Issue TX polling command */
429     DM9000_iow(DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */
430 
431     /* wait for end of transmission */
432     tmo = get_timer(0) + 5 * CONFIG_SYS_HZ;
433     while ( !(DM9000_ior(DM9000_NSR) & (NSR_TX1END | NSR_TX2END)) ||
434         !(DM9000_ior(DM9000_ISR) & IMR_PTM) ) {
     
435         if (get_timer(0) >= tmo) {
     
436             printf("transmission timeout\n");
437             break;
438         }
439     }
440     DM9000_iow(DM9000_ISR, IMR_PTM); /* Clear Tx bit in ISR */
441 
442     DM9000_DBG("transmit done\n\n");
443     return 0;
444 }

该函数的参数

struct eth_device *netdev:设备
void *packet   :发送数据包存放的内存的首地址
int length     :发送的数据包长度

414行 打开debug开关,该行会打印发送的数据包
416行 使能数据包发送,将寄存器ISR的bit[1]设置为1
419行 通过寄存器MWCMD写入一个地址,并向该地址对应的 SRAM 中写数据。执行写该指令之后,写指针会根据操作模式(8 位或 16 位)自动增加 1 或 2。
422行 调用上一节db->outblk所赋值的函数将数据包发送的DM9000A的发送fifo中
425~426行 将发送数据包长度写入到寄存器TXPLL/TXPLH中,这两个寄存器分别对应低字节和高字节
429行 向寄存器TCR的bit[0]写入1,来请求发送数据,发送完毕该位自动清0
432~440行 通过向寄存器ISR的bit[1]写入1,来清楚发送标记位

其中发送函数dm9000_outblk_16bit()
定义如下:

159 static void dm9000_outblk_16bit(volatile void *data_ptr, int count)
160 {
     
161 	int i;
162 	u32 tmplen = (count + 1) / 2;
163 
164 	for (i = 0; i < tmplen; i++)
165 		DM9000_outw(((u16 *) data_ptr)[i], DM9000_DATA);
166 }

164~165行 就是循环从地址DM9000_DATA读取数据并存储到data_ptr执行的内存中
此处我们看到每次都是从相同的地址读取数据,为什么不需要做地址偏移呢?
答:寄存器MWCMD已经和我们说的很清楚了,写该指令之后,指写指针根据操作模式(8 位或 16 位)增
加 1 或 2。

6. 数据的接收

DM9000A的数据接收

464 static int dm9000_rx(struct eth_device *netdev)
465 {
     
466     u8 rxbyte, *rdptr = (u8 *) NetRxPackets[0];
467     u16 RxStatus, RxLen = 0;
468     struct board_info *db = &dm9000_info;
469 
470     /* Check packet ready or not, we must check
471        the ISR status first for DM9000A */
472     if (!(DM9000_ior(DM9000_ISR) & 0x01)) /* Rx-ISR bit must be set. */
473         return 0;
474 
475     DM9000_iow(DM9000_ISR, 0x01); /* clear PR status latched in bit 0 */
476 
477     /* There is _at least_ 1 package in the fifo, read them all */
478     for (;;) {
     
479         DM9000_ior(DM9000_MRCMDX);  /* Dummy read */
480 
481         /* Get most updated data,
482            only look at bits 0:1, See application notes DM9000 */
483         rxbyte = DM9000_inb(DM9000_DATA) & 0x03;
484 
485         /* Status check: this byte must be 0 or 1 */
486         if (rxbyte > DM9000_PKT_RDY) {
     
487             DM9000_iow(DM9000_RCR, 0x00);   /* Stop Device */
488             DM9000_iow(DM9000_ISR, 0x80);   /* Stop INT request */
489             printf("DM9000 error: status check fail: 0x%x\n",
490                 rxbyte);
491             return 0;
492         }
493 
494         if (rxbyte != DM9000_PKT_RDY)
495             return 0; /* No packet received, ignore */
496 
497         DM9000_DBG("receiving packet\n");
498 
499         /* A packet ready now  & Get status/length */
500         (db->rx_status)(&RxStatus, &RxLen);
501 
502         DM9000_DBG("rx status: 0x%04x rx len: %d\n", RxStatus, RxLen);
503 
504         /* Move data from DM9000 */
505         /* Read received packet from RX SRAM */
506         (db->inblk)(rdptr, RxLen);
507    
508         if ((RxStatus & 0xbf00) || (RxLen < 0x40)
509             || (RxLen > DM9000_PKT_MAX)) {
     
510             if (RxStatus & 0x100) {
     
511                 printf("rx fifo error\n");
512             }
513             if (RxStatus & 0x200) {
     
514                 printf("rx crc error\n");
515             }
516             if (RxStatus & 0x8000) {
     
517                 printf("rx length error\n");
518             }
519             if (RxLen > DM9000_PKT_MAX) {
     
520                 printf("rx length too big\n");
521                 dm9000_reset();
522             }
523         } else {
     
524             DM9000_DMP_PACKET(__func__ , rdptr, RxLen);
525 
526             DM9000_DBG("passing packet to upper layer\n");
527             NetReceive(NetRxPackets[0], RxLen);
528         }
529     }
530     return 0;
531 }

472行 DM9000A的寄存器ISR的bit[0]必须设置为1,否则无法接收数据
475行 将ISR的bit[0]设置为1
479行 读取寄存器MRCMDX, 以从接收 SRAM 中读数据;执行读取该指令之后,指向内部 SRAM的读指针不变。DM9000A 开始预取 SRAM 中数据到内部数据缓冲中
483~494行 从地址DM9000_DATA中读取数据,从SRAM中读取的第一个数据的bit[0]必须是1,否则出错
500行 通过函数指针db->rx_status读取网卡的状态和接收到的数据包的长度
506行 通过函数指针db->inblk从网卡中读取数据
527行 通过函数NetReceive()提交给上层协议栈

真正读取数据的函数是dm9000_inblk_16bit();
定义如下:

static void dm9000_inblk_16bit(void *data_ptr, int count)
{
     
	int i;
	u32 tmplen = (count + 1) / 2;

	for (i = 0; i < tmplen; i++)
		((u16 *) data_ptr)[i] = DM9000_inw(DM9000_DATA);
}

原理类似于函数dm9000_outblk_16bit,不再重复。

由此可见,要分析DM9000A的数据收发的原理和流程,就要分析我们注册网卡的以下几个函数:

635   dev->send = dm9000_send;
636   dev->recv = dm9000_rx;

310   db->outblk    = dm9000_outblk_16bit;
311   db->inblk     = dm9000_inblk_16bit;

五、uboot中网络协议栈

网卡的驱动,对于上层协议来说,已经封装好了发送和接收数据的接口,那么上层协议栈只需要按照顺序调用对应的网卡驱动函数就可以进行网络数据的收发。

uboot中的协议栈相对来说比较简单,有以下几个特点:

  1. 传输层只支持UDP协议;
  2. 目前只支持ICMP、TFTP、NFS、DNS、DHCP、CDP、SNTP等几种协议;
  3. 网卡采用poll接收数据包,而不是中断方式;
  4. 数据包的发送和接收操作是串行操作,不支持并发。

1. 网络协议栈架构

下面是uboot网络协议栈的函数调用流程:
23.从0学ARM-网卡DM9000及uboot协议栈详解_第31张图片

2. 通过DNS命令来解析一个数据包的收发流程

uboot中,所有的命令都用宏U_BOOT_CMD来定义,
dns命令的定义如下:

426 U_BOOT_CMD( 
427     dns,    3,  1,  do_dns,
428     "lookup the IP of a hostname",
429     "hostname [envvar]"
430 );

当我们在uboot的命令终端输入命令dns后,命令解析函数就会调用dns执行函数do_dns()

389 int do_dns(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
390 {
     
	……
406     if (strlen(argv[1]) >= 255) {
     
407         printf("dns error: hostname too long\n");
408         return 1;
409     }
410 
411     NetDNSResolve = argv[1];
412 
413     if (argc == 3)
414         NetDNSenvvar = argv[2];
415     else
416         NetDNSenvvar = NULL;
417 
418     if (NetLoop(DNS) < 0) {
     
419         printf("dns lookup of %s failed, check setup\n", argv[1]);
420         return 1;
421     }
422 
423     return 0;
424 }

406行 判断参数字符串长度,大于255非法
411行 参数1必须是要解析的主机,存储在NetDNSResolve 中
413~416行 保存dns命令的环境参数,该参数可以没有
418行 进入网络协议处理函数入口NetLoop(),并将对应的协议DNS传递给该函数

NetLoop()代码比较长,我们只分析其中比较重要的几段代码

 316 /**********************************************************************/
 317 /*
 318  *  Main network processing loop.
 319  */
 320 
 321 int NetLoop(enum proto_t protocol)
 322 {
     
 323     bd_t *bd = gd->bd;
 324     int ret = -1;
 …………
 352     NetInitLoop();
  …………
 367         switch (protocol) {
     
 368         case TFTPGET:
 369 #ifdef CONFIG_CMD_TFTPPUT
 370         case TFTPPUT:
 371 #endif
 372             /* always use ARP to get server ethernet address */
 373             TftpStart(protocol);
 374             break;
  …………
 426 #if defined(CONFIG_CMD_DNS)
 427         case DNS:
 428             DnsStart();
 429             break;
 430 #endif
 438     }
 …………
 461     for (;;) {
     
 462         WATCHDOG_RESET();
 463 #ifdef CONFIG_SHOW_ACTIVITY
 464         show_activity(1);
 465 #endif
 466         /*
 467          *  Check the ethernet for a new packet.  The ethernet
 468          *  receive routine will process it.
 469          */
 470         eth_rx();
 471 
 472         /*
 473          *  Abort if ctrl-c was pressed.
 474          */
 475         if (ctrlc()) {
     
 476             /* cancel any ARP that may not have completed */
 477             NetArpWaitPacketIP = 0;
 478 
 479             net_cleanup_loop();
 480             eth_halt();
 481             /* Invalidate the last protocol */
 482             eth_set_last_protocol(BOOTP);
 483 
 484             puts("\nAbort\n");
 485             /* include a debug print as well incase the debug
 486                messages are directed to stderr */
 487             debug_cond(DEBUG_INT_STATE, "--- NetLoop Abort!\n");
 488             goto done;
 489         }
  …………
 522         switch (net_state) {
     
 523 
 524         case NETLOOP_RESTART:
 525             NetRestarted = 1;
 526             goto restart;
 527 
 528         case NETLOOP_SUCCESS:
 529             net_cleanup_loop();
 530             if (NetBootFileXferSize > 0) {
     
 531                 char buf[20];
 532                 printf("Bytes transferred = %ld (%lx hex)\n",
 533                     NetBootFileXferSize,
 534                     NetBootFileXferSize);
 535                 sprintf(buf, "%lX", NetBootFileXferSize);
 536                 setenv("filesize", buf);
 537 
 538                 sprintf(buf, "%lX", (unsigned long)load_addr);
 539                 setenv("fileaddr", buf);
 540             }
 541             if (protocol != NETCONS)
 542                 eth_halt();
 543             else
 544                 eth_halt_state_only();
 545 
 546             eth_set_last_protocol(protocol);
 547 
 548             ret = NetBootFileXferSize;
 549             debug_cond(DEBUG_INT_STATE, "--- NetLoop Success!\n");
 550             goto done;
 551 
 552         case NETLOOP_FAIL:
 553             net_cleanup_loop();
 554             /* Invalidate the last protocol */
 555             eth_set_last_protocol(BOOTP);
 556             debug_cond(DEBUG_INT_STATE, "--- NetLoop Fail!\n");
 557             goto done;
 558 
 559         case NETLOOP_CONTINUE:
 560             continue;
 561         }
 562     }
 563 
 564 done:
 565 #ifdef CONFIG_CMD_TFTPPUT
 566     /* Clear out the handlers */
 567     net_set_udp_handler(NULL);
 568     net_set_icmp_handler(NULL);
 569 #endif
 570     return ret;
 571 }

函数参数为DNS
352行 初始化网络信息,读取ipaddr、gatewayip、netmask、serverip、dnsip等环境变量的值并复制到对应的全局变量中

static void NetInitLoop(void)
{
     
	static int env_changed_id;
	int env_id = get_env_id();

	/* update only when the environment has changed */
	if (env_changed_id != env_id) {
     
		NetOurIP = getenv_IPaddr("ipaddr");
		NetOurGatewayIP = getenv_IPaddr("gatewayip");
		NetOurSubnetMask = getenv_IPaddr("netmask");
		NetServerIP = getenv_IPaddr("serverip");
		NetOurNativeVLAN = getenv_VLAN("nvlan");
		NetOurVLAN = getenv_VLAN("vlan");
#if defined(CONFIG_CMD_DNS)
		NetOurDNSIP = getenv_IPaddr("dnsip");
#endif
		env_changed_id = env_id;
	}
	memcpy(NetOurEther, eth_get_dev()->enetaddr, 6);

	return;
}

367行 对传入的参数做switch操作,不同的协议进入到不同的处理流程
428行 执行DnsStart(),

197 void
198 DnsStart(void)
199 {
     
200     debug("%s\n", __func__);
201 
202     NetSetTimeout(DNS_TIMEOUT, DnsTimeout);
203     net_set_udp_handler(DnsHandler);
204 
205     DnsSend();
206 } 

203行 函数net_set_udp_handler()主要将dns协议的回调函数DnsHandler()注册到udp协议的回调指针udp_packet_handler,

void net_set_udp_handler(rxhand_f *f)
{
     
	debug_cond(DEBUG_INT_STATE, "--- NetLoop UDP handler set (%p)\n", f);
	if (f == NULL)
		udp_packet_handler = dummy_handler;//注册到udp协议回调函数指针
	else
		udp_packet_handler = f;
}

DnsStart()最终会调用函数DnsSend()发送dns协议数据包,该函数是根据dns协议填充udp数据包

 37 static void
 38 DnsSend(void)
 39 {
     
 40     struct header *header;
 41     int n, name_len;
 42     uchar *p, *pkt;
 43     const char *s;
 44     const char *name;
 45     enum dns_query_type qtype = DNS_A_RECORD;
 46 
 47     name = NetDNSResolve;
 48     pkt = p = (uchar *)(NetTxPacket + NetEthHdrSize() + IP_UDP_HDR_SIZE);
 49 
 50     /* Prepare DNS packet header */
 51     header           = (struct header *) pkt;
 52     header->tid      = 1;
 53     header->flags    = htons(0x100);    /* standard query */
 54     header->nqueries = htons(1);        /* Just one query */
 55     header->nanswers = 0;
 56     header->nauth    = 0;
 57     header->nother   = 0;
 58 
 59     /* Encode DNS name */
 60     name_len = strlen(name);
 61     p = (uchar *) &header->data;    /* For encoding host name into packet */
 62 
 63     do {
     
 64         s = strchr(name, '.');
 65         if (!s)
 66             s = name + name_len;
 67 
 68         n = s - name;           /* Chunk length */
 69         *p++ = n;           /* Copy length  */
 70         memcpy(p, name, n);     /* Copy chunk   */
 71         p += n;
 72 
 73         if (*s == '.')
 74             n++;
 75 
 76         name += n;
 77         name_len -= n;
 78     } while (*s != '\0');
 79 
 80     *p++ = 0;           /* Mark end of host name */
 81     *p++ = 0;           /* Some servers require double null */
 82     *p++ = (unsigned char) qtype;   /* Query Type */
 83 
 84     *p++ = 0;
 85     *p++ = 1;               /* Class: inet, 0x0001 */
 86 
 87     n = p - pkt;                /* Total packet length */
 88     debug("Packet size %d\n", n);
 89 
 90     DnsOurPort = random_port();
 91 
 92     NetSendUDPPacket(NetServerEther, NetOurDNSIP, DNS_SERVICE_PORT,
 93         DnsOurPort, n);
 94     debug("DNS packet sent\n");
 95 }

51~57行 根据dns协议填充dns协议头,数据帧首地址为NetTxPacket,此处通过指针pkt和p来填充dns数据帧
60~85行 根据协议格式要求填充要解析的host名字到数据包
87行 计算数据包长度
90行 产生一个随机的端口号
92~93行 调用udp协议的发送函数NetSendUDPPacket(),参数依次是:以太头信息,DNS服务器 ip地址,DNS服务器端口号,我们的dns服务端口号,数据包长度

 688 int NetSendUDPPacket(uchar *ether, IPaddr_t dest, int dport, int sport,
 689         int payload_len)
 690 {
     
 691     uchar *pkt;
 692     int eth_hdr_size;
 693     int pkt_hdr_size;
 694 
 695     /* make sure the NetTxPacket is initialized (NetInit() was called) */
 696     assert(NetTxPacket != NULL);
 697     if (NetTxPacket == NULL)
 698         return -1;
 699 
 700     /* convert to new style broadcast */
 701     if (dest == 0)
 702         dest = 0xFFFFFFFF;
 703 
 704     /* if broadcast, make the ether address a broadcast and don't do ARP */
 705     if (dest == 0xFFFFFFFF)
 706         ether = NetBcastAddr;
 707 
 708     pkt = (uchar *)NetTxPacket;
 709 
 710     eth_hdr_size = NetSetEther(pkt, ether, PROT_IP);
 711     pkt += eth_hdr_size;
 712     net_set_udp_header(pkt, dest, dport, sport, payload_len);
 713     pkt_hdr_size = eth_hdr_size + IP_UDP_HDR_SIZE;
 714 
 715     /* if MAC address was not discovered yet, do an ARP request */
 716     if (memcmp(ether, NetEtherNullAddr, 6) == 0) {
     
 717         debug_cond(DEBUG_DEV_PKT, "sending ARP for %pI4\n", &dest);
 718 
 719         /* save the ip and eth addr for the packet to send after arp */
 720         NetArpWaitPacketIP = dest;
 721         NetArpWaitPacketMAC = ether;
 722 
 723         /* size of the waiting packet */
 724         NetArpWaitTxPacketSize = pkt_hdr_size + payload_len;
 725 
 726         /* and do the ARP request */
 727         NetArpWaitTry = 1;
 728         NetArpWaitTimerStart = get_timer(0);
 729         ArpRequest();
 730         return 1;   /* waiting */
 731     } else {
     
 732         debug_cond(DEBUG_DEV_PKT, "sending UDP to %pI4/%pM\n",                                                                               
 733             &dest, ether);
 734         NetSendPacket(NetTxPacket, pkt_hdr_size + payload_len);
 735         return 0;   /* transmitted */
 736     }
 737 }

696~706行 参数检查
710行 设置以太头
713行 设置udp协议头
716~730行 如果没有目的MAC地址,就要先发送ARP请求
734行 调用函数NetSendPacket(),参数分别是:要发送数据帧的首地址,数据包长度

529 /* Transmit a packet */  
530 static inline void NetSendPacket(uchar *pkt, int len)
531 {
     
532     (void) eth_send(pkt, len);
533 }

532行 调用我们注册的函数dm9000_send()
该函数已经分析过,根据流程图,回到函数NetLoop()

461~562行 循环接收网络数据包
470行 调用网卡驱动接收函数eth_rx()

int eth_rx(void)
{
     
	if (!eth_current)
		return -1;

	return eth_current->recv(eth_current);
}

eth_current->recv(eth_current)函数就是我们注册的网卡的接收函数dm9000_rx(),该函数我们上一章已经分析过,最终通过调用函数NetReceive(),将数据帧上传到协议栈

 943 void
 944 NetReceive(uchar *inpkt, int len)
 945 {
     
 946     struct ethernet_hdr *et;
 947     struct ip_udp_hdr *ip;
 948     IPaddr_t dst_ip;
 949     IPaddr_t src_ip;
 950     int eth_proto;
	……
 957 
 958     NetRxPacket = inpkt;
 959     NetRxPacketLen = len;
 960     et = (struct ethernet_hdr *)inpkt;
 961 
 962     /* too small packet? */
 963     if (len < ETHER_HDR_SIZE)
 964         return;
 965 
	……
 984 
 985     eth_proto = ntohs(et->et_protlen);
 986 
 987     if (eth_proto < 1514) {
     
 988         struct e802_hdr *et802 = (struct e802_hdr *)et;
 	……
 997 
 998     } else if (eth_proto != PROT_VLAN) {
         /* normal packet */
 999         ip = (struct ip_udp_hdr *)(inpkt + ETHER_HDR_SIZE);
1000         len -= ETHER_HDR_SIZE;
1001 
1002     } else {
                 /* VLAN packet */
	……
1026     }
1027 
	…… 
1045     switch (eth_proto) {
     
	……
1056     case PROT_IP:
1057         debug_cond(DEBUG_NET_PKT, "Got IP\n");
1058         /* Before we start poking the header, make sure it is there */
1059         if (len < IP_UDP_HDR_SIZE) {
     
1060             debug("len bad %d < %lu\n", len,
1061                 (ulong)IP_UDP_HDR_SIZE);
1062             return;
1063         }
1064         /* Check the packet length */
1065         if (len < ntohs(ip->ip_len)) {
     
1066             debug("len bad %d < %d\n", len, ntohs(ip->ip_len));
1067             return;
1068         }
1069         len = ntohs(ip->ip_len);
1070         debug_cond(DEBUG_NET_PKT, "len=%d, v=%02x\n",
1071             len, ip->ip_hl_v & 0xff);
1072 
1073         /* Can't deal with anything except IPv4 */
1074         if ((ip->ip_hl_v & 0xf0) != 0x40)
1075             return;
1076         /* Can't deal with IP options (headers != 20 bytes) */
1077         if ((ip->ip_hl_v & 0x0f) > 0x05)
1078             return;
1079         /* Check the Checksum of the header */
1080         if (!NetCksumOk((uchar *)ip, IP_HDR_SIZE / 2)) {
     
1081             debug("checksum bad\n");
1082             return;
1083         }
1084         /* If it is not for us, ignore it */
1085         dst_ip = NetReadIP(&ip->ip_dst);

1092         /* Read source IP address for later use */
1093         src_ip = NetReadIP(&ip->ip_src);


1184         /*
1185          *  IP header OK.  Pass the packet to the current handler.
1186          */
1187         (*udp_packet_handler)((uchar *)ip + IP_UDP_HDR_SIZE,
1188                 ntohs(ip->udp_dst),
1189                 src_ip,
1190                 ntohs(ip->udp_src),
1191                 ntohs(ip->udp_len) - UDP_HDR_SIZE);
1192         break;
1193     }
1194 }

参数inpkt:指向接收到的以太数据包头 len:接收到的数据包的长度
960行 变量NetRxPacket指向接收的数据头,以太数据包包头比定位以太协议头
985行 从以太协议头提取出协议字段,该字段表示后面是否是ip协议
999行 解析出ip协议头
1045行 根据以太头协议进行switch操作
1059~1083行 对协议头进行合法性检查
1085行 读取出目的ip地址
1093行 读取出源ip地址,
1187行 ip协议头解析成功,调用udp协议回调函数udp_packet_handler(),该函数在之前的DnStart()注册了DnsHandler

104 static void
105 DnsHandler(uchar *pkt, unsigned dest, IPaddr_t sip, unsigned src, unsigned len)
106 {
     
193 
194     net_set_state(NETLOOP_SUCCESS);
195 }

该函数用于解析DNS协议,在此不再详解
解析成功后194行,会设置当前执行状态为NETLOOP_SUCCESS,
代码回到函数NetLoop()470行
475行 判断是否按下ctrl+c快捷键,并作出操作
522~562行 对执行结果进行处理,计入统计信息
564行 如果net_state为NETLOOP_SUCCESS、NETLOOP_FAIL最终都会进入done,从而置空udp回调函数
如果net_state为NETLOOP_CONTINUE,表明仍然有后续数据包要接收,则回到461行,继续下一个数据包的接收

至此DNS协议的处理流程分析完毕,大家可以根据这个流程自行分析其他几个协议的处理流程。

六、容易遇到的问题

有的时候能读取到 DM9000A 的 ID,连续操作就能读取到 DM9000A 的 ID,但间隔一会操作就读取不到 DM9000A 的 ID,通过调试,在 dm9000_reset 函数中加一句延时操作,就可以正常读取 DM9000A 的 ID 了。

277     do {
     
278         DM9000_DBG("resetting the DM9000, 2nd reset\n");
279         udelay(25); /* Wait at least 20 us */
280     } while (DM9000_ior(DM9000_NCR) & 1);
281     udelay(150);
282     /* Check whether the ethernet controller is present */
283     if ((DM9000_ior(DM9000_PIDL) != 0x0) ||
284         (DM9000_ior(DM9000_PIDH) != 0x90))
285         printf("ERROR: resetting DM9000 -> not responding\n");

你可能感兴趣的:(从0学arm,原创)