11-S3C2440驱动学习(七)嵌入式linux-字符设备的另一种写法及RTC驱动程序分析和字符设备驱动框架总结

来源:互联网 发布:下载汉王识别软件 编辑:程序博客网 时间:2024/06/10 15:14

一、字符设备驱动程序的另一种写法

1.1、之前

major = register_chrdev(0, "hello", &hello_fops); /* (major,  0), (major, 1), ..., (major, 255)都对应hello_fops */
缺点:一个主设备号,占据了0-255个次设备号,由主设备号确定file_operations结构体

1.2、现在

将register_chrdev:拆分为三部分


优点:指定次设备号范围,由主设备+次设备号确定file_operations结构体,且同一主设备号可以由多个file_operations结构体

分析register_chrdev_region:

if (major) {devid = MKDEV(major, 0);//从0开始register_chrdev_region(devid, HELLO_CNT, "hello");  /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */}
如果指定了主设备号major,MKDEV将主设备号转换为dev_t类型。

参数1:dev_t类型类型主设备号,且初始化了次设备号的起始值

参数2:设备节点的数量

参数3:驱动名字

分析alloc_chrdev_region:

else {alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */major = MAJOR(devid);   }
如果没有指定住设备号,用次函数。

参数1:主设备号

参数2:此设备号起始值

参数3:设备节点数量

参数4:驱动名字
分析cdev_init:

cdev_init(&hello_cdev, &hello_fops);
关联cdev设备与file_operations结构体

参数1:cdev设备

参数2:file_operations结构体
分析cdev_add:

cdev_add(&hello_cdev, devid, HELLO_CNT);
注册信息

参数1:cdev设备

参数2:主设备号信息

参数3:设备节点数量

1.3、hello world例子代码

#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/irq.h>#include <asm/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>#include <linux/poll.h>#include <linux/cdev.h>/* 1. 确定主设备号 */static int major;static int hello_open(struct inode *inode, struct file *file){printk("hello_open\n");return 0;}/* 2. 构造file_operations */static struct file_operations hello_fops = {.owner = THIS_MODULE,.open  = hello_open,};#define HELLO_CNT   2static struct cdev hello_cdev;static struct class *cls;static int hello_init(void){dev_t devid;/* 3. 告诉内核 */#if 0major = register_chrdev(0, "hello", &hello_fops); /* (major,  0), (major, 1), ..., (major, 255)都对应hello_fops */#elseif (major) {devid = MKDEV(major, 0);//从0开始register_chrdev_region(devid, HELLO_CNT, "hello");  /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */} else {alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */major = MAJOR(devid);                     }cdev_init(&hello_cdev, &hello_fops);cdev_add(&hello_cdev, devid, HELLO_CNT);#endifcls = class_create(THIS_MODULE, "hello");class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */return 0;}static void hello_exit(void){class_device_destroy(cls, MKDEV(major, 0));class_device_destroy(cls, MKDEV(major, 1));class_device_destroy(cls, MKDEV(major, 2));class_destroy(cls);cdev_del(&hello_cdev);unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE("GPL");
1.4、测试程序

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>/*  * hello_test /dev/hello0 */void print_usage(char *file){printf("%s <dev>\n", file);}int main(int argc, char **argv){int fd;if (argc != 2){print_usage(argv[0]);return 0;}fd = open(argv[1], O_RDWR);if (fd < 0)printf("can't open %s\n", argv[1]);elseprintf("can open %s\n", argv[1]);return 0;}


2、RTC驱动程序分析

系统:linux-3.4.2

2.1、框架分析

(1)内核驱动地址:内核中RTC驱动程序:Rtc-s3c.c(linux-3.4.2\drivers\rtc)       

(2)入口函数:

module_platform_driver(s3c_rtc_driver);

展开后是:

static int __init gpio_pmodoled_driver_init(void){        return platform_driver_register(&xxx);}module_init(xxx);static void __exit gpio_pmodoled_driver_init(void){        return platform_driver_unregister(&xx);}module_exit(xxx);
(3)platform_driver总线驱动模型

RTC驱动程序采用platform_driver的方式,内核注册了s3c-rtc设备,设备配置文件里初始化该平台设备。当加载RTC驱动程序的时候,发现内核中有名为“s3c2410-rtc”设备,因此调用platform_driver s3c_rtc_driver的probe函数。

static struct platform_driver s3c_rtc_driver = {.probe= s3c_rtc_probe,.remove= __devexit_p(s3c_rtc_remove),.suspend= s3c_rtc_suspend,.resume= s3c_rtc_resume,.id_table= s3c_rtc_driver_ids,.driver= {.name= "s3c-rtc",.owner= THIS_MODULE,.of_match_table= s3c_rtc_dt_match,},};

内核在Devs.c (linux-3.4.2\arch\arm\plat-samsung)定义了RTC平台设备及其资源

#ifdef CONFIG_PLAT_S3C24XXstatic struct resource s3c_rtc_resource[] = {[0] = DEFINE_RES_MEM(S3C24XX_PA_RTC, SZ_256),[1] = DEFINE_RES_IRQ(IRQ_RTC),[2] = DEFINE_RES_IRQ(IRQ_TICK),};struct platform_device s3c_device_rtc = {.name= "s3c2410-rtc",.id= -1,.num_resources= ARRAY_SIZE(s3c_rtc_resource),.resource= s3c_rtc_resource,};#endif /* CONFIG_PLAT_S3C24XX */

在Common-smdk.c (linux-3.4.2\arch\arm\mach-s3c24xx)里初始化platform_device,并添加相应设备

(4)s3c_rtc_probe函数中向上注册

s3c_rtc_probe{  rtc= rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);}

(5)上层核心文件:

Class.c (linux-3.4.2\drivers\rtc) rtc_device_register、rtc_init

Rtc-dev.c(linux-3.4.2\drivers\rtc) rtc_init—》rtc_dev_init();

(6)上层驱动入口函数:

rtc_dev_init();{err =alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");}
注册设备

(7)rtc_device_register注册rtc_device设备。

并分配、设置、注册rtc_device,并注册设备节点。

rtc_device_register// Class.c{rtc_dev_prepare(rtc); //Rtc-dev.c{    cdev_init(&rtc->char_dev, &rtc_dev_fops);}rtc_dev_add_device(rtc); //Rtc-dev.c{    cdev_add(&rtc->char_dev, rtc->dev.devt, 1)}}
(8)file_operations结构体:

设备对应的file_operations结构体

//Rtc-dev.cstatic const struct file_operations rtc_dev_fops = {.owner= THIS_MODULE,.llseek= no_llseek,.read= rtc_dev_read,.poll= rtc_dev_poll,.unlocked_ioctl= rtc_dev_ioctl,.open= rtc_dev_open,.release= rtc_dev_release,.fasync= rtc_dev_fasync,};
rtc_device对应的ops结构体。
static const struct rtc_class_ops s3c_rtcops = {.read_time= s3c_rtc_gettime,.set_time= s3c_rtc_settime,.read_alarm= s3c_rtc_getalarm,.set_alarm= s3c_rtc_setalarm,.proc= s3c_rtc_proc,.alarm_irq_enable = s3c_rtc_setaie,};

2.2、调用过程分析

app:    open("/dev/rtc0");
-------------------------------------------
kernel: sys_open
      rtc_dev_fops.open
          rtc_dev_open
          // 根据次设备号找到以前用"rtc_device_register"注册的rtc_device
           struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);
           const struct rtc_class_ops *ops = rtc->ops;
           err = ops->open ? ops->open(rtc->dev.parent) : 0;
   s3c_rtc_open

app:    ioctl(fd, RTC_RD_TIME,...)
-------------------------------------------
kernel: sys_ioctl
      rtc_dev_fops.ioctl
          rtc_dev_ioctl
           struct rtc_device *rtc = file->private_data;
           rtc_read_time(rtc, &tm);
           err = rtc->ops->read_time(rtc->dev.parent, tm);
             s3c_rtc_gettime

分析:

(1)应用程序调用open函数:open("/dev/rtc0");

(2)设备节点对应的上层file_operations结构体的open函数被调用,即rtc_dev_fops的rtc_device被调用。
(3)rtc_device函数中,通过次设备号,找到对用的rtc_device设备。

struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);

(4)如果rtc_device设备由open函数,则调用open函数(这个open函数是rtc_device设备的open函数,即rtc_class_ops结构体里的s3c_rtc_open函数被调用)。

同理app:ioctl(fd, RTC_RD_TIME,...)

3、字符设备驱动框架总结

3.1、字符设备驱动

(1)头文件

(2)入口函数:module_init

a、register_chrdev b、register_chrdev_region/alloc_chrdev_region           cdev_init           cdev_add              class_create           class_device_create

(3)出口函数:module_exit

(4)修饰:MODULE_LICENSE("GPL");


3.2、复杂字符设备驱动

内核中的很多驱动程序采用了分层的结构,如RTC、Lcd、V4L2等等。

所谓分层,可以理解为一个驱动程序有多部分组成,内核中帮我们完成并加载了上层驱动部分,并提供了API接口,我们通过API接口完成硬件相关的驱动。其中一种典型的应用就是platform_driver与分层结构的组合。比如在RTC驱动程序中,内核初始化了一个名为s3c-rtc平台设备,在配置在单板的配置初始化文件中完成平台设备的加载。当我们注册一个名为s3c-rtc平台设备的时候,这个平台设备的probe函数被调用,往往在probe函数中会创建一个字符设备。并注册rtc_device。应用程序open时,调用字符设备file_operations里的open函数,这个open函数首先会通过次设备号获取rtc_device,并判断是否有open函数,有的话则调用里面的open函数,同理其他函数。



阅读全文
2 0
原创粉丝点击