以太网(Ethernet)是一种计算机局域网组网技术,该技术基于IEEE制定的IEEE802.3标准,它规定了包括物理层的连线、电信号和介质访问层协议的内容。以太网是当前应用最普遍的局域网技术。它很大程度上取代了其他局域网标准,如令牌环、FDDI和ARCNET。历经100M以太网在上世纪末的飞速发展后,目前千兆以太网甚至10G以太网正在国际组织和领导企业的推动下不断拓展应用范围。基于以太网的应用一定时期内是研究开发热点。
Ethernet的接口实质是MAC通过MII总线控制PHY的过程。
MAC是Media Access Control的缩写,即媒体访问控制子层协议。该协议位于OSI七层协议中数据链路层的下半部分,主要负责控制与连接物理层的物理介质。在发送数据的时候,MAC协议可以事先判断是否可以发送数据,如果可以发送将给数据加上一些控制信息,最终将数据以及控制信息以规定的格式发送到物理层;在接收数据的时候,MAC协议首先判断输入的信息并是否发生传输错误,如果没有错误,则去掉控制信息发送至LLC层。以太网MAC由IEEE-802.3以太网标准定义。
MII即媒体独立接口, “媒体独立”表明在不对MAC硬件重新设计或替换的情况下,任何类型的PHY设备都可以与其兼容并正常工作。包括分别用于发送器和接收器的两条独立信道。每条信道都有自己的数据、时钟和控制信号。
MII以4位半字节方式传送数据双向传输,时钟速率25MHz。其工作速率可达100Mb/s。MII管理接口是个双信号接口,一个是时钟信号,另一个是数据信号。通过管理接口,上层能监视和控制PHY。其管理是使用SMI(Serial Management Interface)总线通过读写PHY的寄存器来完成的。PHY里面的部分寄存器是IEEE定义的,这样PHY把自己的目前的状态反映到寄存器里面,MAC通过SMI总线不断的读取PHY的状态寄存器以得知目前PHY的状态,例如连接速度,双工的能力等。当然也可以通过SMI设置PHY的寄存器达到控制的目的,例如流控的打开关闭,自协商模式还是强制模式等。不论是物理连接的MII总线和SMI总线还是PHY的状态寄存器和控制寄存器都是有IEEE的规范的,因此不同公司的MAC和PHY一样可以协调工作。当然为了配合不同公司的PHY的自己特有的一些功能,驱动需要做相应的修改。
PHY是物理接口收发器,它实现物理层。包括MII/GMII(介质独立接口)子层、PCS(物理编码子层)、PMA(物理介质附加)子层、PMD(物理介质相关)子层、MDI子层。
100BaseTX采用4B/5B编码。PHY在发送数据的时候,收到MAC过来的数据(对PHY来说,没有帧的概念,对它来说,都是数据而不管什么地址,数据还是CRC),每4bit就增加1bit的检错码,然后把并行数据转化为串行流数据,再按照物理层的编码规则把数据编码,再变为模拟信号把数据送出去。收数据时的流程反之。PHY还有个重要的功能就是实现CSMA/CD的部分功能。它可以检测到网络上是否有数据在传送,如果有数据在传送中就等待,一旦检测到网络空闲,再等待一个随机时间后将送数据出去。如果两个碰巧同时送出了数据,那样必将造成冲突,这时候,冲突检测机构可以检测到冲突,然后各等待一个随机的时间重新发送数据。这个随机时间很有讲究的,并不是一个常数,在不同的时刻计算出来的随机时间都是不同的,而且有多重算法来应付出现概率很低的同两台主机之间的第二次冲突。通信速率通过双方协商,协商的结果是两个设备中能同时支持的最大速度和最好的双工模式。这个技术被称为Auto Negotiation或者NWAY。隔离变压器把PHY送出来的差分信号用差模耦合的线圈耦合滤波以增强信号,并且通过电磁场的转换耦合到连接网线的另外一端。RJ-45中1、2是传送数据的,3、6是接收数据的。新的PHY支持AUTO MDI-X功能(也需要隔离变压器支持)。它可以实现RJ-45接口的1、2上的传送信号线和3、6上的接收信号线的功能自动互相交换。
MII(Media Independent Interface)即媒体独立接口,它是IEEE-802.3定义的以太网行业标准。MII接口一定会包含两部分:
MII接口的类型有很多,常用的有MII、RMII、SMII、SSMII、SSSMII、GMII、RGMII、SGMII、TBI、RTBI、XGMII、XAUI、XLAUI等。我们在这里只讨论MII、RMII、SMII、GMII接口。
MII 支持10兆和100兆的操作,它要求是25兆的总线时钟,一个接口由14根线组成(没考虑收发时钟),它的支持还是比较灵活的,但是有一个缺点是因为它一个端口用的信号线太多,如果一个8端口的交换机要用到112根线,16端口就要用到224根线,到32端口的话就要用到448 根线,一般按照这个接口做交换机,是不太现实的,所以现代的交换机的制作都会用到其它的一些从MII简化出来的标准,比如RMII、SMII、GMII 等。
RMII是简化的MII接口,在数据的收发上它比MII接口少了一倍的信号线,所以它一般要求是50兆的总线时钟。RMII一般用在多端口的交换机,它不是每个端口安排收、发两个时钟,而是所有的数据端口公用一个时钟用于所有端口的收发,这里就节省了不少的端口数目。RMII的一个端口要求7 根数据线,比MII少了一倍,所以交换机能够接入多一倍数据的端口。
SMII是由思科提出的一种媒体接口,它有比RMII更少的信号线数目,S表示串行的意思。因为它只用一根信号线发送数据,一根信号线接收数据,所以在时钟上为了满足100M的需求,它的时钟频率很高, 达到了125兆,采用125兆时钟的目的是因为数据线里面会传送一些控制信息。SMII一个端口仅用4根信号线完成100M信号的传输,比起RMII 差不多又少了一倍的信号线。SMII 在工业界的支持力度是很高的。同理,所有端口的数据收发都公用同一个外部的125M时钟。
GMII(Gigabit MII) 是千兆网的MII 接口,GMII采用8位接口数据,一个接口由22根线组成(没考虑收发时钟)工作时钟125MHz,因此传输速率可达1000Mbps。同时兼容MII 所规定的10/100 Mbps工作方式。
图 121. MII数据接口接线图
图 122. RMII数据接口接线图
在100Mbps速率时,TX/RX每个时钟周期采样一个数据;在10Mbps速率时,TX/RX每隔10个周期采样一个数据,因而TX/RX数据需要在数据线上保留10个周期,相当于一个数据发送10次。
当PHY层芯片收到有效的载波信号后,CRS_DV信号变为有效,此时如果FIFO中还没有数据,则它会发送出全0的数据给MAC,然后当FIFO中填入有效的数据帧,数据帧的开头是“101010---”交叉的前导码,当数据中出现“01”的比特时,代表正式数据传输开始,MAC芯片检测到这一变化,从而开始接收数据。
当外部载波信号消失后,CRS_DV会变为无效,但如果FIFO中还有数据要发送时,CRS_DV在下一周期又会变为有效,然后再无效再有效,知道FIFO中数据发送完为止。
管理配置接口控制PHY的特性。该接口有32个寄存器地址,每个地址16位。其中前16个已经在“IEEE 802.3,2000-22.2.4 Management Functions”中规定了用途,其余的则由各器件厂商自己指定。
图 123. MDIO操作时序
大部分不是专门针对以太网网络应用的嵌入式设备设备,比如GPS,监测设备等只需要简单的网络接入功能,也即将信息上传或者下载的功能,此时只需要支持单RJ45口的以太网控制芯片即可,这类产品的价格便宜,接口简单,易于使用。通常开发板所支持的以太网控制器有Davicom的DM9000系列和Cirrus Logic的CS8900系列。
对于路由器,交换机,防火墙等这些网络设备通常需要多口的功能强大的交换芯片。这些芯片具有大吞吐量,接口丰富,功能选项众多,缓存大等特点。有些SOC CPU已经将以太网控制器作为片上子系统集成到CPU内部,这进一步提高了数据传输数量和稳定性。这类芯片的厂商主要有Broadcom,Ralink,Marvell等。与此同时这类芯片的体积也相对较大,价格较高。
搭载三星S3C系列CPU的开发板通常会选用DM9000或者DM9000A。这是由于S3C系列针对多媒体设备的开发,而非网络设备,所以它没有提供MII/MDIO等接口。DM9000A是在DM9000基础上进行了部分功能的修改:表 50. DM9000系列功能比较
功能 | DM9000 | DM9000A |
---|---|---|
封装 | LQFP 100Pin | LQFP 48Pin |
接口 | ISA I/O MII | I/O |
I/O位宽 | 8/16/32 | 8/16 |
Auto MDIX | 不支持 | 支持 |
10/100M | 支持 | 支持 |
SRAM | 4K Dword | 16K Bytes |
GPIO | 4 Pin | 不支持 |
MDIO | 支持 | 支持 |
IP/TCP/UDP校验 | 不支持 | 支持 |
应用 | VoIP CPE (ATA, IP Phone, Video Phone) IP STB, IPC, Internet Radio |
图 124. DM9000功能图
图 125. DM9000A功能图
图 126. DM9000A接线图
S3C6410 SROM控制器(SROMC)支持外部8,16位NOR Flash,PROM,SRAM存储器。而DM9000A就挂载到该控制器总线。该控制器支持6个页,每页高达128M的地址空间。支持字节和半字存取的外部存储器。每个页对应一个外存设备,每个外村又需要一个片选信号。所以当需要访问某个地址时,该地址落在哪个块,就决定了哪个片选信号被使能。这里以DM9000A对应SROMC Bank1为例。
地址范围 大小 描述 0x1000_0000 0x17FF_FFFF 128MB SROMC Bank 0 0x1800_0000 0x1FFF_FFFF 128MB SROMC Bank 1 0x2000_0000 0x27FF_FFFF 128MB SROMC Bank 2 0x2800_0000 0x2FFF_FFFF 128MB SROMC Bank 3 0x3000_0000 0x37FF_FFFF 128MB SROMC Bank 4 0x3800_0000 0x3FFF_FFFF 128MB SROMC Bank 5
基于上述理论介绍,这里看一下读写DM9000A寄存器的相关函数。
static u8 ior(board_info_t *db, int reg) { writeb(reg, db->io_addr); return readb(db->io_data); } static void iow(board_info_t *db, int reg, int value) { writeb(reg, db->io_addr); writeb(value, db->io_data); }
ior和iow分别对reg进行读和写。它们的第一步总是写内部寄存器的地址到io_addr(0x1800 0000对应的虚拟地址),接着就是读或者写value到io_data(0x1800 0004对应的虚拟地址)。writeb和readb考虑了字节序的问题。
系统上电时,S3C6410通过总线配置DM9000A内部网络控制寄存器NCR,中断寄存器ISR等,完成DM9000A的初始化。随后它进入数据收发等待状态。当S3C6410向以太网发送数据时,想将数据打包成UDP,TCP等IP数据包,然后通过16bit总线发送到DM9000A的数据发送缓存中,接着将数据长度等信息填充到DM9000A的相应寄存器内,使能发送。
当DM9000A接收到外部网络的数据包是,首相检测数据包的合法性,如果帧头标志有错或存在CRC校验失败,则将该数据包丢弃。否则将数据帧缓存到内部的SRAM中,并通过中断标志位通知S3C6410,然后CPU通过读取缓存区接收数据。
图 127. 数据收发和配置图
arch/arm/mach-s3c6400/include/mach/map.h #define S3C64XX_PA_DM9000 (0x18000000) #define S3C64XX_SZ_DM9000 SZ_1M #define S3C64XX_VA_DM9000 S3C_ADDR(0x03b00300) arch/arm/plat-s3c64xx/devs.c #define DM9000_ETH_IRQ_EINT0 IRQ_EINT(7) static struct resource dm9000_resources_cs1[] = { [0] = { .start = S3C64XX_VA_DM9000, .end = S3C64XX_VA_DM9000 + S3C64XX_SZ_DM9000 - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = (DM9000_ETH_IRQ_EINT0), .end = (DM9000_ETH_IRQ_EINT0), .flags = IORESOURCE_IRQ, }, };
static struct dm9000_plat_data dm9000_setup_cs1 = { .flags = DM9000_PLATF_16BITONLY }; struct platform_device s3c_device_dm9000_cs1 = { .name = "dm9000_con201", .id = 0, .num_resources = ARRAY_SIZE(dm9000_resources_cs1), .resource = dm9000_resources_cs1, .dev = { .platform_data = &dm9000_setup_cs1, } };s3c_device_dm9000_cs1中定义了设备名,它是非常关键的一个参数,因为在驱动注册时,将根据这个名称来查找设备。DM9000A的设备通过platform设备注册子系统进行注册。
arch/arm/mach-s3c6410/mach-smdk6410.c static struct platform_device *smdk6410_devices[] __initdata = { ...... &s3c_device_dm9000_cs1, ...... };在smdk6410_machine_init函数中,将对smdk6410_devices数组中的所有设备进行注册:
platform_add_devices(smdk6410_devices, ARRAY_SIZE(smdk6410_devices));
图 128. platform设备与通用设备结构关系
struct device platform_bus = { .bus_id = "platform", }; struct bus_type platform_bus_type = { .name = "platform", .dev_attrs = platform_dev_attrs, .match = platform_match, .uevent = platform_uevent, .pm = PLATFORM_PM_OPS_PTR, };
图 129. platform设备注册流程图
Linux操作系统中,根据外围设备的不同,分为三类设备驱动:字符设备驱动(键盘,LCD等);块设备(如硬盘等)驱动和网络设备驱动(如网卡)。
图 130. DM9000A驱动结构图
图 131. DM9000A驱动注册流程图
在driver_probe_device函数中将调用驱动总线对应的匹配函数还匹配驱动和设备,显然这里使用的就是驱动名和设备名。
static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev; pdev = container_of(dev, struct platform_device, dev); return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); }
一旦匹配,将通过really_probe函数调用真正的驱动探测函数,这里就是dm9000_probe。
探测函数主要实现硬件以太网控制器的相关环境配置,发现,验证,初始化和注册,并产生一个含私有数据的struct net_device的数据结构:它对应内核对该以太网控制器的抽象。
寄存器 地址 读/写 描述 复位值 SROM_BW 0x70000000 读/写 SROM总线宽度和等待控制 0x0000_000x SROM_BW 位 描述 初始状态 ByteEnable1 [7] nWBE/nBE(用于UB/LB) 控制,用于存储器Bank1。 0 = 不使用UB/LB (XrnWBE[1:0]专门的nWBE[1:0]) 1 =使用UB/LB (XrnWBE[1:0]专门的nBE[1:0] 0 WaitEnable1 [6] 等待使能控制,用于存储器Bank1 0=WAIT 禁止 1=WAIT 使能 0 Reserved [5] 保留 0 DataWidth1 [4] 数据总线宽度控制,用于存储器Bank1 0=8 位 1=16 位 0首先将SROM_BW[7:4]设置为0b0001,这相当于置DataWidth1为1,所以这里设置数据总线宽度为16bits。
tmp = __raw_readl(S3C64XX_SROM_BW); tmp &=~(0xF<<4); tmp |= (1<<4); __raw_writel(tmp, S3C64XX_SROM_BW);接着配置SROM Bank1的页控制寄存器。这与读写所需的时序周期有关。
寄存器 地址 读/写 描述 复位值 SROM_BC1 0x70000008 读/写 SROM Bank1 控制寄存器 0x000F_0000
__raw_writel(~(0xFFFFFFFF<<0), S3C64XX_SROM_BC1); __raw_writel((0x0<<28)|(0x4<<24)|(0xd<<16)|(0x1<<12)|(0x4<<8)|(0x6<<4)|(0x0<<0), S3C64XX_SROM_BC1);接下来配置外部中断,由于该pin是复用的,既可以作为GPIO的输入输出用,又可以作为外部中断使用,对应的配置寄存器是S3C64XX_GPNCON。
/* GPN7 to EINT */ writel((readl(S3C64XX_GPNCON) & ~(0x3 <<14)) | (0x2 << 14), S3C64XX_GPNCON); writel((readl(S3C64XX_GPNPUD) &~(0x3<<14)),S3C64XX_GPNPUD); /* EINT7 to high level triggered */ writel((readl(S3C64XX_EINT0CON0) & ~(0x7 <<12)) | (0x1 << 12), S3C64XX_EINT0CON0); writel((readl(S3C64XX_EINT0PEND)&~(0x1<<7)),S3C64XX_EINT0PEND); writel(readl(S3C64XX_EINT0MASK) & ~(0x1 << 7), S3C64XX_EINT0MASK);最后S3C64XX_EINT0CON0用来配置高电平触发,并清除该中断对应的挂起寄存器PEND。通过S3C64XX_EINT0MASK使能中断7号中断。