ARM Linux驱动开发

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 位置 1void 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) 定义一个信号量,并且设置信号量的值为 1void 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,如果失
败就返回 0int mutex_is_locked(struct mutex *lock)
判断 mutex 是否被获取,如果是的话就返回
1,否则返回 0int 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方式访问
非阻塞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)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦成大佬的第N天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值