上班已经3个月了,本来就打算工作中有什么收获就能够写出来的,虽然这段时间里看了很多linux子系统(I2C,TTY,SPI,MTD,网络,USB)相关的内容,但是,因为没有做过比较多的实践经验,所以不敢马上写出来,但是又怕过了段时间很多东西都忘了,所以还是先把这段时间的感悟和心得写下来。如果要开始写linux系统的话,我觉得就不得不从platform_device和platform_driver开始,因为在一家芯片公司做的是SoC,片上的很多硬件控制器在linux中抽象成platform_device,全部都挂载在一条虚拟的platform bus上.
platform描述的资源有一个共同点可以在cpu上直接取址。
在linux内核中,匹配函数默认使用bus注册的匹配函数,如果bus没有注册则使用driver注册的匹配函数。
在系统每注册一个驱动的时候,会寻找一个与之匹配的设备,而匹配由总线完成。
对platform_dev0ce定义通常在BSP的板文件中实现,在板文件中,将platform_device归纳为一个数组,最终通过platform_add_device()函数统一注册。
所谓注册就是driver中的内核成员对象kobject的链表(kobject->entry)加入到bus总线中的bus->type->kset->list链表中。
每次系统增加一个platform_driver,platform_bus_type都会启动scan过程,让新加入的driver扫描整个platform_bus上的device链表,遍历整个platform_bus上的driver链表看是否有合适的该device的driver。
编写硬件控制器驱动的时候,如果是platform_device,就应该在module_init(xxx_init)里面的函数.调用platform_driver_probe(&xxx_platform_driver,xxx_probe);这个函数只能够注册非热插拔设备的驱动。函数里最后还是调用platform_driver_register().
/**
* platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
*/
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_register);
其中platform_bus_type:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
其中比较重要的是platform_match:
/**
* platform_match - bind platform device to platform driver.
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
可以看出来platform_driver和platform_device匹配有三种方法,一般来说,都采用最后看driver和device的名字是否相同,如果在arch/xxx_chips/xxx_system/下的platform.c中有已经写好的platform_device(里面包含了中断,物理地址等resource),最终调用platform_add_device().添加到内核中。
如果platform_device和platform_driver的name相同的话,最后调用xxx_probe()函数,里面就是对物理设备控制器的初始化和驱动注册函数(xxx_regsisterx)。
内存映射方式:外设的I/O端口的物理地址被映射到内存地址空间中,使I/O端口成为内存的一部分,CPU可以像访问一个内存单元那样访问I/O端口,而不需要设立专门的外设I/O指令。
一般来说,在系统运行时,外设的I/O内存资源,物理地址都是已知的,由硬件设计决定。
ioremap()用来将I/O资源的物理地址映射到核心虚拟空间。
申请I/O端口:request_region();
申请I/O内存:request_mem_region();没有做实际的映射工作,高速内核要使用一块内存地址声明占有。
内核中有两棵树,一棵是iomem_resource,另一棵时ioport_resource,分别代表着不同性质的地址资源。
struct resource ioport_resource = {
.name = "PCI IO",
.start = 0,
.end = IO_SPACE_LIMIT,
.flags = IORESOURCE_IO,
};
EXPORT_SYMBOL(ioport_resource);
struct resource iomem_resource = {
.name = "PCI mem",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};
EXPORT_SYMBOL(iomem_resource);