根据linux中总线、设备和驱动这个模型来看,所有的设备驱动都挂在总线上,并且驱动应该是和平台无关的。唯一和平台有关的一类驱动应该是芯片内部的各个控制器的驱动,例如芯片内部I2C控制器,芯片内部LCD控制器,芯片内部看门狗等等。如果是一个外部的LCD控制器的驱动程序,它的实现一定要和平台无关,这样针对不同的平台只要修改板级文件即可,而不需要修改驱动程序本身。从linux内核源代码中的S3c2410_wdt.c(位于drivers/watchdog中)可以看出,从不同的角度看,看门狗分别可以属于:平台设备、字符设备和混杂设备。所以可以看到该驱动程序中不仅实现了平台设备驱动的probe, remove等函数,还实现了字符设备的file-operations中的open,write, ioctl, llseek, release等函数。下面分析这个驱动程序。
第一个问题:platform_driver和platform_device是如何建立关联的?
在驱动程序S3c2410_wdt.c中定义了platform_driver结构体s3c2410wdt_driver,代码清单如下:
[c-sharp] view plain copy print ?
- static struct platform_driver s3c2410wdt_driver = {
- .probe = s3c2410wdt_probe,
- .remove = __devexit_p(s3c2410wdt_remove),
- .shutdown = s3c2410wdt_shutdown,
- .suspend = s3c2410wdt_suspend,
- .resume = s3c2410wdt_resume,
- .driver = {
- .owner = THIS_MODULE,
- .name = "s3c2410-wdt",
- },
- };
static struct platform_driver s3c2410wdt_driver = { .probe = s3c2410wdt_probe, .remove = __devexit_p(s3c2410wdt_remove), .shutdown = s3c2410wdt_shutdown, .suspend = s3c2410wdt_suspend, .resume = s3c2410wdt_resume, .driver = { .owner = THIS_MODULE, .name = "s3c2410-wdt", }, };
其中的.name = "s3c2410-wdt"代表了驱动的名称。
而相应的设备在平台相关的文件中已经定义了(在arch/arm/plat-s3c24xx/Devs.c)代码清单如下:
[c-sharp] view plain copy print ?
- struct platform_device s3c_device_wdt = {
- .name = "s3c2410-wdt",
- .id = -1,
- .num_resources = ARRAY_SIZE(s3c_wdt_resource),
- .resource = s3c_wdt_resource,
- };
struct platform_device s3c_device_wdt = { .name = "s3c2410-wdt", .id = -1, .num_resources = ARRAY_SIZE(s3c_wdt_resource), .resource = s3c_wdt_resource, };
其中的设备名称也是"s3c2410-wdt".
并且这个设备已经在平台初始化时使用platform_add_devices函数添加到了总线上。 以smdk2410开发板为例,代码清单如下:
[c-sharp] view plain copy print ?
- static void __init smdk2410_init(void)
- {
- s3c_i2c0_set_platdata(NULL);
- platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
- smdk_machine_init();
- }
static void __init smdk2410_init(void) { s3c_i2c0_set_platdata(NULL); platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices)); smdk_machine_init(); }
可知现在总线已经挂上了一个名为"s3c2410-wdt"的设备了。
在加载驱动模块时,执行了watchdog_init函数,代码清单如下:
[c-sharp] view plain copy print ?
- static int __init watchdog_init(void)
- {
- printk(banner);
- return platform_driver_register(&s3c2410wdt_driver);
- }
static int __init watchdog_init(void) { printk(banner); return platform_driver_register(&s3c2410wdt_driver); }
其中platform_driver_register函数的作用是将这个驱动注册到platform总线上,同时寻找在platform总线上与之匹配的设备。使用sourceinsight可以清楚看到这其中主要函数的执行顺序为:
platform_driver_register --> driver_register --> bus_add_driver --> driver_attach --> bus_for_each_dev --> __driver_attach --> driver_match_device --> platform的match方法:of_platform_bus_match。仔细查看这个方法会发现实际上就是匹配了设备的名称和驱动的名称,只要这两个名字一样,就匹配成功。如果匹配成功,则在__driver_attach中继续调用driver__probe_device方法,这个方法最终调用的就是platform_driver中的probe方法。至此完成了驱动程序与设备的关联。
第二个问题:既然最正确的驱动程序中不应该包含平台相关的代码,那么驱动是怎样得到平台的信息的呢?
这里使用了platform_driver中的probe方法。 probe,顾名思义,是“探测”,是driver对device的探测。结合S3c2410_wdt.c源代码对探测的过程作进一步理解。
[c-sharp] view plain copy print ?
- static int __devinit s3c2410wdt_probe(struct platform_device *pdev)
- {
- struct resource *res;
- struct device *dev;
- unsigned int wtcon;
- int started = 0;
- int ret;
- int size;
-
- DBG("%s: probe=%p/n", __func__, pdev);
-
- dev = &pdev->dev;
- wdt_dev = &pdev->dev;
-
-
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (res == NULL) {
- dev_err(dev, "no memory resource specified/n");
- return -ENOENT;
- }
-
- size = (res->end - res->start) + 1;
- wdt_mem = request_mem_region(res->start, size, pdev->name);
- if (wdt_mem == NULL) {
- dev_err(dev, "failed to get memory region/n");
- ret = -ENOENT;
- goto err_req;
- }
-
- wdt_base = ioremap(res->start, size);
- if (wdt_base == NULL) {
- dev_err(dev, "failed to ioremap() region/n");
- ret = -EINVAL;
- goto err_req;
- }
-
- DBG("probe: mapped wdt_base=%p/n", wdt_base);
-
- wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (wdt_irq == NULL) {
- dev_err(dev, "no irq resource specified/n");
- ret = -ENOENT;
- goto err_map;
- }
-
- ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
- if (ret != 0) {
- dev_err(dev, "failed to install irq (%d)/n", ret);
- goto err_map;
- }
-
- wdt_clock = clk_get(&pdev->dev, "watchdog");
- if (IS_ERR(wdt_clock)) {
- dev_err(dev, "failed to find watchdog clock source/n");
- ret = PTR_ERR(wdt_clock);
- goto err_irq;
- }
-
- clk_enable(wdt_clock);
-
-
-
-
- if (s3c2410wdt_set_heartbeat(tmr_margin)) {
- started = s3c2410wdt_set_heartbeat(
- CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
-
- if (started == 0)
- dev_info(dev,
- "tmr_margin value out of range, default %d used/n",
- CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
- else
- dev_info(dev, "default timer value is out of range, "
- "cannot start/n");
- }
-
- ret = misc_register(&s3c2410wdt_miscdev);
- if (ret) {
- dev_err(dev, "cannot register miscdev on minor=%d (%d)/n",
- WATCHDOG_MINOR, ret);
- goto err_clk;
- }
-
- if (tmr_atboot && started == 0) {
- dev_info(dev, "starting watchdog timer/n");
- s3c2410wdt_start();
- } else if (!tmr_atboot) {
-
-
-
-
- s3c2410wdt_stop();
- }
-
-
-
- wtcon = readl(wdt_base + S3C2410_WTCON);
-
- dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled/n",
- (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in",
- (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",
- (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
-
- return 0;
-
- err_clk:
- clk_disable(wdt_clock);
- clk_put(wdt_clock);
-
- err_irq:
- free_irq(wdt_irq->start, pdev);
-
- err_map:
- iounmap(wdt_base);
-
- err_req:
- release_resource(wdt_mem);
- kfree(wdt_mem);
-
- return ret;
- }
static int __devinit s3c2410wdt_probe(struct platform_device *pdev) { struct resource *res; struct device *dev; unsigned int wtcon; int started = 0; int ret; int size; DBG("%s: probe=%p/n", __func__, pdev); dev = &pdev->dev; wdt_dev = &pdev->dev; /* get the memory region for the watchdog timer */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);/*使用platform_get_resource得到设备的资源信息,标志位IORESOURCE_MEM表示得到的是IO内存资源*/ if (res == NULL) { dev_err(dev, "no memory resource specified/n"); return -ENOENT; } size = (res->end - res->start) + 1; wdt_mem = request_mem_region(res->start, size, pdev->name);/*申请IO内存*/ if (wdt_mem == NULL) { dev_err(dev, "failed to get memory region/n"); ret = -ENOENT; goto err_req; } wdt_base = ioremap(res->start, size);/*IO内存重映射,得到基地址,以后readl和writel等可以使用*/ if (wdt_base == NULL) { dev_err(dev, "failed to ioremap() region/n"); ret = -EINVAL; goto err_req; } DBG("probe: mapped wdt_base=%p/n", wdt_base); wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);/*标志位IORESOURCE_MEM表示得到的是IO内存资源*/ if (wdt_irq == NULL) { dev_err(dev, "no irq resource specified/n"); ret = -ENOENT; goto err_map; } ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);/*注册中断*/ if (ret != 0) { dev_err(dev, "failed to install irq (%d)/n", ret); goto err_map; } wdt_clock = clk_get(&pdev->dev, "watchdog");/*得到watchdog的时钟*/ if (IS_ERR(wdt_clock)) { dev_err(dev, "failed to find watchdog clock source/n"); ret = PTR_ERR(wdt_clock); goto err_irq; } clk_enable(wdt_clock); /*使能watchdog时钟*/ /* see if we can actually set the requested timer margin, and if * not, try the default value */ if (s3c2410wdt_set_heartbeat(tmr_margin)) { started = s3c2410wdt_set_heartbeat( CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); if (started == 0) dev_info(dev, "tmr_margin value out of range, default %d used/n", CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); else dev_info(dev, "default timer value is out of range, " "cannot start/n"); } ret = misc_register(&s3c2410wdt_miscdev);/*注册混杂设备*/ if (ret) { dev_err(dev, "cannot register miscdev on minor=%d (%d)/n", WATCHDOG_MINOR, ret); goto err_clk; } if (tmr_atboot && started == 0) { dev_info(dev, "starting watchdog timer/n"); s3c2410wdt_start();/*启动watchdog*/ } else if (!tmr_atboot) { /* if we're not enabling the watchdog, then ensure it is * disabled if it has been left running from the bootloader * or other source */ s3c2410wdt_stop(); } /* print out a statement of readiness */ wtcon = readl(wdt_base + S3C2410_WTCON);/*得到控制寄存器信息*/ dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled/n", (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in", (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis", (wtcon & S3C2410_WTCON_INTEN) ? "" : "en"); return 0; err_clk: clk_disable(wdt_clock); clk_put(wdt_clock); err_irq: free_irq(wdt_irq->start, pdev); err_map: iounmap(wdt_base); err_req: release_resource(wdt_mem); kfree(wdt_mem); return ret; }
第三个问题: 具体操作硬件的代码在哪?
在S3c2410_wdt.c中直接对硬件进行操作的函数有
s3c2410wdt_start --开始开始看门狗计时器
s3c2410wdt_stop --停止看门狗计时器
s3c2410wdt_keepalive --“喂狗”
s3c2410wdt_set_heartbeat -- 设置“心跳”,就是计数周期
s3c2410wdt_suspend --挂起看门狗,就是在关闭看门狗之前保存状态
s3c2410wdt_resume --恢复看门狗计时器
第四个问题: 这些操作硬件的代码在驱动程序中又是怎么组织的呢?
前面说到,芯片内部的这个看门狗从不同角度看是有不同的身份的:平台设备,字符设备和混杂设备。下图显示了操作硬件的代码是怎样组织的。
------------------------------------------------------以上---------------------------------------------------