继续上一节内容,不知道你有没有注意到,启动u-boot后,运行有一行信息:
输出Flash信息这一部分代码是位于board_init_r阶段,执行initr_net()函数的输出结果。
我们开发板上搭载了型号为DM9000网卡芯片,这一节我们将会介绍u-boot如何支持我们的DM9000网卡,这样我们后续就可以通过网络命令下载程序。
一、DM9000介绍
关于DM9000的相关内容可以先看<Mini2440裸机开发之DM9000>这篇文章。
二、DM9000支持
2.1 宏定义
在include/configs/smdk2440.h文件有网卡相关的定义,如下:
/* * Hardware drivers */ #define CONFIG_CS8900 /* we have a CS8900 on-board */ #define CONFIG_CS8900_BASE 0x19000300 #define CONFIG_CS8900_BUS16 /* the Linux driver does accesses as shorts */
可以看到u-boot默认的网卡是cs8900。
2.2 分析启动信息
在u-boot启动时,我们打印调试信息:
Net: Initial value for argc=3 Final value for argc=3 CS8900-0 Error: CS8900-0 address not set.
我们定位到 initr_net(common/board_r.c):
static int initr_net(void) { puts("Net: "); eth_initialize(); #if defined(CONFIG_RESET_PHY_R) debug("Reset Ethernet PHY\n"); reset_phy(); #endif return 0; }
函数eth_initialize在net/eth_legacy.c中定义:
int eth_initialize(void) { int num_devices = 0; eth_devices = NULL; eth_current = NULL; eth_common_init(); /* * If board-specific initialization exists, call it. * If not, call a CPU-specific one */ if (board_eth_init != __def_eth_init) { if (board_eth_init(gd->bd) < 0) printf("Board Net Initialization Failed\n"); } else if (cpu_eth_init != __def_eth_init) { if (cpu_eth_init(gd->bd) < 0) printf("CPU Net Initialization Failed\n"); } else { printf("Net Initialization Skipped\n"); } if (!eth_devices) { puts("No ethernet found.\n"); bootstage_error(BOOTSTAGE_ID_NET_ETH_START); } else { struct eth_device *dev = eth_devices; char *ethprime = getenv("ethprime"); bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT); do { if (dev->index) puts(", "); printf("%s", dev->name); if (ethprime && strcmp(dev->name, ethprime) == 0) { eth_current = dev; puts(" [PRIME]"); } if (strchr(dev->name, ' ')) puts("\nWarning: eth device name has a space!" "\n"); eth_write_hwaddr(dev, "eth", dev->index); dev = dev->next; num_devices++; } while (dev != eth_devices); eth_current_changed(); putc('\n'); } return num_devices; }
其中eth_devices、eth_current定义如下:
static struct eth_device *eth_devices; struct eth_device *eth_current;
struct eth_device { char name[16]; unsigned char enetaddr[6]; phys_addr_t 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 *, const u8 *enetaddr, u8 set); #endif int (*write_hwaddr)(struct eth_device *); struct eth_device *next; int index; void *priv; };
2.3 eth_common_init(net/eth_common.c)
void eth_common_init(void) { bootstage_mark(BOOTSTAGE_ID_NET_ETH_START); #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB) miiphy_init(); #endif #ifdef CONFIG_PHYLIB phy_init(); #endif }
BOOTSTAGE_ID_NET_ETH_START、BOOTSTAGE_ID_COUNT为枚举类型enum bootstage_id其中的一个元素。这个枚举类型定义了u-boot在启动中的各个阶段,并使用record记录运行到每个阶段时一些信息,比如当前boot时间、id、flags等信息。
BOOTSTAGE_ID_NET_ETH_START = 64,
BOOTSTAGE_ID_COUNT = BOOTSTAGE_ID_USER + CONFIG_BOOTSTAGE_USER_COUNT,
static struct bootstage_record record[BOOTSTAGE_ID_COUNT] = { {1} };
bootstage_mark在common/bootstage.c中定义:
static inline ulong bootstage_mark(enum bootstage_id id) { show_boot_progress(id); return 0; }
show_boot_progress函数为空。
2.4 board_eth_init(board/samsung/smdk2440/smdk2440.c)
#ifdef CONFIG_CMD_NET #在include/generated/autoconf.h定义 int board_eth_init(bd_t *bis) { int rc = 0; #ifdef CONFIG_CS8900 rc = cs8900_initialize(0, CONFIG_CS8900_BASE); #endif return rc; } #endif
这里根据定义的宏CONFIG_CS8900,执行cs8900_initialize初始化CS8900网卡。
2.5 cs8900_initialize(drivers/net/cs8900.c)
int cs8900_initialize(u8 dev_num, int base_addr) { struct eth_device *dev; struct cs8900_priv *priv; dev = malloc(sizeof(*dev)); if (!dev) { return 0; } memset(dev, 0, sizeof(*dev)); priv = malloc(sizeof(*priv)); if (!priv) { free(dev); return 0; } memset(priv, 0, sizeof(*priv)); priv->regs = (struct cs8900_regs *)base_addr; dev->iobase = base_addr; dev->priv = priv; dev->init = cs8900_init; dev->halt = cs8900_halt; dev->send = cs8900_send; dev->recv = cs8900_recv; /* Load MAC address from EEPROM */ cs8900_get_enetaddr(dev); sprintf(dev->name, "%s-%hu", CS8900_DRIVERNAME, dev_num); eth_register(dev); return 0; }
这里我们不介绍这部分代码。我们仅仅关注drivers/net路径下的Makefile文件:
obj-$(CONFIG_CS8900) += cs8900.o
cs8900是否编译取决于宏CONFIG_CS8900。因此我们如果想支持DM9000网卡,只需要进行部分代码修改即可。
2.6 代码修改
修改include/configs/smdk2440.h,把原先的cs8900的宏取消,添加dm9000的宏:
#if 0 #define CONFIG_CS8900 /* we have a CS8900 on-board */ #define CONFIG_CS8900_BASE 0x19000300 #define CONFIG_CS8900_BUS16 /* the Linux driver does accesses as shorts */ #else #define CONFIG_DRIVER_DM9000 #define CONFIG_DM9000_BASE 0x20000000 //这三个是新加的 #define DM9000_IO CONFIG_DM9000_BASE //这三个是新加的 #define DM9000_DATA (CONFIG_DM9000_BASE + 4) //这三个是新加的 #endif
网卡属于内存设备,它的片选由写入的地址范围决定。
从DM9000的文档中可以知道知DM9000的cmd线(即LADDR2线)为0时,数据线做数据寄存器,为1时数据线做地址寄存器。
因为DM9000的片选是bank4,我们不难从S3C2440的文档中得知其地址是0x20000000开始的,既然LADDR2线控制着数据线的类型,我们只需改变LADDR2的值便可。所以推出DM9000的数据寄存器地址是0x20000000,地址寄存器的地址是0x20000004。
修改board/samsung/smdk2440/smdk2440.c:
#ifdef CONFIG_CMD_NET #在include/generated/autoconf.h定义 int board_eth_init(bd_t *bis) { int rc = 0; #ifdef CONFIG_CS8900 rc = cs8900_initialize(0, CONFIG_CS8900_BASE); #endif #ifdef CONFIG_DRIVER_DM9000 rc = dm9000_initialize(bis); #endif return rc; } #endif
修改board/samsung/smdk2440/lowlevel_init.S,只需要在那里修改bank4的内容即可:
.long 0x00000740; //BANKCON4
查看drivers/net路径下的Makefile文件,有这么一行:
obj-$(CONFIG_DRIVER_DM9000) += dm9000x.o
dm9000x是否编译取决于宏CONFIG_DRIVER_DM9000。
三、编译下载运行
3.1 编译
make distclean make smdk2440_config make ARCH=arm CROSS_COMPILE=arm-linux- V=1
3.2 下载到NOR FALSH
我们再次下载运行,输出如下:
initcall: 0005da94 U-Boot 2016.05 (Nov 29 2021 - 23:20:03 +0800) initcall: 0000f1b4 U-Boot code: 00000000 -> 0007F07C BSS: -> 000CE970 initcall: 000003f8 CPUID: 32440001 FCLK: 400 MHz HCLK: 100 MHz PCLK: 50 MHz initcall: 0000f408 DRAM: initcall: 000011f0 initcall: 0000f358 Monitor len: 000CE970 Ram size: 04000000 Ram top: 34000000 initcall: 0000ef90 initcall: 0000f15c TLB table from 33ff0000 to 33ff4000 initcall: 0000efa8 initcall: 0000f110 Reserving 826k for U-Boot at: 33f21000 initcall: 0000f0e4 Reserving 4160k for malloc() at: 33b11000 initcall: 0000f30c Reserving 80 Bytes for Board Info at: 33b10fb0 initcall: 0000efb0 initcall: 0000f0b0 Reserving 168 Bytes for Global Data at: 33b10f08 initcall: 0000f038 initcall: 0000efe4 initcall: 0000efb8 initcall: 0000f3f8 initcall: 0000f284 RAM Configuration: Bank #0: 30000000 64 MiB DRAM: 64 MiB initcall: 0000f01c New Stack Pointer is: 33b10ee0 initcall: 0000f248 initcall: 0000f1e0 Relocation Offset is: 33f21000 Relocating to 33f21000, new gd at 33b10f08, sp at 33b10ee0 initcall: 33f30484 initcall: 33f3048c initcall: 0000f670 (relocated to 33f30670) WARNING: Caches not enabled initcall: 0000f4a4 (relocated to 33f304a4) initcall: 0000f4cc (relocated to 33f304cc) initcall: 0000f654 (relocated to 33f30654) using memory 0x33b11000-0x33f21000 for malloc() initcall: 0000f4d4 (relocated to 33f304d4) initcall: 0000f460 (relocated to 33f30460) initcall: 0000f640 (relocated to 33f30640) initcall: 00001224 (relocated to 33f22224) dram_bank_mmu_setup: bank: 0 initcall: 0001829c (relocated to 33f3929c) initcall: 0000f630 (relocated to 33f30630) initcall: 0000f598 (relocated to 33f30598) Now running in RAM - U-Boot at: 33f21000 initcall: 0000f4dc (relocated to 33f304dc) initcall: 0000f5b4 (relocated to 33f305b4) Flash: fwc addr 00000000 cmd f0 00f0 16bit x 16 bit fwc addr 0000aaaa cmd aa 00aa 16bit x 16 bit fwc addr 00005554 cmd 55 0055 16bit x 16 bit fwc addr 0000aaaa cmd 90 0090 16bit x 16 bit fwc addr 00000000 cmd f0 00f0 16bit x 16 bit JEDEC PROBE: ID 1 2249 0 Found JEDEC Flash: AMD AM29LV160DB unlock address index 1 unlock addresses are 0x555/0x2aa erase_region_count = 1 erase_region_size = 16384 erase_region_count = 2 erase_region_size = 8192 erase_region_count = 1 erase_region_size = 32768 erase_region_count = 31 erase_region_size = 65536 flash_protect ON: from 0x00000000 to 0x00089A33 protect on 0 protect on 1 protect on 2 protect on 3 protect on 4 protect on 5 protect on 6 protect on 7 protect on 8 protect on 9 protect on 10 protect on 11 flash_protect ON: from 0x00070000 to 0x0007FFFF protect on 10 2 MiB initcall: 0000f57c (relocated to 33f3057c) NAND: board_nand_init() end of nand_init hwcontrol(): 0xff 0x83 hwcontrol(): 0xffffffff 0x81 dev_ready hwcontrol(): 0x90 0x83 hwcontrol(): 0x00 0x85 hwcontrol(): 0xffffffff 0x81 dev_ready hwcontrol(): 0x90 0x83 hwcontrol(): 0x00 0x85 hwcontrol(): 0xffffffff 0x81 dev_ready hwcontrol(): 0x90 0x83 hwcontrol(): 0x40 0x85 hwcontrol(): 0xffffffff 0x81 dev_ready hwcontrol(): 0xffffffff 0x80 0 MiB initcall: 0000f54c (relocated to 33f3054c) *** Warning - bad CRC, using default environment Destroy Hash Table: 33f9a890 table = 00000000 Create Hash Table: N=75 INSERT: table 33f9a890, filled 1/79 rv 33b11238 ==> name="bootdelay" value="5" INSERT: table 33f9a890, filled 2/79 rv 33b110f8 ==> name="baudrate" value="115200" INSERT: table 33f9a890, filled 3/79 rv 33b110a8 ==> name="ipaddr" value="10.0.0.110" INSERT: table 33f9a890, filled 4/79 rv 33b11260 ==> name="serverip" value="10.0.0.1" INSERT: table 33f9a890, filled 5/79 rv 33b114f4 ==> name="netmask" value="255.255.255.0" INSERT: free(data = 33b11008) INSERT: done initcall: 0000f474 (relocated to 33f30474) initcall: 0001835c (relocated to 33f3935c) initcall: 0000f53c (relocated to 33f3053c) initcall: 00015fb0 (relocated to 33f36fb0) In: serial Out: serial Err: serial Initial value for argc=3 Final value for argc=3 Initial value for argc=3 Final value for argc=3 Initial value for argc=3 Final value for argc=3 initcall: 00000c1c (relocated to 33f21c1c) initcall: 0000f52c (relocated to 33f3052c) initcall: 0000f50c (relocated to 33f3050c) initcall: 0000f4f0 (relocated to 33f304f0) Net: Initial value for argc=3 Final value for argc=3 dm9000 Error: dm9000 address not set. initcall: 0000f4e4 (relocated to 33f304e4) ### main_loop entered: bootdelay=5 ### main_loop: bootcmd="<UNDEFINED>" SMDK2440 #
报告有错误:dm9000 address not set,MAC地址未设置。对着打印信息查看代码,可以知道打印此条信息的代码是eth_write_hwaddr这个函数,这个函数在eth_initlize中调用:
int eth_write_hwaddr(struct eth_device *dev, const char *base_name, int eth_number) { unsigned char env_enetaddr[6]; int ret = 0; eth_getenv_enetaddr_by_index(base_name, eth_number, env_enetaddr); if (!is_zero_ethaddr(env_enetaddr)) { if (!is_zero_ethaddr(dev->enetaddr) && memcmp(dev->enetaddr, env_enetaddr, 6)) { printf("\nWarning: %s MAC addresses don't match:\n", dev->name); printf("Address in SROM is %pM\n", dev->enetaddr); printf("Address in environment is %pM\n", env_enetaddr); } memcpy(dev->enetaddr, env_enetaddr, 6); } else if (is_valid_ethaddr(dev->enetaddr)) { eth_setenv_enetaddr_by_index(base_name, eth_number, dev->enetaddr); } else if (is_zero_ethaddr(dev->enetaddr)) { #ifdef CONFIG_NET_RANDOM_ETHADDR net_random_ethaddr(dev->enetaddr); printf("\nWarning: %s (eth%d) using random MAC address - %pM\n", dev->name, eth_number, dev->enetaddr); #else printf("\nError: %s address not set.\n", dev->name); return -EINVAL; #endif } if (dev->write_hwaddr && !eth_mac_skip(eth_number)) { if (!is_valid_ethaddr(dev->enetaddr)) { printf("\nError: %s address %pM illegal value\n", dev->name, dev->enetaddr); return -EINVAL; } ret = dev->write_hwaddr(dev); if (ret) printf("\nWarning: %s failed to set MAC address\n", dev->name); } return ret; }
上面定义了一个宏 CONFIG_NET_RANDOM_ETHADDR ,如果定义了此宏的话就会随机分配网卡物理地址,否则就打印错误信息,我们并不需要此宏。自己定义物理地址,从代码流程看,网卡地址是直接写进环境变量中的,然后再读取环境变量,这个时候就需要看看设置环境变量的地方了。
环境变量的设置在 board_r.c的hash table中,initr_env函数,initr_env 会调用 env_relocate() ,env_relocate() 调用 set_default_env 函数,set_default_env 函数中有一个结构体 default_environment ,这里面定义了默认的参数,进去看看一看就知道里面全部定义的是默认的环境变量参数,其中也有网络的:
#ifdef CONFIG_ETHPRIME "ethprime=" CONFIG_ETHPRIME "\0" #endif #ifdef CONFIG_IPADDR "ipaddr=" __stringify(CONFIG_IPADDR) "\0" #endif #ifdef CONFIG_SERVERIP "serverip=" __stringify(CONFIG_SERVERIP) "\0"
这里面没有物理地址的定义,所以我们可以自己定义物理地址,在这里加入下面的内容:
#ifdef CONFIG_ETHADDR "ethaddr=" __stringify(CONFIG_ETHADDR) "\0"
在 include/configs/smdk2440.h 中加入 CONFIG_ETHADDR的宏 ,这里我们可以根据自己本机上的MAC地址进行定义。
#define CONFIG_ETHADDR 08:00:3e:26:0a:5b
保存编译,下载重新运行,查看结果。
3.3 shell命令
更改mac地址:
set ethaddr 00:0c:29:d3:fe:1d
ping目标主机:
ping 192.168.0.104
如果成功了,试着用网络下载程序试试,在winPC端打开tftp服务器,文件地址写为包含内核的文件地址,在板子上设置服务器ip:
set serverip 192.168.0.104
这个ip一定要和tftp服务器上显示的ip一样,向板子输入命令等待传输:
tftp 30000000 xxx
烧写完成了,启动程序:
bootm 30000000
参考文章