欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-14 16:26:10
...

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

1.1、之前

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

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

1.2、现在

将register_chrdev:拆分为三部分

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

优点:指定次设备号范围,由主设备+次设备号确定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;
}
11-S3C2440驱动学习(七)嵌入式linux-字符设备的另一种写法及RTC驱动程序分析和字符设备驱动框架总结11-S3C2440驱动学习(七)嵌入式linux-字符设备的另一种写法及RTC驱动程序分析和字符设备驱动框架总结


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、调用过程分析

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函数,同理其他函数。