Linux Device Driver Development的笔记, 和LDD3有点区别, 这本比较偏应用. 看完了, 慢慢补笔记. 可惜没讲Block Device.
Linux Driver Development
Intro & Basis
内核配置和编译
配置: make ARCH=<arch> menuconfig/<defconfig>
编译: make -j<cores-1> ARCH=<arch> CROSS_COMPILE=<gcc_prefix>
lint: script/gen_compile_commands.py
编译模块: make modules -j<cores-1> ARCH=<arch> CROSS_COMPILE=<gcc_prefix> && make INSTALL_MOD_PATH=out modules_install
Hello World
helloworld.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int __init helloworld_init(void){
printk("Hello World: init");
};
static void __exit helloworld_exit(void){
printk("Hello World: exit");
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_AUTHOR("IgaYuka <chino@igayuka.moe>")
MODULE_DESCRIPTION("Hello, world!");
MODULE_LICENSE("GPL")
makefile
1
2
3
4
5obj-m := helloworld.o
KERNELDIR ?= ../torvalds/linux
all default: modules
modules help clean:
$(MAKE) -c $(KERNELDIR) M=$(shell pwd) $@
take a look at include/uapi/asm-generic/errno-base.h
声明: EXPORT_SYMBOL()
参数: module_param(name, type, perm);
Kernel Facilities and Helper Functions
Useful Macros
container_of(pointer, container_type, container_field);
: 通过成员指针取得结构体指针
Linked lists
双向链表. #include <linux/list.h>
1 | struct list_head {struct list_head *next, *prev;}; |
Delay and timer
Standard Timer
#include <linux/timer.h>
标准定时器用内核时间单位jiffy定义时间. 32位系统默认使用32位jiffies, 可通过jiffies_64访问64位jiffies. 64位系统默认使用64位jiffies.
1 | struct timer_list { |
note: expires
经常使用jiffies + msecs_to_jiffies(<ms>)
这种形式.
del_timer_sync
用于等待定时器停止.
mod_timer
可以在回调中重新激活自身的Timer.
回调函数的unsigned long
实际类型为struct timer_list*
High Resolution timers
#include <linux/hrtimer.h>
, CONFIG_HIGH_RES_TIMERS=y
高分辨率时钟.
1 | struct hrtimer { |
attention: HRT API是GPL协议的, 需要声明模块协议为GPL. 想规避的话还是自己写定时器驱动好一点.
note: absolute time
会随系统时间变化而变化.
回调函数实际返回类型是enum hrtimer_restart
, 只有两个值HRTIMER_NORESTART
, HRTIMER_RESTART
.
Delay
延时, 依靠了上面的两个定时器.
分ndelay
, udelay
, mdelay
.
一般用udelay
. ndelay
, mdelay
一般精度不够.
udelay
一般用于10us以下的延时. 循环延时.
usleep_range
一般用于10us~20us的延时. 依靠hrt延时
msleep
一般用于10ms以上的延时. 依靠标准定时器延时.
Kernel locking machanism
Mutex
互斥锁. #include <linux/mutex.h>
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
35struct mutex {
atomic_long_t owner;
spinlock_t wait_lock;
struct optimistic_spin_queue osq; /* Spinner MCS lock */
struct list_head wait_list;
void *magic;
struct lockdep_map dep_map;
};
// Statically Declare
struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)
// Dynamically Declare
struct mutex my_mutex;
mutex_init(&my_mutex);
do { \
static struct lock_class_key __key; \
__mutex_init((mutex), #mutex, &__key); \
} while (0)
// Lock
void mutex_lock(struct mutex *lock);
int mutex_lock_interruptible(struct mutex *lock);
int mutex_lock_killable(struct mutex *lock);
// Unlock
void mutex_unlock(struct mutex *lock);
// check
int mutex_is_locked(struct mutex *lock);
// try
int mutex_trylock(struct mutex *lock);
note: mutex_lock
, mutex_trylock
在等待时不中断程序运行, 相当于自旋锁.
mutex_lock_interruptable
在等待时可以被炒作系统中断
mutex_lock_killable
只在收到信号时中断.
mutex_unlock
不能在中断中使用.
Spinlock
自旋锁. #include <linux/spinlock.h>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16spinlock_t my_spinlock;
spin_lock_init(my_spinlock);
// lock
// unlock
// lock during irq
do { \
raw_spin_lock_irqsave(spinlock_check(lock), flags); \
} while (0)
// unlock during irq
do { \
raw_spin_lock_irqsave(spinlock_check(lock), flags); \
} while (0)
note: do{...}while(0)
可以使宏不管怎么用总是正确的(主要是简化的if).
暂时没搞明白为什么要#define和__always_inline混用.
自旋锁保持CPU占用.
ps: 搞错了(x, __always_inline定义原型函数.
versus
各有优点.
互斥锁保护关键资源, 自旋锁保护IRQ关键部分.
互斥锁可以进入sleep状态, 自旋锁保持CPU占用.
不可以长时间持有自旋锁. 但是可以长时间持有互斥锁.
Softirq
软中断. 很少直接使用. 一般只有网络设备和块设备直接使用softirq.
仅用于非常快速的处理.
Kernel Task Management
Tasklets
tasklets经常用于bottom-half, 依靠softirqs进行工作.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
// Dynamically Declare
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
// Statically Declare
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
// enable a tasklet
void tasklet_enable(struct tasklet_struct *);
// disable a tasklet
void tasklet_disable(struct tasklet_struct *);
void tasklet_disable_nosync(struct tasklet_struct *);
// scheduling
void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);
// stopping
void tasklet_kill(struct tasklet_struct *t);
note: tasklet_disable
等待tasklet终止执行. tasklet_disable_nosync
不等待tasklet终止执行.
tasklet_hi_schedule
创建的任务优先级较高.
work queues
任务队列. #include <linux/workqueue.h>
使用系统预定义的队列struct workqueue_struct *system_wq __read_mostly
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
31struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
struct lockdep_map lockdep_map;
};
struct delayed_work {
struct work_struct work;
struct timer_list timer;
/* target workqueue and CPU ->timer uses to queue ->work */
struct workqueue_struct *wq;
int cpu;
};
// ties the work on the current CPU
int schedule_work(struct work_struct *work);
// same but delayed function
static inline bool schedule_delayed_work(struct delayed_work *dwork,
unsigned long delay)
// actually schedules the work on a given CPU
int schedule_work_on(int cpu, struct work_struct *work);
// same but delayed function
int scheduled_delayed_work_on(int cpu, struct delayed_work *dwork, unsigned long delay)
// cancle work
extern bool cancel_work_sync(struct work_struct *work);
extern bool cancel_delayed_work(struct delayed_work *dwork);
extern bool cancel_delayed_work_sync(struct delayed_work *dwork);
// flush work
extern bool flush_work(struct work_struct *work);
自建队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/* declare work and work queue */
struct workqueue_struct *myqueue;
struct work_struct thework;
// Define the worker function (the handler):
void dowork(void *data) { /* Code goes here */ };
// Initialize our work queue and embed our work into:
myqueue = create_singlethread_workqueue( "mywork" );
INIT_WORK( &thework, dowork, <data-pointer> );
// Scheduling work
queue_work(myqueue, &thework);
queue_dalayed_work(myqueue, &thework, <delay>);
// Wait on all pending work on the given work queue:
void flush_workqueue(struct workqueue_struct *wq)
// Cleanup
int cancel_work_sync(struct work_struct *work);
int cancel_delayed_work_sync(struct delayed_work *dwork);
// destory
extern void destroy_workqueue(struct workqueue_struct *wq);
自建队列与系统预定义队列操作的差异:
|预定义队列|自建队列|ps|
|:——-:|:——:|:-:|
schedule_work(w)|queue_work(keventd_wq,w)|on current CPU
schedule_delayed_work(w,d)|queue_delayed_work(keventd_wq,w,d)|on any CPU
schedule_delayed_work_on(cpu,w,d)|queue_delayed_work(keventd_wq,w,d)|on a given CPU
flush_scheduled_work()|flush_workqueue(keventd_wq)|
note: delay
使用的时间单位为jiffy
Kernel interruption mechanism
interrupt basis
#include <linux/interrupt.h>
take a look at linux/interrupt.h:38
/ IRQF_*
1
2
3
4
5
6// Registering an interrupt handler
typedef irqreturn_t (*irq_handler_t)(int, void *);
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags, const char *name, void *dev)
// Unregist
void free_irq(unsigned int irq, void *dev)
Character Devices
字符设备, 在/dev目录下建立文件, 通过文件读写与驱动交互.
1 |
|
appendix: struct file_operations.
1 | struct file_operations { |
file_operation 结构是一个字符驱动如何建立这个连接.
struct module *owner
, 指向拥有该文件的模块指针.
FLAGS: FOPS
Platform Devices
driver / device
当Driver和Device同时在总线注册时会调用Driver的Probe函数. Device可以由设备树给出, 也可以由ACPI给出(热插拔).
OF Style / ACPI style
OF匹配表一般用于设备树固定与板子上的设备. ACPI匹配表一般用于热插拔设备.
Device Tree Basis
暂时没想好写什么(x
I2C Client
1 | struct i2c_driver { |
note: OF表一个probe就够了. ACPI表一般提供remove用于清理.
client一般由I2C驱动程序给出
1 | struct i2c_client { |
设备独有数据
1 | /* set the data */ |
i2c驱动声明
1 | struct i2c_device_id { |
note: MODULE_DEVICE_TABLE
最后一项必须为空标识匹配表结束.
通信
1 | /* basic */ |
SMBus(System Management Bus) 兼容性
SMBus兼容I2C设备, SMBus是由Intel开发的.
1 | s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command); |
board configuration file
过时了.
设备树I2C节点
1 | &i2c2 { /* Phandle of the bus node */ |
OF, ID表不是必须的, 但是保险起见还是两个一起写比较好(指的同型号厂商不同
SPI Device
Regmap
1 | struct regmap_config { |