Linux 设备驱动的软件架构思想(四)(摘录)
发布日期:2021-06-29 11:35:09 浏览次数:2 分类:技术文章

本文共 16437 字,大约阅读时间需要 54 分钟。

Linux 设备驱动的软件架构思想(四)(摘录)

RTC 设备驱动

RTC (实时钟)借助电池供电,在系统掉电的情况下依然可以正常计时。它通常还具有产生周期性中断以及闹钟( Alarm )中断的能力,是一种典型的字符设备。作为一种字符设备驱动, RTC 需要有 file_operations 中接口函数的实现,如 open ()、 release ()、 read ()、 poll ()、 ioctl ()等,而典型的 IOCTL 包括 RTC_SET_TIME 、RTC_ALM_READ 、 RTC_ALM_SET 、 RTC_IRQP_SET 、 RTC_IRQP_READ 等,这些对于所有的 RTC 是通用的,只有底层的具体实现是与设备相关的。

因此, drivers/rtc/rtc-dev.c 实现了 RTC 驱动通用的字符设备驱动层,它实现了 file_opearations 的成员函数以及一些通用的关于 RTC 的控制代码,并向底层导出 rtc_device_register ()、rtc_device_unregister ()以注册和注销RTC ;导出 rtc_class_ops 结构体以描述底层的 RTC 硬件操作。这个 RTC 通用层实现的结果是,底层的 RTC 驱动不再需要关心 RTC 作为字符设备驱动的具体实现,也无需关心一些通用的 RTC 控制逻辑,如图所示:

在这里插入图片描述drivers/rtc/rtc-s3c.c 实现了 S3C6410 的 RTC 驱动,其注册 RTC 以及绑定 rtc_class_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,};static int s3c_rtc_probe(struct platform_device *pdev){
... rtc = devm_rtc_device_register(&pdev->dev, "s3c", &s3c_rtcops,THIS_MODULE); ...}

drivers/rtc/rtc-dev.c 以及其调用的 drivers/rtc/interface.c 等 RTC 核心层相当于把 file_operations 中的 open ()、release ()、读取和设置时间等都间接 “ 转发 ” 给了底层的实例,代码清单 摘取了部分 RTC 核心层调用具体底层驱动 callback 的过程。

static int rtc_dev_open(struct inode *inode, struct file *file){
... err = ops->openops->open(rtc->dev.parent) : 0; ...}static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm){
int err; if (!rtc->ops)err = -ENODEV; else if (!rtc->ops->read_time) //底层驱动 callbackerr = -EINVAL; ... return err; }int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm){
int err; err = mutex_lock_interruptible(&rtc->ops_lock); if (err)return err; err = __rtc_read_time(rtc, tm); mutex_unlock(&rtc->ops_lock); return err;}int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm){
... if (!rtc->ops)err = -ENODEV; else if (rtc->ops->set_time) //底层驱动 callbackerr = rtc->ops->set_time(rtc->dev.parent, tm); ... return err;}static long rtc_dev_ioctl(struct file *file,unsigned int cmd, unsigned long arg){
... case RTC_RD_TIME:mutex_unlock(&rtc->ops_lock); err = rtc_read_time(rtc, &tm); if (err < 0)return err;if (copy_to_user(uarg, &tm, sizeof(tm)))err = -EFAULT;return err;case RTC_SET_TIME:mutex_unlock(&rtc->ops_lock);if (copy_from_user(&tm, uarg, sizeof(tm)))return -EFAULT;return rtc_set_time(rtc, &tm); ...}

Framebuffer 设备驱动

Framebuffer (帧缓冲)是 Linux 系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域内写入颜色值,对应的颜色会自动在屏幕上显示。
图示为 Linux 帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的 file_operations 结构体由drivers/video/fbdev/core/fbmem.c 中的 file_operations 提供,而特定帧缓冲设备 fb_info 结构体的注册、注销以及其中成员的维护,尤其是 fb_ops 中成员函数的实现则由对应的 xxxfb.c 文件实现, fb_ops 中的成员函数最终会操作 LCD 控制其硬件寄存器。
在这里插入图片描述多数显存的操作方法都是规范的,可以按照像素点格式的要求顺序写帧缓冲区。但是有少量 LCD 的显存写法可能比较特殊,这时候,在核心层 drivers/video/fbdev/core/fbmem.c 实现的 fb_write ()中,实际上可以给底层提供一个重写自己的机会
LCD 的 framebuffer write ()函数

static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){
unsigned long p = *ppos; struct fb_info *info = file_fb_info(file); u8*buffer, *src; u8__iomem *dst; int c, cnt = 0, err = 0; unsigned long total_size; if (!info || !info->screen_base)return -ENODEV; if (info->state != FBINFO_STATE_RUNNING)return -EPERM;/*检查底层 LCD 有没有实现自己特殊显存写法的代码,如果有,直接调底层的;如果没有,用中间层标准的显存写法就搞定了底层的那个不特殊的 LCD*/ if (info->fbops->fb_write)return info->fbops->fb_write(info, buf, count, ppos); total_size = info->screen_size; if (total_size == 0)total_size = info->fix.smem_len; if (p > total_size)return -EFBIG; if (count > total_size) {
err = -EFBIG; count = total_size; } if (count + p > total_size) {
if (!err)err = -ENOSPC;count = total_size - p; } buffer = kmalloc((count > PAGE_SIZE)PAGE_SIZE : count,GFP_KERNEL); if (!buffer)return -ENOMEM; dst = (u8__iomem *) (info->screen_base + p); if (info->fbops->fb_sync)info->fbops->fb_sync(info); while (count) {
c = (count > PAGE_SIZE) src = buffer;PAGE_SIZE : count;if (copy_from_user(src, buf, c)) {
err = -EFAULT; break;} fb_memcpy_tofb(dst, src, c); dst += c; src += c; *ppos += c; buf += c; cnt += c; count -= c; } kfree(buffer); return (cnt)cnt : err;}

终端设备驱动

在 Linux 系统中,终端是一种字符型设备,它有多种类型,通常使用 tty ( Teletype )来简称各种类型的终端设备。对于嵌入式系统而言,最普遍采用的是 UART ( UniversalAsynchronousReceiver/Transmitter )串行端口,日常生活中简称串口。

Linux 内核中 tty 的层次结构如图 12.9 所示,它包含 tty 核心 tty_io.c 、 tty 线路规程 n_tty.c (实现 N_TTY 线路规程)和tty 驱动实例 xxx_tty.c , tty 线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式。

tty_io.c 本身是一个标准的字符设备驱动,它对上有字符设备的职责,实现 file_operations 成员函数。但是 tty 核心层对下又定义了 tty_driver 的架构,这样 tty 设备驱动的主体工作就变成了填充 tty_driver 结构体中的成员,实现其中的tty_operations 的成员函数,而不再是去实现 file_operations 这一级的工作。 tty_driver 结构体和 tty_operations 的定义分别如代码清单 12.17 和 12.18 所示。
在这里插入图片描述tty_driver 结构体

struct tty_driver {
int magic; /* magic number for this structure */ struct kref kref; /* Reference management */ struct cdev **cdevs; struct module *owner; const char *driver_name; const char *name; int name_base; /* offset of printed name */ int major; /* major device number */ int minor_start; /* start of minor device number */ unsigned int num; /* number of devices allocated */ short type; /* type of tty driver */ short subtype; /* subtype of tty driver */ struct ktermios init_termios; /* Initial termios */ unsigned long flags; /* tty driver flags */ struct proc_dir_entry *proc_entry; /* /proc fs entry */ struct tty_driver *other; /* only used for the PTY driver */ /* * Pointer to the tty data structures */ struct tty_struct **ttys; struct tty_port **ports; struct ktermios **termios; void *driver_state; /* * Driver methods */ const struct tty_operations *ops; struct list_head tty_drivers;} __randomize_layout;

tty_operations 结构体

struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver, struct file *filp, int idx); int (*install)(struct tty_driver *driver, struct tty_struct *tty); void (*remove)(struct tty_driver *driver, struct tty_struct *tty); int (*open)(struct tty_struct * tty, struct file * filp); void (*close)(struct tty_struct * tty, struct file * filp); void (*shutdown)(struct tty_struct *tty); void (*cleanup)(struct tty_struct *tty); int (*write)(struct tty_struct * tty, const unsigned char *buf, int count); int (*put_char)(struct tty_struct *tty, unsigned char ch); void (*flush_chars)(struct tty_struct *tty); int (*write_room)(struct tty_struct *tty); int (*chars_in_buffer)(struct tty_struct *tty); int (*ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); long (*compat_ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); void (*set_termios)(struct tty_struct *tty, struct ktermios * old); void (*throttle)(struct tty_struct * tty); void (*unthrottle)(struct tty_struct * tty); void (*stop)(struct tty_struct *tty); void (*start)(struct tty_struct *tty); void (*hangup)(struct tty_struct *tty); int (*break_ctl)(struct tty_struct *tty, int state); void (*flush_buffer)(struct tty_struct *tty); void (*set_ldisc)(struct tty_struct *tty); void (*wait_until_sent)(struct tty_struct *tty, int timeout); void (*send_xchar)(struct tty_struct *tty, char ch); int (*tiocmget)(struct tty_struct *tty); int (*tiocmset)(struct tty_struct *tty, unsigned int set, unsigned int clear); int (*resize)(struct tty_struct *tty, struct winsize *ws); int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew); int (*get_icount)(struct tty_struct *tty, struct serial_icounter_struct *icount); void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);#ifdef CONFIG_CONSOLE_POLL int (*poll_init)(struct tty_driver *driver, int line, char *options); int (*poll_get_char)(struct tty_driver *driver, int line); void (*poll_put_char)(struct tty_driver *driver, int line, char ch);#endif const struct file_operations *proc_fops;} __randomize_layout;

如图 12.10 所示, tty 设备发送数据的流程为: tty 核心从一个用户获取将要发送给一个 tty 设备的数据, tty 核心将数据传递给 tty 线路规程驱动,接着数据被传递到 tty 驱动, tty 驱动将数据转换为可以发送给硬件的格式。接收数据的流程为:从 tty 硬件接收到的数据向上交给 tty 驱动,接着进入 tty 线路规程驱动,再进入 tty 核心,在这里它被一个用户获取。

在这里插入图片描述代码清单 12.18 中第 10 行的 tty_driver 操作集 tty_operations 的成员函数 write ()函数接收 3 个参数: tty_struct 、发送数据指针及要发送的字节数。该函数是被 file_operations 的 write ()成员函数间接触发调用的。从接收角度看, tty驱动一般收到字符后会通过 tty_flip_buffer_push ()将接收缓冲区推到线路规程。

尽管一个特定的底层 UART 设备驱动完全可以遵循上述 tty_driver 的方法来设计,即定义 tty_driver 并实现tty_operations 中的成员函数,但是鉴于串口之间的共性, Linux 考虑在文件 drivers/tty/serial/serial_core.c 中实现了UART 设备的通用 tty 驱动层(我们可以称其为串口核心层)。这样, UART 驱动的主要任务就进一步演变成了实现 serial-core.c 中定义的一组 uart_xxx 接口而不是 tty_xxx 接口,如图 12.11 所示。因此,按照面向对象的思想,可以认为 tty_driver 是字符设备的泛化、 serial-core 是 tty_driver 的泛化,而具体的串口驱动又是 serial-core 的泛化。

在这里插入图片描述串口核心层又定义了新的 uart_driver 结构体和其操作集 uart_ops 。一个底层的 UART 驱动需要创建和通过uart_register_driver ()注册一个 uart_driver 而不是 tty_driver

struct uart_driver {
struct module *owner; const char *driver_name; const char *dev_name; int major; int minor; int nr; struct console *cons; /* * these are private; the low level driver should not * touch these; they should be initialised to NULL */ struct uart_state *state; struct tty_driver *tty_driver;};

uart_driver 结构体在本质上是派生自 tty_driver 结构体,因此,它的第 15 行也包含了一个 tty_driver 结构体成员。tty_operations 在 UART 这个层面上也被进一步泛化为了 uart_ops

struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *); void (*set_mctrl)(struct uart_port *, unsigned int mctrl); unsigned int (*get_mctrl)(struct uart_port *); void (*stop_tx)(struct uart_port *); void (*start_tx)(struct uart_port *); void (*throttle)(struct uart_port *); void (*unthrottle)(struct uart_port *); void (*send_xchar)(struct uart_port *, char ch); void (*stop_rx)(struct uart_port *); void (*enable_ms)(struct uart_port *); void (*break_ctl)(struct uart_port *, int ctl); int (*startup)(struct uart_port *); void (*shutdown)(struct uart_port *); void (*flush_buffer)(struct uart_port *); void (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old); void (*set_ldisc)(struct uart_port *, struct ktermios *); void (*pm)(struct uart_port *, unsigned int state, unsigned int oldstate); /* * Return a string describing the type of the port */ const char *(*type)(struct uart_port *); /* * Release IO and memory resources used by the port. * This includes iounmap if necessary. */ void (*release_port)(struct uart_port *); /* * Request IO and memory resources used by the port. * This includes iomapping the port if necessary. */ int (*request_port)(struct uart_port *); void (*config_port)(struct uart_port *, int); int (*verify_port)(struct uart_port *, struct serial_struct *); int (*ioctl)(struct uart_port *, unsigned int, unsigned long);#ifdef CONFIG_CONSOLE_POLL int (*poll_init)(struct uart_port *); void (*poll_put_char)(struct uart_port *, unsigned char); int (*poll_get_char)(struct uart_port *);#endif};

由于 drivers/tty/serial/serial_core.c 是一个 tty_driver ,因此在 serial_core.c 中,存在一个tty_operations 的实例,这个实例的成员函数会进一步调用 struct uart_ops 的成员函数,这样就把 file_operations 里的成员函数、 tty_operations 的成员函数和 uart_ops 的成员函数串起来了。

misc 设备驱动

由于 Linux 驱动倾向于分层设计,所以各个具体的设备都可以找到它归属的类型,从而套到它相应的架构里面去,并且只需要实现最底层的那一部分。但是,也有部分类似 globalmem 、 globalfifo 的字符设备,确实不知道它属于什么类型,我们一般推荐大家采用 miscdevice 框架结构。 miscdevice 本质上也是字符设备,只是在 miscdevice核心层的 misc_init ()函数中,通过 register_chrdev ( MISC_MAJOR , “misc” , &misc_fops )注册了字符设备,而具体 miscdevice 实例调用 misc_register ()的时候又自动完成了 device_create ()、获取动态次设备号的动作。miscdevice 的主设备号是固定的, MISC_MAJOR 定义为 10 ,在 Linux 内核中,大概可以找到 200 多处使用 miscdevice框架结构的驱动。

miscdevice 结构体的定义如代码清单 12.21 所示,在它的第 4 行,指向了一个 file_operations 的结构体。 miscdevice 结构体内 file_operations 中的成员函数实际上是由 drivers/char/misc.c 中 misc 驱动核心层的 misc_fops 成员函数间接调用的,比如 misc_open ()就会间接调用底层注册的 miscdevice 的 fops->open 。

struct miscdevice  {
int minor; const char *name; const struct file_operations *fops; struct list_head list; struct device *parent; struct device *this_device; const struct attribute_group **groups; const char *nodename; umode_t mode;};

如果上述代码第 2 行的 minor 为 MISC_DYNAMIC_MINOR , miscdevice 核心层会自动找一个空闲的次设备号,否则用 minor 指定的次设备号。第 3 行的 name 是设备的名称。miscdevice 驱动的注册和注销分别用下面两个 API :

int misc_register(struct miscdevice * misc);int misc_deregister(struct miscdevice *misc);

因此 miscdevice 驱动的一般结构形如:

static const struct file_operations xxx_fops = {
.unlocked_ioctl = xxx_ioctl,.mmap = xxx_mmap,...};static struct miscdevice xxx_dev = {
.minor = MISC_DYNAMIC_MINOR,.name = "xxx",.fops = &xxx_fops};static int __init xxx_init(void){
pr_info("ARC Hostlink driver mmap at 0x%p\n", __HOSTLINK__);return misc_register(&xxx_dev);}在调用 misc_register ( &xxx_dev )时,该函数内部会自动调用 device_create (),而 device_create ()会以 xxx_dev作为 drvdata 参数。其次,在 miscdevice 核心层misc_open ()函数的帮助下,在 file_operations 的成员函数中, xxx_dev 会自动成为file 的 private_data ( misc_open 会完成 file->private_data 的赋值操作)。

如果我们用面向对象的封装思想把一个设备的属性、自旋锁、互斥体、等待队列、 miscdevice 等封装在一个结构体里面:

struct xxx_dev {
unsigned int version;unsigned int size;spinlock_t lock;...struct miscdevice miscdev;};

在 file_operations 的成员函数中,就可以通过 container_of ()和 file->private_data 反推出 xxx_dev 的实例。

static long xxx_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
struct xxx_dev *xxx = container_of(file->private_data, struct xxx_dev, miscdev);...}container_of()的作用就是通过一个结构变量中一个成员的地址找到这个结构体变量的首地址。container_of(ptr,type,member),这里面有ptr,type,member分别代表指针、类型、成员。

下面我们把 globalfifo 驱动改造成基于 platform_driver 且采用 miscdevice 框架的结构体。首先这个新的驱动变成了要通过 platform_driver 的 probe ()函数来初始化,其次不再直接采用 register_chrdev ()、 cdev_add ()之类的原始API ,而采用 miscdevice 的注册方法。

新的 globalfifo 驱动相对于 globalfifo 驱动变化的部分

struct globalfifo_dev {
... struct miscdevice miscdev;};static int globalfifo_fasync(int fd, struct file *filp, int mode){
struct globalfifo_dev *dev = container_of(filp->private_data,struct globalfifo_dev, miscdev);...}static long globalfifo_ioctl(struct file *filp, unsigned int cmd,unsigned long arg){
struct globalfifo_dev *dev = container_of(filp->private_data,struct globalfifo_dev, miscdev);...}static unsigned int globalfifo_poll(struct file *filp, poll_table * wait){
struct globalfifo_dev *dev = container_of(filp->private_data,struct globalfifo_dev, miscdev);...}static ssize_t globalfifo_read(struct file *filp, char __user *buf,size_t count, loff_t *ppos){
struct globalfifo_dev *dev = container_of(filp->private_data,32struct globalfifo_dev, miscdev);...}static ssize_t globalfifo_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos){
struct globalfifo_dev *dev = container_of(filp->private_data,struct globalfifo_dev, miscdev);...}static int globalfifo_probe(struct platform_device *pdev){
struct globalfifo_dev *gl; int ret; gl = devm_kzalloc(&pdev->dev, sizeof(*gl), GFP_KERNEL); if (!gl)return -ENOMEM; gl->miscdev.minor = MISC_DYNAMIC_MINOR; gl->miscdev.name = "globalfifo"; gl->miscdev.fops = &globalfifo_fops; mutex_init(&gl->mutex); init_waitqueue_head(&gl->r_wait); init_waitqueue_head(&gl->w_wait); platform_set_drvdata(pdev, gl); ret = misc_register(&gl->miscdev); if (ret < 0)goto err;...return 0;err:return ret;}static int globalfifo_remove(struct platform_device *pdev){
struct globalfifo_dev *gl = platform_get_drvdata(pdev); misc_deregister(&gl->miscdev); return 0;}static struct platform_driver globalfifo_driver = {
.driver = {
.name = "globalfifo", .owner = THIS_MODULE, }, .probe = globalfifo_probe, .remove = globalfifo_remove,};module_platform_driver(globalfifo_driver);

在上述代码中, file_operations 的各个成员函数都使用 container_of ()反向求出 private_data ,第 61 行在platform_driver 的 probe ()函数中完成了 miscdev 的注册,而在 remove ()函数中使用 misc_deregister ()完成了miscdev 的注销。

上述代码也改用了 platform_device 和 platform_driver 的体系结构。我们增加了一个模块来完成 platform_device 的注册,在模块初始化的时候通过 platform_device_alloc ()和 platform_device_add ()分配并添加 platform_device ,而在模块卸载的时候则通过 platform_device_unregister ()注销platform_device

static struct platform_device *globalfifo_pdev;static int __init globalfifodev_init(void){
int ret; globalfifo_pdev = platform_device_alloc("globalfifo", -1); if (!globalfifo_pdev)return -ENOMEM; ret = platform_device_add(globalfifo_pdev); if (ret) {
platform_device_put(globalfifo_pdev); return ret;}return 0;}module_init(globalfifodev_init);static void __exit globalfifodev_exit(void){
platform_device_unregister(globalfifo_pdev);}module_exit(globalfifodev_exit);

在该目录运行 make ,会生成两个模块: globalfifo.ko 和 globalfifo-dev.ko ,把 globalfifo.ko 和 globalfifo-dev.ko 先后 insmod ,会导致 platform_driver 和 platform_device 的匹配, globalfifo_probe ()会执行, /dev/globalfifo 节点会自动生成,默认情况下需要 root 权限来访问 /dev/globalfifo 。

转载地址:https://blog.csdn.net/zytgg123456/article/details/110424468 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Linux 设备驱动的软件架构思想(五)(摘录)
下一篇:Linux 设备驱动的软件架构思想(三)(摘录)

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月13日 12时14分12秒