网卡是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件。由于其拥有MAC地址,因此属于OSI模型的第2层。它使得用户可以通过电缆或无线相互连接。
每一个网卡都有一个被称为MAC地址的独一无二的48位串行号,它被写在卡上的一块ROM中。在网络上的每一个计算机都必须拥有一个独一无二的MAC地址。没有任何两块被生产出来的网卡拥有同样的地址。这是因为电气电子工程师协会(IEEE)负责为网络接口控制器(网卡)销售商分配唯一的MAC地址。
网卡上面装有处理器和存储器(包括RAM和ROM)。网卡和局域网之间的通信是通过电缆或双绞线以串行传输方式进行的。而网卡和计算机之间的通信则是通过计算机主板上的I/O总线以并行传输方式进行。
因此,网卡的一个重要功能就是要进行串行/并行转换。由于网络上的数据率和计算机总线上的数据率并不相同,因此在网卡中必须装有对数据进行缓存的存储芯片。
网卡以前是作为扩展卡插到计算机总线上的,但是由于其价格低廉而且以太网标准普遍存在,大部分新的计算机都在主板上集成了网络接口。
这些主板或是在主板芯片中集成了以太网的功能,或是使用一块通过PCI (或者更新的PCI-Express总线)连接到主板上的廉价网卡。
除非需要多接口或者使用其它种类的网络,否则不再需要一块独立的网卡。甚至更新的主板可能含有内置的双网络(以太网)接口。
1、数据的封装与解封
发送时将上一层传递来的数据加上首部和尾部,成为以太网的帧。接收时将以太网的帧剥去首部和尾部,然后送交上一层
2、链路管理
主要是通过CSMA/CD(Carrier Sense Multiple Access with Collision Detection ,带冲突检测的载波监听多路访问)协议来实现
3、数据编码与译码
即曼彻斯特编码与译码。其中曼彻斯特码,又称数字双向码、分相码或相位编码(PE),是一种常用的二元码线路编码方式之一,被物理层使用来编码一个同步位流的时钟和数据。在通信技术中,用来表示所要发送比特 流中的数据与定时信号所结合起来的代码。 常用在以太网通信,列车总线控制,工业总线等领域。
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 全双工流量控制。
EEPROM Interface接口用于存放mac地址,Internal SRAM用于存放收发数据,MII部分把MAC部分与PHY部分连接起来通信,AUTO-MDIX用于自适应10/100M网络,在物理层上,MAC在PHY之下。
开发板FS4412的网卡DM9000A连接到了SROM控制器,下面我们分析数据线、地址线和信号线连接
SD0~15: 16位数据线连接到引脚BUF_B_Xm0DATA[0:15],由CMD引脚决定访问类型。
可见数据和地址线都连接到了SOC的XM0上。
dm9000 外围电路 转换电路 soc
CMD--------BUF_B_Xm0ADDR2--------Xm0ADDR2-----Xm0ADDR2
如下图所示:
CMD: 命令线,当CMD为高,表示SD 传输的是数据,CMD为低表示传输的是地址,接在exynos4412的BUF_B_Xm0ADDR2上,可见CMD复用了地址线Xm0ADDR2引脚。
dm9000 外围电路 转换电路 soc
IOR--------BUF_Xm0OEn--------Xm0OEn-----Xm0OEn
IOW--------BUF_Xm0WEn--------Xm0WEn-----Xm0WEn
dm9000 外围电路 转换电路 soc
CS--------BUF_Xm0cs1--------Xm0cs1-----Xm0CSn1
CS#:片选,放在exynos4412的Bank1的片选上面,内存基地址是0x05000000。
我们的DM9000A是放在exynos4412的Bank1(0X05000000)的片选上面。
而DM9000的CMD引脚接在Bank1的LADDR2上面
读写DM9000A的地址
CMD拉低, 此时向0X05000000地址上读写的数据便是DM9000A的内部寄存器地址
读写DM9000A的数据
CMD拉高,此时向0X05000000+4地址上读写的数据便是DM9000A的数据
设置exynos4412的bank1的硬件位宽,时序,因为不同的硬件,涉及的数据收发都不同。
中断线DM9000_IRQ通过U8转接到引脚XEINT6
由上图可知中断引脚INT,接在exynos4412的GPX0_6脚上。
uboot中的DM9000A的驱动没有用到中断。
XM0引脚复用了GPIO引脚,所以需要初始化对应的GPIO引脚来使能SROMC。
1) GPY0CON
2) GPY1CON
3)GPY3CON
SROM是高速存储器,Cache技术就是通过在DROM和CPU之间插入一小块SROM来减小CPU和存储之间的速度差异的。
EXYNOS 4412包含了SROM控制器,特性如下:
首先我们要初始化 exynos4412的 SROM 控制器,设置总线宽度和相关时序。
针对 SROM 控制器的每一个 bank 只有2 个寄存器: SROM_BW 和 SROM_BC。
在 SROM_BW 寄存器中,我们只关心与 bank1 相关的域。
上面分析过,
DM9000A 的 16 根数据线全部接在 exynos 4412的数据线上,所以 DataWidth1 设置为 1;
DM9000A 的地址是按字节存取的,所以 AddrMode1 设置为 1;
通过查看原理图,没有使用 Xm0WAITn和 Xm0BEn 引脚;
所以 WaitEnable1 和 ByteEnable1 均设置为 0。
SROM_BW[7:4]=0x3
SROM 控制器读时序和 DM9000A 的读时序主要通过SROM_BCn控制寄存器设置。
设置这些时序之前,首先来看DM9000A芯片手册时序图和exynos4412的时序图
详尽时序分析:,内存控制器使用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 )
故设置参考值为:
#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
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
在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:
从 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 */
在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所能支持的功能非常的多,驱动的实现相对比较复杂,搞清楚裸机的网卡驱动,我们再去学习Linux内核的DM9000驱动就相对容易一些。
本节将详细讲解DM9000A网卡的数据的收发操作的流程。
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;
};
该结构体维护了操作网卡的回调函数等信息,我们只需要把网口的收发数据操作封装到对应的回调函数中,然后注册到系统即可。
进入到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中;
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;
}
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 步:
DM9000A的寄存器很多,但是我们并需要都掌握,我们只需要掌握其中几个最重要的寄存器的使用即可。
网卡的初始化函数入口位于文件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 全双工模式。
发送流程
网卡数据的发送函数是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。
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中,所有的命令都用宏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");