上班已经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);