当前位置: 星创客 > 学习资源 > 学员笔记 > 驱动开发笔记
驱动开发笔记 时间:2018-01-15     来源:星创客

0 课程安排

1.字符设备驱动

2.io相关操作 + 并发竟态

3.io模型 + 中断

4.linux延缓机制 + 内存

5.led + platfrom

6.接口

7.iic

8.block + net

1. 字符设备驱动

驱动的概念:连接计算机与相关硬件的一段程序;

【计算机组成】

硬件:power、in/out、内存、机械硬盘、cpu……

软件:app (微信、QQ),sys operation

linux体系架构:

用户空间【0-3g】

app:

libc:

内核空间【3-4g】

[1]进程管理:负责进程的创建,销毁,通信等;

[2]内存管理:内存分配

[3]文件系统:文件的存储,管理。

[4]设备驱动:主要是负责与相关硬件的控制操作逻辑,给上层提供操作硬件的接口;

字符设备驱动:例如:鼠标,键盘,lcd(帧缓冲),以字节为单位进行,顺序访问,无缓存。

块设备驱动: 例如:U盘,机械硬盘,ssd。以块(512字节)为单位进行访问,可以随意访问,有缓存;

网路设备驱动:路由器,网卡;

[5]网络协议栈:对网络协议的封装,逻辑编写等;

arch: x86 arm

hardware

【基于linux内核的设备驱动开发】

(1)什么叫做模块?为什么引入模块?

module:linux 为外部提供的一个接口 ,(LOADABLE KERNEL MODULE )lkm。

为了简化对于内核修改操作的流程及时间;

模块特性:可以被单独编译,但是不能单独运行;

单内核[宏]:将 linux 整体作为一个大过程来实现,并运行在一块地址空间内,期间的通信通过函数调用;

优点:运行效率高;

缺点:灵活性,可扩展性比较差;

(2)linux设备驱动开发的一般流程?

【1】将具体的功能编写成 xxx.c ----->设备驱动

【2】将其测试完毕之后(生成对应的模块,插入到相关内核中,并对其功能进行测试,测试成功之后)

【3】将该该模块直接编译进内核。uImage

(3)内核模块编写?

内核模块三要素:男 + 女--->父母 -->领证

男:入口函数:

女:出口函数:

见父母:将入口、出口函数见内核:

领证:GPL协议:

(4)内核打印函数?打印等级?级别更改?[/proc/sys/kerenl/printk]

printf(“打印格式”,待打印的数据);

printk(打印等级“打印格式”,待打印的数据);

打印等级:

#define KERN_EMERG "<0>" /* system is unusable */

#define KERN_ALERT "<1>" /* action must be taken immediately */

#define KERN_CRIT "<2>" /* critical conditions */

#define KERN_ERR "<3>" /* error conditions */

#define KERN_WARNING "<4>" /* warning conditions */

#define KERN_NOTICE "<5>" /* normal but significant condition */

#define KERN_INFO "<6>" /* informational */

#define KERN_DEBUG "<7>" /* debug-level messages */

<0>------------<7>

高 低

控制台相关的打印级别:

#define console_loglevel (console_printk[0]) //当前控制台的打印级别

#define default_message_loglevel (console_printk[1]) //默认消息的打印机别 printk(“打印格式”,待打印的数据);

#define minimum_console_loglevel (console_printk[2]) //控制台可设置的小打印级别

#define default_console_loglevel (console_printk[3]) //内核启动时的打印级别

模块-------------------------->内核里面----->Makefile

|\/

依赖待插入的内核来编译<----参数,符号

(5)编译模块Makefile?

uname -r:获取当前内核的版本---》3.13.0-135-generic

Makefile路径:/lib/modules/3.13.0-135-generic/build

KERN_PATH:=/lib/modules/$(shell uname -r )/buildall:

make -C $(KERN_PATH) M=$(shell pwd) modules

clean:

make -C $(KERN_PATH) M=$(shell pwd) clean

obj-m:=demo.o

-C:获取待插入内核的makefile的路径

M:待编译的模块的路径

执行 make --生成->xxxx.ko---->模块文件

file demo.ko

demo.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV),

BuildID[sha1]=5d8db51a9a9f456bb6070ab580c1c8e1e7fafe06, not stripped

(6)查看模块信息?插入模块?移除模块?

linux@ubuntu64-vm:~/Docu/Driver/cdev$ modinfo demo.ko

filename: /home/linux/Docu/Driver/cdev/demo.ko

license: GPL

srcversion: A31DE5AAC03DFEA37EB625F

depends:

vermagic: 3.13.0-135-generic SMP mod_unload modversions

sudo dmesg -C/-c #清除内核打印信息

insert module--->sudo insmod xxx.ko #插入模块

rm module--->sudo rmmod xxx #移除模块

当执行模块相关操作的时候,要使用sudo

(7)模块与应用程序区别?

模块 应用程序

API来源 linux内核 libc库

编译方式 makefile gcc

入口函数 init main

运行权限 super user user

运行空间 内核空间 用户空间

(8)内核模块传参? moduleparam.h ---> 权限:/sys/module/模块名/parameters

模块描述信息:

MODULE_DESCRIPTION("STRING")

作者描述信息:

MODULE_AUTHOR("FARSIGHT")

参数描述信息:

MODULE_PARM_DESC(对应的参数名称, “参数描述信息”)

例:

MODULE_PARM_DESC(num, "int num module_param");

MODULE_PARM_DESC(buf_out,"buf_out-->buf_in module_param_named");

MODULE_PARM_DESC(string,"string module_param_string");

MODULE_PARM_DESC(array, "array module_param_array"

传递指定类型的变量[全局]:

module_param(“参数的名称”, type参数的类型, 参数的权限)

rwx rwx r-x

root group user

421 421 4-1

----------------->普通用户不能有写权限

type: bool, invbool, charp, int, long, short, uint, ulong, ushort;

例:

module_param(num, int, 0775)

module_param(str, charp, 0775)

传递变量:

module_param_named(外部传递参数的名称, 内部接收的参数的名称, 参数类型, 参数的权限

例:

module_param_named(buf_out, buf_in, int, 0775)

传递字符串到全局字符数组:

module_param_string(外部传递字符串的参数名称, 内部接收字符串的参数名称, 字符串的长度, 参数的权限)

例:

module_param_string(string, string, sizeof(string), 0775);传递数组型参数:

module_param_array(数组名称, 数组成员的类型, 外部向内部模块传递的数组成员的个数, 参数的权限)

例:

module_param_array(array, int, &size, 0775)

(9)内核模块导出符号?[注意:加载卸载顺序]export.h-->/usr/src/linux-headers-3.5.0-23-generic/Module.symvers: addr + symbol + module +

EXPORT_SYMBOL/proc/kallsyms

#include <linux/export.h>

1.有一个导出符号的程序;--->导出 ---插入

2.有对应的使用者:使用1中导出的符号;-->引入--->使用--->插入到对应的内核

模块插入顺序:先插导出者 再插使用者 【因为导出对应符号之后才能使用】

模块卸载顺序:先卸载使用者 再卸载导出者 【因为只有没有使用导出符号的模块时,才能卸载导出者】

EXPORT_SYMBOL(待导出的符号)

EXPORT_SYMBOL_GPL(待导出的符号)

EXPORT_SYMBOL_GPL_FUTURE(待导出的符号)

68 #define

EXPORT_SYMBOL(sym) \

69 __EXPORT_SYMBOL(sym, "")

例:

int add(int a, int b)

{

return a + b;

} i

nt sub(int a, int b)

{

if(a > b)

return(a - b);

else

return(b - a);

} E

XPORT_SYMBOL(add);

EXPORT_SYMBOL_GPL(sub);

extern int add(int a, int b);

extern int sub(int a, int b);

int a = 6, b = 3;

printk("add: %d, sub: %d\n", add(a,b), sub(a, b));

内核版本:3.5--->符号3.5

模块用3.6内核 符号来编译的

/*

//导出符号的版本crc:

56 #define __EXPORT_SYMBOL(sym,

sec) \

57 extern typeof(sym) sym; \

58 __CRC_SYMBOL(sym, sec) \

//当前导出符号存放的段

59 static const char __kstrtab_##sym[] \

60 __attribute__((section("__ksymtab_strings"), aligned(1))) \

61 = VMLINUX_SYMBOL_STR(sym);

//区分EXPORT_SYMBOL、EXPORT_SYMBOL_GPL、EXPORT_SYMBOL_FUTURE

62 extern const struct kernel_symbol __ksymtab_##sym; \

63 __visible const struct kernel_symbol __ksymtab_##sym \

64 __used \

65 __attribute__((section("___ksymtab" sec "+" #sym), unused)) \

66 = { (unsigned long)&sym, __kstrtab_##sym }

vmlinux.lds:./arch/arm/kernel/vmlinux.lds

*/

【字符设备驱动】

struct inode, struct cdev, struct file结构体介绍

‘/dev/xxx’----->struct inode

struct inode {

umode_t i_mode;//记录设备类型const struct inode_operations *i_op;//inode的相关操作集合

dev_t i_rdev;//设备号--->如果说当前的‘path’对应的时设备文件,那么该设备文件会有对应的 设备号

union { //描述设备结构体

struct pipe_inode_info *i_pipe;//net

struct block_device *i_bdev;//块

struct cdev *i_cdev;//字符

};

……

};

struct cdev {

/*struct kobject kobj; linux设备模型相关*/

struct module *owner; //当前cdev对应的模块

const struct file_operations *ops;//操作当前设备的相关的方法:open,close,read,write,llseek

dev_t dev; //设备号

unsigned int count; //设备数量

};

struct file_operations {

struct module *owner;//指代该操作方法集合所指向的模块

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//读方法

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//写方法

unsigned int (*poll) (struct file *, struct poll_table_struct *); //异步阻塞io --->select

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//相关控制操作

int (*open) (struct inode *, struct file *);//open

int (*release) (struct inode *, struct file *);//close

int (*fasync) (int, struct file *, int);//异步通知

};

(1)设备号

dev_t 设备号的数据类型: 12bit + 20bit

主设备号 次设备号

驱动 驱动控制的若干设备

dev_t dev_no;

获取主设备号major:

major = MAJOR(dev_no)

获取次设备号minor:

minor = MINOR(dev_no)

利用主次设备号获取设备号:

dev_no = MKDEV(major,minor)

(2)函数API

买房: 房间号 设备号

根据房间号--->房间 cdev申请

装修 cdev初始化

入住 cdev注册

设备号 :

(1)自动申请设备号

alloc char device region

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

功能:自动申请设备号

参数:dev:设备号的指针对象

baseminor:起始次设备号

count:申请的设备的数量

name:设备名称

返回值:成功:0 失败:errno code

例:

#define DEV_NAME "demo"

dev_t from;

int baseminor = 0;

int count = 1;

ret = alloc_chrdev_region(&from, baseminor, count, DEV_NAME);

if(ret)

printk("alloc_chrdev_region fail...%s, %d", __func__, __LINE__)

(2)手动申请设备号register char device region

int register_chrdev_region(dev_t from, unsigned count, const char *name)

功能:手动申请设备号

参数:from:设备号

count:申请的设备的数量

name:设备名称

返回值:成功:0 失败:errno code

(3)释放设备号

void unregister_chrdev_region(dev_t from, unsigned count)

功能:释放设备号

参数:from:设备号

count:申请的设备的数量

返回值:无

例:

ERR_STEP1:

unregister_chrdev_region(from, count);

cdev申请:

struct cdev *cdev_alloc(void)

功能:cdev申请

参数:无

返回值:成功:cdev指针对象 失败:NULL

例:

struct cdev *cdev = NULL;

cdev = cdev_alloc();

if(!cdev){

printk("cdev_alloc fail...%s, %d", __func__, __LINE__);

goto ERR_STEP1;

}

cdev初始化:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

功能:cdev初始化

参数:cdev:cdev的指针对象

fops:cdev的操作方法集合

返回值:无

例:

cdev_init(cdev, &f_ops);

cdev注册:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

功能:cdev的注册

参数:p:cdev的指针对象

dev:cdev的设备号

count:设备数量

返回值:成功:0 失败:errno code

例:

ret = cdev_add(cdev, from, count);

if(ret)

printk("alloc_chrdev_region fail...%s, %d", __func__, __LINE__);

app:open read write ...

模块中:open read write

[struct cdev.fops = struct file_operations]

[struct file_operations.open/read/write]

例:

struct file_operations f_ops = {

.owner = THIS_MODULE,.open = demo_open,

.release = demo_release,

};

[36917.054428] major:250 minor:0

mknod /dev/xxx c/*b*/ 250 0

设备文件[path] 表示当前的设备节点是为字符(char)还是块设备(block)创建的

/*回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数

时,我们就说这是回调函数*/

(3)驱动程序 + 测试程序

#define PATH "/dev/demo"

fd = open(PATH,O_RDWR);

if(fd < 0){

perror("open");

} c

lose(fd);

/*(4)调用关系

(5)将模块编译进内核*/

2 linux 设备驱动 IO 操作

【字符设备的高级操作】

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops

功能:完成字符设备框架相关的东西

参数:major:主设备号【0:自动分配 其他数据:手动】

name:设备名

fops:操作方法集合

返回:返回主设备号

static inline void unregister_chrdev(unsigned int major, const char *name)

功能:与register_chrdev相反

参数:major:主设备号

name:设备名

返回:无

(1)读写

#include <asm/uaccess.h>

READ:

ker_date ---- copy_to_user ----》user

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

功能:将内核的数据传递给用户空间

参数:to:用户空间的地址

from:内核空间的指针

n:传递的数据的大小

返回值:成功: 0 失败:传输的数据的个数

WRITE:

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)

功能:将用户的数据传递给内核空间

参数:to:内核空间的地址

from:用户空间的指针

n:传递的数据的大小

返回值:成功: 0 失败:传输的数据的个数

(2)控制(32bits 被分为四部分)ioctl:

#include <asm-generic/ioctl.h>

命令码定义: _IO/_IOR/_IOW/_IOWR

#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)

#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOC(dir,type,nr,size) \

(((dir) << _IOC_DIRSHIFT) | \ /*<<29=30:31*/

((type) << _IOC_TYPESHIFT) | \ /*<<8=8:15*/

((nr) << _IOC_NRSHIFT) | \ /*<<0=0:7*/

((size) << _IOC_SIZESHIFT)) /*<<16=16:29*/

DIR size type nr

31:30 29:16 8:15 0:7

#define TYPE 'A'

#define ONE _IO(TYPE,1)

#define TWO _IO(TYPE,2)

内核中已经被占用的命令码文件:linux-3.14/Documentation/ioctl/unlocked_ioctl

(3)自动创建设备节点[/proc/sys/kernel/hotplug]--->udev

#include <linux/device.h>

[1] 设备类

struct class {

const char *name;//类名

struct module *owner;//拥有该类的模块指针

};

#define class_create(owner, name) \

/*({ \

static struct lock_class_key __key; \

__class_create(owner, name, &__key); \struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)

})*/

struct class *class_create(struct module *owner,const char *name)

功能:创建struct class

参数:owner:指代其所在的模块//THIS_MODULE

name:类名

返回值:成功:struct class指针对象 失败:NULL

现象:在/sys/class下产生一个名字为‘name’的目录

例:

#define NAME "demo"

#define CLASS_NAME "demo_class"

cls = class_create(THIS_MODULE, CLASS_NAME);

if(IS_ERR(cls)){

ret = PTR_ERR(cls);

goto ERR_STEP1;

}

void class_destroy(struct class *cls)

功能:销毁struct class

参数:cls:struct class的指针对象

返回值:无

IS_ERR(void *ptr); //判断指针的正确与否

PTR_ERR(void *ptr); //将错误指针---->errno

[2]设备

struct device {

struct device *parent; //当前设备的父设备

struct device_private *p; //指向该设备的驱动相关的数据

const char *init_name; //设备名

const struct device_type *type;

struct device_driver *driver;

};

#define NAME demo

int i;

struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const

char *fmt, ...)

功能:创建对应的 device

参数:cls:struct class的指针对象

parent:设备父对象一般NULL

dev_t:设备号

drvdata:设备私有数据

fmt:设备命名的格式“%s%d”

...:NAME

demo0 demo1 demo2....

/dev/demo0 /dev/demo1

返回值:成功:struct device指针对象 失败:NULL

例:

//device: 250 0

//device: 250 1

//dev_no = MKDEV(major, minor);

for(i = minor; i < minor + count; i++){

dev = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", NAME, i);

if(IS_ERR(dev)){

ret = PTR_ERR(dev);

goto ERR_STEP2;

}

}

现象:在/sys/class/(class名称)/(device) 名称下产生一个名字为‘name’的目录

void device_destroy(struct class *class, dev_t devt)

功能:销毁对应的 device

参数:cls:struct class的指针对象

dev_t:设备号

返回值:成功:struct device指针对象 失败:NULL

例:

ERR_STEP2:

for(i--; i >= minor; i--)

device_destroy(cls, MKDEV(major, i));class_destroy(cls);

3 Linux 设备驱动内存分配

内存分配函数都依赖于内核中一个构件:内存管理

linux下对内存管理总体上可以分为两大类:(1)物理内存管理 (2)虚拟内存管理

【物理内存相关】

(1)物理内存中内存相关结构体

内存节点node:

(1)引入此概念的原因:

内存管理模型:

uma:一致性内存管理模型

numa:非一致性内存管理模型--->速度

(2)数据结构 struct pglist_data

内存区域zone:

概念:内存区域是内存节点中的一个概念,将内存节点管理的物理内存划分为不同的区域。

分区:低端内存区(dma, normal),/*高端内存区*/

结构体:struct zone

区域的类型:

enum zone_type {

#ifdef CONFIG_ZONE_DMA

ZONE_DMA,

#endif

ZONE_NORMAL,

#ifdef CONFIG_HIGHMEM

ZONE_HIGHMEM,

#endif

};

内存页 page:内存管理的小单元--->一般来说 4k----->大小取决于mmu memory manager unit

struct mem_map[]

【内存分配区间图】

(2)物理内存中管理机制:

{1}页面级内存管理(页面分配器)

用于连续大块的物理内存区分配;

分配区间取决于gfp_mask标志:

[1]内存管理部件内部使用的标志

__GFP_WAIT | __GFP_IO | __GFP_FS

__GFP_HIGH

[2]外部使用的内存分配标志

GFP_KERNEL/GFP_ATOMIC

[3]标志查找效果:

[3.1]ZONE_DMA/ZONE_NORMAL:

[3.2]ZONE_HIGHMEM:标志查找过程:

__GFP_DMA: ZONE_DMA

__GFP_NORMAL: ZONE_NORMAL--->ZONE_DMA

该区域内物理-->虚拟:page_address

__GFP_HIGHMEM: ZONE_HIGHMEM----->ZONE_NORMAL--->ZONE_DMA

__GFP_HIGHMEM由于虚拟地址空间的高端页面尚未建立映射关系,故需要:

[3.2.1]申请对应虚拟高端内存

[3.2.2]将申请的内存区经过kmap进行映射 才能映射到实际的物理内存区

[4]页面分配器函数的核心:

alloc_pages && __get_free_pages ---------->alloc_pages_node

alloc_pages 与 __get_free_pages的不同:

前者较后者能分配高端内存以及低端内存;

后者只能获取低端内存区的内容;

[4.1]__alloc_pages/alloc_page<-------->__free_pages

static inline struct page *__alloc_pages(gfp_t gfp_mask, unsigned int order,struct zonelist

*zonelist)

#define alloc_page(gfp_mask)

分配4K或者其的若干倍

gfp_mask:表征其分配的内存区属于那一块

[4.2]__get_free_pages/__get_free_page<-------->free_pages

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

#define __get_free_page(gfp_mask)

gfp_mask:表征其分配的内存区属于那一块

[4.3]get_zeroed_page:分配相关内存区并清零

unsigned long get_zeroed_page(gfp_t gfp_mask)

gfp_mask:表征其分配的内存区属于那一块

#define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS)

表征在内存分配时可以被阻塞 | 表征在内存分配时可以执行对应的 io操作

| 可以执行对应文件系统相关的

#define GFP_ATOMIC (__GFP_HIGH)

在内存分配时,不可被打断(以原子方式)

{2}给予页面管理之上的 slab 管理:

linux 系统在物理页分配的基础上实现了对更小内存空间进行管理的slab分配器。

slab 思想:将一个内存页或者若干内存页分配成若干相等的小内存,供程序员使用。

slab 分配器限制:只能在低端内存区进行分配;

典型:kmalloc :128k

static __always_inline void *kmalloc(size_t size, gfp_t flags)

static inline void *kzalloc(size_t size, gfp_t flags);//基于kmalloc,并将其分配的内存区清零

分配的虚拟内存连续,物理内存区也是连续的。

【2】虚拟内存管理

转换过程: 物理地址---mmu---建立页表--->虚拟地址

[1]vmalloc / vfree

void *vmalloc(unsigned long size);

void vfree(const void *addr);

分配的虚拟内存连续,物理内存区不一定连续的。[2]linux内核虚拟地址空间构成

4g------------------------------------

/* 4k

-----------------------------------

固定内存映射区 4m-4k

-----------------------------------

高端内存映射区 4m

------------------------------------*/

NULL 8K(保护)

------------------------------------vmalloc end

vmalloc内存区120m-8m-8k

------------------------------------vmalloc start

vmalloc offset 8m

------------------------------------

物理内存映射区896M

3g------------------------------------ 物理内存 3g 偏移 4k + 3g

用户空间

0g------------------------------------

[3]与硬件操作相关;各个地址的概念

(1)物理地址:实际物理总线上的地址

(2)逻辑地址:程序经过反汇编出现在反汇编代码中的地址就是逻辑地址

(3)线性地址:又称之为虚拟地址,程序员操作虚拟地址。

[4]ioremap

ioremap 将 vmalloc 区的某段虚拟内存块映射到 io 空间,io 空间在不同的架构上有不同的解释,为了统一内核为 io 操作提供了统一的接

口:readl/writel 等其在不同的平台上会展开成架构相关的代码。

void __iomem *ioremap(unsigned long paddr, unsigned long size)

功能:映射物理地址--》虚拟地址

参数:paddr:物理地址

size:映射大小

返回值:成功:返回该物理地址对应的虚拟地址 失败:NULL

static inline void iounmap(volatile void __iomem *addr)

功能:解除映射关系

参数:addr:虚拟地址

返回值:无

static inline u32 readl(const volatile void __iomem *addr)

功能:读取虚拟地址中的数据

参数:addr:虚拟地址

返回值:对应的数据

static inline void writel(u32 b, volatile void __iomem *addr)

功能:像虚拟地址中写入指定的数据

参数:b:待写入的数据

addr:虚拟地址

返回值:无

【*】程序编写:led灯驱动

[1]查看原理图--->led的控制管脚

GPF3_5

[2]根据对应的管脚查看芯片手册,去配置该管脚的功能及使用

#define GPF3CON 0X114001E0 --> OUTPUT

#define GPF3DAT 0X114001E4 --> value[3]写驱动

[3.1]将对应的io管脚控制的物理地址进行相关映射

virt_addr = ioremap(GPF3CON);

[3.2]完成配置

readl、writel

[3.3]实现功能

led:IO---->设置对应的IO模式:输入、输出、中断。。。

GPF3_5: 23:20 ---> 0x1 --->output

arm:读 改 写

读取 23:20--->readl--->data

改:23:20--->data: x x x x ---->data = readl(gpf3con);

8 4 2 1

清零:&~ (0xf<<20)------->data &~ (0xf<<20)

写:23:20--->data: writel((0x1<<20), gpf3con);

4 Linux 设备驱动中的并发与竟态

【并发竟态】

(1)概念来源?

linux支持多进程,多cpu,可能因为多个并发执行单元操作,造成竟态。

竟态:若干并发执行单元,对同一块资源进行操作时,造成该资源数据紊乱的状态;

为了确保共享资源的访问安全的措施:

互斥:强调排他性;

同步:强调顺序性;

补充概念:

临界区:访问共享资源的的代码段;

临界资源:共享资源

并发源:多个执行单元

(2)竟态的产生的情况:

[1]smp:对称多处理器结构,cpu并发执行造成的竟态;

单cpu:

[2]可抢占:进程和进程间抢占

[3]进程和中断间的竟态

为了防止产生竟态,将访问共享资源的代码段进行排他性访问或者顺序性访问;

(3)解决方式:

单cpu:[2][3]--->中断屏蔽:在进入临界区之前屏蔽中断,之后进入临界区对共享资源进行独占,完成后解除中断屏蔽;

/*中断屏蔽*/ --->高等级的进程 + 中断 不能干扰程序执行

执行访问共享资源的操作...//临界区代码访问临界资源

/*中断解除屏蔽*/--->高等级的进程 + 中断能 干扰程序执行

应用注意事项:尽量快速【原因:异步io及进程调度依赖于中断】

【1】基础版:

local_irq_disable();

......

//临界区代码访问临界资源,耗时尽量少

local_irq_enable();

【2】升级版:

与基础版不同于:寄存器 FLAG 中的标志被 flags

local_irq_save(flags);//关闭中断

//临界区代码访问临界资源,耗时尽量少

local_save_flags(flags);//开启中断

[1][2][3]--------->自旋锁:适用于smp,对称多处理器系统

单cpu且内核可抢占系统<--->在上锁期间内核抢占被禁止

进入临界区处理完之后解锁;若在其他进程上锁期间欲获取锁则会导致该进程忙等;

目的:保护共享资源

核心思想: 在多处理器上设置了一个全局变量 V , V = 1 ,表征上锁, V = 0,

上锁过程:"READ-TEST-SET"/*[原子:将若干条执行语句,在cpu上的执行过程认为是一条指令]*/获取锁过程:

应用注意事项:任何上下文、不可睡眠、尽量快忙等锁,易导致死锁;

函数API:

类型:

spinlock_t lock;

初始化:

spin_lock_init(spinlock_t *lock);

功能:初始化自旋锁

#define spin_lock_init(_lock)

static inline raw_spinlock_t *spinlock_check(spinlock_t *lock)

上锁:

static inline void spin_lock(spinlock_t *lock);

解锁:

static inline void spin_unlock(spinlock_t *lock);

/*volatile:被设计用来修饰被不同线程访问和修改的变量。确保本条指令不会因编译器的优化而省略,且要求每次直接读值。*/

【*】程序编写:使用自旋锁实现设备只能被一个进程打开

[1][2]--------->信号量:与自旋锁类似,进入临界区处理完之后解锁,若在其他进程上锁期间欲获取锁则会导致该进程休眠;

应用注意事项:进程上下文、可睡眠;

结构体:

struct semaphore

struct semaphore {

raw_spinlock_t lock;//自锁锁

unsigned int count;//信号量的个数

};

函数:

初始化:

static inline void sema_init(struct semaphore *sem, int val);

例:

sema_init(&sem, 1);

获取信号量的函数:

int down_trylock(struct semaphore *sem);

例:

if(down_trylock(&sem)){

printk("can not access...\n");

return -EBUSY;

} 释放

信号量:

void up(struct semaphore *sem);

【*】程序编写:使用信号量实现设备只能被一个进程打开

PS:互斥锁 mutex,是信号量的 count 成员为1的特殊用法,以实现互斥

定义:

struct mutex

函数:

初始化:

void mutex_init(struct mutex *lock);

#define mutex_init(mutex) \

do { \

static struct lock_class_key __key; \

__mutex_init((mutex), #mutex, &__key); \

void __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)

} while (0)

获取互斥体:

int __sched mutex_trylock(struct mutex *lock);

/*Returns 1 if the mutex* has been acquired successfully, and 0 on contention.*

例:

if(!mutex_trylock(&mutex)){

printk("can not get mutex...\n");

return -EBUSY;

} 释放

互斥体:

void __sched mutex_unlock(struct mutex *lock);

【*】程序编写:使用互斥体实现设备只能被一个进程打开

[1][2][3]------>原子变量:在执行过程中,不可以被打断的操作;例如:buf 被 A、B 进程读改写

定义:

atomic_t

typedef struct {

int counter;//1

} atomic_t;函数:

初始化:

#define ATOMIC_INIT(i) { (i) }//i-->counter

例:

atomic_t at = ATOMIC_INIT(1);

获取 atomic:atomic_dec_and_test

atomic_t v;

#define atomic_dec_and_test(atomic_t *v) (atomic_sub_return(1, v) == 0)

(val == 0)--》true

/* static inline int atomic_sub_return(1,v )

{

unsigned long flags;

int val;

raw_local_irq_save(flags);

val = v->counter;//1

v->counter = val -= i;//val -= 1;--->val = 0;

//v->counter = 0

raw_local_irq_restore(flags);

return val;

}

*/

例:

if(!atomic_dec_and_test(&at)){

atomic_inc(&at);

printk("can not get mutex...\n");

return -EBUSY;

} 释放

对应的 atomic:

atomic_inc(atomic_t *v)

#define atomic_inc(v) atomic_add(1, v)

#define atomic_add(i, v) (void) atomic_add_return(i, v)

static inline int atomic_add_return(int i, atomic_t *v)

【*】程序编写:使用原子变量实现设备只能被一个进程打开笔记本: 228 - 驱动开发

创建时间: 2017/11/23 8:48 更新时间: 2017/11/28 21:56

作者: 干觐瑞

5 Linux 设备驱动中的阻塞与非阻塞 I/O

【同步阻塞 io 概念】

系统调用 read/write ,如果程序所需的条件不满足,则应用程序阻塞,在这段时间里,程序所代表的进程并不会消耗CPU时间。

底层驱动实现对应的同步阻塞io的相关API:

(1)初始化对应的等待队列

void init_waitqueue_head(wait_queue_head_t *q)

#define init_waitqueue_head(q) \

/*do { \

static struct lock_class_key __key; \

__init_waitqueue_head((q), #q, &__key); \

//void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)

} while (0)*/

(2)将条件不满足的事件添加至等待队列

int wait_event_interruptible(wait_queue_head_t wq, int condition)

#define wait_event_interruptible(wq, condition)

({ \

int __ret = 0; \

if (!(condition)) \

__ret = __wait_event_interruptible(wq, condition); \

//#define __wait_event_interruptible(wq, condition) \

//___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0, \

#define ___wait_event(wq, condition, state, exclusive, ret, cmd)

long __int = prepare_to_wait_event(&wq, &__wait, state);\

long prepare_to_wait_event(wait_queue_head_t *q, wait_queue_t *wait, int state)

__ret;

})

例:

if(!flags){ //条件不满足

if(wait_event_interruptible(wq, flags)) //被信号打断返回非0值,被正常唤醒

return -ERESTARTSYS;

}

[3]当条件满足时,唤醒等待队列上对应的进程

void wake_up(wait_queue_head_t *q);

#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)

void __wake_up(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, void *key)

【同步非阻塞io概念】

系统调用read/write ,如果程序所需的条件不满足,则应用程序非阻塞,直接返回错误码。

同步IO在内核中的具体实现:

基于的数据结构:/*等待队列项*/ 等待队列头

struct __wait_queue_head {

spinlock_t lock; //自旋锁

struct list_head task_list;//等待队列上挂载的不满足条件的等待队列项

};

例:

if(!flags){//条件不满足

if(file->f_flags & O_NONBLOCK)

return -EAGAIN;

if(wait_event_interruptible(wq,flags))//被信号打断返回非0值,被正常唤醒return -ERESTARTSYS;

}

相关函数:

【*】程序编写:同步阻塞IO

【*】程序编写:同步非阻塞IO

阻塞非阻塞基于资源不能及时获取到而言的

阻塞: 条件不满足----> 阻塞等待 ---->直到有条件满足---->解除阻塞--->

非阻塞:条件不满足----> 报错返回

O_NONBLOCK:表征不阻塞

O_RDWR

【异步阻塞io概念】

异步阻塞io:

并非阻塞在设备的读写操作本身,而是阻塞在某一组设备文件的描述符上,当其中的某些描述符上代表的设备对读写操作已经就绪时,阻塞

状态将被解除,用户程序对文件描述符可操作。

底层实现:

相关函数:

struct file_operations -> poll

unsigned int (*poll) (struct file *, struct poll_table_struct *);

poll--调用->static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

+

返回检测的可满足条件的掩码mask POLLIN 读/POLLOUT 写【针对用户层而言】

app实现:[select/poll]

#include <poll.h>

struct pollfd {

int fd; /* 需要监听的文件描述符 */

short events; /* 需要监听的事件 */

short revents; /* 已发生的事件 */

};

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

功能:等待一组文件描述符中准备好文件描述符执行输入输出

参数:fds:struct pollfd 结构体指针,在fds参数中指定要监视的文件描述符集

nfds:监控的文件描述符的个数

timeout:超时时间设置[0:立即返回不阻塞 >0:等待指定毫秒数 其他:永远等待]

返回值:成功:>0 超时:=0 出错:-errno

【*】程序编写:非阻塞读(轮询app)[多路复用IO]

【异步非阻塞io概念】

读写操作立即返回,用户程序的读写请求将放入一个请求队列中由设备在后台异步完成,当设备完成了本次读写操作时,将通过信号或者回调函数

的方式通知用户程序。【块、网络】

【异步通知】

一种通知机制,驱动使用信号通知应用层有事情发生(一般是读写)

底层实现:

struct fasync_struct

(1)kill_fasync//发送消息

kill_fasync 当设备的某一事件发生时负责通知链表中所有相关的进程。

band(带宽),一般都是使用POLL_IN,表示设备可读,如果设备可写,使用POLL_OUT

void kill_fasync(struct fasync_struct **fp, int sig, int band)

(2)fasync_helper//主要在struct file_operation->fasync

fasync_helper将当前要通知的进程加入到一个链表或者从链表删除

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

应用层实现:

int fcntl(int fd, int cmd, ... /* arg */ );

功能:设置fd相关的附加信息

参数:cmd:设置信息相关的命令F_GETFL:获取flag相关

F_SETFL:设置flags相关--->FASYNC异步通知的标志

F_SETOWN:设置进程号..

..:被设置的相关信息

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

【*】程序编写:异步通知(fasync)

作业:自行实现poll调用底层select函数的应用程序编写;

int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

功能:用于多路监控,当没有一个文件满足要求时,select将阻塞调用进程

参数:numfds:需要检查的号码高的文件描述符加1

readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集

timeout:struct timeval类型的指针

=0时:不管有无满足要求的文件均立即返回

=NULL时:将阻塞,直到某个文件满足要求

>0时:在timeout时间范围内阻塞进程

返回值:成功:返回满足要求的文件描述符的个数

失败:-1

超时:0

void FD_CLR(int fd, fd_set *set); //清除描述符

int FD_ISSET(int fd, fd_set *set); //查询描述符

void FD_SET(int fd, fd_set *set); //设置描述符

void FD_ZERO(fd_set *set); //清空集合:

6 Linux 设备驱动中断子系统

(1)中断的概念

当某个事件发生时,CPU停止运行正在执行的程序,而转去执行处理该事件的程序,处理该事件后,还可以返回原程序继续正确的执行下去,这种

功能称为中断。

(2)linux 中断硬件层面的剖析

PIC:可编程中断控制器。 progrom interrupt controller

----------- --------

外设一-------->| P | INT | C |

外设二-------->| I |-------| P |

外设三-------->| C | 软 | U |

... 硬件中断号 ----------- --------

(3)linux 中断软件层次的剖析

[1]PIC 的作用:中断触发电平的设置 ,中断号的映射,中断屏蔽(内部,外部)

[2]中断向量表与外设中断

中断向量表:是cpu内部的一概念,指向的函数处理的入口地址

外设中断 :irq,fiq;---->普通中断,快速中断的对应的通用处理函数

通用中断处理函数的作用:根据对应的软件中断号---->描述该中断的结构体成员

中断线--->irq_desc[irq]

struct irq_desc {

struct irq_data irq_data; /*主要保存软件中断号 irq 和 chip 相关数据*/

irq_flow_handler_t handle_irq; /*指向当前设备中断触发电信号的处理函数*/

/*

typedef void (*irq_flow_handler_t)(unsigned int irq,struct irq_desc *desc);

*/

struct irqaction *action; /* 针对具体设备中断处理的抽象*/

struct module *owner;

const char *name; /*handle_irq对应的名称,终显示在/proc/interrupts下*/

} ____cacheline_internodealigned_in_smp;

struct irq_data {

unsigned int irq; /*中断号*/

struct irq_chip *chip; /*中断控制器的软件抽象 pic*/

};

struct irqaction {

irq_handler_t handler; /*具体的外设中断处理函数*/

/*

typedef irqreturn_t (*irq_handler_t)(int, void *);//中断处理程序的函数原型

*/

void *dev_id; /*调用中断处理函数时传递的参数*/

struct irqaction *next; /*指向下一个action对象,用于共享中断*/

} ____cacheline_internodealigned_in_smp;

IRQF_SHARED

中断的过程:

(1)cpu硬件逻辑将任务的上下文保存到特定的中断栈,并跳转至处理外设中断IRQ/FIQ[IRQF_DISABLED] 的中断向量表所对应的

一项,之后跳转至通用中断处理函数;

(2)从pic中获取软件中断号irq

(3)通用中断处理函数

[3.1]中断现场保存

[3.2]顶半部

[3.3]底半部

[3.4]中断现场恢复

(4)中断程序函数api介绍

static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char

*name, void *dev)

功能:设置相关电平及中断服务程序

参数:irq:中断号

handler:中断服务程序

flags:设置该中断的相关标志IRF_SHARED / IRQF_DISABLED

电平:

#define IRQF_TRIGGER_RISING 0x00000001 //上升沿

#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿

#define IRQF_TRIGGER_HIGH 0x00000004

#define IRQF_TRIGGER_LOW 0x00000008

name:中断名称

dev:一般设置为NULL ,除了 共享中断 [如果是共享中断的话,那么要将该参数填充为该设备的具体设备信息]

返回值:成功:0 失败:errno

(5)设备树文档讲解

文件:

xxx.dts xxx.dtsi xxx.dtb

xxx.c xxx.h bin

#include <xxx.dtsi>

struct dog {

char *name ;

int age;

};

节点,属性

/{

dogs:dog@0x112{

name = "xiaogou";//键值对

age = 2;

hua_dog{//子节点

hua = "huanghua";

reg = <0x114000e0 0x4>

};

};

};

dog@0x110{

dog = &dogs;

};

---->struct device_node

想查找对应节点中的信息的话------>找到对应的节点中转--->才能获取节点中的键值

struct device_node {

const char *name; //节点名

const char *full_name; //全路径节点名

struct device_node *parent; //父节点指针

struct device_node *child; //子节点指针

...

};

相关设备节点实例:

设备树的结构:

#include "exynos4412.dtsi"

/(/*'/'表征是该设备树的根节点*/)

{

model = "Insignal Origen evaluation board based on Exynos4412";

compatible = "insignal,origen4412", "samsung,exynos4412";

memory { ---->"/memory"

reg = <0x40000000 0x40000000>;

};

kk{ ---->"/kk/bb"

bb{

};

};

chosen {

bootargs ="console=ttySAC2,115200";

};

firmware@0203F000 {

compatible = "samsung,secure-firmware";

reg = <0x0203F000 0x1000>;

};

....

};

设备树写法:

compatible 属性:描述设备的生产商以及设备型号

led节点:"fs4412,red", "fs6818,green"

/{

#address_cells 2#sizes_cells 1

gpx1: gpx1 {

gpio-controller;//gpio-controller:说明该节点描述的是一个gpio控制器

#gpio-cells = <2>;//#gpio-cells:描述gpio使用节点的属性一个cell的内容

interrupt-controller;//interrupt-controller 一个空属性用来声明这个node接收中断信号

interrupt-parent = <&gic>;// interrupt-parent 标识此设备节点属于哪一个中断控制器

0 1 2 3

interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,// <中断域 中断 触发方式>

<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;

4 5 6 7

#interrupt-cells = <2>;

};

led{

compatible = "fs4412,red","fs6818,green";

reg = <0x114001e0 0x114001e3 0x4>;

};

key-int{

interrupt-parent = &gpx1;

index: 0 1

interrupts = <0 2> , <1 2>; //中断和触发方式

/*1 = low-to-high edge triggered

2 = high-to-low edge triggered

4 = active high level-sensitive

8 = active low level-sensitive

*/

};

/*key--->gpx1_1*/

key-gpio{

compatible = "fs4412,key","fs6818,key";

key = <&gpx1 1 0>;//属性名=<&引用GPIO节点别名 GPIO标号 工作模式>;

};

};

设备树API:实际上是获取对应节点中的键值对(设备信息),首先应获取对应的键值对所在的设备节点,再在该设备节点中查找对应的键值;

struct device_node *of_find_node_by_path(const char *path)

功能:根据节点路径获取当前节点信息

参数:path:节点路径

返回值:成功: device_node的设备对象

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

功能:根据节点获取该节点中的中断号并完成映射

参数:dev:设备节点

index:序号

返回值:成功:返回对应的映射完的中断号

irqreturn_t (*irq_handler_t)(int, void *);//中断处理程序的函数原型

主要实现中断服务

*程序编写:按键中断

[1]原理图:key2----->(XEINT9)--->gpx1_1 25:57:int9

[2]在设备树中填充对应管脚的中断信息

/{

fs4412-key-int{

interrupt-parent = <&gpx1>;

interrupts = <1 2>,<2 2>;

index: 0 1

};

};

make dtbs

cp arch/arm/boot/dts/xxx.dtb ~/tftpboot

[3]在驱动中编写中断;

[3.1]模块三要素

[3.2]获取设备节点信息 np = of_find_node_by_path--->"/fs4412-key-int"

[3.3]获取该节点中的中断信息,并完成对应中断号的映射 irq_of_parse_and_map(np , 0);

[3.4]请求中断 request_irq();-->中断号+中断处理函数+中断触发电平

[3.5]中断处理函数笔记本: 228 - 驱动开发

创建时间: 2017/11/24 21:41 更新时间: 2017/11/29 9:21

作者: 干觐瑞

7 Linux设备驱动延缓机制

引入底半部的原因?

为了解决中断耗时,系统又必须得及时响应其他io或者是中断,进程调度这样一个矛盾;

上半部:主要是响应中断,并清除对应的标志位; + 调度底半部函数

底半部:做一些冗余的耗时操作;

【底半部】softirq:

tasklet: 中断上下文

[1]分类:TASKLET_SOFTIRQ HI_SOFTIRQ

[2]结构体、函数API

struct tasklet_struct

{

/*struct tasklet_struct *next;//下一个tasklet*/

void (*func)(unsigned long);//底半部处理函数

unsigned long data;//给func传递参数

};

static inline void tasklet_schedule(struct tasklet_struct *t)

主要实现 tasklet 的函数调用

[3]程序编写流程

1.定义对应的 struct tasklet_struct: func, data 的填充

2.在中断处理函数中调度底半部 tasklet: tasklet_schedule

工作队列:进程上下文,允许睡眠

[1]结构体

struct work_struct {

atomic_long_t data;

work_func_t func;//底半部函数

/*typedef void (*work_func_t)(struct work_struct *work);*/

};

[2]初始化函数

INIT_WORK(struct work_struct *wk ,work_func_t func);

/*

#define INIT_WORK(_work, _func) \

do { \

__INIT_WORK((_work), (_func), 0); \

} while (0)

#define __INIT_WORK(_work, _func, _onstack) \

do { \

__init_work((_work), _onstack); \

(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \

INIT_LIST_HEAD(&(_work)->entry); \

PREPARE_WORK((_work), (_func)); \

} while (0)

#endif

#define PREPARE_WORK(_work, _func) \

do { \

(_work)->func = (_func); \

} while (0)

*/

[3]提交工作队列

static inline bool schedule_work(struct work_struct *work);

[4]编写流程

1.定义当前的工作队列的结构体:struct work_struct wk;

2.初始化:INIT_WORK(struct work_struct *wk ,work_func_t func);

3.在中断的处理函数中对底半部进行相关的调度:schedule_work

8 定时器timer:

在内核中对时间的操作分类:

延时:A--------忙等/切换进程---------B

定时:指定时间点到达后执行某种动作;

START: t0 = 10jiffies//当前时刻

end: t2 = 20

expires = jiffies + 10jiffies

[1]jiffies:时钟滴答

通常 jiffies 在 linux 系统初始化阶段被初始化为0,当系统完成了“时钟中断”初始化之后,每个时钟中断“时钟滴答”处理例程中该值都会被加

一;

jiffies 存储自近启动以来的时钟滴答数;

[2]HZ:1s = n * jiffies

/*CONFIG_HZ = 200 可以更改*/

[3]jiffies时间操作

/*[3.1]时间比较:

time_after(a,b); //时间点a 在 b 之后 返回 true

time_before(a,b); //时间点a 在 b 之前 返true

time_after_eq(a,b); //时间点a 在 b 之后,等a=b时 返回 true

time_before_eq(a,b); //时间点a 在 b 之前,等a=b时 返回 true

time_in_range(a,b,c);//时间点a 在 b 与 c 之间,返回 true

a: jiffiesa b: jiffiesb

[3.2]时间转化:

unsigned int jiffies_to_msecs(const unsigned long j);

unsigned int jiffies_to_usecs(const unsigned long j);

unsigned long msecs_to_jiffies(const unsigned int m);

unsigned long usecs_to_jiffies(const unsigned int u);

[3.3]时间延时:

长延时:msleep(); //基于jiffies 让出处理器

短延时:udelay(); //mdelay();... for忙等待,粒度小

*/

[4]定时器

[4.1]结构体

struct timer_list {

unsigned long expires; //当前时间 : t0-->jiffies

延时: t1

t0+t1 =t2 --->定时处理函数

void (*function)(unsigned long ); //定时处理函数

unsigned long data; //定时器处理函数的传参

};

[4.2]函数

初始化定时器

void init_timer(struct timer_list *timer);

/*

#define init_timer(timer) \

__init_timer((timer), 0)

#define __init_timer(_timer, _flags) \

init_timer_key((_timer), (_flags), NULL, NULL)

void init_timer_key(struct timer_list *timer, unsigned int flags,const char *name, struct lock_class_key *key)

*/

向系统中添加对应的定时器

void add_timer(struct timer_list *timer);

修改定时器时间

int mod_timer(struct timer_list *timer, unsigned long expires)

每隔 20jiffies 调用一次对应的定时处理函数:

mod_timer(&tm,jiffies + 20 * jiffies);

struct timeval {__kernel_time_t tv_sec; /* seconds */

__kernel_suseconds_t tv_usec; /* microseconds */

};

void do_gettimeofday(struct timeval *tv);

--->old_time do_gettimeofday 获取一个老的时间---add_time r添加定时器

-->相隔一个时间段 --->do_gettimeofday 再去读取一个新的时间

*程序编写:timer:

9 Linux 设备驱动 GPIO 子系统

【1】内核中gpio的函数API--->gpiolib.c

[1]测试gpio端口是否合法:

int gpio_is_valid(int number);

[2]申请、释放gpio管脚

static inline int gpio_request(unsigned gpio, const char *label);

gpio:待控制的GPIO管脚编号 “gpa0_0”

“8”:表示控制8个引脚

static inline void gpio_free(unsigned gpio)

控制的GPIO管脚编号

[3]标记 gpio 的使用方向包括输入还是输出/*成功返回零失败返回负的错误值*/

static inline int gpio_direction_input(unsigned gpio)

控制的GPIO管脚编号

static inline int gpio_direction_output(unsigned gpio, int value)

控制的GPIO管脚编号 1

[4]获得 gpio 引脚的值和设置 gpio 引脚的值(对于输出)

static inline int gpio_get_value(unsigned gpio)

该管脚输出的电平状态 控制的GPIO管脚编号

static inline void gpio_set_value(unsigned int gpio, int value)

控制的GPIO管脚编号 ‘1’高 ‘0’低

[5]gpio当作中断口使用

static inline int gpio_to_irq(unsigned int gpio)

gpio对应的中断号 控制的GPIO管脚编号

【2】用户空间 gpio 的调用

在/sys/下提供了相关用户层与底层进行交互的属性文件;

/sys/class/gpio/

(0)内核配置

Device Drivers --->

-*- GPIO Support --->

[*] /sys/class/gpio/... (sysfs interface)如果说用户空间想跟内核中的GPIO管脚进行交互的话,首先需要将该管脚导出至用户空间;

(1)export/unexport

/sys/class/gpio/export

(2)/sys/class/gpio/gpioN:pin

direction: in/out

value:0/1

(3)/sys/class/gpio/gpiochipN

【3】gpio的led编写方式

1. GPIO管脚设备树的写法:

[1]改写设备树

led2 gpx2_7----来自于 gpx2 这一组管脚中的第七个

gpx2 这一组对应的设备树:exynos4x12-pinctrl.dtsi

gpx2: gpx2 {

gpio-controller;

#gpio-cells = <2>;----》表征的是其子节点或者是继承者的GPIO的<&gpx2 7 0>

中&gpx2后边的参数个数,7代表第七个管脚

interrupt-controller;

#interrupt-cells = <2>;

};

文件中 <2>:&gpx2 后面多跟两个参数

led2 gpx2_7

led3 gpx1_0

led4 gpf3_4

led5 gpf3_5

fs4412-leds-gpio{

compatible = "fs4412,leds";

/* index : 0 1*/

led2 = <&gpx2 7 0>,<&gpx2 4 0>;

led3 = <&gpx1 1 0>;

led4 = <&gpf3 4 0>;

led5 = <&gpf3 5 0>;

};

0:对应的工作模式

make dtbs

cp xxx.dtb ~/tftpboot

Makefile 的编写:

KERN_PATH := /home/linux/Docu/linux-3.14

all:

make -C $(KERN_PATH) M=$(shell pwd) modules CROSS_COMPILE=arm-linux- ARCH=arm

cp *.ko /home/linux/Docu/busybox-1.22.1/_install

clean:

make -C $(KERN_PATH) M=$(shell pwd) clean

obj-m:=demo.o

[2]程序编写

[2.1]将设备树中的设备信息所在的节点获取

np = of_find_node_by_path

gpio获取:

of_get_named_gpio

static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index)返回值:管脚号

参数:np:该gpio所在的节点

propname:键值“led2”

index:该键值对应的gpio组号

[2.2]获取该节点中的设备信息

[2.3]执行gpio相关的操作

【4】按键消抖

gpx1: gpx1 {

gpio-controller;

#gpio-cells = <2>;

interrupt-controller;

interrupt-parent = <&gic>;

interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,

<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;

#interrupt-cells = <2>;

};

lesson4-interrupt:

fs4412-key-int{

compatible = "fs4412,key-int";

interrupt-parent = <&gpx1>;

interrupts = <1 2> ;

};

lesson5-gpio:

fs4412-key-gpio{

compatible = "fs4412,key-gpio";

key2 = <&gpx1 1 0>;

};

思路:设备树--->key-->gpio--->irq-->request_irq

-->int_handler->delay(添加、修改对应的定时器的到时时间)

-->在定时器处理函数中进行按键状态的再次判定

【0】了解设备信息--->原理图

【1】设备树:

fs4412-key-gpio{

compatible = "fs4412,key";

/* index : 0 1*/

key2 = <&gpx1 1 0>;

};

make dtbs

cp xxx.dtb ~/tftpboot

【2】设备驱动相关

[2.1]获取该按键对应的节点

[2.2]根据该节点获取按键所对应的 gpio 管脚号

[2.3]gpio_to_irq:将gpio管脚所对应的中断号拿到

[2.4]绑定中断号,中断处理函数,触发电平

[2.5]在中断处理函数中修改定时器时间,在该时间内,起到了消抖的作用

[2.6]在定时器处理函数中验证是否真的按下;

10 Linux 虚拟总线 platform 框架

【宏观:总线、设备、驱动】

为什么引入 platform?主要用在哪呢?

设 备 、 驱 动 分 离 的 提 出 主 要 是 为 了 隔 离 BSP 与 驱 动 , 在 BSP 中 定 义 设 备 和 设 备 使 用 的 资 源 ( IO 、 irq 等) 、 设 备

的 具 体 配 置 信 息 , 而 在 驱 动 中 只 需 要 通 过 API 去 获 取 资 源 和 数 据 , 做 到 板 级 相 关 代 码 与 驱 动 代 码 进 行 分 离 , 使

驱 动 具 有 更 好 的 可 扩 展 性 及 跨 平 台 性 。

如何联系设备与驱动?

答 . 总 线 。

原理?【绘图】

platform 的基于?

1. 总线

[1.1]总线结构体

struct bus_type

[1.2]相关API

int bus_register(struct bus_type *bus); //注册总线

void bus_unregister(struct bus_type *bus); //注销总线

[1.3]注册成功现象--->/sys/bus/i2c /sys/bus/platform

2. 设备

[2.1]设备结构体

struct device

[2.2]相关API

int device_register(struct device *dev); //注册设备

void device_unregister(struct device *dev); //注销设备

[2.3]注册成功现象

/sys/bus/i2c/device

/sys/bus/platform/device

3. 驱动

[3.1]驱动结构体

struct device_driver

[3.2]相关API

int driver_register(struct device_driver *drv); //注册设备驱动

void driver_unregister(struct device_driver *drv); //注销设备驱动[3.3]注册成功现象

/sys/bus/i2c/driver

/sys/bus/platform/driver

【虚拟总线platform框架】用于实际没有总线的设备及驱动;

1. platform总线

[1.1]总线结构体

struct bus_type platform_bus_type = {

.name = "platform",

.match = platform_match,//匹配该总线上的设备及驱动

};

2. platform设备 + 资源

主要都是对结构体的填充和注册

[2.1]设备结构体struct platform_device

struct platform_device {

const char *name; //表征当前挂载在platfrom总线上的设备的名称

int id; // -1 name.9

struct device dev;

u32 num_resources;//当前设备的资源的数量

struct resource *resource;

};

struct device {

void (*release)(struct device *dev); //设备注销时执行

};

struct resource {

unsigned long flags;//IORESOURCE_MEM,IORESOURCE_IRQ

resource_size_t start;

resource_size_t end;

};

0x114001e4 --->0x114001e8

.flags = IORESOURCE_MEM; start,end:表征内存资源的起始及终止地址

.flags = IORESOURCE_IRQ; start,end:一致,都写生对应的中断号

[2.2]相关API

int platform_device_register(struct platform_device *pdev);

功能: 完成 platfrom_device 的注册

void platform_device_unregister(struct platform_device *pdev);

功能: 完成 platfrom_device 的注销

[2.3]注册成功现象

3. platform 驱动

[3.1]驱动结构体:struct platform_driver

struct platform_driver {

int (*probe)(struct platform_device *); //当设备与驱动匹配成功之后,自动调用

int (*remove)(struct platform_device *); //当设备或者驱动移除时,被调用

struct device_driver driver;

const struct platform_device_id *id_table;

};

.remove:当dev或drv中任意一个移除时调用

1. 获取设备资源

2. 映射内存地址

3. 配置寄存器

4. 输出电平

struct device_driver {

const char *name; //当前驱动支持的设备的名称

struct module *owner; //指向该模块--->THIS_MODULE

const struct of_device_id *of_match_table; //设备节点匹配信息

};

struct of_device_id

{char compatible[128];

};

struct platform_device_id {

char name[PLATFORM_NAME_SIZE];//支持的设备名称

};

platform_device 与 platform——driver 匹配成功的规则:

device driver

(1)platfrom_device->name = platform_driver->device_driver->name 相等时,进行匹配--->probe

[ps:struct platform_device_id不写的情况下执行]

(2)platfrom_device->name = struct platform_device_id->name

-----------------------------------------------------------------------------------------

device_tree driver

(3)compatible = platform_driver->device_driver->of_device_id

[3.2]相关API

注册 platform_driver

int platform_driver_register(struct platform_driver *drv);

/*

#define platform_driver_register(drv) \

__platform_driver_register(drv, THIS_MODULE)

int __platform_driver_register(struct platform_driver *drv,struct module *owner)

*/

注销 platform_driver

void platform_driver_unregister(struct platform_driver *drv)

MODULE_DEVICE_TABLE();

创建一个名为__mod_type_device_table的局部变量,在稍后的内核构建过程中,depmod程序在所有的模块中搜索符号

__mod_type_device_table。如果找到了该符号,它把数据从该模块中抽出,添加到文件 / lib/ modules/ KERNEL_VERSION/

module.typemap 中。当depmod结束之后,内核模块支持的所有type类型设备连同他们的模块名都在改文件中被列出。当内核告知热

插拔系统一个新的type类型设备已经被发现时,热插拔系统使用module.typemap文件来寻找要加载的适当的驱动程序。

[3.3]注册成功现象

[root@farsight ]# insmod pdev.ko

[ 9.290000] pdev_init,45

[root@farsight ]# insmod pdrv.ko

[ 12.215000] pdrv_probe,11

[ 12.220000] pdrv_init,41

[root@farsight ]# rmmod pdrv

[ 36.310000] pdrv_remove,16

[ 36.315000] pdrv_exit,48

[root@farsight ]# insmod pdrv.ko

[ 71.390000] pdrv_probe,11

[ 71.395000] pdrv_init,41

[root@farsight ]# rmmod pdev

[ 79.150000] pdrv_remove,16

[ 79.155000] i am go ...pdev_release,29--->

[ 79.160000] pdev_exit,52

4. platform框架驱动编写:框架,*/

填充结构体://const struct platform_device_id *id_table;

const struct of_device_id of_table[] = {//设备节点匹配信息

[0] = {.compatible = "142526"},

[1] = {/*NULL*/},

};

struct platform_driver pdrv = {

.probe = pdrv_probe, //当设备与驱动匹配成功之后,自动调用

.remove = pdrv_remove, //当设备或者驱动移除时,被调用

.driver = {

.owner = THIS_MODULE, //指向该模块--->THIS_MODULE

.name = "pdev0", //当前驱动支持的设备的名称

.of_match_table = of_match_ptr(of_table), //设备节点匹配信息

},

};

//const struct platform_device_id *id_table;

const struct of_device_id of_table[] = {//设备节点匹配信息

[0] = {.compatible = "fs4412,key-int"},

[1] = {/*NULL*/},

};

struct platform_driver pdrv = {

.probe = pdrv_probe, //当设备与驱动匹配成功之后,自动调用

.remove = pdrv_remove, //当设备或者驱动移除时,被调用

.driver = {

.owner = THIS_MODULE, //指向该模块--->THIS_MODULE

.name = "xxxx", //当前驱动支持的设备的名称

.of_match_table = of_match_ptr(of_table), //设备节点匹配信息

},

};

设备端提供资源--->platfrom_device->resource

驱动端获取资源:platform_get_resource

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)

功能:获取对应平台设备或者设备树中的设备资源

参数:dev:platform_device的指针对象

type:获取的资源的类型:IORESOURCE_MEM, IORESOURCE_IRQ

num:资源序号

返回值:成功: struct resource的指针对象 失败:NULL

/*led,key(device_tree无需在映射irq)*/

前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2018 北京华清远见科技发展有限公司 版权所有 ,京ICP备16055225号,京公海网安备11010802025203号
返回

学员笔记

星创客 - 华清远见旗下高端IT培训品牌

当前位置: 星创客 > 学习资源 > 学员笔记 >

驱动开发笔记
来源: 星创客 作者: 星创客 时间:2018-01-15

0 课程安排 1.字符设备驱动 2.io相关操作 + 并发竟态 3.io模型 + 中断 4.linux延缓机制 + 内存 5.led + platfrom 6.接口 7.iic 8.block + net笔记本: 228 - 驱动开发 创建时间: 2017/11/17 19:26 更新时间: 2017/...

0 课程安排

1.字符设备驱动

2.io相关操作 + 并发竟态

3.io模型 + 中断

4.linux延缓机制 + 内存

5.led + platfrom

6.接口

7.iic

8.block + net

1. 字符设备驱动

驱动的概念:连接计算机与相关硬件的一段程序;

【计算机组成】

硬件:power、in/out、内存、机械硬盘、cpu……

软件:app (微信、QQ),sys operation

linux体系架构:

用户空间【0-3g】

app:

libc:

内核空间【3-4g】

[1]进程管理:负责进程的创建,销毁,通信等;

[2]内存管理:内存分配

[3]文件系统:文件的存储,管理。

[4]设备驱动:主要是负责与相关硬件的控制操作逻辑,给上层提供操作硬件的接口;

字符设备驱动:例如:鼠标,键盘,lcd(帧缓冲),以字节为单位进行,顺序访问,无缓存。

块设备驱动: 例如:U盘,机械硬盘,ssd。以块(512字节)为单位进行访问,可以随意访问,有缓存;

网路设备驱动:路由器,网卡;

[5]网络协议栈:对网络协议的封装,逻辑编写等;

arch: x86 arm

hardware

【基于linux内核的设备驱动开发】

(1)什么叫做模块?为什么引入模块?

module:linux 为外部提供的一个接口 ,(LOADABLE KERNEL MODULE )lkm。

为了简化对于内核修改操作的流程及时间;

模块特性:可以被单独编译,但是不能单独运行;

单内核[宏]:将 linux 整体作为一个大过程来实现,并运行在一块地址空间内,期间的通信通过函数调用;

优点:运行效率高;

缺点:灵活性,可扩展性比较差;

(2)linux设备驱动开发的一般流程?

【1】将具体的功能编写成 xxx.c ----->设备驱动

【2】将其测试完毕之后(生成对应的模块,插入到相关内核中,并对其功能进行测试,测试成功之后)

【3】将该该模块直接编译进内核。uImage

(3)内核模块编写?

内核模块三要素:男 + 女--->父母 -->领证

男:入口函数:

女:出口函数:

见父母:将入口、出口函数见内核:

领证:GPL协议:

(4)内核打印函数?打印等级?级别更改?[/proc/sys/kerenl/printk]

printf(“打印格式”,待打印的数据);

printk(打印等级“打印格式”,待打印的数据);

打印等级:

#define KERN_EMERG "<0>" /* system is unusable */

#define KERN_ALERT "<1>" /* action must be taken immediately */

#define KERN_CRIT "<2>" /* critical conditions */

#define KERN_ERR "<3>" /* error conditions */

#define KERN_WARNING "<4>" /* warning conditions */

#define KERN_NOTICE "<5>" /* normal but significant condition */

#define KERN_INFO "<6>" /* informational */

#define KERN_DEBUG "<7>" /* debug-level messages */

<0>------------<7>

高 低

控制台相关的打印级别:

#define console_loglevel (console_printk[0]) //当前控制台的打印级别

#define default_message_loglevel (console_printk[1]) //默认消息的打印机别 printk(“打印格式”,待打印的数据);

#define minimum_console_loglevel (console_printk[2]) //控制台可设置的小打印级别

#define default_console_loglevel (console_printk[3]) //内核启动时的打印级别

模块-------------------------->内核里面----->Makefile

|\/

依赖待插入的内核来编译<----参数,符号

(5)编译模块Makefile?

uname -r:获取当前内核的版本---》3.13.0-135-generic

Makefile路径:/lib/modules/3.13.0-135-generic/build

KERN_PATH:=/lib/modules/$(shell uname -r )/buildall:

make -C $(KERN_PATH) M=$(shell pwd) modules

clean:

make -C $(KERN_PATH) M=$(shell pwd) clean

obj-m:=demo.o

-C:获取待插入内核的makefile的路径

M:待编译的模块的路径

执行 make --生成->xxxx.ko---->模块文件

file demo.ko

demo.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV),

BuildID[sha1]=5d8db51a9a9f456bb6070ab580c1c8e1e7fafe06, not stripped

(6)查看模块信息?插入模块?移除模块?

linux@ubuntu64-vm:~/Docu/Driver/cdev$ modinfo demo.ko

filename: /home/linux/Docu/Driver/cdev/demo.ko

license: GPL

srcversion: A31DE5AAC03DFEA37EB625F

depends:

vermagic: 3.13.0-135-generic SMP mod_unload modversions

sudo dmesg -C/-c #清除内核打印信息

insert module--->sudo insmod xxx.ko #插入模块

rm module--->sudo rmmod xxx #移除模块

当执行模块相关操作的时候,要使用sudo

(7)模块与应用程序区别?

模块 应用程序

API来源 linux内核 libc库

编译方式 makefile gcc

入口函数 init main

运行权限 super user user

运行空间 内核空间 用户空间

(8)内核模块传参? moduleparam.h ---> 权限:/sys/module/模块名/parameters

模块描述信息:

MODULE_DESCRIPTION("STRING")

作者描述信息:

MODULE_AUTHOR("FARSIGHT")

参数描述信息:

MODULE_PARM_DESC(对应的参数名称, “参数描述信息”)

例:

MODULE_PARM_DESC(num, "int num module_param");

MODULE_PARM_DESC(buf_out,"buf_out-->buf_in module_param_named");

MODULE_PARM_DESC(string,"string module_param_string");

MODULE_PARM_DESC(array, "array module_param_array"

传递指定类型的变量[全局]:

module_param(“参数的名称”, type参数的类型, 参数的权限)

rwx rwx r-x

root group user

421 421 4-1

----------------->普通用户不能有写权限

type: bool, invbool, charp, int, long, short, uint, ulong, ushort;

例:

module_param(num, int, 0775)

module_param(str, charp, 0775)

传递变量:

module_param_named(外部传递参数的名称, 内部接收的参数的名称, 参数类型, 参数的权限

例:

module_param_named(buf_out, buf_in, int, 0775)

传递字符串到全局字符数组:

module_param_string(外部传递字符串的参数名称, 内部接收字符串的参数名称, 字符串的长度, 参数的权限)

例:

module_param_string(string, string, sizeof(string), 0775);传递数组型参数:

module_param_array(数组名称, 数组成员的类型, 外部向内部模块传递的数组成员的个数, 参数的权限)

例:

module_param_array(array, int, &size, 0775)

(9)内核模块导出符号?[注意:加载卸载顺序]export.h-->/usr/src/linux-headers-3.5.0-23-generic/Module.symvers: addr + symbol + module +

EXPORT_SYMBOL/proc/kallsyms

#include <linux/export.h>

1.有一个导出符号的程序;--->导出 ---插入

2.有对应的使用者:使用1中导出的符号;-->引入--->使用--->插入到对应的内核

模块插入顺序:先插导出者 再插使用者 【因为导出对应符号之后才能使用】

模块卸载顺序:先卸载使用者 再卸载导出者 【因为只有没有使用导出符号的模块时,才能卸载导出者】

EXPORT_SYMBOL(待导出的符号)

EXPORT_SYMBOL_GPL(待导出的符号)

EXPORT_SYMBOL_GPL_FUTURE(待导出的符号)

68 #define

EXPORT_SYMBOL(sym) \

69 __EXPORT_SYMBOL(sym, "")

例:

int add(int a, int b)

{

return a + b;

} i

nt sub(int a, int b)

{

if(a > b)

return(a - b);

else

return(b - a);

} E

XPORT_SYMBOL(add);

EXPORT_SYMBOL_GPL(sub);

extern int add(int a, int b);

extern int sub(int a, int b);

int a = 6, b = 3;

printk("add: %d, sub: %d\n", add(a,b), sub(a, b));

内核版本:3.5--->符号3.5

模块用3.6内核 符号来编译的

/*

//导出符号的版本crc:

56 #define __EXPORT_SYMBOL(sym,

sec) \

57 extern typeof(sym) sym; \

58 __CRC_SYMBOL(sym, sec) \

//当前导出符号存放的段

59 static const char __kstrtab_##sym[] \

60 __attribute__((section("__ksymtab_strings"), aligned(1))) \

61 = VMLINUX_SYMBOL_STR(sym);

//区分EXPORT_SYMBOL、EXPORT_SYMBOL_GPL、EXPORT_SYMBOL_FUTURE

62 extern const struct kernel_symbol __ksymtab_##sym; \

63 __visible const struct kernel_symbol __ksymtab_##sym \

64 __used \

65 __attribute__((section("___ksymtab" sec "+" #sym), unused)) \

66 = { (unsigned long)&sym, __kstrtab_##sym }

vmlinux.lds:./arch/arm/kernel/vmlinux.lds

*/

【字符设备驱动】

struct inode, struct cdev, struct file结构体介绍

‘/dev/xxx’----->struct inode

struct inode {

umode_t i_mode;//记录设备类型const struct inode_operations *i_op;//inode的相关操作集合

dev_t i_rdev;//设备号--->如果说当前的‘path’对应的时设备文件,那么该设备文件会有对应的 设备号

union { //描述设备结构体

struct pipe_inode_info *i_pipe;//net

struct block_device *i_bdev;//块

struct cdev *i_cdev;//字符

};

……

};

struct cdev {

/*struct kobject kobj; linux设备模型相关*/

struct module *owner; //当前cdev对应的模块

const struct file_operations *ops;//操作当前设备的相关的方法:open,close,read,write,llseek

dev_t dev; //设备号

unsigned int count; //设备数量

};

struct file_operations {

struct module *owner;//指代该操作方法集合所指向的模块

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//读方法

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//写方法

unsigned int (*poll) (struct file *, struct poll_table_struct *); //异步阻塞io --->select

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//相关控制操作

int (*open) (struct inode *, struct file *);//open

int (*release) (struct inode *, struct file *);//close

int (*fasync) (int, struct file *, int);//异步通知

};

(1)设备号

dev_t 设备号的数据类型: 12bit + 20bit

主设备号 次设备号

驱动 驱动控制的若干设备

dev_t dev_no;

获取主设备号major:

major = MAJOR(dev_no)

获取次设备号minor:

minor = MINOR(dev_no)

利用主次设备号获取设备号:

dev_no = MKDEV(major,minor)

(2)函数API

买房: 房间号 设备号

根据房间号--->房间 cdev申请

装修 cdev初始化

入住 cdev注册

设备号 :

(1)自动申请设备号

alloc char device region

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

功能:自动申请设备号

参数:dev:设备号的指针对象

baseminor:起始次设备号

count:申请的设备的数量

name:设备名称

返回值:成功:0 失败:errno code

例:

#define DEV_NAME "demo"

dev_t from;

int baseminor = 0;

int count = 1;

ret = alloc_chrdev_region(&from, baseminor, count, DEV_NAME);

if(ret)

printk("alloc_chrdev_region fail...%s, %d", __func__, __LINE__)

(2)手动申请设备号register char device region

int register_chrdev_region(dev_t from, unsigned count, const char *name)

功能:手动申请设备号

参数:from:设备号

count:申请的设备的数量

name:设备名称

返回值:成功:0 失败:errno code

(3)释放设备号

void unregister_chrdev_region(dev_t from, unsigned count)

功能:释放设备号

参数:from:设备号

count:申请的设备的数量

返回值:无

例:

ERR_STEP1:

unregister_chrdev_region(from, count);

cdev申请:

struct cdev *cdev_alloc(void)

功能:cdev申请

参数:无

返回值:成功:cdev指针对象 失败:NULL

例:

struct cdev *cdev = NULL;

cdev = cdev_alloc();

if(!cdev){

printk("cdev_alloc fail...%s, %d", __func__, __LINE__);

goto ERR_STEP1;

}

cdev初始化:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

功能:cdev初始化

参数:cdev:cdev的指针对象

fops:cdev的操作方法集合

返回值:无

例:

cdev_init(cdev, &f_ops);

cdev注册:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

功能:cdev的注册

参数:p:cdev的指针对象

dev:cdev的设备号

count:设备数量

返回值:成功:0 失败:errno code

例:

ret = cdev_add(cdev, from, count);

if(ret)

printk("alloc_chrdev_region fail...%s, %d", __func__, __LINE__);

app:open read write ...

模块中:open read write

[struct cdev.fops = struct file_operations]

[struct file_operations.open/read/write]

例:

struct file_operations f_ops = {

.owner = THIS_MODULE,.open = demo_open,

.release = demo_release,

};

[36917.054428] major:250 minor:0

mknod /dev/xxx c/*b*/ 250 0

设备文件[path] 表示当前的设备节点是为字符(char)还是块设备(block)创建的

/*回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数

时,我们就说这是回调函数*/

(3)驱动程序 + 测试程序

#define PATH "/dev/demo"

fd = open(PATH,O_RDWR);

if(fd < 0){

perror("open");

} c

lose(fd);

/*(4)调用关系

(5)将模块编译进内核*/

2 linux 设备驱动 IO 操作

【字符设备的高级操作】

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops

功能:完成字符设备框架相关的东西

参数:major:主设备号【0:自动分配 其他数据:手动】

name:设备名

fops:操作方法集合

返回:返回主设备号

static inline void unregister_chrdev(unsigned int major, const char *name)

功能:与register_chrdev相反

参数:major:主设备号

name:设备名

返回:无

(1)读写

#include <asm/uaccess.h>

READ:

ker_date ---- copy_to_user ----》user

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

功能:将内核的数据传递给用户空间

参数:to:用户空间的地址

from:内核空间的指针

n:传递的数据的大小

返回值:成功: 0 失败:传输的数据的个数

WRITE:

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)

功能:将用户的数据传递给内核空间

参数:to:内核空间的地址

from:用户空间的指针

n:传递的数据的大小

返回值:成功: 0 失败:传输的数据的个数

(2)控制(32bits 被分为四部分)ioctl:

#include <asm-generic/ioctl.h>

命令码定义: _IO/_IOR/_IOW/_IOWR

#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)

#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOC(dir,type,nr,size) \

(((dir) << _IOC_DIRSHIFT) | \ /*<<29=30:31*/

((type) << _IOC_TYPESHIFT) | \ /*<<8=8:15*/

((nr) << _IOC_NRSHIFT) | \ /*<<0=0:7*/

((size) << _IOC_SIZESHIFT)) /*<<16=16:29*/

DIR size type nr

31:30 29:16 8:15 0:7

#define TYPE 'A'

#define ONE _IO(TYPE,1)

#define TWO _IO(TYPE,2)

内核中已经被占用的命令码文件:linux-3.14/Documentation/ioctl/unlocked_ioctl

(3)自动创建设备节点[/proc/sys/kernel/hotplug]--->udev

#include <linux/device.h>

[1] 设备类

struct class {

const char *name;//类名

struct module *owner;//拥有该类的模块指针

};

#define class_create(owner, name) \

/*({ \

static struct lock_class_key __key; \

__class_create(owner, name, &__key); \struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)

})*/

struct class *class_create(struct module *owner,const char *name)

功能:创建struct class

参数:owner:指代其所在的模块//THIS_MODULE

name:类名

返回值:成功:struct class指针对象 失败:NULL

现象:在/sys/class下产生一个名字为‘name’的目录

例:

#define NAME "demo"

#define CLASS_NAME "demo_class"

cls = class_create(THIS_MODULE, CLASS_NAME);

if(IS_ERR(cls)){

ret = PTR_ERR(cls);

goto ERR_STEP1;

}

void class_destroy(struct class *cls)

功能:销毁struct class

参数:cls:struct class的指针对象

返回值:无

IS_ERR(void *ptr); //判断指针的正确与否

PTR_ERR(void *ptr); //将错误指针---->errno

[2]设备

struct device {

struct device *parent; //当前设备的父设备

struct device_private *p; //指向该设备的驱动相关的数据

const char *init_name; //设备名

const struct device_type *type;

struct device_driver *driver;

};

#define NAME demo

int i;

struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const

char *fmt, ...)

功能:创建对应的 device

参数:cls:struct class的指针对象

parent:设备父对象一般NULL

dev_t:设备号

drvdata:设备私有数据

fmt:设备命名的格式“%s%d”

...:NAME

demo0 demo1 demo2....

/dev/demo0 /dev/demo1

返回值:成功:struct device指针对象 失败:NULL

例:

//device: 250 0

//device: 250 1

//dev_no = MKDEV(major, minor);

for(i = minor; i < minor + count; i++){

dev = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", NAME, i);

if(IS_ERR(dev)){

ret = PTR_ERR(dev);

goto ERR_STEP2;

}

}

现象:在/sys/class/(class名称)/(device) 名称下产生一个名字为‘name’的目录

void device_destroy(struct class *class, dev_t devt)

功能:销毁对应的 device

参数:cls:struct class的指针对象

dev_t:设备号

返回值:成功:struct device指针对象 失败:NULL

例:

ERR_STEP2:

for(i--; i >= minor; i--)

device_destroy(cls, MKDEV(major, i));class_destroy(cls);

3 Linux 设备驱动内存分配

内存分配函数都依赖于内核中一个构件:内存管理

linux下对内存管理总体上可以分为两大类:(1)物理内存管理 (2)虚拟内存管理

【物理内存相关】

(1)物理内存中内存相关结构体

内存节点node:

(1)引入此概念的原因:

内存管理模型:

uma:一致性内存管理模型

numa:非一致性内存管理模型--->速度

(2)数据结构 struct pglist_data

内存区域zone:

概念:内存区域是内存节点中的一个概念,将内存节点管理的物理内存划分为不同的区域。

分区:低端内存区(dma, normal),/*高端内存区*/

结构体:struct zone

区域的类型:

enum zone_type {

#ifdef CONFIG_ZONE_DMA

ZONE_DMA,

#endif

ZONE_NORMAL,

#ifdef CONFIG_HIGHMEM

ZONE_HIGHMEM,

#endif

};

内存页 page:内存管理的小单元--->一般来说 4k----->大小取决于mmu memory manager unit

struct mem_map[]

【内存分配区间图】

(2)物理内存中管理机制:

{1}页面级内存管理(页面分配器)

用于连续大块的物理内存区分配;

分配区间取决于gfp_mask标志:

[1]内存管理部件内部使用的标志

__GFP_WAIT | __GFP_IO | __GFP_FS

__GFP_HIGH

[2]外部使用的内存分配标志

GFP_KERNEL/GFP_ATOMIC

[3]标志查找效果:

[3.1]ZONE_DMA/ZONE_NORMAL:

[3.2]ZONE_HIGHMEM:标志查找过程:

__GFP_DMA: ZONE_DMA

__GFP_NORMAL: ZONE_NORMAL--->ZONE_DMA

该区域内物理-->虚拟:page_address

__GFP_HIGHMEM: ZONE_HIGHMEM----->ZONE_NORMAL--->ZONE_DMA

__GFP_HIGHMEM由于虚拟地址空间的高端页面尚未建立映射关系,故需要:

[3.2.1]申请对应虚拟高端内存

[3.2.2]将申请的内存区经过kmap进行映射 才能映射到实际的物理内存区

[4]页面分配器函数的核心:

alloc_pages && __get_free_pages ---------->alloc_pages_node

alloc_pages 与 __get_free_pages的不同:

前者较后者能分配高端内存以及低端内存;

后者只能获取低端内存区的内容;

[4.1]__alloc_pages/alloc_page<-------->__free_pages

static inline struct page *__alloc_pages(gfp_t gfp_mask, unsigned int order,struct zonelist

*zonelist)

#define alloc_page(gfp_mask)

分配4K或者其的若干倍

gfp_mask:表征其分配的内存区属于那一块

[4.2]__get_free_pages/__get_free_page<-------->free_pages

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

#define __get_free_page(gfp_mask)

gfp_mask:表征其分配的内存区属于那一块

[4.3]get_zeroed_page:分配相关内存区并清零

unsigned long get_zeroed_page(gfp_t gfp_mask)

gfp_mask:表征其分配的内存区属于那一块

#define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS)

表征在内存分配时可以被阻塞 | 表征在内存分配时可以执行对应的 io操作

| 可以执行对应文件系统相关的

#define GFP_ATOMIC (__GFP_HIGH)

在内存分配时,不可被打断(以原子方式)

{2}给予页面管理之上的 slab 管理:

linux 系统在物理页分配的基础上实现了对更小内存空间进行管理的slab分配器。

slab 思想:将一个内存页或者若干内存页分配成若干相等的小内存,供程序员使用。

slab 分配器限制:只能在低端内存区进行分配;

典型:kmalloc :128k

static __always_inline void *kmalloc(size_t size, gfp_t flags)

static inline void *kzalloc(size_t size, gfp_t flags);//基于kmalloc,并将其分配的内存区清零

分配的虚拟内存连续,物理内存区也是连续的。

【2】虚拟内存管理

转换过程: 物理地址---mmu---建立页表--->虚拟地址

[1]vmalloc / vfree

void *vmalloc(unsigned long size);

void vfree(const void *addr);

分配的虚拟内存连续,物理内存区不一定连续的。[2]linux内核虚拟地址空间构成

4g------------------------------------

/* 4k

-----------------------------------

固定内存映射区 4m-4k

-----------------------------------

高端内存映射区 4m

------------------------------------*/

NULL 8K(保护)

------------------------------------vmalloc end

vmalloc内存区120m-8m-8k

------------------------------------vmalloc start

vmalloc offset 8m

------------------------------------

物理内存映射区896M

3g------------------------------------ 物理内存 3g 偏移 4k + 3g

用户空间

0g------------------------------------

[3]与硬件操作相关;各个地址的概念

(1)物理地址:实际物理总线上的地址

(2)逻辑地址:程序经过反汇编出现在反汇编代码中的地址就是逻辑地址

(3)线性地址:又称之为虚拟地址,程序员操作虚拟地址。

[4]ioremap

ioremap 将 vmalloc 区的某段虚拟内存块映射到 io 空间,io 空间在不同的架构上有不同的解释,为了统一内核为 io 操作提供了统一的接

口:readl/writel 等其在不同的平台上会展开成架构相关的代码。

void __iomem *ioremap(unsigned long paddr, unsigned long size)

功能:映射物理地址--》虚拟地址

参数:paddr:物理地址

size:映射大小

返回值:成功:返回该物理地址对应的虚拟地址 失败:NULL

static inline void iounmap(volatile void __iomem *addr)

功能:解除映射关系

参数:addr:虚拟地址

返回值:无

static inline u32 readl(const volatile void __iomem *addr)

功能:读取虚拟地址中的数据

参数:addr:虚拟地址

返回值:对应的数据

static inline void writel(u32 b, volatile void __iomem *addr)

功能:像虚拟地址中写入指定的数据

参数:b:待写入的数据

addr:虚拟地址

返回值:无

【*】程序编写:led灯驱动

[1]查看原理图--->led的控制管脚

GPF3_5

[2]根据对应的管脚查看芯片手册,去配置该管脚的功能及使用

#define GPF3CON 0X114001E0 --> OUTPUT

#define GPF3DAT 0X114001E4 --> value[3]写驱动

[3.1]将对应的io管脚控制的物理地址进行相关映射

virt_addr = ioremap(GPF3CON);

[3.2]完成配置

readl、writel

[3.3]实现功能

led:IO---->设置对应的IO模式:输入、输出、中断。。。

GPF3_5: 23:20 ---> 0x1 --->output

arm:读 改 写

读取 23:20--->readl--->data

改:23:20--->data: x x x x ---->data = readl(gpf3con);

8 4 2 1

清零:&~ (0xf<<20)------->data &~ (0xf<<20)

写:23:20--->data: writel((0x1<<20), gpf3con);

4 Linux 设备驱动中的并发与竟态

【并发竟态】

(1)概念来源?

linux支持多进程,多cpu,可能因为多个并发执行单元操作,造成竟态。

竟态:若干并发执行单元,对同一块资源进行操作时,造成该资源数据紊乱的状态;

为了确保共享资源的访问安全的措施:

互斥:强调排他性;

同步:强调顺序性;

补充概念:

临界区:访问共享资源的的代码段;

临界资源:共享资源

并发源:多个执行单元

(2)竟态的产生的情况:

[1]smp:对称多处理器结构,cpu并发执行造成的竟态;

单cpu:

[2]可抢占:进程和进程间抢占

[3]进程和中断间的竟态

为了防止产生竟态,将访问共享资源的代码段进行排他性访问或者顺序性访问;

(3)解决方式:

单cpu:[2][3]--->中断屏蔽:在进入临界区之前屏蔽中断,之后进入临界区对共享资源进行独占,完成后解除中断屏蔽;

/*中断屏蔽*/ --->高等级的进程 + 中断 不能干扰程序执行

执行访问共享资源的操作...//临界区代码访问临界资源

/*中断解除屏蔽*/--->高等级的进程 + 中断能 干扰程序执行

应用注意事项:尽量快速【原因:异步io及进程调度依赖于中断】

【1】基础版:

local_irq_disable();

......

//临界区代码访问临界资源,耗时尽量少

local_irq_enable();

【2】升级版:

与基础版不同于:寄存器 FLAG 中的标志被 flags

local_irq_save(flags);//关闭中断

//临界区代码访问临界资源,耗时尽量少

local_save_flags(flags);//开启中断

[1][2][3]--------->自旋锁:适用于smp,对称多处理器系统

单cpu且内核可抢占系统<--->在上锁期间内核抢占被禁止

进入临界区处理完之后解锁;若在其他进程上锁期间欲获取锁则会导致该进程忙等;

目的:保护共享资源

核心思想: 在多处理器上设置了一个全局变量 V , V = 1 ,表征上锁, V = 0,

上锁过程:"READ-TEST-SET"/*[原子:将若干条执行语句,在cpu上的执行过程认为是一条指令]*/获取锁过程:

应用注意事项:任何上下文、不可睡眠、尽量快忙等锁,易导致死锁;

函数API:

类型:

spinlock_t lock;

初始化:

spin_lock_init(spinlock_t *lock);

功能:初始化自旋锁

#define spin_lock_init(_lock)

static inline raw_spinlock_t *spinlock_check(spinlock_t *lock)

上锁:

static inline void spin_lock(spinlock_t *lock);

解锁:

static inline void spin_unlock(spinlock_t *lock);

/*volatile:被设计用来修饰被不同线程访问和修改的变量。确保本条指令不会因编译器的优化而省略,且要求每次直接读值。*/

【*】程序编写:使用自旋锁实现设备只能被一个进程打开

[1][2]--------->信号量:与自旋锁类似,进入临界区处理完之后解锁,若在其他进程上锁期间欲获取锁则会导致该进程休眠;

应用注意事项:进程上下文、可睡眠;

结构体:

struct semaphore

struct semaphore {

raw_spinlock_t lock;//自锁锁

unsigned int count;//信号量的个数

};

函数:

初始化:

static inline void sema_init(struct semaphore *sem, int val);

例:

sema_init(&sem, 1);

获取信号量的函数:

int down_trylock(struct semaphore *sem);

例:

if(down_trylock(&sem)){

printk("can not access...\n");

return -EBUSY;

} 释放

信号量:

void up(struct semaphore *sem);

【*】程序编写:使用信号量实现设备只能被一个进程打开

PS:互斥锁 mutex,是信号量的 count 成员为1的特殊用法,以实现互斥

定义:

struct mutex

函数:

初始化:

void mutex_init(struct mutex *lock);

#define mutex_init(mutex) \

do { \

static struct lock_class_key __key; \

__mutex_init((mutex), #mutex, &__key); \

void __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)

} while (0)

获取互斥体:

int __sched mutex_trylock(struct mutex *lock);

/*Returns 1 if the mutex* has been acquired successfully, and 0 on contention.*

例:

if(!mutex_trylock(&mutex)){

printk("can not get mutex...\n");

return -EBUSY;

} 释放

互斥体:

void __sched mutex_unlock(struct mutex *lock);

【*】程序编写:使用互斥体实现设备只能被一个进程打开

[1][2][3]------>原子变量:在执行过程中,不可以被打断的操作;例如:buf 被 A、B 进程读改写

定义:

atomic_t

typedef struct {

int counter;//1

} atomic_t;函数:

初始化:

#define ATOMIC_INIT(i) { (i) }//i-->counter

例:

atomic_t at = ATOMIC_INIT(1);

获取 atomic:atomic_dec_and_test

atomic_t v;

#define atomic_dec_and_test(atomic_t *v) (atomic_sub_return(1, v) == 0)

(val == 0)--》true

/* static inline int atomic_sub_return(1,v )

{

unsigned long flags;

int val;

raw_local_irq_save(flags);

val = v->counter;//1

v->counter = val -= i;//val -= 1;--->val = 0;

//v->counter = 0

raw_local_irq_restore(flags);

return val;

}

*/

例:

if(!atomic_dec_and_test(&at)){

atomic_inc(&at);

printk("can not get mutex...\n");

return -EBUSY;

} 释放

对应的 atomic:

atomic_inc(atomic_t *v)

#define atomic_inc(v) atomic_add(1, v)

#define atomic_add(i, v) (void) atomic_add_return(i, v)

static inline int atomic_add_return(int i, atomic_t *v)

【*】程序编写:使用原子变量实现设备只能被一个进程打开笔记本: 228 - 驱动开发

创建时间: 2017/11/23 8:48 更新时间: 2017/11/28 21:56

作者: 干觐瑞

5 Linux 设备驱动中的阻塞与非阻塞 I/O

【同步阻塞 io 概念】

系统调用 read/write ,如果程序所需的条件不满足,则应用程序阻塞,在这段时间里,程序所代表的进程并不会消耗CPU时间。

底层驱动实现对应的同步阻塞io的相关API:

(1)初始化对应的等待队列

void init_waitqueue_head(wait_queue_head_t *q)

#define init_waitqueue_head(q) \

/*do { \

static struct lock_class_key __key; \

__init_waitqueue_head((q), #q, &__key); \

//void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)

} while (0)*/

(2)将条件不满足的事件添加至等待队列

int wait_event_interruptible(wait_queue_head_t wq, int condition)

#define wait_event_interruptible(wq, condition)

({ \

int __ret = 0; \

if (!(condition)) \

__ret = __wait_event_interruptible(wq, condition); \

//#define __wait_event_interruptible(wq, condition) \

//___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0, \

#define ___wait_event(wq, condition, state, exclusive, ret, cmd)

long __int = prepare_to_wait_event(&wq, &__wait, state);\

long prepare_to_wait_event(wait_queue_head_t *q, wait_queue_t *wait, int state)

__ret;

})

例:

if(!flags){ //条件不满足

if(wait_event_interruptible(wq, flags)) //被信号打断返回非0值,被正常唤醒

return -ERESTARTSYS;

}

[3]当条件满足时,唤醒等待队列上对应的进程

void wake_up(wait_queue_head_t *q);

#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)

void __wake_up(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, void *key)

【同步非阻塞io概念】

系统调用read/write ,如果程序所需的条件不满足,则应用程序非阻塞,直接返回错误码。

同步IO在内核中的具体实现:

基于的数据结构:/*等待队列项*/ 等待队列头

struct __wait_queue_head {

spinlock_t lock; //自旋锁

struct list_head task_list;//等待队列上挂载的不满足条件的等待队列项

};

例:

if(!flags){//条件不满足

if(file->f_flags & O_NONBLOCK)

return -EAGAIN;

if(wait_event_interruptible(wq,flags))//被信号打断返回非0值,被正常唤醒return -ERESTARTSYS;

}

相关函数:

【*】程序编写:同步阻塞IO

【*】程序编写:同步非阻塞IO

阻塞非阻塞基于资源不能及时获取到而言的

阻塞: 条件不满足----> 阻塞等待 ---->直到有条件满足---->解除阻塞--->

非阻塞:条件不满足----> 报错返回

O_NONBLOCK:表征不阻塞

O_RDWR

【异步阻塞io概念】

异步阻塞io:

并非阻塞在设备的读写操作本身,而是阻塞在某一组设备文件的描述符上,当其中的某些描述符上代表的设备对读写操作已经就绪时,阻塞

状态将被解除,用户程序对文件描述符可操作。

底层实现:

相关函数:

struct file_operations -> poll

unsigned int (*poll) (struct file *, struct poll_table_struct *);

poll--调用->static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

+

返回检测的可满足条件的掩码mask POLLIN 读/POLLOUT 写【针对用户层而言】

app实现:[select/poll]

#include <poll.h>

struct pollfd {

int fd; /* 需要监听的文件描述符 */

short events; /* 需要监听的事件 */

short revents; /* 已发生的事件 */

};

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

功能:等待一组文件描述符中准备好文件描述符执行输入输出

参数:fds:struct pollfd 结构体指针,在fds参数中指定要监视的文件描述符集

nfds:监控的文件描述符的个数

timeout:超时时间设置[0:立即返回不阻塞 >0:等待指定毫秒数 其他:永远等待]

返回值:成功:>0 超时:=0 出错:-errno

【*】程序编写:非阻塞读(轮询app)[多路复用IO]

【异步非阻塞io概念】

读写操作立即返回,用户程序的读写请求将放入一个请求队列中由设备在后台异步完成,当设备完成了本次读写操作时,将通过信号或者回调函数

的方式通知用户程序。【块、网络】

【异步通知】

一种通知机制,驱动使用信号通知应用层有事情发生(一般是读写)

底层实现:

struct fasync_struct

(1)kill_fasync//发送消息

kill_fasync 当设备的某一事件发生时负责通知链表中所有相关的进程。

band(带宽),一般都是使用POLL_IN,表示设备可读,如果设备可写,使用POLL_OUT

void kill_fasync(struct fasync_struct **fp, int sig, int band)

(2)fasync_helper//主要在struct file_operation->fasync

fasync_helper将当前要通知的进程加入到一个链表或者从链表删除

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

应用层实现:

int fcntl(int fd, int cmd, ... /* arg */ );

功能:设置fd相关的附加信息

参数:cmd:设置信息相关的命令F_GETFL:获取flag相关

F_SETFL:设置flags相关--->FASYNC异步通知的标志

F_SETOWN:设置进程号..

..:被设置的相关信息

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

【*】程序编写:异步通知(fasync)

作业:自行实现poll调用底层select函数的应用程序编写;

int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

功能:用于多路监控,当没有一个文件满足要求时,select将阻塞调用进程

参数:numfds:需要检查的号码高的文件描述符加1

readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集

timeout:struct timeval类型的指针

=0时:不管有无满足要求的文件均立即返回

=NULL时:将阻塞,直到某个文件满足要求

>0时:在timeout时间范围内阻塞进程

返回值:成功:返回满足要求的文件描述符的个数

失败:-1

超时:0

void FD_CLR(int fd, fd_set *set); //清除描述符

int FD_ISSET(int fd, fd_set *set); //查询描述符

void FD_SET(int fd, fd_set *set); //设置描述符

void FD_ZERO(fd_set *set); //清空集合:

6 Linux 设备驱动中断子系统

(1)中断的概念

当某个事件发生时,CPU停止运行正在执行的程序,而转去执行处理该事件的程序,处理该事件后,还可以返回原程序继续正确的执行下去,这种

功能称为中断。

(2)linux 中断硬件层面的剖析

PIC:可编程中断控制器。 progrom interrupt controller

----------- --------

外设一-------->| P | INT | C |

外设二-------->| I |-------| P |

外设三-------->| C | 软 | U |

... 硬件中断号 ----------- --------

(3)linux 中断软件层次的剖析

[1]PIC 的作用:中断触发电平的设置 ,中断号的映射,中断屏蔽(内部,外部)

[2]中断向量表与外设中断

中断向量表:是cpu内部的一概念,指向的函数处理的入口地址

外设中断 :irq,fiq;---->普通中断,快速中断的对应的通用处理函数

通用中断处理函数的作用:根据对应的软件中断号---->描述该中断的结构体成员

中断线--->irq_desc[irq]

struct irq_desc {

struct irq_data irq_data; /*主要保存软件中断号 irq 和 chip 相关数据*/

irq_flow_handler_t handle_irq; /*指向当前设备中断触发电信号的处理函数*/

/*

typedef void (*irq_flow_handler_t)(unsigned int irq,struct irq_desc *desc);

*/

struct irqaction *action; /* 针对具体设备中断处理的抽象*/

struct module *owner;

const char *name; /*handle_irq对应的名称,终显示在/proc/interrupts下*/

} ____cacheline_internodealigned_in_smp;

struct irq_data {

unsigned int irq; /*中断号*/

struct irq_chip *chip; /*中断控制器的软件抽象 pic*/

};

struct irqaction {

irq_handler_t handler; /*具体的外设中断处理函数*/

/*

typedef irqreturn_t (*irq_handler_t)(int, void *);//中断处理程序的函数原型

*/

void *dev_id; /*调用中断处理函数时传递的参数*/

struct irqaction *next; /*指向下一个action对象,用于共享中断*/

} ____cacheline_internodealigned_in_smp;

IRQF_SHARED

中断的过程:

(1)cpu硬件逻辑将任务的上下文保存到特定的中断栈,并跳转至处理外设中断IRQ/FIQ[IRQF_DISABLED] 的中断向量表所对应的

一项,之后跳转至通用中断处理函数;

(2)从pic中获取软件中断号irq

(3)通用中断处理函数

[3.1]中断现场保存

[3.2]顶半部

[3.3]底半部

[3.4]中断现场恢复

(4)中断程序函数api介绍

static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char

*name, void *dev)

功能:设置相关电平及中断服务程序

参数:irq:中断号

handler:中断服务程序

flags:设置该中断的相关标志IRF_SHARED / IRQF_DISABLED

电平:

#define IRQF_TRIGGER_RISING 0x00000001 //上升沿

#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿

#define IRQF_TRIGGER_HIGH 0x00000004

#define IRQF_TRIGGER_LOW 0x00000008

name:中断名称

dev:一般设置为NULL ,除了 共享中断 [如果是共享中断的话,那么要将该参数填充为该设备的具体设备信息]

返回值:成功:0 失败:errno

(5)设备树文档讲解

文件:

xxx.dts xxx.dtsi xxx.dtb

xxx.c xxx.h bin

#include <xxx.dtsi>

struct dog {

char *name ;

int age;

};

节点,属性

/{

dogs:dog@0x112{

name = "xiaogou";//键值对

age = 2;

hua_dog{//子节点

hua = "huanghua";

reg = <0x114000e0 0x4>

};

};

};

dog@0x110{

dog = &dogs;

};

---->struct device_node

想查找对应节点中的信息的话------>找到对应的节点中转--->才能获取节点中的键值

struct device_node {

const char *name; //节点名

const char *full_name; //全路径节点名

struct device_node *parent; //父节点指针

struct device_node *child; //子节点指针

...

};

相关设备节点实例:

设备树的结构:

#include "exynos4412.dtsi"

/(/*'/'表征是该设备树的根节点*/)

{

model = "Insignal Origen evaluation board based on Exynos4412";

compatible = "insignal,origen4412", "samsung,exynos4412";

memory { ---->"/memory"

reg = <0x40000000 0x40000000>;

};

kk{ ---->"/kk/bb"

bb{

};

};

chosen {

bootargs ="console=ttySAC2,115200";

};

firmware@0203F000 {

compatible = "samsung,secure-firmware";

reg = <0x0203F000 0x1000>;

};

....

};

设备树写法:

compatible 属性:描述设备的生产商以及设备型号

led节点:"fs4412,red", "fs6818,green"

/{

#address_cells 2#sizes_cells 1

gpx1: gpx1 {

gpio-controller;//gpio-controller:说明该节点描述的是一个gpio控制器

#gpio-cells = <2>;//#gpio-cells:描述gpio使用节点的属性一个cell的内容

interrupt-controller;//interrupt-controller 一个空属性用来声明这个node接收中断信号

interrupt-parent = <&gic>;// interrupt-parent 标识此设备节点属于哪一个中断控制器

0 1 2 3

interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,// <中断域 中断 触发方式>

<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;

4 5 6 7

#interrupt-cells = <2>;

};

led{

compatible = "fs4412,red","fs6818,green";

reg = <0x114001e0 0x114001e3 0x4>;

};

key-int{

interrupt-parent = &gpx1;

index: 0 1

interrupts = <0 2> , <1 2>; //中断和触发方式

/*1 = low-to-high edge triggered

2 = high-to-low edge triggered

4 = active high level-sensitive

8 = active low level-sensitive

*/

};

/*key--->gpx1_1*/

key-gpio{

compatible = "fs4412,key","fs6818,key";

key = <&gpx1 1 0>;//属性名=<&引用GPIO节点别名 GPIO标号 工作模式>;

};

};

设备树API:实际上是获取对应节点中的键值对(设备信息),首先应获取对应的键值对所在的设备节点,再在该设备节点中查找对应的键值;

struct device_node *of_find_node_by_path(const char *path)

功能:根据节点路径获取当前节点信息

参数:path:节点路径

返回值:成功: device_node的设备对象

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

功能:根据节点获取该节点中的中断号并完成映射

参数:dev:设备节点

index:序号

返回值:成功:返回对应的映射完的中断号

irqreturn_t (*irq_handler_t)(int, void *);//中断处理程序的函数原型

主要实现中断服务

*程序编写:按键中断

[1]原理图:key2----->(XEINT9)--->gpx1_1 25:57:int9

[2]在设备树中填充对应管脚的中断信息

/{

fs4412-key-int{

interrupt-parent = <&gpx1>;

interrupts = <1 2>,<2 2>;

index: 0 1

};

};

make dtbs

cp arch/arm/boot/dts/xxx.dtb ~/tftpboot

[3]在驱动中编写中断;

[3.1]模块三要素

[3.2]获取设备节点信息 np = of_find_node_by_path--->"/fs4412-key-int"

[3.3]获取该节点中的中断信息,并完成对应中断号的映射 irq_of_parse_and_map(np , 0);

[3.4]请求中断 request_irq();-->中断号+中断处理函数+中断触发电平

[3.5]中断处理函数笔记本: 228 - 驱动开发

创建时间: 2017/11/24 21:41 更新时间: 2017/11/29 9:21

作者: 干觐瑞

7 Linux设备驱动延缓机制

引入底半部的原因?

为了解决中断耗时,系统又必须得及时响应其他io或者是中断,进程调度这样一个矛盾;

上半部:主要是响应中断,并清除对应的标志位; + 调度底半部函数

底半部:做一些冗余的耗时操作;

【底半部】softirq:

tasklet: 中断上下文

[1]分类:TASKLET_SOFTIRQ HI_SOFTIRQ

[2]结构体、函数API

struct tasklet_struct

{

/*struct tasklet_struct *next;//下一个tasklet*/

void (*func)(unsigned long);//底半部处理函数

unsigned long data;//给func传递参数

};

static inline void tasklet_schedule(struct tasklet_struct *t)

主要实现 tasklet 的函数调用

[3]程序编写流程

1.定义对应的 struct tasklet_struct: func, data 的填充

2.在中断处理函数中调度底半部 tasklet: tasklet_schedule

工作队列:进程上下文,允许睡眠

[1]结构体

struct work_struct {

atomic_long_t data;

work_func_t func;//底半部函数

/*typedef void (*work_func_t)(struct work_struct *work);*/

};

[2]初始化函数

INIT_WORK(struct work_struct *wk ,work_func_t func);

/*

#define INIT_WORK(_work, _func) \

do { \

__INIT_WORK((_work), (_func), 0); \

} while (0)

#define __INIT_WORK(_work, _func, _onstack) \

do { \

__init_work((_work), _onstack); \

(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \

INIT_LIST_HEAD(&(_work)->entry); \

PREPARE_WORK((_work), (_func)); \

} while (0)

#endif

#define PREPARE_WORK(_work, _func) \

do { \

(_work)->func = (_func); \

} while (0)

*/

[3]提交工作队列

static inline bool schedule_work(struct work_struct *work);

[4]编写流程

1.定义当前的工作队列的结构体:struct work_struct wk;

2.初始化:INIT_WORK(struct work_struct *wk ,work_func_t func);

3.在中断的处理函数中对底半部进行相关的调度:schedule_work

8 定时器timer:

在内核中对时间的操作分类:

延时:A--------忙等/切换进程---------B

定时:指定时间点到达后执行某种动作;

START: t0 = 10jiffies//当前时刻

end: t2 = 20

expires = jiffies + 10jiffies

[1]jiffies:时钟滴答

通常 jiffies 在 linux 系统初始化阶段被初始化为0,当系统完成了“时钟中断”初始化之后,每个时钟中断“时钟滴答”处理例程中该值都会被加

一;

jiffies 存储自近启动以来的时钟滴答数;

[2]HZ:1s = n * jiffies

/*CONFIG_HZ = 200 可以更改*/

[3]jiffies时间操作

/*[3.1]时间比较:

time_after(a,b); //时间点a 在 b 之后 返回 true

time_before(a,b); //时间点a 在 b 之前 返true

time_after_eq(a,b); //时间点a 在 b 之后,等a=b时 返回 true

time_before_eq(a,b); //时间点a 在 b 之前,等a=b时 返回 true

time_in_range(a,b,c);//时间点a 在 b 与 c 之间,返回 true

a: jiffiesa b: jiffiesb

[3.2]时间转化:

unsigned int jiffies_to_msecs(const unsigned long j);

unsigned int jiffies_to_usecs(const unsigned long j);

unsigned long msecs_to_jiffies(const unsigned int m);

unsigned long usecs_to_jiffies(const unsigned int u);

[3.3]时间延时:

长延时:msleep(); //基于jiffies 让出处理器

短延时:udelay(); //mdelay();... for忙等待,粒度小

*/

[4]定时器

[4.1]结构体

struct timer_list {

unsigned long expires; //当前时间 : t0-->jiffies

延时: t1

t0+t1 =t2 --->定时处理函数

void (*function)(unsigned long ); //定时处理函数

unsigned long data; //定时器处理函数的传参

};

[4.2]函数

初始化定时器

void init_timer(struct timer_list *timer);

/*

#define init_timer(timer) \

__init_timer((timer), 0)

#define __init_timer(_timer, _flags) \

init_timer_key((_timer), (_flags), NULL, NULL)

void init_timer_key(struct timer_list *timer, unsigned int flags,const char *name, struct lock_class_key *key)

*/

向系统中添加对应的定时器

void add_timer(struct timer_list *timer);

修改定时器时间

int mod_timer(struct timer_list *timer, unsigned long expires)

每隔 20jiffies 调用一次对应的定时处理函数:

mod_timer(&tm,jiffies + 20 * jiffies);

struct timeval {__kernel_time_t tv_sec; /* seconds */

__kernel_suseconds_t tv_usec; /* microseconds */

};

void do_gettimeofday(struct timeval *tv);

--->old_time do_gettimeofday 获取一个老的时间---add_time r添加定时器

-->相隔一个时间段 --->do_gettimeofday 再去读取一个新的时间

*程序编写:timer:

9 Linux 设备驱动 GPIO 子系统

【1】内核中gpio的函数API--->gpiolib.c

[1]测试gpio端口是否合法:

int gpio_is_valid(int number);

[2]申请、释放gpio管脚

static inline int gpio_request(unsigned gpio, const char *label);

gpio:待控制的GPIO管脚编号 “gpa0_0”

“8”:表示控制8个引脚

static inline void gpio_free(unsigned gpio)

控制的GPIO管脚编号

[3]标记 gpio 的使用方向包括输入还是输出/*成功返回零失败返回负的错误值*/

static inline int gpio_direction_input(unsigned gpio)

控制的GPIO管脚编号

static inline int gpio_direction_output(unsigned gpio, int value)

控制的GPIO管脚编号 1

[4]获得 gpio 引脚的值和设置 gpio 引脚的值(对于输出)

static inline int gpio_get_value(unsigned gpio)

该管脚输出的电平状态 控制的GPIO管脚编号

static inline void gpio_set_value(unsigned int gpio, int value)

控制的GPIO管脚编号 ‘1’高 ‘0’低

[5]gpio当作中断口使用

static inline int gpio_to_irq(unsigned int gpio)

gpio对应的中断号 控制的GPIO管脚编号

【2】用户空间 gpio 的调用

在/sys/下提供了相关用户层与底层进行交互的属性文件;

/sys/class/gpio/

(0)内核配置

Device Drivers --->

-*- GPIO Support --->

[*] /sys/class/gpio/... (sysfs interface)如果说用户空间想跟内核中的GPIO管脚进行交互的话,首先需要将该管脚导出至用户空间;

(1)export/unexport

/sys/class/gpio/export

(2)/sys/class/gpio/gpioN:pin

direction: in/out

value:0/1

(3)/sys/class/gpio/gpiochipN

【3】gpio的led编写方式

1. GPIO管脚设备树的写法:

[1]改写设备树

led2 gpx2_7----来自于 gpx2 这一组管脚中的第七个

gpx2 这一组对应的设备树:exynos4x12-pinctrl.dtsi

gpx2: gpx2 {

gpio-controller;

#gpio-cells = <2>;----》表征的是其子节点或者是继承者的GPIO的<&gpx2 7 0>

中&gpx2后边的参数个数,7代表第七个管脚

interrupt-controller;

#interrupt-cells = <2>;

};

文件中 <2>:&gpx2 后面多跟两个参数

led2 gpx2_7

led3 gpx1_0

led4 gpf3_4

led5 gpf3_5

fs4412-leds-gpio{

compatible = "fs4412,leds";

/* index : 0 1*/

led2 = <&gpx2 7 0>,<&gpx2 4 0>;

led3 = <&gpx1 1 0>;

led4 = <&gpf3 4 0>;

led5 = <&gpf3 5 0>;

};

0:对应的工作模式

make dtbs

cp xxx.dtb ~/tftpboot

Makefile 的编写:

KERN_PATH := /home/linux/Docu/linux-3.14

all:

make -C $(KERN_PATH) M=$(shell pwd) modules CROSS_COMPILE=arm-linux- ARCH=arm

cp *.ko /home/linux/Docu/busybox-1.22.1/_install

clean:

make -C $(KERN_PATH) M=$(shell pwd) clean

obj-m:=demo.o

[2]程序编写

[2.1]将设备树中的设备信息所在的节点获取

np = of_find_node_by_path

gpio获取:

of_get_named_gpio

static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index)返回值:管脚号

参数:np:该gpio所在的节点

propname:键值“led2”

index:该键值对应的gpio组号

[2.2]获取该节点中的设备信息

[2.3]执行gpio相关的操作

【4】按键消抖

gpx1: gpx1 {

gpio-controller;

#gpio-cells = <2>;

interrupt-controller;

interrupt-parent = <&gic>;

interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,

<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;

#interrupt-cells = <2>;

};

lesson4-interrupt:

fs4412-key-int{

compatible = "fs4412,key-int";

interrupt-parent = <&gpx1>;

interrupts = <1 2> ;

};

lesson5-gpio:

fs4412-key-gpio{

compatible = "fs4412,key-gpio";

key2 = <&gpx1 1 0>;

};

思路:设备树--->key-->gpio--->irq-->request_irq

-->int_handler->delay(添加、修改对应的定时器的到时时间)

-->在定时器处理函数中进行按键状态的再次判定

【0】了解设备信息--->原理图

【1】设备树:

fs4412-key-gpio{

compatible = "fs4412,key";

/* index : 0 1*/

key2 = <&gpx1 1 0>;

};

make dtbs

cp xxx.dtb ~/tftpboot

【2】设备驱动相关

[2.1]获取该按键对应的节点

[2.2]根据该节点获取按键所对应的 gpio 管脚号

[2.3]gpio_to_irq:将gpio管脚所对应的中断号拿到

[2.4]绑定中断号,中断处理函数,触发电平

[2.5]在中断处理函数中修改定时器时间,在该时间内,起到了消抖的作用

[2.6]在定时器处理函数中验证是否真的按下;

10 Linux 虚拟总线 platform 框架

【宏观:总线、设备、驱动】

为什么引入 platform?主要用在哪呢?

设 备 、 驱 动 分 离 的 提 出 主 要 是 为 了 隔 离 BSP 与 驱 动 , 在 BSP 中 定 义 设 备 和 设 备 使 用 的 资 源 ( IO 、 irq 等) 、 设 备

的 具 体 配 置 信 息 , 而 在 驱 动 中 只 需 要 通 过 API 去 获 取 资 源 和 数 据 , 做 到 板 级 相 关 代 码 与 驱 动 代 码 进 行 分 离 , 使

驱 动 具 有 更 好 的 可 扩 展 性 及 跨 平 台 性 。

如何联系设备与驱动?

答 . 总 线 。

原理?【绘图】

platform 的基于?

1. 总线

[1.1]总线结构体

struct bus_type

[1.2]相关API

int bus_register(struct bus_type *bus); //注册总线

void bus_unregister(struct bus_type *bus); //注销总线

[1.3]注册成功现象--->/sys/bus/i2c /sys/bus/platform

2. 设备

[2.1]设备结构体

struct device

[2.2]相关API

int device_register(struct device *dev); //注册设备

void device_unregister(struct device *dev); //注销设备

[2.3]注册成功现象

/sys/bus/i2c/device

/sys/bus/platform/device

3. 驱动

[3.1]驱动结构体

struct device_driver

[3.2]相关API

int driver_register(struct device_driver *drv); //注册设备驱动

void driver_unregister(struct device_driver *drv); //注销设备驱动[3.3]注册成功现象

/sys/bus/i2c/driver

/sys/bus/platform/driver

【虚拟总线platform框架】用于实际没有总线的设备及驱动;

1. platform总线

[1.1]总线结构体

struct bus_type platform_bus_type = {

.name = "platform",

.match = platform_match,//匹配该总线上的设备及驱动

};

2. platform设备 + 资源

主要都是对结构体的填充和注册

[2.1]设备结构体struct platform_device

struct platform_device {

const char *name; //表征当前挂载在platfrom总线上的设备的名称

int id; // -1 name.9

struct device dev;

u32 num_resources;//当前设备的资源的数量

struct resource *resource;

};

struct device {

void (*release)(struct device *dev); //设备注销时执行

};

struct resource {

unsigned long flags;//IORESOURCE_MEM,IORESOURCE_IRQ

resource_size_t start;

resource_size_t end;

};

0x114001e4 --->0x114001e8

.flags = IORESOURCE_MEM; start,end:表征内存资源的起始及终止地址

.flags = IORESOURCE_IRQ; start,end:一致,都写生对应的中断号

[2.2]相关API

int platform_device_register(struct platform_device *pdev);

功能: 完成 platfrom_device 的注册

void platform_device_unregister(struct platform_device *pdev);

功能: 完成 platfrom_device 的注销

[2.3]注册成功现象

3. platform 驱动

[3.1]驱动结构体:struct platform_driver

struct platform_driver {

int (*probe)(struct platform_device *); //当设备与驱动匹配成功之后,自动调用

int (*remove)(struct platform_device *); //当设备或者驱动移除时,被调用

struct device_driver driver;

const struct platform_device_id *id_table;

};

.remove:当dev或drv中任意一个移除时调用

1. 获取设备资源

2. 映射内存地址

3. 配置寄存器

4. 输出电平

struct device_driver {

const char *name; //当前驱动支持的设备的名称

struct module *owner; //指向该模块--->THIS_MODULE

const struct of_device_id *of_match_table; //设备节点匹配信息

};

struct of_device_id

{char compatible[128];

};

struct platform_device_id {

char name[PLATFORM_NAME_SIZE];//支持的设备名称

};

platform_device 与 platform——driver 匹配成功的规则:

device driver

(1)platfrom_device->name = platform_driver->device_driver->name 相等时,进行匹配--->probe

[ps:struct platform_device_id不写的情况下执行]

(2)platfrom_device->name = struct platform_device_id->name

-----------------------------------------------------------------------------------------

device_tree driver

(3)compatible = platform_driver->device_driver->of_device_id

[3.2]相关API

注册 platform_driver

int platform_driver_register(struct platform_driver *drv);

/*

#define platform_driver_register(drv) \

__platform_driver_register(drv, THIS_MODULE)

int __platform_driver_register(struct platform_driver *drv,struct module *owner)

*/

注销 platform_driver

void platform_driver_unregister(struct platform_driver *drv)

MODULE_DEVICE_TABLE();

创建一个名为__mod_type_device_table的局部变量,在稍后的内核构建过程中,depmod程序在所有的模块中搜索符号

__mod_type_device_table。如果找到了该符号,它把数据从该模块中抽出,添加到文件 / lib/ modules/ KERNEL_VERSION/

module.typemap 中。当depmod结束之后,内核模块支持的所有type类型设备连同他们的模块名都在改文件中被列出。当内核告知热

插拔系统一个新的type类型设备已经被发现时,热插拔系统使用module.typemap文件来寻找要加载的适当的驱动程序。

[3.3]注册成功现象

[root@farsight ]# insmod pdev.ko

[ 9.290000] pdev_init,45

[root@farsight ]# insmod pdrv.ko

[ 12.215000] pdrv_probe,11

[ 12.220000] pdrv_init,41

[root@farsight ]# rmmod pdrv

[ 36.310000] pdrv_remove,16

[ 36.315000] pdrv_exit,48

[root@farsight ]# insmod pdrv.ko

[ 71.390000] pdrv_probe,11

[ 71.395000] pdrv_init,41

[root@farsight ]# rmmod pdev

[ 79.150000] pdrv_remove,16

[ 79.155000] i am go ...pdev_release,29--->

[ 79.160000] pdev_exit,52

4. platform框架驱动编写:框架,*/

填充结构体://const struct platform_device_id *id_table;

const struct of_device_id of_table[] = {//设备节点匹配信息

[0] = {.compatible = "142526"},

[1] = {/*NULL*/},

};

struct platform_driver pdrv = {

.probe = pdrv_probe, //当设备与驱动匹配成功之后,自动调用

.remove = pdrv_remove, //当设备或者驱动移除时,被调用

.driver = {

.owner = THIS_MODULE, //指向该模块--->THIS_MODULE

.name = "pdev0", //当前驱动支持的设备的名称

.of_match_table = of_match_ptr(of_table), //设备节点匹配信息

},

};

//const struct platform_device_id *id_table;

const struct of_device_id of_table[] = {//设备节点匹配信息

[0] = {.compatible = "fs4412,key-int"},

[1] = {/*NULL*/},

};

struct platform_driver pdrv = {

.probe = pdrv_probe, //当设备与驱动匹配成功之后,自动调用

.remove = pdrv_remove, //当设备或者驱动移除时,被调用

.driver = {

.owner = THIS_MODULE, //指向该模块--->THIS_MODULE

.name = "xxxx", //当前驱动支持的设备的名称

.of_match_table = of_match_ptr(of_table), //设备节点匹配信息

},

};

设备端提供资源--->platfrom_device->resource

驱动端获取资源:platform_get_resource

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)

功能:获取对应平台设备或者设备树中的设备资源

参数:dev:platform_device的指针对象

type:获取的资源的类型:IORESOURCE_MEM, IORESOURCE_IRQ

num:资源序号

返回值:成功: struct resource的指针对象 失败:NULL

/*led,key(device_tree无需在映射irq)*/

相关推荐

全国咨询热线:400-611-6270

?2004-2018华清远见教育科技集团 版权所有 京ICP备16055225号 京公海网安备11010802025203号