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)*/
热点新闻
学员笔记
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号