11-S3C2440驱动学习(七)嵌入式linux-字符设备的另一种写法及RTC驱动程序分析和字符设备驱动框架总结
一、字符设备驱动程序的另一种写法
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 2
static struct cdev hello_cdev;
static struct class *cls;
static int hello_init(void)
{
dev_t devid;
/* 3. 告诉内核 */
#if 0
major = register_chrdev(0, "hello", &hello_fops); /* (major, 0), (major, 1), ..., (major, 255)都对应hello_fops */
#else
if (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);
#endif
cls = 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]);
else
printf("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_S3C24XX
static 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.c
static 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、调用过程分析
-------------------------------------------
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函数,同理其他函数。