电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
网卡DM9000及uboot协议栈详解
分 享
扫描二维码分享
网卡DM9000及uboot协议栈详解
dm9000
uboot
一口Linux
关注
发布时间: 2021-05-26
丨
阅读: 767
# 第二十三章 ## 一、网卡 ### 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总线接口网卡 2. 按网络接口划分 除了可以按网卡的总线接口类型划分外,我们还可以按网卡的网络接口类型来划分。网卡最终是要与网络进行连接,所以也就必须有一个接口使网线通过它与其它计算机网络设备连接起来。不同的网络接口适用于不同的网络类型,目前常见的接口主要有以太网的RJ-45接口、细同轴电缆的BNC接口和粗同轴电AUI接口、FDDI接口、ATM接口等。而且有的网卡为了适用于更广泛的应用环境,提供了两种或多种类型的接口,如有的网卡会同时提供RJ-45、BNC接口或AUI接口。 * (1)RJ-45接口网卡 * (2)BNC接口网卡 * (3)AUI接口网卡 * (4)FDDI接口网卡 * (5)ATM接口网卡 3. 按带宽划分 随着网络技术的发展,网络带宽也在不断提高,但是不同带宽的网卡所应用的环境也有所不同,目前主流的网卡主要有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. 模块图 ![图1 DM9000内部结构框架](https://img-blog.csdnimg.cn/20210220202454183.png) EEPROM Interface接口用于存放mac地址,Internal SRAM用于存放收发数据,MII部分把MAC部分与PHY部分连接起来通信,AUTO-MDIX用于自适应10/100M网络,在物理层上,MAC在PHY之下。 ### 2. 引脚分析 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210219203906794.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rhb2Nhb2thZmVp,size_16,color_FFFFFF,t_70) ![ ](https://img-blog.csdnimg.cn/20210219203923559.png) ![ ](https://img-blog.csdnimg.cn/20210218131510240.png)(#:表示低电平有效) 开发板FS4412的网卡DM9000A连接到了SROM控制器,下面我们分析数据线、地址线和信号线连接 #### 1) SD0~15 SD0~15: 16位数据线连接到引脚BUF_B_Xm0DATA[0:15],由CMD引脚决定访问类型。 ![转接芯片U5 ](https://img-blog.csdnimg.cn/20210219194855641.png) ![连接到SOC的XM0](https://img-blog.csdnimg.cn/20210219194958972.png) 可见数据和地址线都连接到了SOC的XM0上。 ![SROMC的I/O描述 ](https://img-blog.csdnimg.cn/20210219195118186.png)数据线和信号线对应的SROMC的引脚如上图。 #### 2) CMD ```c dm9000 外围电路 转换电路 soc CMD--------BUF_B_Xm0ADDR2--------Xm0ADDR2-----Xm0ADDR2 ``` 如下图所示: CMD: 命令线,当CMD为高,表示SD 传输的是数据,CMD为低表示传输的是地址,接在exynos4412的BUF_B_Xm0ADDR2上,可见CMD复用了地址线Xm0ADDR2引脚。 ![U7](https://img-blog.csdnimg.cn/20210212232426426.png) #### 3) IOR#、IOW# ```c dm9000 外围电路 转换电路 soc IOR--------BUF_Xm0OEn--------Xm0OEn-----Xm0OEn IOW--------BUF_Xm0WEn--------Xm0WEn-----Xm0WEn ``` ![ ](https://img-blog.csdnimg.cn/20210219195503503.png) #### 4) CS# ```c dm9000 外围电路 转换电路 soc CS--------BUF_Xm0cs1--------Xm0cs1-----Xm0CSn1 ``` ![memory map](https://img-blog.csdnimg.cn/20210212234123780.png) 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 ![中断引脚](https://img-blog.csdnimg.cn/20210212231710358.png)![GPX0_6](https://img-blog.csdnimg.cn/20210212231820587.png) 由上图可知中断引脚INT,接在exynos4412的GPX0_6脚上。 uboot中的DM9000A的驱动没有用到中断。 ### 3. 复用GPIO引脚 XM0引脚复用了GPIO引脚,所以需要初始化对应的GPIO引脚来使能SROMC。 1) GPY0CON ![GPY0CON](https://img-blog.csdnimg.cn/20210219210359714.png) 2) GPY1CON ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210219210539595.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rhb2Nhb2thZmVp,size_16,color_FFFFFF,t_70)3)GPY3CON ![ ](https://img-blog.csdnimg.cn/20210219210702776.png) 4) GPY5CON ![GPY5CON](https://img-blog.csdnimg.cn/20210219210747646.png) 5) GPY6CON ![GPY6CON](https://img-blog.csdnimg.cn/20210219210901855.png) ## 三、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 相关的域。 ![SROM_BW](https://img-blog.csdnimg.cn/20210213000723624.png) 上面分析过, DM9000A 的 16 根数据线全部接在 exynos 4412的数据线上,所以 DataWidth1 设置为 1; DM9000A 的地址是按字节存取的,所以 AddrMode1 设置为 1; 通过查看原理图,没有使用 Xm0WAITn和 Xm0BEn 引脚; 所以 WaitEnable1 和 ByteEnable1 均设置为 0。 ```c SROM_BW[7:4]=0x3 ``` ### 3. SROM_BC1 SROM 控制器读时序和 DM9000A 的读时序主要通过SROM_BCn控制寄存器设置。 设置这些时序之前,首先来看DM9000A芯片手册时序图和exynos4412的时序图 ![时序图](https://img-blog.csdnimg.cn/20210213104945484.png)![SROMC读时序](https://img-blog.csdnimg.cn/20210220180913640.png) ![DM9000AE写时序](https://img-blog.csdnimg.cn/20210220181549979.png) 详尽时序分析:,内存控制器使用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)定义如下: ![SROM_BCn](https://img-blog.csdnimg.cn/20210213101655221.png)![SROM_BCn](https://img-blog.csdnimg.cn/20210213101713692.png) 故设置参考值为: ```c #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 的有一段说明: ```c 06/03/2008 Remy Bohmer
- 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驱动程序。 分析驱动涉及到以下几个文件: ```c 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](https://img-blog.csdnimg.cn/20210218151655825.png)从 Makefile 得知,要把 DM9000A 的驱动编译进 u-boot中,需要定义 CONFIG_DRIVER_DM9000 这个宏。 宏定义如下: ```c #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,我们要打开它们。 ```c /@@* 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 也列出了一些可用的命令。 ```c #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中有如下代码: ```c void board_init_r(gd_t *id, ulong dest_addr) { …… board_init(); /@@* Setup chipselects */ …… } ``` 函数board_init()定义在board/samsung/origen/origen.c中,我们在该函数中添加了初始化srom代码: ```c 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 控制器。 ```c 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() - se
lect 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** ```c /@@* 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提供的标准的统一的网卡设备接口。 ```c 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 函数: ```c 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 中定义,下面是该函数部分代码: ```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 中定义 ```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 中定义 ```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,初始化函数如下: ```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地址, ```c 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定义在同一文件下: ```c 108 static board_info_t dm9000_info; ``` 函数eth_register()位于net/eth.c中; * 功能:用于注册网卡到系统中,如果之前网卡设备链表为空,则直接复制给全局指针变量eth_devices和eth_current ,如果不为空,则把当前网卡插入到链表eth_devices中。 ```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。 ```c 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) 在下面寄存器描述中,默认栏采用如下形式: ```bash
,
``` 其中
```bash 1 该位设为逻辑 1 0 该位设为逻辑 0 X 没有默认值 P 电源复位恢复默认值 H 硬件复位恢复默认值 S 软件复位恢复默认值 E 从 EEPROM 得到默认值 T 从捆绑引脚(strap pin)得到默认值 ```
: ```bash 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) ![NCR](https://img-blog.csdnimg.cn/20210211114226545.png) 2. 网络状态寄存器(NSR) ![NSR](https://img-blog.csdnimg.cn/20210211114349918.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210213122800523.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210213122852138.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rhb2Nhb2thZmVp,size_16,color_FFFFFF,t_70)![在这里插入图片描述](https://img-blog.csdnimg.cn/20210213122922689.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rhb2Nhb2thZmVp,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210222214303574.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rhb2Nhb2thZmVp,size_16,color_FFFFFF,t_70) ![ISR](https://img-blog.csdnimg.cn/20210219214328917.png) DAVICOM 指定配置和状态寄存器(DSCSR) ![DSCSR](https://img-blog.csdnimg.cn/20210222213421439.png) ### 4. 网卡的初始化 网卡的初始化函数入口位于文件net/eth.c下的函数eth_init(): ```c 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(): ```c 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() ```c 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 } ``` 该函数的参数 ```c 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() 定义如下: ```c 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的数据接收 ```c 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(); 定义如下: ```c 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的数据收发的原理和流程,就要分析我们注册网卡的以下几个函数: ```c 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网络协议栈的函数调用流程: ![uboot中网络协议栈](https://img-blog.csdnimg.cn/20210222203133241.png) ### 2. 通过DNS命令来解析一个数据包的收发流程 uboot中,所有的命令都用宏U_BOOT_CMD来定义, dns命令的定义如下: ```c 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() ```c 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()代码比较长,我们只分析其中比较重要的几段代码 ```c 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等环境变量的值并复制到对应的全局变量中 ```c static void NetInitLoop(void) { static int env_changed_id; int env_id = get_env_id(); /@@* up
date 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(), ```c 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, ```c 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数据包 ```c 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服务端口号,数据包长度 ```c 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(),参数分别是:要发送数据帧的首地址,数据包长度 ```c 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() ```c int eth_rx(void) { if (!eth_current) return -1; return eth_current->recv(eth_current); } ``` eth_current->recv(eth_current)函数就是我们注册的网卡的接收函数dm9000_rx(),该函数我们上一章已经分析过,最终通过调用函数NetReceive(),将数据帧上传到协议栈 ```c 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 ```c 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 了。 ```c 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
)
一口Linux
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交