并发与竞争

原子操作

定义:

1
2
3
typedef struct {
int counter;
} atomic_t;

整形操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
ATOMIC_INIT(int i);//定义时初始化
int atomic_read(atomic_t *v);
void atomic_set(atomic_t *v, int i)
void atomic_add(int i, atomic_t *v)
void atomic_sub(int i, atomic_t *v)
void atomic_dec(atomic_t *v)//自减
void atomic_inc(atomic_t *v)//自增
int atomic_dec_return(atomic_t *v)//自减并返回v
int atomic_inc_return(atomic_t *v)//自增并返回v
int atomic_sub_and_test(int i, atomic_t *v)//(v-i)==0?1:0(返回真、假)
int atomic_dec_and_test(atomic_t *v)//(v--)==0?1:0(返回真、假)
int atomic_inc_and_test(atomic_t *v)//(v++)==0?1:0(返回真、假)
int atomic_add_negative(int i, atomic_t *v)//(v+i)<0?1:0(返回真、假)

位操作(直接对内存操作):

1
2
3
4
5
6
7
void set_bit(int nr, void *p)//将p地址的第nr位置 1
void clear_bit(int nr,void *p)
void change_bit(int nr, void *p)//翻转
int test_bit(int nr, void *p)//获取
int test_and_set_bit(int nr, void *p)//将p地址的第nr位置1,并返回nr位原来的值
int test_and_clear_bit(int nr, void *p)//...清零...
int test_and_change_bit(int nr, void *p)//...翻转...

自旋锁

基本API

1
2
3
4
5
6
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)//尝试加锁
int spin_is_locked(spinlock_t *lock)//检查是否加锁,是返回非 0,否返回 0

中断相关:

一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用spin_lock/spin_unlock

1
2
3
4
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)//恢复中断状态,并且激活本地中断,释放自旋锁

中断下半部相关:

1
2
void spin_lock_bh(spinlock_t *lock)//关闭下半部,并获取自旋锁
void spin_unlock_bh(spinlock_t *lock)//打开下半部,并释放自旋锁

信号量

1
2
3
4
5
6
DEFINE_SEAMPHORE(name)//定义信号量 并设值为1
void sema_init(struct semaphore *sem, int val)//初始化,并设值为val
void down(struct semaphore *sem)//获取信号量(会导致休眠,不能在中断中使用,不能被信号打断)
int down_interruptible(struct semaphore *sem)//获取信号量(会导致休眠,不能在中断中使用,可以被信号打断)
int down_trylock(struct semaphore *sem)//尝试获取信号量(成功返回 0。否则返回非 0)
void up(struct semaphore *sem)//释放信号量

互斥体

会导致休眠,不能在中断中使用 mutex,中断中只能使用自旋锁

1
2
3
4
5
6
7
DEFINE_MUTEX(name)//定义并初始化
void mutex_init(mutex *lock)//初始化
void mutex_lock(struct mutex *lock)//上锁,失败则休眠,不可以被信号打断
int mutex_lock_interruptible(struct mutex *lock)//上锁,失败则休眠,可以被信号打断
void mutex_unlock(struct mutex *lock)//解锁
int mutex_trylock(struct mutex *lock)//尝试加锁,成功返回1,失败返回0
int mutex_is_locked(struct mutex *lock)//判断是否加锁,是返回1,否返回0

字符设备驱动

常用宏

1
2
3
4
5
6
7
8
9
10
11
12
13
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author");
//printk
#define KERN_EMERG KERN_SOH "0" /* 紧急事件,一般是内核崩溃 */
#define KERN_ALERT KERN_SOH "1" /* 必须立即采取行动 */
#define KERN_CRIT KERN_SOH "2" /* 临界条件,比如严重的软件或硬件错误*/
#define KERN_ERR KERN_SOH "3" /* 错误状态,一般设备驱动程序中使用KERN_ERR 报告硬件错误 */
#define KERN_WARNING KERN_SOH "4" /* 警告信息,不会对系统造成严重影响 */
#define KERN_NOTICE KERN_SOH "5" /* 有必要进行提示的一些信息 */
#define KERN_INFO KERN_SOH "6" /* 提示性的信息 */
#define KERN_DEBUG KERN_SOH "7" /* 调试信息 */

注册与注销

老方法,不推荐:

1
2
3
4
//注册字符设备(浪费设备号)
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);
  • name:设备名字,指向一串字符串

新方法,推荐:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*注册(方法一,自己确定设备号)*/
int register_chrdev_region(dev_t from, unsigned count, const char *name);//详见设备号API
cdev.owner = THIS_MODULE;
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
/*注册(方法二,系统分配设备号)*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);//详见设备号API
cdev.owner = THIS_MODULE;
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
/*注销*/
void cdev_del(struct cdev *p);
void unregister_chrdev_region(dev_t from, unsigned count);

设备节点

类相关:

1
2
class_create(owner, name)//创建类 owner一般为THIS_MODULE
void class_destroy(struct class *cls);//卸载类

设备相关:

1
2
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);//创建设备 parent=NULL drvdata=NULL
void device_destroy(struct class *class, dev_t devt)//卸载设备

设备号

相关宏:

1
2
3
MAJOR(dev)//dev_t -> 主设备号
MINOR(dev)//dev_t -> 次设备号
MKDEV(ma,mi)//主设备号+次设备号 -> dev_t

动态分配设备号:

系统分配设备号+自动注册设备号

1
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
  • dev:存放起始设备编号的指针,当注册成功,*dev就会等于分配到的起始设备编号,可以通过MAJOR()MINNOR()宏来提取主次设备号
  • baseminor:次设备号基地址,也就是起始次设备号
  • count:要申请的数量,一般都是一个
  • name:字符设备名称
  • 返回值小于0,表示注册失败

注册设备号:

手动确定设备号+手动注册设备号

1
int register_chrdev_region(dev_t from, unsigned count, const char *name)
  • from:要申请的起始设备号,也就是给定的设备号
  • count:要申请的数量,一般都是一个
  • name:设备名字
  • 当返回值小于0,表示注册失败

用户空间与内核空间

内核空间–>用户空间

1
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
  • to:用户空间指针
  • from:内核空间指针
  • n:从内核空间向用户空间拷贝数据的字节数
  • 成功返回0,失败返回失败数目

用户空间–>内核空间

1
static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
  • to:内核空间指针
  • from:用户空间指针
  • n:从用户空间向内核空间拷贝数据的字节数
  • 成功返回0,失败返回失败数目

地址映射

1
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
  • phys_addr:要映射的物理起始地址。
  • size:要映射的内存空间大小。
1
void iounmap (volatile void __iomem *addr)

对映射后的内存进行读写操作的函数:

1
2
3
4
5
6
7
8
//读
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
//写
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)

Linux常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#查看设备号
cat /proc/devices
#创建设备节点
mknod /dev/xxx c 主设备号 次设备号
#查看设备树节点
ls /proc/device-tree
ls /sys/firmware/devicetree/base
#查看platform相关
ls /sys/bus/platform/devices # 设备
ls /sys/bus/platform/drivers # 驱动
#查看misc相关
ls /sys/class/misc # 驱动
#驱动相关
depmod
modprobe xxx.ko
insmod xxx.ko
lsmod
rmmod xxx.ko

内核定时器

1
2
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

相关变量:

  • HZ:宏,就是CONFIG_HZ,系统频率,通过menuconfig配置,默认100
  • jiffiesjiffies_64:系统运行的节拍数,由宏HZ确定,默认10ms累加一次
  • jiffies/HZ:系统运行时间(s)

时间先后函数:

1
2
3
4
5
//unkown 通常为 jiffies,known 通常是需要对比的值
time_after(unkown, known)//unkown 时间上超过 known 则返回真
time_before(unkown, known)//unkown 时间上滞后 known 则返回真
time_after_eq(unkown, known)//unkown 时间上超过或等于 known 则返回真
time_before_eq(unkown, known)//unkown 时间上滞后或等于 known 则返回真

时间转换函数:

1
2
3
4
5
6
int jiffies_to_msecs(const unsigned long j)//jiffies类型j --> ms
int jiffies_to_usecs(const unsigned long j)//jiffies类型j --> us
u64 jiffies_to_nsecs(const unsigned long j)//jiffies类型j --> ns
long msecs_to_jiffies(const unsigned int m)//ms --> jiffies
long usecs_to_jiffies(const unsigned int u)//us --> jiffies
unsigned long nsecs_to_jiffies(u64 n)//ns --> jiffies

短延时函数:

1
2
3
void ndelay(unsigned long nsecs)
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)

定时器类型:

1
2
3
4
5
6
7
8
struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};

定时器函数:

1
2
3
4
5
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);//修改定时值(会使能定时器)
  • 返回值: 0,定时器未被使能; 1,定时器已经使能

中断

上半部

申请

1
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里面查看所有的中断标志。

常用的中断标志:

标志 描述
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。 如果使用共享中断的话,request_irq函数的 dev 参数是唯一区分他们的标志
IRQF_ONESHOT 单次中断,中断执行一次就结束
IRQF_TRIGGER_NONE 无触发
IRQF_TRIGGER_RISING 上升沿触发
IRQF_TRIGGER_FALLING 下降沿触发
IRQF_TRIGGER_HIGH 高电平触发
IRQF_TRIGGER_LOW 低电平触发
  • name:中断名字,设置以后可以在/proc/interrupts文件中看到对应的中断名字
  • dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数
  • 返回值: 0 中断申请成功;其他负值:中断申请失败;-EBUSY:中断已经被申请了

对于一种不太重要而又比较耗时的中断,可以使用以下函数进行申请:

1
int 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)
  • 该函数会使得中断线程化(中断使用下半部虽然可以被延迟处理,但是依旧先于线程执行,中断线程化可以让这些比较耗时的下半部与进程进行公平竞争)
  • devm_前缀表明申请到的资源可以由系统自动释放,无需手动处理
  • 注意:并不是所有的中断都可以被线程化。该函数有利有弊,具体是否使用需要根据实际情况来衡量(触摸中断一般会用)

释放

1
void free_irq(unsigned int irq, void *dev)
  • irq: 要释放的中断
  • dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉

中断处理函数

格式:

1
irqreturn_t (*irq_handler_t) (int, void *)

返回值:

一般为return IRQ_RETVAL(IRQ_HANDLED)

1
2
3
4
5
6
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;

使能与禁止

1
2
3
4
5
6
7
8
9
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)//需等待 正在执行的中断处理函数执行完
void disable_irq_nosync(unsigned int irq)//无需等待 正在执行的中断处理函数执行完
//不推荐
local_irq_disable()//关闭全局中断
local_irq_enable()//使能全局中断
//推荐
local_irq_save(flags)//保存中断标志到flags并 关闭全局中断
local_irq_restore(flags)//根据flags设置中断标志并 使能全局中断

下半部

推荐使用tasklet实现

软中断

定义:

1
2
3
4
struct softirq_action
{
void (*action)(struct softirq_action *);
};

系统定义的10个软中断:

多核处理器中,各个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断,但是所执行的软中断服务函数却是相同的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum
{
HI_SOFTIRQ=0,//高优先级软中断
TIMER_SOFTIRQ,//定时器软中断
NET_TX_SOFTIRQ,//网络数据发送软中断
NET_RX_SOFTIRQ,//网络数据接收软中断
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,//tasklet 软中断
SCHED_SOFTIRQ,//调度软中断
HRTIMER_SOFTIRQ,//高精度定时器软中断
RCU_SOFTIRQ,//RCU 软中断
NR_SOFTIRQS
};

static struct softirq_action softirq_vec[NR_SOFTIRQS];

注册软中断:

软中断必须在编译的时候静态注册。Linux内核默认会在软中断初始化函数softirq_init中打开TASKLET_SOFTIRQHI_SOFTIRQ软中断

1
void open_softirq(int nr, void (*action)(struct softirq_action *))
  • nr:要开启的软中断,在上述enum中选择一个
  • action:软中断对应的处理函数

触发软中断:

1
void raise_softirq(unsigned int nr)

tasklet

定义:

1
2
3
4
5
6
7
8
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 的参数 */
};

初始化:

1
2
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);//初始化
DECLARE_TASKLET(name, func, data)//宏,定义并初始化
  • func:tasklet 的处理函数
  • data:要传递给 func 函数的参数

调度函数:

一般在中断的上半部中调用

1
void tasklet_schedule(struct tasklet_struct *t)
  • t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name

工作队列

工作队列在进程上下文执行,即,将要推后的工作交给一个内核线程去执行,因此工作队列允许睡眠重新调度

如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet

定义:

工作–>工作队列–>工作者线程

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
//工作(重点关注):
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
//工作队列:
struct workqueue_struct {
struct list_head pwqs;
struct list_head list;
struct mutex mutex;
int work_color;
int flush_color;
atomic_t nr_pwqs_to_flush;
struct wq_flusher *first_flusher;
struct list_head flusher_queue;
struct list_head flusher_overflow;
struct list_head maydays;
struct worker *rescuer;
int nr_drainers;
int saved_max_active;
struct workqueue_attrs *unbound_attrs;
struct pool_workqueue *dfl_pwq;
char name[WQ_NAME_LEN];
struct rcu_head rcu;
unsigned int flags ____cacheline_aligned;
struct pool_workqueue __percpu *cpu_pwqs;
struct pool_workqueue __rcu *numa_pwq_tbl[];
};
//工作者线程
struct worker {
union {
struct list_head entry;
struct hlist_node hentry;
};
struct work_struct *current_work;
work_func_t current_func;
struct pool_workqueue *current_pwq;
bool desc_valid;
struct list_head scheduled;
struct task_struct *task;
struct worker_pool *pool;
struct list_head node;
unsigned long last_active;
unsigned int flags;
int id;
char desc[WORKER_DESC_LEN];
struct workqueue_struct *rescue_wq;
};

初始化:

1
2
#define INIT_WORK(_work, _func)//初始化,需要自己创建work_struct
#define DECLARE_WORK(n, f)//创建和初始化,无需自己创建work_struct

调度:

一般在中断的上半部中调用

1
bool schedule_work(struct work_struct *work)
  • work: 要调度的工作
  • 返回值: 0 成功,其他值 失败

阻塞与非阻塞

阻塞:访问资源时,如果资源不可用,将会挂起线程,直到资源可用再唤醒。open函数中大部分flag均为阻塞访问

非阻塞:访问资源时,如果资源不可用,将会直接返回错误码。open函数中flag参数O_NONBLOCK为非阻塞访问

等待队列

一般用于在中断中唤醒阻塞操作而引起的线程挂起

等待队列头

等待队列的头部

定义:

1
2
3
4
5
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

初始化:

1
2
void init_waitqueue_head(wait_queue_head_t *q)//仅初始化
DECLARE_WAIT_QUEUE_HEAD//宏,定义+初始化

等待队列项

每个访问设备的进程都需要创建一个队列项,当设备不可用的时候需要将这些进程对应的等待队列项添加到等待队列里面

定义:

1
2
3
4
5
6
7
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;

初始化:

1
DECLARE_WAITQUEUE(name, tsk)//宏,定义并初始化
  • name:等待队列项的名字
  • tsk:这个等待队列项属于哪个任务(进程),一般设置为current(全局变量,表示当前进程)

添加/移除等待队列

当设备不可用的时候需要将该进程对应的等待队列项添加到等待队列里面

1
2
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)//删除
  • q: 等待队列项要加入的等待队列头
  • wait:要加入/删除的等待队列项

等待唤醒

当设备可以使用的时候就要唤醒进入休眠态的进程

1
2
void wake_up(wait_queue_head_t *q)//唤醒队列中的所有进程(包括TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE状态)
void wake_up_interruptible(wait_queue_head_t *q)//唤醒队列中的所有进程(仅唤醒TASK_INTERRUPTIBLE状态进程)

等待事件

当事件满足以后能够自动唤醒等待队列中的进程

1
2
3
4
wait_event(wq, condition)//若condition为真,则唤醒等待队列,否则一直阻塞。(会将进程设置为TASK_UNINTERRUPTIBLE状态)
wait_event_timeout(wq, condition, timeout)//与wait_event类似,timeout为超时时间,单位为jiffies。返回0:超时时间到,且condition为假;返回1:condition为真
wait_event_interruptible(wq, condition)//与wait_event类似,此函数会将进程设置为TASK_INTERRUPTIBLE,即可以被信号打断
wait_event_interruptible_timeout(wq, condition, timeout)//与wait_event_timeout类似,此函数会将进程设置为TASK_INTERRUPTIBLE,即可以被信号打断

轮询

一般用于非阻塞操作

pollepollselect 用于处理轮询。应用程序通过 selectepollpoll 来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用 selectepollpoll 函数的时候设备驱动程序中的 poll 函数就会执行

select

单线程中 select 函数能够监视的文件描述符数量一般最大为1024,可以修改该数量但会降低效率

1
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
  • nfds: 要操作的文件描述符个数
  • readfds:指向描述符集合,fd_set类型。用于监视指定描述符集的变化,所指定集合中有一个文件可以读取,则返回大于 0 的值,否则根据 timeout 参数来判断是否超时。设置为 NULL,表示不关心任何文件的变化
  • writefds:指向描述符集合,fd_set类型。用于监视指定描述符集的变化,所指定集合中有一个文件可以写入,则返回大于 0 的值,否则根据 timeout 参数来判断是否超时。设置为 NULL,表示不关心任何文件的变化
  • exceptfds:指向描述符集合,fd_set类型。用于监视指定描述符集的异常变化,所指定集合中有一个文件异常,则返回大于 0 的值,否则根据 timeout 参数来判断是否超时。设置为 NULL,表示不关心任何文件的异常变化

fd_set变量定义相关宏:

1
2
3
4
void FD_ZERO(fd_set *set)//将 fd_set 变量的所有位都清零
void FD_SET(int fd, fd_set *set)//将 fd_set 变量的某个位置 1(向 fd_set 添加一个文件描述符), fd:要加入的文件描述符
void FD_CLR(int fd, fd_set *set)//将 fd_set 变量的某个位清零(向 fd_set 删除一个文件描述符), fd:要删除的文件描述符
int FD_ISSET(int fd, fd_set *set)//测试 fd_set 的某个位是否置 1(判断某个文件是否可以进行操作), fd:要判断的文件描述符
  • timeout:超时时间,设为 NULL 表示无限期等待。timeval结构体类型

    1
    2
    3
    4
    struct timeval {
    long tv_sec; /* 秒 */
    long tv_usec; /* 微秒 */
    };
  • 返回值: 0,超时发生,没有任何文件描述符可以进行操作; -1,发生错误;其他值,可以进行操作的文件描述符个数

select使用示例:

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
void main(void)
{
int ret, fd; /* 要监视的文件描述符 */
fd_set readfds; /* 读操作文件描述符集 */
struct timeval timeout; /* 超时结构体 */
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
FD_ZERO(&readfds); /* 清除 readfds */
FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */
/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 超时 */
......
break;
case -1: /* 错误 */
......
break;
default: /* 可以读取数据 */
if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 */
/* 使用 read 函数读取数据 */
}
break;
}
}

poll

本质上和 select 没有太大的差别,但是 poll 函数没有最大文件描述符限制

1
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
  • fds: 要监视的文件描述符集合以及要监视的事件,为结构体pollfd数组类型,pollfd类型定义:

    1
    2
    3
    4
    5
    struct pollfd {
    int fd; /* 文件描述符 */
    short events; /* 请求的事件 */
    short revents; /* 返回的事件 */
    };
    • fd:要监视的文件描述符,如果 fd 无效则 events 监视事件也无效,并且 revents 返回 0

    • events:要监视的事件,类型如下:

      1
      2
      3
      4
      5
      6
      7
      POLLIN 有数据可以读取
      POLLPRI 有紧急的数据需要读取
      POLLOUT 可以写数据
      POLLERR 指定的文件描述符发生错误
      POLLHUP 指定的文件描述符挂起
      POLLNVAL 无效的请求
      POLLRDNORM 等同于POLLIN
    • revents:返回参数,由 Linux 内核设置具体的返回事件

  • nfds:poll 函数要监视的文件描述符数量

  • timeout:超时时间,单位为 ms

  • 返回值:返回 revents 域中不为 0 的 pollfd 结构体个数,即发生事件或错误的文件描述符数量; 0,超时; -1,发生错误,并设置相应的错误码

poll使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void main(void)
{
int ret;
int fd; /* 要监视的文件描述符 */
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
/* 构造结构体 */
fds.fd = fd;
fds.events = POLLIN; /* 监视数据是否可以读取 */
ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时 500ms */
if (ret) { /* 数据有效 */
......
/* 读取数据 */
......
} else if (ret == 0) { /* 超时 */
......
} else if (ret < 0) { /* 错误 */
......
}
}

epoll

selcet 和 poll 函数会随着所监听的 fd 数量的增加,而导致效率的降低,且遍历所有的描述符比较浪费时间。epoll 专为处理大并发而准备,一般用于网络编程中。 其相关函数如下:

创建 epoll 句柄:

1
int epoll_create(int size)
  • size:Linux2.6.8 后此参数已无意义,大于 0 即可
  • 返回值: epoll 句柄,如果为-1 的话表示创建失败

向 epoll 句柄中添加要监视的文件描述符以及监视的事件:

1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
  • epfd:epoll 句柄(即epoll_create的返回值)

  • op:对 epfd 的操作,可以设置为:

    1
    2
    3
    EPOLL_CTL_ADD 向epfd添加文件参数fd表示的描述符
    EPOLL_CTL_MOD 修改参数fd的event事件
    EPOLL_CTL_DEL 从epfd中删除fd描述符
  • fd:要监视的文件描述符

  • event:要监视的事件类型,epoll_event结构体指针类型:

    1
    2
    3
    4
    struct epoll_event {
    uint32_t events; /* epoll 事件 */
    epoll_data_t data; /* 用户数据 */
    };
    • events:要监视的事件:

      1
      2
      3
      4
      5
      6
      7
      EPOLLIN 有数据可以读取
      EPOLLPRI 有紧急的数据需要读取
      EPOLLOUT 可以写数据
      EPOLLERR 指定的文件描述符发生错误
      EPOLLHUP 指定的文件描述符挂起
      EPOLLET 设置epoll为边沿触发(默认为水平触发)
      EPOLLONESHOT 一次性监视(当监视完成以后需要再次监视某个fd,就需要将fd重新添加到epoll里面)
  • 返回值: 0,成功; -1,失败,并设置相应的错误码

等待事件发生:

1
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
  • epfd:epoll 句柄
  • events:指向 epoll_event 结构体的数组,当有事件发生的时候 Linux 内核会填写 events,调用者可以根据 events 判断发生了哪些事件
  • maxevents:events 数组大小(必须大于 0)
  • timeout:超时时间,单位为 ms
  • 返回值: 0,超时; -1,错误;其他值,准备就绪的文件描述符数量

驱动中的poll函数

函数原型:

1
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
  • filp: 要打开的设备文件(文件描述符)

  • wait: 结构体 poll_table_struct 类型指针, 由应用程序传递进来的。一般将此参数传递给
    poll_wait 函数

  • 返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下:

    1
    2
    3
    4
    5
    6
    7
    POLLIN 有数据可以读取
    POLLPRI 有紧急的数据需要读取
    POLLOUT 可以写数据
    POLLERR 指定的文件描述符发生错误
    POLLHUP 指定的文件描述符挂起
    POLLNVAL 无效的请求
    POLLRDNORM 等同于POLLIN,普通数据可读

添加到poll_table:

非阻塞函数,不会引起阻塞,只是将应用程序添加到 poll_table 中

1
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
  • wait_address:要添加到 poll_table 中的等待队列头
  • p:poll_table,就是file_operations 中 poll 函数的 wait 参数

驱动通用模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct xxxx_dev {
......
struct wait_queue_head_t r_wait; /* 等待队列头 */
};

unsigned int xxx_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct xxxx_dev *dev = (struct xxxx_dev *)filp->private_data;

poll_wait(filp, &dev->r_wait, wait);

if(......) { /* 相关条件满足 */
mask = POLLIN | POLLRDNORM; /* 返回 PLLIN */
}
return mask;
}

异步通知(信号)

Linux中的信号宏:

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
#define SIGHUP 		1 /* 终端挂起或控制进程终止 */
#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGILL 4 /* 非法指令 */
#define SIGTRAP 5 /* debug 使用,有断点指令产生 */
#define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
#define SIGIOT 6 /* IOT 指令 */
#define SIGBUS 7 /* 总线错误 */
#define SIGFPE 8 /* 浮点运算错误 */
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGUSR1 10 /* 用户自定义信号 1 */
#define SIGSEGV 11 /* 段违例(无效的内存段) */
#define SIGUSR2 12 /* 用户自定义信号 2 */
#define SIGPIPE 13 /* 向非读管道写入数据 */
#define SIGALRM 14 /* 闹钟 */
#define SIGTERM 15 /* 软件终止 */
#define SIGSTKFLT 16 /* 栈异常 */
#define SIGCHLD 17 /* 子进程结束 */
#define SIGCONT 18 /* 进程继续 */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
#define SIGURG 23 /* 有"紧急"数据 */
#define SIGXCPU 24 /* 超过 CPU 资源限制 */
#define SIGXFSZ 25 /* 文件大小超额 */
#define SIGVTALRM 26 /* 虚拟时钟信号 */
#define SIGPROF 27 /* 时钟信号描述 */
#define SIGWINCH 28 /* 窗口大小改变 */
#define SIGIO 29 /* 可以进行输入/输出操作 */
#define SIGPOLL SIGIO
/* #define SIGLOS 29 */
#define SIGPWR 30 /* 断点重启 */
#define SIGSYS 31 /* 非法的系统调用 */
#define SIGUNUSED 31 /* 未使用信号 */

上述信号中,除了 SIGKILL(9)和 SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以忽略

应用程序API

指定信号的处理函数:

1
sighandler_t signal(int signum, sighandler_t handler)
  • signum:要设置处理函数的信号

  • handler: 信号的处理函数,函数原型:

    1
    typedef void (*sighandler_t)(int)
  • 返回值: 设置成功返回信号的前一个处理函数,设置失败返回 SIG_ERR

应用程序的一般模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}

/* 设置信号 SIGIO 的处理函数 */
signal(SIGIO, sigio_signal_func);

fcntl(fd, F_SETOWN, getpid()); /* 将当前进程的进程号告诉给内核 */
flags = fcntl(fd, F_GETFD); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC);/* 设置进程状态为 FASYNC 启用异步通知功能(此时会调用驱动中的fasync函数) */

while(1) {
sleep(2);
}

close(fd);
return 0;

驱动程序API

异步相关结构体:

1
2
3
4
5
6
7
8
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
};

向应用程序发送中断信号:

1
void kill_fasync(struct fasync_struct **fp, int sig, int band)
  • fp:要操作的 fasync_struct 结构体
  • sig: 要发送的信号
  • band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT
  • 返回值: 无

file_operations操作集中异步接口函数:

应用程序通过fcntl(fd, F_SETFL, flags | FASYNC)改变fasync标记的时候,该函数就会执行

1
int (*fasync) (int fd, struct file *filp, int on)

初始化 fasync_struct 结构体:

1
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
  • 前三个参数与fasync函数中参数一致
  • fapp:要初始化的 fasync_struct 结构体指针变量

驱动一般模板:

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
struct xxx_dev {
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};

static int xxx_fasync(int fd, struct file *filp, int on)
{
struct xxx_dev *dev = (xxx_dev)filp->private_data;

if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)//直接调用该函数即可,不用干其他事
return -EIO;
return 0;
}

static struct file_operations xxx_ops = {
......
.fasync = xxx_fasync,
.release = xxx_release,
......
};

static int xxx_release(struct inode *inode, struct file *filp)
{
return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
}

常用框架API

platform

platform设备相关:

1
2
void platform_device_unregister(struct platform_device *pdev)//卸载platform设备
int platform_device_register(struct platform_device *pdev)//注册platform设备

platform驱动相关:

1
2
void platform_driver_unregister(struct platform_driver *drv)//卸载platform驱动
int platform_driver_register (struct platform_driver *driver)//注册platform驱动

MISC

MISC 驱动也叫做杂项驱动,当某些外设无法进行分类的时候就可以使用 MISC 驱动。 MISC 驱动其实就是最简单的字符设备驱动,通常嵌套在 platform 总线驱动中,实现复杂的驱动

所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。MISC 设备会自动创建 cdev

1
2
int misc_register(struct miscdevice * misc)//注册MISC驱动
int misc_deregister(struct miscdevice *misc)//销毁MISC驱动

INPUT

初始化及卸载

申请及释放:

1
2
struct input_dev *input_allocate_device(void)//申请input_dev
void input_free_device(struct input_dev *dev)//释放input_dev

注册及注销:

1
2
int input_register_device(struct input_dev *dev)//注册
void input_unregister_device(struct input_dev *dev)//注销

修改input_dev:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct input_dev *inputdev;
inputdev = input_allocate_device(); /* 申请 input_dev */
inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */

/*********第一种设置事件和事件值的方法***********/
__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
__set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
/************************************************/

/*********第二种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
/************************************************/

/*********第三种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
/************************************************/

/* 注册 input_dev */
input_register_device(inputdev);

上报数据

上报指定的事件以对应值:

1
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
  • dev:需要上报的 input_dev
  • type:上报的事件类型,比如 EV_KEY
  • code:事件码,即注册的按键值,比如 KEY_0、 KEY_1 等等
  • value:事件值,比如 1 表示按键按下, 0 表示按键松开

专用上报函数(本质是调用input_event):

1
2
3
4
5
6
void input_report_key(struct input_dev *dev, unsigned int code, int value)//上报按键
void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)

同步事件:

1
void input_sync(struct input_dev *dev)

应用层相关

应用层通过获得 input_event 结构体来获取input子系统发送的输入事件

1
2
3
4
5
6
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
  • time:时间,即此事件发生的时间,timeval 结构体类型:

    1
    2
    3
    4
    5
    6
    7
    8
    typedef long __kernel_long_t;
    typedef __kernel_long_t __kernel_time_t;
    typedef __kernel_long_t __kernel_suseconds_t;

    struct timeval {
    __kernel_time_t tv_sec; /* 秒 */
    __kernel_suseconds_t tv_usec; /* 微秒 */
    };
  • type: 事件类型,比如 EV_KEY 表示此次事件为按键事件

  • code: 事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如: KEY_0、KEY_1等

  • value:值,比如 EV_KEY 事件中 value 就是按键值,为 1 表示按键按下,为 0 的话表示按键没有被按下

应用层读取方式:

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
static struct input_event inputevent;
int err = 0;
err = read(fd, &inputevent, sizeof(inputevent));
if (err > 0) { /* 读取数据成功 */
switch (inputevent.type) {
case EV_KEY:
if (inputevent.code < BTN_MISC) { /* 键盘键值 */
printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
} else {
printf("button %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
}
break;
/* 其他类型的事件,自行处理 */
case EV_REL:
break;
case EV_ABS:
break;
case EV_MSC:
break;
case EV_SW:
break;
}
} else {
printf("读取数据失败\r\n");
}

多点触摸

  • linux内核中讲解多点电容触摸屏协议文档路径:Documentation/input/multitouch-protocol.txt
  • 老版本(2.x 版本)的 linux内核不支持多点电容触摸(Multi-touch,简称 MT)

MT 协议分为两种类型:

  • Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少)
  • Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个触摸点的信息,一般的多点电容触摸屏 IC 都有此能力
Type A

步骤(时序)如下:

1
2
3
4
5
6
7
8
9
ABS_MT_POSITION_X x[0]//上报第一个点的x坐标 input_report_abs()
ABS_MT_POSITION_Y y[0]//上报第一个点的y坐标
SYN_MT_REPORT// input_mt_sync()

ABS_MT_POSITION_X x[1]//上报第二个点的x坐标 input_report_abs()
ABS_MT_POSITION_Y y[1]//上报第二个点的y坐标
SYN_MT_REPORT// input_mt_sync()

SYN_REPORT// input_sync() 该轮数据发送完毕

例子:drivers/input/touchscreen/st1232.c

Type B

步骤(时序)如下:

1
2
3
4
5
6
7
8
9
10
11
ABS_MT_SLOT 0// input_mt_slot()
ABS_MT_TRACKING_ID 45// input_mt_report_slot_state()
ABS_MT_POSITION_X x[0]//上报第一个点的x坐标 input_report_abs()
ABS_MT_POSITION_Y y[0]//上报第一个点的y坐标

ABS_MT_SLOT 1// input_mt_slot()
ABS_MT_TRACKING_ID 46// input_mt_report_slot_state()
ABS_MT_POSITION_X x[1]//上报第二个点的x坐标 input_report_abs()
ABS_MT_POSITION_Y y[1]//上报第二个点的y坐标

SYN_REPORT// input_sync() 该轮数据发送完毕

例子:drivers/input/touchscreen/ili210x.c

相关函数:

初始化 MT 的输入 slots(初始化时使用):

1
int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots, unsigned int flags)
  • dev: MT 设备对应的 input_dev,因为 MT 设备隶属于 input_dev

  • num_slots:设备要使用的 SLOT 数量,也就是触摸点的数量

  • flags: 其他一些 flags 信息

    1
    2
    3
    4
    5
    #define INPUT_MT_POINTER 0x0001 /* pointer device, e.g. trackpad */
    #define INPUT_MT_DIRECT 0x0002 /* direct device, e.g. touchscreen */
    #define INPUT_MT_DROP_UNUSED0x0004 /* drop contacts not seen in frame */
    #define INPUT_MT_TRACK 0x0008 /* use in-kernel tracking */
    #define INPUT_MT_SEMI_MT 0x0010 /* semi-mt device, finger count handled manually */
  • 返回值: 0,成功;负值,失败

产生 ABS_MT_SLOT 事件:

1
static inline void input_mt_slot(struct input_dev *dev, int slot)
  • dev: MT 设备对应的 input_dev
  • slot:当前发送的是哪个 slot 的坐标信息,也就是哪个触摸点

产生ABS_MT_TRACKING_ID和ABS_MT_TOOL_TYPE事件:

1
void input_mt_report_slot_state(struct input_dev *dev, unsigned int tool_type, bool active)
  • dev: MT 设备对应的 input_dev
  • tool_type:上报触摸工具类型(即ABS_MT_TOOL_TYPE事件),目前的协议支持MT_TOOL_FINGER(手指)、 MT_TOOL_PEN(笔)和 MT_TOOL_PALM(手掌)
  • active:true,连续触摸,添加一个新的触摸点,linux 内核自动分配一个 ABS_MT_TRACKING_ID;false,触摸点抬起,移除一个触摸点,ABS_MT_TRACKING_ID由内核设为-1

上传真实的触摸点数量:

1
void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
  • dev: MT 设备对应的 input_dev
  • use_count:true,有效的触摸点数量(上报数量就是当前数模点数量); false,追踪到的触摸点数量多于当前上报的数量(使用 BTN_TOOL_TAP 事件通知用户空间当前追踪到的触摸点总数量)
  • 举个例子:硬件能够追踪5个触摸点,无论是否有触摸,硬件都会有5个值输出,此时use_count就是false,即无论触摸的数量为多少,追踪到的(硬件输出的5个值)总比上报的(真实触摸数量)多

例子:

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
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
......
/* 读取FT5X06触摸点坐标从0X02寄存器开始,连续读取29个寄存器 */
ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);

/* 上报每一个触摸点坐标 */
for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
u8 *buf = &rdbuf[i * tplen + offset];

/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:
* bit7:6 Event flag 0:按下 1:释放 2:接触 3:没有事件
* bit5:4 保留
* bit3:0 X轴触摸点的11~8位。
*/
type = buf[0] >> 6; /* 获取触摸类型 */
if (type == TOUCH_EVENT_RESERVED)
continue;

/* 我们所使用的触摸屏和FT5X06是反过来的 */
x = ((buf[2] << 8) | buf[3]) & 0x0fff;
y = ((buf[0] << 8) | buf[1]) & 0x0fff;

/* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:
* bit7:4 Touch ID 触摸ID,表示是哪个触摸点
* bit3:0 Y轴触摸点的11~8位。
*/
id = (buf[2] >> 4) & 0x0f;
down = type != TOUCH_EVENT_UP;//是否按下 1:按下 0:松开

input_mt_slot(multidata->input, id);
input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);

if (!down)
continue;

input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
}

input_mt_report_pointer_emulation(multidata->input, true);
input_sync(multidata->input);
......
}

Framebuffer

fb_info结构体:

1
2
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
void framebuffer_release(struct fb_info *info)
  • size:分配完framebuffer结构体后附加的额外空间(一般用于存放用户私有数据)
  • dev:最终会绑定到fb_info->device上,可以设为NULL

注册与卸载:

1
2
int register_framebuffer(struct fb_info *fb_info)
int unregister_framebuffer(struct fb_info *fb_info)

显存分配与释放:

1
2
static inline void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *dma_addr, gfp_t gfp)
static inline void dma_free_writecombine(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_addr)

RTC

申请并注册rtc_device:

1
struct rtc_device *rtc_device_register(const char *name, struct device *dev, const struct rtc_class_ops *ops, struct module *owner)
  • name:设备名字
  • dev: 设备
  • ops: RTC 底层驱动函数集
  • owner:驱动模块拥有者

注销rtc_device:

1
void rtc_device_unregister(struct rtc_device *rtc)

IIC

适配器注册和注销:

一般不会用到,SOC厂商会写好这部分代码

1
2
3
4
5
//注册
int i2c_add_adapter(struct i2c_adapter *adapter)/* 使用动态总线号 */
int i2c_add_numbered_adapter(struct i2c_adapter *adap)/* 使用静态总线号 */
//注销
void i2c_del_adapter(struct i2c_adapter * adap)

i2c驱动注册与注销:

1
2
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
void i2c_del_driver(struct i2c_driver *driver)

iic通信

内核驱动

内核文档:Documentation\i2c\i2c-protocolDocumentation\i2c\smbus-protocol

其中smbus-protocol是i2c-protocol的一个子集,官方更加推荐使用后者的smbus-protoco中的函数

收发函数:

1
2
//S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
  • adap: 所使用的 I2C 适配器, i2c_client 会保存其对应的 i2c_adapter

  • msgs: I2C 要发送的一个或多个消息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct i2c_msg {
    __u16 addr; /* 从机地址 */
    __u16 flags; /* 标志 */
    #define I2C_M_TEN 0x0010
    #define I2C_M_RD 0x0001
    #define I2C_M_STOP 0x8000
    #define I2C_M_NOSTART 0x4000
    #define I2C_M_REV_DIR_ADDR 0x2000
    #define I2C_M_IGNORE_NAK 0x1000
    #define I2C_M_NO_RD_ACK 0x0800
    #define I2C_M_RECV_LEN 0x0400
    __u16 len; /* 消息(本 msg)长度 */
    __u8 *buf; /* 消息数据 */
    };
  • num: 消息数量,即 msgs 的数量

  • 返回值: 负值,失败,其他非负值,发送的 msgs 数量

发送函数(最终调用i2c_transfer):

1
2
//S Addr Wr [A] Data [A] Data [A] ... [A] Data [A] P
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
  • client: I2C 设备对应的 i2c_client
  • buf:要发送的数据
  • count: 要发送的数据字节数,必须小于 64KB(i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据)
  • 返回值: 负值,失败,其他非负值,发送的字节数

接收函数(最终调用i2c_transfer):

1
2
//S Addr Rd [A] [Data] A [Data] A ... A [Data] NA P
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
  • client: I2C 设备对应的 i2c_client
  • buf:要接收的数据
  • count: 要接收的数据字节数,必须小于 64KB(i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据)
  • 返回值: 负值,失败,其他非负值,发送的字节数

smbus-protoco中的函数:

1
2
3
4
5
6
7
8
9
10
i2c_smbus_read_byte()//S Addr Rd [A] [Data] NA P
i2c_smbus_write_byte()//S Addr Wr [A] Data [A] P
i2c_smbus_read_byte_data()//S Addr Wr [A] Comm [A] S Addr Rd [A] [Data] NA P
i2c_smbus_read_word_data()//S Addr Wr [A] Comm [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P
i2c_smbus_write_byte_data()//S Addr Wr [A] Comm [A] Data [A] P
i2c_smbus_write_word_data()//S Addr Wr [A] Comm [A] DataLow [A] DataHigh [A] P
i2c_smbus_read_block_data()//S Addr Wr [A] Comm [A] S Addr Rd [A] [Count] A [Data] A [Data] A ... A [Data] NA P
i2c_smbus_write_block_data()//S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P
i2c_smbus_read_i2c_block_data()//S Addr Wr [A] Comm [A] S Addr Rd [A] [Data] A [Data] A ... A [Data] NA P
i2c_smbus_write_i2c_block_data()//S Addr Wr [A] Comm [A] Data [A] Data [A] ... [A] Data [A] P
应用层直接访问

内核文档:Documentation\i2c\dev-interface

通常,I2C设备由设备驱动来控制,但是通过/dev接口也可以提供用户空间直接访问适配器上的设备。前提是需要加载I2C-DEV内核模块

在应用层,i2c-tools工具包帮你写好了接口,下载链接镜像仓库,只需要包含该库的头文件include\linux\i2c-dev.h即可直接控制iic(最新版本的4.1没有发现该文件)。其本质是调用的内核自带的驱动模块I2C-DEV。该模块位于\drivers\i2c\i2c-dev.c,通过宏CONFIG_I2C_CHARDEV配置,内核menuconfig路径为Device Drivers-> I2C support,一般默认为通过模块加载

i2c-tools工具包的本质是通过调用ioctl打开实现各种功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//include\linux\i2c-dev.h部分源码
static inline __s32 i2c_smbus_access(int file, char read_write, __u8 command, int size, union i2c_smbus_data *data)
{
struct i2c_smbus_ioctl_data args;

args.read_write = read_write;
args.command = command;
args.size = size;
args.data = data;
return ioctl(file,I2C_SMBUS,&args);//本质就是调用ioctl
}


static inline __s32 i2c_smbus_write_quick(int file, __u8 value)
{
return i2c_smbus_access(file,value,0,I2C_SMBUS_QUICK,NULL);
}

上述部分源码中file就是需要访问的iic控制器,如:/dev/i2c-0,与自己写的驱动程序类似

模板

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
62
63
64
65
66
/* 设备结构体 */
struct xxx_dev {
......
void *private_data; /* 私有数据,一般会设置为 i2c_client */
};

/*
* @description : 读取 I2C 设备多个寄存器数据
* @param – dev : I2C 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;

/* msg[0],第一条写消息,发送要读取的寄存器首地址 */
msg[0].addr = client->addr; /* I2C 器件地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = &reg; /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */

/* msg[1],第二条读消息,读取寄存器数据 */
msg[1].addr = client->addr; /* I2C 器件地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度 */
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}

/*
* @description : 向 I2C 设备多个寄存器写入数据
* @param – dev : 要写入的设备结构体
* @param – reg : 要写入的寄存器首地址
* @param – val : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)
dev->private_data;

b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */

msg.addr = client->addr; /* I2C 器件地址 */
msg.flags = 0; /* 标记为写数据 */

msg.buf = b; /* 要发送的数据缓冲区 */
msg.len = len + 1; /* 要发送的数据长度 */

return i2c_transfer(client->adapter, &msg, 1);
}

SPI

spi_master注册和注销:

一般不会用到,SOC厂商会写好这部分代码

1
2
3
4
5
6
//注册
struct spi_master *spi_alloc_master(struct device *dev,unsigned size)//申请
int spi_register_master(struct spi_master *master)//注册 spi_bitbang_start
//注销
void spi_master_put(struct spi_master *master)//释放
void spi_unregister_master(struct spi_master *master)//注销 spi_bitbang_stop

spi驱动注册与注销:

1
2
int spi_register_driver(struct spi_driver *sdrv);
void spi_unregister_driver(struct spi_driver *sdrv)

相关结构体:

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
/*-----------------------spi_message-----------------------*/
struct spi_message {
    struct list_head    transfers;
    struct spi_device   *spi;
    unsigned        is_dma_mapped:1;
    /* REVISIT:  we might want a flag affecting the behavior of the
     * last transfer ... allowing things like "read 16 bit length L"
     * immediately followed by "read L bytes".  Basically imposing
     * a specific message scheduling algorithm.
     *
     * Some controller drivers (message-at-a-time queue processing)
     * could provide that as their default scheduling algorithm.  But
     * others (with multi-message pipelines) could need a flag to
     * tell them about such special cases.
     */
    /* completion is reported through a callback */
    void            (*complete)(void *context);/*异步传输完成后,会调用该函数*/
    void            *context;
    unsigned        frame_length;
    unsigned        actual_length;
    int         status;
    /* for optional use by whatever driver currently owns the
     * spi_message ...  between calls to spi_async and then later
     * complete(), that's the spi_master controller driver.
     */
    struct list_head    queue;
    void            *state;
};
/*-----------------------spi_transfer-----------------------*/
struct spi_transfer {
    /* it's ok if tx_buf == rx_buf (right?)
     * for MicroWire, one buffer must be null
     * buffers must work with dma_*map_single() calls, unless
     *   spi_message.is_dma_mapped reports a pre-existing mapping
     */
    const void  *tx_buf;/* 要发送的数据 */
    void        *rx_buf;/* 保存接收到的数据 */
    unsigned    len;/* 进行传输的数据长度 */
    dma_addr_t  tx_dma;
    dma_addr_t  rx_dma;
    struct sg_table tx_sg;
    struct sg_table rx_sg;
    unsigned    cs_change:1;
    unsigned    tx_nbits:3;
    unsigned    rx_nbits:3;
#define SPI_NBITS_SINGLE    0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL      0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD      0x04 /* 4bits transfer */
    u8      bits_per_word;
    u16     delay_usecs;
    u32     speed_hz;
    struct list_head transfer_list;
};

spi通信

初始化:

1
2
3
int spi_setup(struct spi_device *spi)//初始化时钟和SPI模式
void spi_message_init(struct spi_message *m)
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)//将spi_transfer添加到spi_message队列中

同步传输(阻塞,会等待SPI数据传输完成)

1
int spi_sync(struct spi_device *spi, struct spi_message *message)

异步传输(不会阻塞,不会等到SPI数据传输完成)

异步传输需要设置 spi_message 中的complete成员变量,当 SPI 异步传输完成以后complete函数就会被调用

1
int spi_async(struct spi_device *spi, struct spi_message *message)

模板

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
/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message m;
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
};
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */
ret = spi_sync(spi, &m); /* 同步传输 */
return ret;
}
/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message m;
struct spi_transfer t = {
.rx_buf = buf,
.len = len,
};
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */
ret = spi_sync(spi, &m); /* 同步传输 */
return ret;
}

PWM

设备树绑定内核参考文档:Documentation/devicetree/bindings/pwm/imx-pwm.txt

设备树

通用

查找节点

  1. 通过名字查找

    1
    struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
    • from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    • name:要查找的节点名字(不是table和name属性)。
    • 返回值: 找到的节点,如果为 NULL 表示查找失败。
  2. 通过device_type 属性查找

    1
    struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
    • from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    • type:要查找的节点对应的 type 字符串,即 device_type 属性值。
    • 返回值: 找到的节点,如果为 NULL 表示查找失败。
  3. 根据 device_type 和 compatible查找

    1
    struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
    • from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    • type:要查找的节点对应的 type 字符串,即 device_type 属性值(若为 NULL则表示忽略 device_type 属性)
    • compatible: 要查找的节点所对应的 compatible 属性列表。
    • 返回值: 找到的节点,如果为 NULL 表示查找失败
  4. 通过 of_device_id 匹配表来查找

    1
    struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)
    • from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    • matches: of_device_id 匹配表,也就是在此匹配表里面查找节点。
    • match: 找到的匹配的 of_device_id。
    • 返回值: 找到的节点,如果为 NULL 表示查找失败
  5. 通过路径查找

    1
    inline struct device_node *of_find_node_by_path(const char *path)
    • path:带有全路径的节点名,可以使用节点的别名。
    • 返回值: 找到的节点,如果为 NULL 表示查找失败
  6. 查找指定节点的父节点

    1
    struct device_node *of_get_parent(const struct device_node *node)
    • node:要查找的父节点的节点。
    • 返回值: 找到的父节点。
  7. 查找指定节点的子节点

    1
    struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
    • node:父节点。
    • prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
    • 返回值: 找到的下一个子节点。

提取属性

  1. 查找节点中的指定属性

    1
    property *of_find_property(const struct device_node *np, const char *name, int *lenp)
    • np:设备节点。
    • name: 属性名字。
    • lenp:属性值的字节数,一般为NULL
    • 返回值: 找到的属性。
  2. 获取属性中元素的数量

    1
    int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
    • np:设备节点。
    • proname: 需要统计元素数量的属性名字。
    • elem_size:每个元素的长度。(如果元素为u32类型则此处填sizeof(u32))
    • 返回值: 得到的属性元素数量。
  3. 从属性中获取指定标号的 u32 类型数据值

    1
    int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
    • np:设备节点。
    • proname: 要读取的属性名字。
    • index:要读取的值标号。
    • out_value:读取到的值
    • 返回值: 0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。
  4. 读取属性中 u8、 u16、 u32 和 u64 类型的数组数据

    1
    2
    3
    4
    int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
    int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
    int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
    int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
    • np:设备节点。
    • proname: 要读取的属性名字。
    • out_values:读取到的数组值,分别为 u8、 u16、 u32 和 u64。
    • sz: 要读取的数组元素数量。
    • 返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。
  5. 读取只有一个整形值的属性

    1
    2
    3
    4
    int of_property_read_u8(const struct device_node *np,const char *propname, u8 *out_value)
    int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value)
    int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)
    int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value)
    • np:设备节点。
    • proname: 要读取的属性名字。
    • out_value:读取到的数组值。
    • 返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。
  6. 读取属性中字符串值

    1
    int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)
    • np:设备节点。
    • proname: 要读取的属性名字。
    • out_string:读取到的字符串值。
    • 返回值: 0,读取成功,负值,读取失败。
  7. 获取#address-cells 属性值

    1
    int of_n_addr_cells(struct device_node *np)
    • np:设备节点。
    • 返回值: 获取到的#address-cells 属性值。
  8. 获取#size-cells 属性值

    1
    int of_n_size_cells(struct device_node *np)
    • np:设备节点。
    • 返回值: 获取到的#size-cells 属性值。

其他常用函数

  1. 查看节点的 compatible 属性是否有包含指定的字符串

    1
    int of_device_is_compatible(const struct device_node *device, const char *compat)
    • device:设备节点。
    • compat:要查看的字符串。
    • 返回值: 0,节点的 compatible 属性中不包含 compat 指定的字符串; 正数,节点的compatible属性中包含 compat 指定的字符串。
  2. 获取地址相关属性

    主要是“reg”或者“assigned-addresses”属性值

    1
    const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags)
    • dev:设备节点。
    • index:要读取的地址标号。
    • size:地址长度。
    • flags:参数,比如 IORESOURCE_IO、 IORESOURCE_MEM 等
    • 返回值: 读取到的地址数据首地址,为 NULL 的话表示读取失败。
  3. 将从设备树读取到的地址转换为物理地址

    1
    u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
    • dev:设备节点。
    • in_addr:要转换的地址。
    • 返回值: 得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。
  4. 从设备树里面提取资源值

    本质上是将 reg 属性值转换为 resource 结构体类型

    1
    int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
    • dev:设备节点。
    • index:地址资源标号。
    • r:得到的 resource 类型的资源值。
    • 返回值: 0,成功;负值,失败。
  5. 直接内存映射(获取内存地址所对应的虚拟地址 )

    本质上是将 reg 属性中地址信息转换为虚拟地址(将原来的先提取属性在映射结合起来),如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是哪一段

    1
    void __iomem *of_iomap(struct device_node *np, int index)
    • np:设备节点。
    • index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为0。(从0开始,一次映射一对,即一个地址一个长度)
    • 返回值: 经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。

GPIO子系统

of函数

  1. 获取设备树某个属性中定义 GPIO 的个数(空的 GPIO 信息(即值为0)也会被统计到)

    1
    int of_gpio_named_count(struct device_node *np, const char *propname)
    • np:设备节点。
    • propname:要统计的 GPIO 属性。
    • 返回值: 正值,统计到的 GPIO 数量;负值,失败。
  2. 获取设备树gpios属性中定义 GPIO 的个数(空的 GPIO 信息(即值为0)也会被统计到)

    1
    int of_gpio_count(struct device_node *np)
  3. 获取 GPIO 编号

    1
    int of_get_named_gpio(struct device_node *np, const char *propname, int index)
    • index: GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO 的编号,如果只有一个 GPIO 信息的话此参数为 0

驱动层函数

  1. 申请GPIO

    1
    int gpio_request(unsigned gpio, const char *label)
    • gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数返回值
    • label:给 gpio 设置个名字。
    • 返回值: 0,申请成功;其他值,申请失败。
  2. 释放GPIO

    1
    void gpio_free(unsigned gpio)
  3. 设置方向

    1
    2
    int gpio_direction_input(unsigned gpio)
    int gpio_direction_output(unsigned gpio, int value)
    • 返回值: 0,设置成功;负值,设置失败
  4. 设置值

    1
    2
    #define gpio_get_value __gpio_get_value
    int __gpio_get_value(unsigned gpio)
    • 返回值: 非负值,得到的 GPIO 值;负值,获取失败
  5. 获取值

    1
    2
    #define gpio_set_value __gpio_set_value
    void __gpio_set_value(unsigned gpio, int value)
  6. 获取 gpio 对应的中断号

    1
    int gpio_to_irq(unsigned int gpio)
    • gpio: 要获取的 GPIO 编号
    • 返回值: GPIO 对应的中断号

中断相关

提取 interrupts 属性中的中断号

1
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
  • dev: 设备节点
  • index:索引号, interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息
  • 返回值:中断号

获取 gpio 对应的中断号(与上面的函数功能一样)

1
int gpio_to_irq(unsigned int gpio)
  • gpio: 要获取的 GPIO 编号,由gpio_request申请而来
  • 返回值: GPIO 对应的中断号