0%

Linux基础复学(3)- 字符设备驱动

前言

截至当前,工作中涉及的驱动基本都是字符设备驱动,内核通过字符设备向用户空间提供API,实现内核与用户态间的交互。

学习字符设备需要了解以下几方面的知识:

  • 了解Linux内核字符设备驱动程序的架构
  • 了解Linux内核字符设备驱动相关的API
  • 了解Linux内核内存管理的API
  • 了解Linux内核中断管理的API
  • 了解Linux内核同步和锁相关的API
  • 了解具体芯片的工作原理及操作方式

下面围绕几点,概述一下相关知识。

字符设备驱动

字符设备驱动架构

lscpu

驱动架构如图所示,最底层是硬件、向上是Linux的核心模块(比如文件管理、内存管理、中断管理等),然后是驱动程序、虚拟文件系统、系统调用,最上层是应用程序。应用程序通过打开字符设备,使用open、read、write以及mmap等系统调用,获取相关资源,进行对应的操作,最后控制硬件的行为。

字符设备驱动的API

相关接口的功能包括创建一个字符设备、管理相关的文件操作集函数。大概归纳一下有如下接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 申请一个字符设备号
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);

// 创建一个 struct class
class_create(owner, name)

// 初始化 struct cdev
void cdev_init(struct cdev *, const struct file_operations *);

// 创建字符设备
int cdev_add(struct cdev *, dev_t, unsigned);

// 创建设备实例
struct device *
device_create(struct class *cls, struct device *parent, dev_t devt,
void *drvdata, const char *fmt, ...);


// 这样就在/dev下创建好一个文件了

// 销毁设备实例
void device_destroy(struct class *cls, dev_t devt);

// 注销class
void class_unregister(struct class *class);
void class_destroy(struct class *cls);

// 删除cdev
void cdev_del(struct cdev *);

// 释放设备号
void unregister_chrdev_region(dev_t, unsigned);

// 实现基础文件的打开关闭读写操作
static int my_open(struct inode *inodep, struct file *filep) {
printk(KERN_INFO "Device has been opened\n");
return 0;
}

static int my_release(struct inode *inodep, struct file *filep) {
printk(KERN_INFO "Device successfully closed\n");
return 0;
}

static ssize_t my_read(struct file *filep, char *buffer, size_t len, loff_t *offset) {
// 读取逻辑
return 0;
}

static ssize_t my_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) {
// 写入逻辑
return len;
}

static struct file_operations fops = {
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
};

内存管理的API

主要就是分配内存,设备内存映射等接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

// 分配设备内存,一般用于存放设备上下文的结构, 设备管理的内存分配函数,它会自动处理内存的释放工作
void *devm_kzalloc(struct device *dev, size_t size, gfp_t flags);

// 获取设备空间并映射到内存,返回内存地址, devm 开头的函数,在设备或者模块被移除时 会自动做反方向的释放工作
void __iomem *
devm_platform_get_and_ioremap_resource(struct platform_device *pdev,
unsigned int index, struct resource **res);

// 往映射的内存写数据, 根据操作的长度,存在对应writex函数
static inline void writel(u32 value, volatile void __iomem *addr)

// 从映射的内存读数据, 根据操作的长度,存在对应readx函数
static inline u32 readl(const volatile void __iomem *addr)

// 内存分配
static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
unsigned int order)
void free_pages(unsigned long addr, unsigned int order);

void *kmalloc(size_t size, gfp_t flags);
void kfree(const void *ptr);


// dma相关接口
#define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, 0)
#define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)
#define dma_map_sg(d, s, n, r) dma_map_sg_attrs(d, s, n, r, 0)
#define dma_unmap_sg(d, s, n, r) dma_unmap_sg_attrs(d, s, n, r, 0)
#define dma_map_page(d, p, o, s, r) dma_map_page_attrs(d, p, o, s, r, 0)
#define dma_unmap_page(d, a, s, r) dma_unmap_page_attrs(d, a, s, r, 0)

void dma_sync_single_for_cpu(struct device *dev, dma_addr_t addr, size_t size,
enum dma_data_direction dir);
void dma_sync_single_for_device(struct device *dev, dma_addr_t addr,
size_t size, enum dma_data_direction dir);

static inline void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp)
static inline void dma_free_coherent(struct device *dev, size_t size,
void *cpu_addr, dma_addr_t dma_handle)

中断管理的API

中断一般由设备发起,驱动程序通过注册中断函数处理数据,而不需要驱动程序主动去poll数据是否准备好。当然,根据应用场景,也有禁用中断使用轮询方式的,比如DPDK。

相关API如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int platform_get_irq(struct platform_device *pdev, int index);

int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs,
unsigned int max_vecs, unsigned int flags);
void pci_free_irq_vectors(struct pci_dev *dev);

int pci_irq_vector(struct pci_dev *dev, unsigned int nr);

int __must_check
devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id);

static inline int __must_check
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)

同步和锁相关的API

驱动常用的主要是struct mutex 和 struct spinlock,其中mutex会导致线程阻塞挂起,而spinlock则类似while循环一直在忙。API省略。

具体芯片的工作原理及操作方式

根据具体硬件的手册来操作,了解实现逻辑和寄存器地址与功能。


行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。

欢迎关注

博客地址: https://fishmwei.github.io

掘金主页: https://juejin.cn/user/2084329776486919