linux 驱动开发常用知识点与API

linux 驱动开发常用知识点与API

前言

之前的读书笔记,以.c 文件的方式记录,在这里也以代码的方式记录

笔记正文

/***************************************中断常用API****************************************/
/*
flags 是中断处理的属性,若设置了 SA_INTERRUPT,则表示中断处理程序
是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若
设置了 SA_SHIRQ,则表示多个设备共享中断,dev_id 在中断共享时会用到,一般
设置为这个设备的设备结构体或者 NULL

request_irq()返回 0 表示成功,返回-INVAL 表示中断号无效或处理函数指针为
NULL,返回-EBUSY 表示中断已经被占用且不能共享
*/

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}


const void *free_irq(unsigned int irq, void *dev_id);


//下面对系统所有cpu生效
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq); //等待目前的中断处理完成
void enable_irq(unsigned int irq);


// 以local_开头的方法的作用范围是本 CPU 内
//屏蔽本 CPU 内的所有中断
void local_irq_save(unsigned long flags); //将目前的中断状态保留在 flags 中
void local_irq_disable(void);
//与之对应的恢复中断
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);



/*********************tasklet 的使用*****************/
void my_tasklet_func(unsigned long); /*定义一个处理函数*/
/*定义一个 tasklet 结构 my_tasklet,与 my_tasklet_func(data)函数相关联*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); //data 是前面long的参数

tasklet_schedule(&my_tasklet); //调度 tasklet ,在中顶半部(中断注函数可调用)



/*********************工作队列的使用*****************/
struct work_struct my_wq; /*定义一个工作队列*/
void my_wq_func(unsigned long); /*定义一个处理函数*/

/*初始化工作队列并将其与处理函数绑定*/
INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL);

/*调度工作队列执行,放在中断服务程序中调用*/
schedule_work(&my_wq);



/*********************定时器*****************/
struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct hlist_node	entry; //定时器列表
	unsigned long		expires; //定时器到期时间
	void			(*function)(struct timer_list *); //定时器处理函数
	u32			flags; //作为参数被传入定时器处理函数

#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
};


struct timer_list my_timer; //定义一个定时器
timer_setup(timer, callback, flags); //4 点几版的本内核可以用这个宏来初始化内核
DEFINE_TIMER(name,callback_func);//也可以使用这个来定义一个定时器

void add_timer(struct timer_list *timer); 
int mod_timer(struct timer_list *timer, unsigned long expires); //修改定时器
int del_timer(struct timer_list *timer); //删除定时器

//相互转换
msecs_to_jiffies;
usecs_to_jiffies;


/*********************内核延时*****************/
//下面三个延迟的实现原理本质上是忙等待,它根据 CPU 频率进行一定次数的循环
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);


//上述ms 级延时不推荐使用,对CPU影响很大,可以用下面ms级的
//下面函数将使得调用它的进程睡眠参数指定的时间,msleep()、ssleep()不能被打断,而 msleep_interruptible()则可以被打断
void msleep(unsigned int millisecs);

/*如果你的驱动位于一个等待队列并且你想唤醒来打断睡眠, 
使用 msleep_interruptible. 从 msleep_interruptible 的返回值正常地是 0; 如果这个进程被提早唤醒,
返回值是在初始请求睡眠周期中剩余的毫秒数. 对 ssleep 的调用使进程进入一个不可中断的睡眠给定的秒数.
*/
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);

#define time_before(a,b)	time_after(b,a)
time_after(a,b);
/*延迟 100 个 jiffies*/
unsigned long delay = jiffies + 100;
while (time_before(jiffies, delay)); // jiffies < delay 为真



/*
fatal_signal_pending/signal_pending()接口为内核提供用于捕捉信号接口,
一般在开发驱动中为了防止死循环卡死内核,可以使用上述接口用于捕获KILL信号
*/
signal_pending();

//msleep_interruptible 和ssleep 都依赖于schedule_timeout
signed long __sched schedule_timeout(signed long timeout); 


/*********************内存分配*****************/
/*
其他的相对不常用的申请标志还包括 
GFP_USER(用来为用户空间页分配内存,可能阻塞)、
GFP_HIGHUSER(类似 GFP_USER,但是从高端内存分配)、
GFP_NOIO(不允许任何 I/O 初始化)、
GFP_NOFS(不允许进行任何文件系统调用)、
_ _GFP_DMA(要求分配在能够 DMA 的内存区)、
_ _GFP_HIGHMEM(指示分配的内存可以位于高端内存)、
_ _GFP_COLD(请求一个较长时间不访问的页)、
_ _GFP_NOWARN(当一个分配无法满足时,阻止内核发出警告)、
_ _GFP_HIGH(高优先级请求,允许获得被内核保留给紧急状况使用的最后的内存页)、
_ _GFP_REPEAT(分配失败则尽力重复尝试)、_
_GFP_NOFAIL(标志只许申请成功,不推荐)和
_ _GFP_NORETRY(若申请不到,则立即放弃
*/

//GFP 缩写为 get free pages
/*使用 GFP_ KERNEL 标志申请内存时,若暂时不能满足,则进程会睡眠等待页,即会引起阻塞,
因此不能在中断上下文或持有自旋锁的时候使用GFP_KERNE 申请内存 
*/

//vmalloc 用于分配大量的内存,分配开销大,可能会发生睡眠。


//slab与内存池
//用于创建一个 slab 缓存
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,
		  slab_flags_t flags, void (*ctor)(void *));
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags);
void kmem_cache_free(struct kmem_cache *s, void *x); //释放 slab 缓存   先free 再 destroy
void kmem_cache_destroy(struct kmem_cache *s); //收回 slab 缓存

//下面是slab 的使用示例
/*创建 slab 缓存*/
static kmem_cache_t *xxx_cachep;
xxx_cachep = kmem_cache_create("xxx", sizeof(struct xxx),0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);


/*分配 slab 缓存*/
struct xxx *ctx;
ctx = kmem_cache_alloc(xxx_cachep, GFP_KERNEL);

/*释放 slab 缓存*/
kmem_cache_free(struct kmem_cache * s, void * x)(xxx_cachep, ctx);
kmem_cache_destroy(xxx_cachep);


//内存池的使用. mempool_create()函数用于创建一个内存池,min_nr 参数是需要预分配对象的数
//目,alloc_fn 和 free_fn 是指向内存池机制提供的标准对象分配和回收函数的指针
mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,
				mempool_free_t *free_fn, void *pool_data);
// /linux/mempool.h
typedef void * (mempool_alloc_t)(gfp_t gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);

void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask);
void mempool_free(void *element, mempool_t *pool); //回收对象
void mempool_destroy(mempool_t *pool); //回收内存池



//地址的转换 virt_to_phys   virt_to_phys 注意,上述方法仅适用于常规内存,高端内存的虚拟地址与物理地址之间不存在如此简单的换算关系


/*********************设备IO*****************/
//arm 端的代码最后调用 io-readsl.S  位于arch/arm/lib/下 
//读写字节端口(8 位宽)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
//16 
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
//32 长字节
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);

//读写一串字节
//insb()从端口 port 开始读 count 个字节端口,并将读取结果写入 addr 指向的内存
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
//16
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
//32
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);


//io 内存
//在内核中访问 I/O 内存之前,需首先使用 ioremap()函数将设备所处的物理地址映射到虚拟地址
//通过 ioremap()获得的虚拟地址应该被 iounmap()函数释放
void *ioremap(unsigned long offset, unsigned long size);
void iounmap(void * addr);

/*
在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地
址,但是可以使用 Linux 内核的如下一组函数来完成设备内存映射的虚拟地址的读写
*/
//内存映射后的操作
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);

void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);

//读一串IO
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
//写一串IO
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);

//复制IO内存
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);

//设置IO内存
void memset_io(void *addr, u8 value, unsigned int count);
//把 I/O 端口映射到内存空间
void *ioport_map(unsigned long port, unsigned int count);
void ioport_unmap(void *addr);


// I/O 端口申请 
// 这个函数向内核申请 n 个端口,这些端口从 first 开始,name 参数为设备的名称
struct resource *request_region(unsigned long first, unsigned long n,const char *name);
void release_region(unsigned long start, unsigned long n);

// I/O 内存申请
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
void release_mem_region(unsigned long start, unsigned long len);

/*
设备 I/O 端口和 I/O 内存访问流程 
	request_region (如果不映射到内存空间)
		inb,outb
	release_region	


	request_region (映射到内存空间)    / request_mem_region
		ipport_map
			ioread8,iowrite8 (注意内存映射后使用不是使用inb等方法)
		ioport_unmap
	release_region	/  release_mem_region

*/



/*将设备地址映射到用户空间
这种能力对于显示适配器一类的设备非常有意义,如果用户空间可直接通过内存
映射访问显存的话,屏幕帧的各点的像素将不再需要一个从用户空间到内核空间的复
制的过程

mmap()必须以 PAGE_SIZE 为单位进行映射,实际上,内存只能以页为单位进行映
射,若要映射非 PAGE_SIZE 整数倍的地址范围,要先进行页对齐,强行以 PAGE_SIZE
的倍数大小进行映射


当用户调用 mmap()的时候,内核会进行如下处理。
① 在进程的虚拟空间查找一块 VMA。
② 将这块 VMA 进行映射。
③ 如果设备驱动程序或者文件系统的 file_operations 定义了 mmap()操作,则调用
它。
④ 将这个 VMA 插入到进程的 VMA 链表中。

*/
//mmap()  下面是一个示例,驱动中实现
static int impd1fb_clcd_mmap(struct clcd_fb *fb, struct vm_area_struct *vma)
{
	unsigned long start, size;

	start = vma->vm_pgoff + (fb->fb.fix.smem_start >> PAGE_SHIFT);
	size = vma->vm_end - vma->vm_start;

	return remap_pfn_range(vma, vma->vm_start, start, size,
			       vma->vm_page_prot);
}


struct vm_area_struct {
	/* The first cache line has the info for VMA tree walking. */

	unsigned long vm_start;		/* Our start address within vm_mm. */
	unsigned long vm_end;		/* The first byte after our end address
					   within vm_mm. */
	.........
	struct mm_struct *vm_mm;	/* The address space we belong to. */ /* 所处的地址空间 */
	pgprot_t vm_page_prot;		/* Access permissions of this VMA. */ /* 访问权限 */
	unsigned long vm_flags;		/* Flags, see mm.h. */
	........
	/* Function pointers to deal with this struct. */
	const struct vm_operations_struct *vm_ops;

	/* Information about our backing store: */
	unsigned long vm_pgoff;		/* Offset (within vm_file) in PAGE_SIZE
					   units */
	.........
} __randomize_layout;


 //后面可以写一个示例,实现 映射 kmalloc()申请的内存到用户空间范例   待完成。。。

 /*
使用DMA 时要注意DMA 与CACHE一致性问题
解决由于 DMA导致的 Cache 一致性问题的最简单方法是直接禁止 DMA 目标地址范围内内存的 Cache 功能

DMA 的硬件使用总线地址而非物理地址,总线地址是从设备角度上看到的
内存地址,物理地址则是从 CPU 角度上看到的未经转换的内存地址(经过转换的为
虚拟地址)
*/

//内核提供了如下函数用于进行简单的虚拟地址/总线地址转换
unsigned long virt_to_bus(volatile void *address);
void *bus_to_virt(unsigned long address);

/*
设备并不一定能在所有的内存地址上执行 DMA 操作,在这种情况下应该通过下
列函数执行 DMA 地址掩码
int dma_set_mask(struct device *dev, u64 mask);
例如,对于只能在 24 位地址上执行 DMA 操作的设备而言,就应该调用dma_set_mask (dev, 0xffffff)。

DMA 映射包括两个方面的工作:分配一片 DMA 缓冲区;为这片缓冲区产生设备
可访问的地址。同时,DMA 映射也必须考虑 Cache 一致性问题。内核中提供了以下函数用于分配一个 DMA 一致性的内存区域
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t*handle, gfp_t gfp);

上述函数的返回值为申请到的 DMA 缓冲区的虚拟地址,此外,该函数还通过参数 handle 返回 DMA 缓冲区的总线地址。
handle 的类型为 dma_addr_t,代表的是总线地址。


*/
int dma_set_mask(struct device *dev, u64 mask);
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t*handle, gfp_t gfp);
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,dma_addr_t handle);

//申请和释放DMA
int request_dma(unsigned int dmanr, const char * device_id);
void free_dma(unsigned int dmanr);

//dma 操作要实际在项目代码中学习



/*********************并发控制*****************/

//原子操作
atomic_t v = ATOMIC_INIT(0); //定义原子变量 v 并初始化为 0
void atomic_set(atomic_t *v, int i); //设置原子变量的值为 i
atomic_read(atomic_t *v); //返回原子变量的值
void atomic_add(int i, atomic_t *v); //原子变量增加 i
void atomic_sub(int i, atomic_t *v); //原子变量减少 i
void atomic_inc(atomic_t *v); //原子变量增加 1
void atomic_dec(atomic_t *v); //原子变量减少 1

//对原子变量执行自增、自减和减操作后(注意没有加)测试其是否为 0,为 0 则返回 true,否则返回 false
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);

int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);

//位原子操作
void set_bit(nr, void *addr);
void clear_bit(nr, void *addr);
void change_bit(nr, void *addr);
test_bit(nr, void *addr);

//test_and_xxx_bit(nr, void *addr)操作等同于执行 test_bit(nr, void *addr)后再执行 xxx_bit(nr, void *addr)
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);

//自旋锁 基本使用
//定义一个自旋锁
spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock) ; //获取自旋锁,保护临界区
//...//临界区
spin_unlock (&lock) ; //解锁

/*
尽管用了自旋锁可以保证临界区不受别的 CPU 和本 CPU 内的抢占进程打扰,但
是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。
为了防止这种影响,就需要用到自旋锁的衍生。spin_lock()/spin_unlock()是自旋锁机
制的基础,它们和
关中断 local_irq_ disable()/开中断 local_irq_enable()、
关底半部local_bh_disable()/开底半部 local_bh_enable()、
关中断并保存状态字 local_irq_save()/开中断并恢复状态 local_irq_restore()
结合就形成了整套自旋锁机制
*/

spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_unlock() + local_irq_save()
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
spin_lock_bh() = spin_lock() + local_bh_disable()
spin_unlock_bh() = spin_unlock() + local_bh_enable()

//!注意 copy_from_user()、copy_to_user()和 kmalloc()等函数都有可能引起阻塞,因此在自旋锁的占用期间不能调用这些函数


//读写锁
//读写锁是一种比自旋锁粒度更小的锁机制,它保留了“自旋”的概念

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 静态初始化 */
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* 动态初始化 */

void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);

void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);

void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);

void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);


//顺序锁    读写自旋锁
/*
顺序锁(seqlock)是对读写锁的一种优化,若使用顺序锁,读执行单元绝不会被
写执行单元阻塞,也就是说,读执行单元可以在写执行单元对被顺序锁保护的共享资
源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也
不需要等待所有读执行单元完成读操作才去进行写操作。

顺序锁有一个限制,它必须要求被保护的共享资源不含有指针,因为写执行单元可能使得指针失效,
但读执行单元如果正要访问该指针,将导致 Oops

*/
#include <linux/rwlock.h>

void write_seqlock(seqlock_t *sl);
int write_tryseqlock(seqlock_t *sl);
write_seqlock_irqsave(lock, flags);
write_seqlock_irq(lock);
write_seqlock_bh(lock);

void write_sequnlock(seqlock_t *sl);
write_sequnlock_irqrestore(lock, flags);
write_sequnlock_irq(lock);
write_sequnlock_bh(lock);


unsigned read_seqbegin(const seqlock_t *sl);
read_seqbegin_irqsave(lock, flags);

//读执行单元在访问完被顺序锁 s1 保护的共享资源后需要调用该函数来检查,在读
//访问期间是否有写操作。如果有写操作,读执行单元就需要重新进行读操作.
int read_seqretry(const seqlock_t *sl, unsigned iv);
read_seqretry_irqrestore(lock, iv, flags);

//用法
do {
seqnum = read_seqbegin(&seqlock_a);
//读操作代码块
//...
} while (read_seqretry(&seqlock_a, seqnum));


//RCU的使用

//信号量的使用
struct semaphore sem;
static inline void sema_init(struct semaphore *sem, int val)

void down(struct semaphore *sem); //获取信号量,不建议使用此函数,因为是 UNINTERRUPTABLE 的睡眠
int down_interruptible(struct semaphore *sem); //可被中断地获取信号量,如果睡眠被信号中断,返回错误-EINTR。
int down_killable(struct semaphore *sem);//可被杀死地获取信号量。如果睡眠被致命信号中断,返回错误-EINTR
int down_trylock(struct semaphore *sem);//尝试原子地获取信号量,如果成功获取,返回0,不能获取,返回1。
int down_timeout(struct semaphore *sem, long jiffies); //在指定的时间jiffies内获取信号量,若超时未获取,返回错误-ETIME
void up(struct semaphore *sem);//释放信号量sem。

//读写信号量
rw_semaphore rw_sem; //定义读写信号量
init_rwsem(&rw_sem); //初始化读写信号量
//读时获取信号量
down_read(&rw_sem);
//... //临界资源
up_read(&rw_sem);

//写时获取信号量
down_write(&rw_sem);
//... //临界资源
up_write(&rw_sem);


//互斥,比信号量的实现互斥 效率要高
struct mutex my_mutex; //定义 mutex
mutex_init(&my_mutex); //初始化 mutex
mutex_lock(&my_mutex); //获取 mutex
//...//临界资源
mutex_unlock(&my_mutex); //释放 mutex


//完成量
struct completion my_completion;

init_completion(&my_completion);
reinit_completion(&my_completion)

//用于等待一个完成量被唤醒
void wait_for_completion(struct completion *c);

//唤醒完成量
void complete(struct completion *c);
void complete_all(struct completion *c);


/*********************阻塞与非阻塞*****************/
//可以使用等待队列(wait queue)来实现阻塞进程的唤醒
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
DECLARE_WAIT_QUEUE_HEAD (name);
DECLARE_WAITQUEUE(name, tsk);

void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t*wait);
void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t*wait);

//等待事件
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout); //timeout 以 jiffy 为单位
wait_event_interruptible_timeout(queue, condition, timeout); 

//唤醒队列,会唤醒以 queue 作为等待队列头的所有等待队列中所有属的进程
//注意(interruptible)成对的使用 
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

sleep_on(wait_queue_head_t *q );
interruptible_sleep_on(wait_queue_head_t *q );

//如果进程被其他地方唤醒,将等待队列移出等待队列头

/*
在内核中使用set_current_state()函数或_ _add_wait_queue()函数来实现目前进程状态的改变,
直接采用 current->state = TASK_UNINTERRUPTIBLE 类似的赋值语句也是可行的。
通常而言,set_current_state()函数在任何环境下都可以使用,不会存在并发问题,但是效率要低于 __add_ wait_queue();
*/

//轮询操作 select poll
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);//这个函数并不会引起阻塞

//异步操作
//处理FASYNC标志变更的函数
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);

static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
	struct globalfifo_dev *dev = filp->private_data;
	return fasync_helper(fd, filp, mode, &dev->async_queue);
}

//释放信号用的函数
void kill_fasync(struct fasync_struct **fa, int sig, int band);

//在设备资源可以获得时, 应该调用kill_fasync() 释放SIGIO信号。 
//在可读时, 第3个参数设置为POLL_IN, 在可写时, 第3个参数设置为POLL_OUT
//SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

static int globalfifo_release(struct inode *inode, struct file *filp)
{
	globalfifo_fasync(-1, filp, 0); /* 将文件从异步通知列表中删除 */
	return 0;
}

//应用端
signal(SIGIO, signalio_handler);
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);

//异步IO
int aio_read( struct aiocb *aiocbp );
int aio_write( struct aiocb *aiocbp );
int aio_error( struct aiocb *aiocbp );
ssize_t aio_return( struct aiocb *aiocbp );



/*
Linux设计中强调的一个基本观点是机制和策略的分离。 机制是做某
样事情的固定步骤、 方法, 而策略就是每一个步骤所采取的不同方式。 机制是相对固定的, 而每个步骤采
用的策略是不固定的。 机制是稳定的, 而策略则是灵活的, 因此, 在Linux内核中, 不应该实现策略
*/



/*********************字符设备*****************/
struct cdev {
	 struct kobject kobj; /* 内嵌的kobject对象 */
	 struct module *owner; /* 所属模块*/
	 struct file_operations *ops; /* 文件操作结构体*/
	 struct list_head list;
	 dev_t dev; /* 设备号  其中12位为主设备号, 20位为次设备号*/
	 unsigned int count;
};

MAJOR(dev_t dev);
MINOR(dev_t dev);
MKDEV(int major, int minor);

//Linux内核提供了一组函数以用于操作cdev结构体
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);

/*
在调用cdev_add() 函数向系统注册字符设备之前, 应首先调用register_chrdev_region() 或
alloc_chrdev_region() 函数向系统申请设备号
*/

int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); //自动分配设备号


//内核空间虽然可以访问用户空间的缓冲区, 但是在访问之前, 一般需要先检查其合法性, 通过
//access_ok(type, addr, size) 进行判断, 以确定传入的缓冲区的确属于用户空间
access_ok(type,addr,size);//access_ok(VERIFY_WRITE, buf, count)
/*
如果要复制的内存是简单类型, 如char、 int、 long等, 则可以使用简单的put_user() 和get_user() 
*/

int val; /* 内核空间整型变量*/

get_user(val, (int *) arg); /* 用户→内核, arg是用户空间的地址 */
put_user(val, (int *) arg); /* 内核→用户, arg是用户空间的地址 */

__get_user(val, (int *) arg); //get_user类似,但没有检测合法性



//乱序
/*
在Linux内核中, 定义了读写屏障mb( ) 、 读屏障rmb( ) 、 写屏障wmb( ) 、 以及作用于寄存器读
写的__iormb( ) 、 __iowmb( ) 这样的屏障API。 读写寄存器的readl_relaxed( ) 和readl( ) 、
writel_relaxed( ) 和writel( ) API的区别就体现在有无屏障方面。

比如我们通过writel_relaxed( ) 写完DMA的开始地址、 结束地址、 大小之后, 我们一定要调用
writel( ) 来启动DMA

*/

#define readb(c) ({ u8 __v = readb_relaxed(c); __iormb(); __v; })
#define readw(c) ({ u16 __v = readw_relaxed(c); __iormb(); __v; })
#define readl(c) ({ u32 __v = readl_relaxed(c); __iormb(); __v; })
#define writeb(v,c) ({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c) ({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c) ({ __iowmb(); writel_relaxed(v,c); })


writel_relaxed(DMA_SRC_REG, src_addr);
writel_relaxed(DMA_DST_REG, dst_addr);
writel_relaxed(DMA_SIZE_REG, size);
writel (DMA_ENABLE, 1);


/*********************设备树相关操作*****************/
//根据兼容属性, 获得设备节点。 遍历设备树中的设备节点, 看看哪个节点的类型、 兼容属性与本函数的输入参数匹配,
//在大多数情况下, from、 type为NULL, 则表示遍历了所有节点
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible);

//通过 of_device_id 匹配表来查找指定的节点
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);

//读取属性
int of_property_read_u32_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz);
//示例
//if (of_property_read_u32_array(pdev->dev.of_node, "freq-range", range,ARRAY_SIZE(range)) == 0) 

static inline int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value); 
int of_property_read_string(const struct device_node *np, const char *propname,const char **out_string);

//读取字符串数组属性中的第index个字符串
static inline int of_property_read_string_index(const struct device_node *np, const char *propname, int index, const char **output)

//如果设备节点np含有propname属性, 则返回true, 否则返回false。 一般用于检查空属性是否存在
static inline bool of_property_read_bool(const struct device_node *np, const char *propname);

//内存映射
/*
reg属性
可以直接通过设备节点进行设备内存区间的ioremap() , index是内存段的索引
一些设备驱动通过of_iomap() 而不再通过传统的ioremap() 进行映射
*/
void __iomem *of_iomap(struct device_node *node, int index);

//解析中断
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);

//获取与节点对应的platform_device
struct platform_device *of_find_device_by_node(struct device_node *np);

struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name);

//通过节点名字查找指定的节点
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);

struct device_node *of_find_node_by_type(struct device_node *from,const char *type);
inline struct device_node *of_find_node_by_path(const char *path);
//struct device_node *of_find_node_opts_by_path(const char *path, const char **opts);


//查找父子节点
struct device_node *of_get_parent(const struct device_node *node);
struct device_node *of_get_next_child(const struct device_node *node,
	struct device_node *prev);

static inline int of_property_count_u32_elems(const struct device_node *np,const char *propname);
//int of_property_count_elems_of_size(const struct device_node *np,
//				const char *propname, int elem_size);

//用于获取#address-cells 属性值
int of_n_addr_cells(struct device_node *np);
int of_n_size_cells(struct device_node *np);



//for_each_property_of_node(of_aliases, pp) { }
for_each_property_of_node


/*********************GPIO的操作*****************/
int gpio_request(unsigned gpio, const char *label);
//include/asm-generic/gpio.h
static inline int gpio_direction_input(unsigned gpio);
static inline int gpio_get_value(unsigned int gpio);

atomic_set
jiffies
unsigned int jiffies_to_msecs(const unsigned long j); //转换

container_of 
//下面为container_of 的实际使用
static ssize_t dts_operator_show(struct device*dev, struct device_attribute *attr, char *buf)
{
	struct dts_test_dev* dts_dev = container_of(dev,struct dts_test_dev,device); //注意这种用法是错误的 device在结构体中的类型不能是指针
	if(!dts_dev){
		printk("dts_operator_show  dts_dev=null ...\n");
		return -1;
	}
	return sprintf(buf, "%s\n", dts_test_dev->data);
}



/*********************LIST的使用*****************/
//方式1
LIST_HEAD(name)  //name就是链表头的名字,该宏会自动定义一个名字为name 的 list_head 结构体。
//方式2
struct list_head head;	  //声明链表头
INIT_LIST_HEAD(&head);	  //链表头初始化

list_add(struct list_head *new, struct list_head *head);        //加入链表头部
list_add_tail(struct list_head *new, struct list_head *head);   //加入到链表尾部

list_for_each(pos, head);    //遍历链表,可以结合list_entry()接口对数据操作
list_for_each_safe(pos, n, head);    //如果在遍历过程中涉及结点删除操作,则需要使用这个接口

list_del(struct list_head *entry);
list_empty(const struct list_head *head);     //判断链表是否为空



/*********************其它*****************/
//内核提供关闭软中断的锁机抽
local_bh_disable();
locall_bh_enable();

//软中断的回调函数不能睡眠

//CPUFreq核心层提供了如下API以供SoC注册自身的CPUFreq驱动
int cpufreq_register_driver(struct cpufreq_driver *driver_data);

//GPIO驱动
struct gpio_chip gpio;
int gpiochip_add(struct gpio_chip *chip); //注册giop_chip;

//常见接口
int gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);

int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
int gpio_set_debounce(unsigned gpio, unsigned debounce);
int gpio_get_value_cansleep(unsigned gpio);
void gpio_set_value_cansleep(unsigned gpio, int value);
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
int gpio_request_array(const struct gpio *array, size_t num);
void gpio_free_array(const struct gpio *array, size_t num);
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
int devm_gpio_request_one(struct device *dev, unsigned gpio,unsigned long flags, const char *label);
void devm_gpio_free(struct device *dev, unsigned int gpio);

/*
注意: 内核中针对内存、 IRQ、 时钟、 GPIO、 pinctrl、 Regulator都有以devm_开头的API, 使用这部分
API的时候, 内核会有类似于Java的资源自动回收机制, 因此在代码中进行出错处理时, 无须释放相关的资源。
*/

#include <linux/pinctrl/consumer.h>
ret = pin_config_set("foo-dev", "FOO_GPIO_PIN", PLATFORM_X_PULL_UP);


//根据file 查找cdev
struct file *filp;
struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
struct ap3216c_dev *dev = container_of(cdev, struct ap3216c_dev, cdev);




最后

笔记中有这么一段话:Linux设计中强调的一个基本观点是机制和策略的分离。 机制是做某样事情的固定步骤、 方法, 而策略就是每一个步骤所采取的不同方式。 机制是相对固定的, 而每个步骤采
用的策略是不固定的。 机制是稳定的, 而策略则是灵活的, 因此, 在Linux内核中, 不应该实现策略。

其中的机制与策略面向对象语言的抽象的思想很相似。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值