----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
在学习Mini2440裸机程序时,我们介绍过关于DM9000网卡的相关知识,包括电路图、以及DM9000寄存器等信息。具体可以参考Mini2440裸机开发之DM9000。
本节对之前已经介绍过的知识不会再进行重复介绍。这一节我们将直入主题,介绍如何移植DM9000网卡驱动。
一、platform设备注册(dm9000)
在刚学习驱动移植的时候,我们为了使用nfs作为根文件系统,我们在 linux驱动移植-DM9000网卡驱动小节介绍了DM9000网卡驱动的移植,但是那时候我们仅仅是移植,并未对源码进行深入研究。 DM9000网卡设备驱动,其采用的也是platform设备驱动模型。1.1 smdk2440_device_eth
我们定位到arch/arm/mach-s3c24xx/mach-smdk2440.c文件,在该文件中我们引入了dm9000.h头文件:
#include <linux/dm9000.h>
定义了DM9000网卡设备的物理基地址:
#define MACH_SMDK2440_DM9K_BASE (S3C2410_CS4 + 0x300) # S3C2410_CS4 = 0X20000000
定义了网卡platform设备smdk2440_device_eth:
/* DM9000AEP 10/100 ethernet controller */
static struct resource smdk2440_dm9k_resource[] = {
[0] = DEFINE_RES_MEM(MACH_SMDK2440_DM9K_BASE, 4),
[1] = DEFINE_RES_MEM(MACH_SMDK2440_DM9K_BASE + 4, 4),
[2] = DEFINE_RES_NAMED(IRQ_EINT7, 1, NULL, IORESOURCE_IRQ
| IORESOURCE_IRQ_HIGHEDGE), // 高电平触发
};
/*
* The DM9000 has no eeprom, and it's MAC address is set by
* the bootloader before starting the kernel.
*/
static struct dm9000_plat_data smdk2440_dm9k_pdata = {
.flags = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM), // 标志位,16位模式、DM9000没有外挂EPPROM
};
static struct platform_device smdk2440_device_eth = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(smdk2440_dm9k_resource),
.resource = smdk2440_dm9k_resource,
.dev = {
.platform_data = &smdk2440_dm9k_pdata,
},
};
我们重点关注一下platform设备资源定义,这里定义了三个资源,前两个是内存资源、第三个是中断资源:
注意:
- 在定义DM9000网卡platform设备的时,第一个资源必须是IO内存资源,且定义的是DM9000地址寄存器地址;
- 在定义DM9000网卡platform设备的时,第二个资源必须是IO内存资源,且定义的是DM9000数据寄存器地址;
- 在定义DM9000网卡platform设备的时,第三个资源必须是中断资源,CPU中断引脚连接DM9000 INT引脚;
dm9000_plat_data用来保存DM9000设备的初始化配置,除了flags,还有一些其它参数,比如:
- dev_addr:设置DM9000设备的初始化MAC地址;
- inblk:设置接收数据操作函数;
- outblk:设置发送数据操作函数;
我们已经定义了网卡相关的platform_device设备smdk2440_device_eth,并进行了初始化,那platform设备啥时候注册的呢?
linux内核启动的时候会根据uboot中设置的机器id执行相应的初始化工作,比如.init_machine、.init_irq,我们首先定位到arch/arm/mach-s3c24xx/mach-smdk2440.c:
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben-linux@fluff.org> */
.atag_offset = 0x100,
.init_irq = s3c2440_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.init_time = smdk2440_init_time,
MACHINE_END
重点关注init_machine,init_machine中保存的是开发板资源注册的初始化代码。
1.2 smdk2440_machine_init
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); // s3c2440若干个platform设备注册 usb host controller、lcd、wdt等
smdk_machine_init(); // s3c24x0系列若干个platform设备注册(通用)
}
这里利用platform_add_devices进行若干个platform设备的注册,该函数通过调用platform_device_register实现platform设备注册.
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&smdk2440_device_eth,
};
二、platform驱动注册(dm9000)
我们需要配置内核支持DM9000网卡驱动:
Device Drivers --->
[*] Network device support -->
[*] Ethernet driver support -->
[*] DM9000 support
这样我们内核才会支持DM9000网卡驱动。配置了DM9000 support之后,配置文件.config中会包含如下项:
# CONFIG_GEMINI_ETHERNET is not set
CONFIG_DM9000=y
当我们使用make uImage编译内核时会将dm9000.o编译进内核:
drivers/net/ethernet/davicom/Makefile:6:obj-$(CONFIG_DM9000) += dm9000.o
dm9000.c文件位于drivers/net/ethernet/davicom目录下。
2.1 入口和出口函数
我们在dm9000.c文件定位到驱动模块的入口和出口:
module_platform_driver(dm9000_driver);
module_platform_driver宏展开后本质上就是:
module_init(dm9000_driver_init);
module_exit(dm9000_driver_exit);
static int __init dm9000_driver_init(void)
{
platform_driver_register(dm9000_driver);
}
static void __exit dm9000_driver_exit(void)
{
platform_driver_unregister(dm9000_driver);
}
看到这里是不是有点意外,这里是通过platform_driver_register函数注册了一个platform驱动。
在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是dm9000_probe函数。
#ifdef CONFIG_OF
static const struct of_device_id dm9000_of_matches[] = { // 设备树匹配使用
{ .compatible = "davicom,dm9000", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dm9000_of_matches);
#endif
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.pm = &dm9000_drv_pm_ops,
.of_match_table = of_match_ptr(dm9000_of_matches),
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,
};
2.2 dm9000_probe
/*
* Search DM9000 board, allocate space and register it
*/
static int
dm9000_probe(struct platform_device *pdev)
{
struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev); // pdev->dev.platform_data
struct board_info *db; /* Point a board information structure */
struct net_device *ndev;
struct device *dev = &pdev->dev;
const unsigned char *mac_src;
int ret = 0;
int iosize;
int i;
u32 id_val;
int reset_gpios;
enum of_gpio_flags flags;
struct regulator *power;
bool inv_mac_addr = false;
power = devm_regulator_get(dev, "vcc");
if (IS_ERR(power)) {
if (PTR_ERR(power) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_dbg(dev, "no regulator provided\n");
} else {
ret = regulator_enable(power);
if (ret != 0) {
dev_err(dev,
"Failed to enable power regulator: %d\n", ret);
return ret;
}
dev_dbg(dev, "regulator enabled\n");
}
reset_gpios = of_get_named_gpio_flags(dev->of_node, "reset-gpios", 0,
&flags);
if (gpio_is_valid(reset_gpios)) {
ret = devm_gpio_request_one(dev, reset_gpios, flags,
"dm9000_reset");
if (ret) {
dev_err(dev, "failed to request reset gpio %d: %d\n",
reset_gpios, ret);
return -ENODEV;
}
/* According to manual PWRST# Low Period Min 1ms */
msleep(2);
gpio_set_value(reset_gpios, 1);
/* Needs 3ms to read eeprom when PWRST is deasserted */
msleep(4);
}