Linux驱动开发
嵌入式
前言
作者用的是正点原子linux阿尔法开发板emmc版本
一、字符设备驱动开发
(1)字符设备就是一个一个字节,按照字节流进行读写操作的设备
驱动的加载与卸载:
module_init();
module_exit();
字符设备的注册与注销:
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
首先驱动编写需要有init和exit函数
在init函数中调用register_chrdev注册字符设备
在exit函数中调用unregister_chrdev取消注册字符设备
然后需要实现file_operations这个结构体以及其中的函数,最基本的 owner open read write release写完代码之后,需要写一个makefile来完成模块编译
1 KERNELDIR := /home/zhenghan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
2
3 CURRENT_PATH := $(shell pwd)
4
5 obj-m = chardevbase.o
6
7 build: kernel_modules
8
9 kernel_modules:
10 $(MAKE) -C ${KERNELDIR} M=$(CURRENT_PATH) modules
11 clean:
12 $(MAKE) -C ${KERNELDIR} M=$(CURRENT_PATH) clean
编译好的ko拷贝到单板/lib/module/4.1.15下
在单板上insmod xxx.ko xxx是你编译的驱动
加载驱动后,需要手动创建设备节点 makenod /dev/name c 200 0 name为你驱动的名字 c为设备类型 200 为主设备号 0 为次设备号
(2)接下来我们尝试点亮LED灯
首先找到LED灯所连接的引脚,还要了解MMU以及操作mmu的函数
地址映射
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)
void iounmap (volatile void __iomem *addr)
内存访问
1 u8 readb(const volatile void __iomem *addr)
2 u16 readw(const volatile void __iomem *addr)
3 u32 readl(const volatile void __iomem *addr)
1 void writeb(u8 value, volatile void __iomem *addr)
2 void writew(u16 value, volatile void __iomem *addr)
3 void writel(u32 value, volatile void __iomem *addr)
使能时钟
设置gpio引脚的复用
设置gpio引脚的电气属性 压摆率 速度 驱动能力 开漏 上下拉
配置gpio输入输出
二、linux设备树
1.DTS语法
(1)设备节点
节点格式:label:node-name@unit-address
可以直接&label来访问这个节点
(2)常用的几种数据形式
字符串 compatible = "arm-cotex-a7";
32位无符号整数reg = <0>; reg = <0 0x123456 100>
字符串列表 compatible = "fsl","nand";
(3)model属性
model = "wm8960-audio"; 描述设备模块信息
(4)status属性
描述设备状态: "okay" 设备是可操作的
"disable" 设备是不可操作的
"fail" 设备不可操作检测到错误
"fail-sss" 设备不可操作出现了sss错误
(5)#address-cells 、 #size-cells
#address-cells:地址信息所占用的字长
#size-cells:长度信息所占用的字长
(6)reg属性
描述设备地址空间资源信息
(7)ranges属性
可以为空
按照(child—bus-address,parent-bus-address,length)格式的数据矩阵
(8)特殊节点
aliases子节点:别名
chosen子节点:用于uboot传递参数给linux
2.使用dts驱动led灯
(1)修改dts,添加alphaled节点
alphaled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-led";
status = "okay";
reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
0X0209C000 0X04 /* GPIO1_DR_BASE */
0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};
(2)写led驱动程序
1.init函数
获取设备节点 of_find_node_by_path
获取compatible of_find_property
获取status of_property_read_string
获取reg of_property_read_u32_array
寄存器映射 of_iomap
初始化led
申请设备号 register_chrdev_region alloc_chrdev_region
注册cdev cdev_init cdev_add
创建mdev (创建类,创建设备)device_create
2.exit函数
取消寄存器映射 iounmap
销毁设备 device_distroy
销毁类 class_distroy
删除cdev cdev_del
注销设备号 unregister_chrdev_region
3.write函数
操作硬件
3.pinctrl和gpio子系统
pinctrl子系统的主要工作内容
1、获取设备树中的pin信息
2、根据获取到的pin信息来设置pin的复用功能
3、根据获取到的pin信息来设置pin的电器特性,比如上下拉、速度、驱动能力等
只需要在设备树里设置好某个pin的相关属性即可,其他的初始化交给pinctrl子系统完成,pinctrl子系统源码/driver/pinctrl
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
<mux_reg conf_reg input_reg mux_mode input_val> conf_val
gpio子系统API函数
申请gpio管脚
gpio_request(unsigned gpio, const char *label);
释放gpio
gpio_free(unsigned gpio);
设置gpio为输入
gpio_direction_input(unsigned gpio);
设置gpio为输出
gpio_direction_output(unsigned gpio,int value);
获取gpio的值
gpio_get_value(unsigned gpio);
设置gpio的值
gpio_set_value(unsigned gpio, int value);
与gpio相关的OF函数
用于获取设备树中某个属性里定义了几个GPIO信息
of_gpio_named_count(struct device_node *np,const char *propname);
统计gpios这个属性的GPIO数量
of_gpio_count(struct device_node *np);
获取GPIO编号
of_get_named_gpio(struct device_node *np, const char *propname, int index);
使用pinctrl子系统和gpio子系统驱动led
(1)添加dts节点
找到iomux添加pinctrl节点
在/节点下添加设备节点
(2)写驱动程序
重要的是获取设备节点和获取gpio编号,有了gpio编号才能操作gpio
三、并发与竞争
1.锁
1.原子整形操作API
typedef struct {
int counter;
} atomic_t;
ATOMIC_INIT(int i) 定义原子变量的时候对其初始化。
int atomic_read(atomic_t *v) 读取 v 的值,并且返回。
void atomic_set(atomic_t *v, int i) 向 v 写入 i 值。
void atomic_add(int i, atomic_t *v) 给 v 加上 i 值。
void atomic_sub(int i, atomic_t *v) 从 v 减去 i 值。
void atomic_inc(atomic_t *v) 给 v 加 1,也就是自增。
void atomic_dec(atomic_t *v) 从 v 减 1,也就是自减
int atomic_dec_return(atomic_t *v) 从 v 减 1,并且返回 v 的值。
int atomic_inc_return(atomic_t *v) 给 v 加 1,并且返回 v 的值。
int atomic_sub_and_test(int i, atomic_t *v) 从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v) 从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v) 给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v) 给 v 加 i,如果结果为负就返回真,否则返回假
2.原子位操作API
void set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1。
void clear_bit(int nr,void *p) 将 p 地址的第 nr 位清零。
void change_bit(int nr, void *p) 将 p 地址的第 nr 位进行翻转。
int test_bit(int nr, void *p) 获取 p 地址的第 nr 位的值。
int test_and_set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
int test_and_clear_bit(int nr, void *p) 将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
int test_and_change_bit(int nr, void *p) 将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。
3.自旋锁
会不停的获取资源,直到资源可用,浪费处理器时间,降低系统性能
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
DEFINE_SPINLOCK(spinlock_t lock) 定义并初始化一个自选变量。
int spin_lock_init(spinlock_t *lock) 初始化自旋锁。
void spin_lock(spinlock_t *lock) 获取指定的自旋锁,也叫做加锁。
void spin_unlock(spinlock_t *lock) 释放指定的自旋锁。
int spin_trylock(spinlock_t *lock) 尝试获取指定的自旋锁,如果没有获取到就返回 0
int spin_is_locked(spinlock_t *lock)
检查指定的自旋锁是否被获取,如果没有被获取就
返回非 0,否则返回 0
获取锁之前关闭本地中断相关api
void spin_lock_irq(spinlock_t *lock) 禁止本地中断,并获取自旋锁。
void spin_unlock_irq(spinlock_t *lock) 激活本地中断,并释放自旋锁。
void spin_lock_irqsave(spinlock_t *lock,
unsigned long flags)
保存中断状态,禁止本地中断,并获取自旋锁。
void spin_unlock_irqrestore(spinlock_t
*lock, unsigned long flags)
将中断状态恢复到以前的状态,并且激活本地中断,
释放自旋锁
下半部使用自旋锁api
void spin_lock_bh(spinlock_t *lock) 关闭下半部,并获取自旋锁。
void spin_unlock_bh(spinlock_t *lock) 打开下半部,并释放自旋锁。
4.读写自旋锁
同一时刻只能一个线程持有写锁,其他程序不能读,同一时刻可以多个线程持有读锁,其他线程不能写
typedef struct {
arch_rwlock_t raw_lock;
} rwlock_t;
DEFINE_RWLOCK(rwlock_t lock) 定义并初始化读写锁
void rwlock_init(rwlock_t *lock) 初始化读写锁。
读锁
void read_lock(rwlock_t *lock) 获取读锁。
void read_unlock(rwlock_t *lock) 释放读锁。
void read_lock_irq(rwlock_t *lock) 禁止本地中断,并且获取读锁。
void read_unlock_irq(rwlock_t *lock) 打开本地中断,并且释放读锁。
void read_lock_irqsave(rwlock_t *lock,
unsigned long flags)
保存中断状态,禁止本地中断,并获取读锁。
void read_unlock_irqrestore(rwlock_t *lock,
unsigned long flags)
将中断状态恢复到以前的状态,并且激活本地
中断,释放读锁。
void read_lock_bh(rwlock_t *lock) 关闭下半部,并获取读锁。
void read_unlock_bh(rwlock_t *lock) 打开下半部,并释放读锁。
写锁
void write_lock(rwlock_t *lock) 获取写锁。
void write_unlock(rwlock_t *lock) 释放写锁。
void write_lock_irq(rwlock_t *lock) 禁止本地中断,并且获取写锁。
void write_unlock_irq(rwlock_t *lock) 打开本地中断,并且释放写锁。
void write_lock_irqsave(rwlock_t *lock,
unsigned long flags)
保存中断状态,禁止本地中断,并获取写锁。
void write_unlock_irqrestore(rwlock_t *lock,
unsigned long flags)
将中断状态恢复到以前的状态,并且激活本地
中断,释放读锁。
void write_lock_bh(rwlock_t *lock) 关闭下半部,并获取读锁。
void write_unlock_bh(rwlock_t *lock) 打开下半部,并释放读锁。
5.顺序锁
允许在写的时候进行读操作,不允许同时进行并发写操作
typedef struct {
struct seqcount seqcount;
spinlock_t lock;
} seqlock_t;
DEFINE_SEQLOCK(seqlock_t sl) 定义并初始化顺序锁
void seqlock_ini seqlock_t *sl) 初始化顺序锁。
顺序锁写操作
void write_seqlock(seqlock_t *sl) 获取写顺序锁。
void write_sequnlock(seqlock_t *sl) 释放写顺序锁。
void write_seqlock_irq(seqlock_t *sl) 禁止本地中断,并且获取写顺序锁
void write_sequnlock_irq(seqlock_t *sl) 打开本地中断,并且释放写顺序锁。
void write_seqlock_irqsave(seqlock_t *sl,
unsigned long flags)
保存中断状态,禁止本地中断,并获取写顺序
锁。
void write_sequnlock_irqrestore(seqlock_t *sl,
unsigned long flags)
将中断状态恢复到以前的状态,并且激活本地
中断,释放写顺序锁。
void write_seqlock_bh(seqlock_t *sl) 关闭下半部,并获取写读锁。
void write_sequnlock_bh(seqlock_t *sl) 打开下半部,并释放写读锁。
顺序锁读操作
unsigned read_seqbegin(const seqlock_t *sl)
读单元访问共享资源的时候调用此函数,此函
数会返回顺序锁的顺序号。
unsigned read_seqretry(const seqlock_t *sl,
unsigned start)
读结束以后调用此函数检查在读的过程中有
没有对资源进行写操作,如果有的话就要重读
2.信号量
同步的一种方式,适用于哪些占用资源比较久的场合
信号量api
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
DEFINE_SEAMPHORE(name) 定义一个信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore *sem, int val) 初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem)
获取信号量,因为会导致休眠,因此不能在中断中使用。
int down_trylock(struct semaphore *sem);
尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。
int down_interruptible(struct semaphore *sem)
获取信号量,和 down 类似,只是使用 down 进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。
void up(struct semaphore *sem) 释放信号量
3.互斥体(mutex)
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
};
DEFINE_MUTEX(name) 定义并初始化一个 mutex 变量。
void mutex_init(mutex *lock) 初始化 mutex。
void mutex_lock(struct mutex *lock)
获取 mutex,也就是给 mutex 上锁。如果获
取不到就进休眠。
void mutex_unlock(struct mutex *lock) 释放 mutex,也就给 mutex 解锁。
int mutex_trylock(struct mutex *lock)
尝试获取 mutex,如果成功就返回 1,如果失
败就返回 0。
int mutex_is_locked(struct mutex *lock)
判断 mutex 是否被获取,如果是的话就返回
1,否则返回 0。
int mutex_lock_interruptible(struct mutex *lock)
使用此函数获取信号量失败进入休眠以后可
以被信号打断。
四、内核定时器
include/asm-generic/param.h
# define HZ CONFIG_HZ 系统频率
处理32位jiffies绕回
time_after(unkown, known)
time_before(unkown, known) unkown 通常为 jiffies, known 通常是需要对比的值。
time_after_eq(unkown, known)
time_before_eq(unkown, known)
jiffies和ms、us、ns之间的转换函数
int jiffies_to_msecs(const unsigned long j)
将 jiffies 类型的参数 j 分别转换为对应的毫秒、
int jiffies_to_usecs(const unsigned long j) 微秒、纳秒。
u64 jiffies_to_nsecs(const unsigned long j)
long msecs_to_jiffies(const unsigned int m)
long usecs_to_jiffies(const unsigned int u) 将毫秒、微秒、纳秒转换为 jiffies 类型。
unsigned long nsecs_to_jiffies(u64 n)
timer_list结构体表示内核定时器
struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};
void init_timer(struct timer_list *timer) 初始化定时器
void add_timer(struct timer_list *timer) 添加一个定时器并开始运行
int del_timer(struct timer_list * timer) 删除定时器
int del_timer_sync(struct timer_list *timer) 等待定时器用完在删除
int mod_timer(struct timer_list *timer, unsigned long expires) 修改定时值
内核短延时函数
void ndelay(unsigned long nsecs)
void udelay(unsigned long usecs) 纳秒、微秒和毫秒延时函数。
void mdelay(unsigned long mseces)
五、中断
1、中断号
每个中断都有一个中断号,通过中断号区分不同的中断
2、request_irq函数
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将
dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
返回值: 0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经
被申请了
这里我们介绍几个常用的中断标志
IRQF_SHARED
多个设备共享一个中断线,共享的所有中断都必须指定此标志。
如果使用共享中断的话, request_irq 函数的 dev 参数就是唯一
区分他们的标志。
IRQF_ONESHOT 单次中断,中断执行一次就结束。
IRQF_TRIGGER_NONE 无触发。
IRQF_TRIGGER_RISING 上升沿触发。
IRQF_TRIGGER_FALLING 下降沿触发。
IRQF_TRIGGER_HIGH 高电平触发。
IRQF_TRIGGER_LOW 低电平触发。
3、free_irq函数
使用完成以后,要释放中断
void free_irq(unsigned int irq,
void *dev)
irq: 要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
4、中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)
第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。
中断处理函数的返回值为 irqreturn_t 类型
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
一般中断服务函数返回值使用如下形式
return IRQ_RETVAL(IRQ_HANDLED)
5、中断使能与禁止
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq) 等待中断执行结束才返回
void disable_irq_nosync(unsigned int irq) 立即返回
关闭全局中断,这个时候可以使用如下两个函数:
local_irq_enable()
local_irq_disable()
禁止和恢复中断
local_irq_save(flags)
local_irq_restore(flags)
6、上半部与下半部
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
下半部机制:
软中断
注册软中断void open_softirq(int nr, void (*action)(struct softirq_action *))
触发软中断void raise_softirq(unsigned int nr)
tasklet:
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
初始化tasklet void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);
DECLARE_TASKLET(name, func, data)
在中断上半部调用tasklet void tasklet_schedule(struct tasklet_struct *t)
7、工作队列
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
工作队列初始化
#define INIT_WORK(_work, _func)
#define DECLARE_WORK(n, f) 一次性完成创建和初始化
调用工作队列
bool schedule_work(struct work_struct *work)
8、从dts获取中断号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
获取gpio对应的中断号
int gpio_to_irq(unsigned int gpio)
六、阻塞与非阻塞
1、阻塞IO方式访问
当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式IO就会将应用程序对应的线程挂起,直到设备资源可以获取
2、非阻塞IO方式访问
当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,程序不会挂起,要么一直轮询等待,要么直接放弃
3、等待队列
阻塞访问最大的好处就是当设备文件不可操作时,进程可以进入休眠态、可以将cpu资源让出来,当设备可以操作时必须唤醒进程,一般在中断函数中完成唤醒工作。
在驱动中使用等待队列,需要创建并初始化一个等待队列头
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
初始化等待队列头
void init_waitqueue_head(wait_queue_head_t *q)
也可以一次性初始化
DECLARE_WAIT_QUEUE_HEAD()
等待队列项
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
也可以使用宏定义并初始化一个等待队列项
DECLARE_WAITQUEUE(name, tsk)
name:等待队列项的名字
tsk:一般为current
将队列项添加\移除 等待队列头
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
等待唤醒 (主动唤醒)
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
等待事件(满足条件唤醒)
wait_event(wq, condition)
wait_event_timeout(wq, condition, timeout)
wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wq,condition, timeout)
与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断
4、轮询(非阻塞)
1、select
int select( int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
nfds:最大文件描述符+1
readfds:读集合
writefds:写集合
exceptfds:异常
timeout:超时
void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)
2、poll
int poll( struct pollfd *fds,
nfds_t nfds,
int timeout )
fds:要监视的文件描述符集合以及要监视的事件
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
};
events:
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于 POLLIN
nfds:要监视的文件描述符数量
timeout :超时时间
3、epoll
int epoll_create(int size)
int epoll_ctl(int epfd,
int op,
int fd,
struct epoll_event *event)
op:要进行的操作
EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符。
EPOLL_CTL_MOD 修改参数 fd 的 event 事件。
EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符。
fd:要监视的文件描述符
event:要监视的时间类型
struct epoll_event {
uint32_t events; /* epoll 事件 */
epoll_data_t data; /* 用户数据 */
};
events :
EPOLLIN 有数据可以读取。
EPOLLOUT 可以写数据。
EPOLLPRI 有紧急的数据需要读取。
EPOLLERR 指定的文件描述符发生错误。
EPOLLHUP 指定的文件描述符挂起。
EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发。
EPOLLONESHOT 一次性的监视,当监视完成以后还需要再次监视某个 fd,那么就需要将fd 重新添加到 epoll 里面。
int epoll_wait(int epfd,
struct epoll_event *events,
int maxevents,
int timeout)
总结
驱动开发接口汇总
字符设备的注册与注销:
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
内存访问
1 u8 readb(const volatile void __iomem *addr)
2 u16 readw(const volatile void __iomem *addr)
3 u32 readl(const volatile void __iomem *addr)
1 void writeb(u8 value, volatile void __iomem *addr)
2 void writew(u16 value, volatile void __iomem *addr)
3 void writel(u32 value, volatile void __iomem *addr)
地址映射与取消映射
ioremap(cookie,size)
void iounmap (volatile void __iomem *addr)
申请与注销设备号
没有设备号:extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
给定设备号:extern int register_chrdev_region(dev_t, unsigned, const char *);
extern void unregister_chrdev_region(dev_t, unsigned);
cdev的注册与注销
struct cdev test_cdev;
void cdev_init(struct cdev *, const struct file_operations *);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
类的创建与销毁
struct class *class_create(owner, name);
设备的创建与销毁
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
extern void device_destroy(struct class *cls, dev_t devt);
extern void class_destroy(struct class *cls);
获取设备树中的节点
static inline struct device_node *of_find_node_by_path(const char *path)
获取compatible属性内容
static inline struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp)
获取status属性内容
static inline int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string)
获取reg属性内容
static inline int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values, size_t sz)