设备树
设备树文件机制创建原因
Linux系统一直都在提倡 驱动、设备 分离与分层 的软件设计思路。
旧内核的 分离与分层 体现是,设备描述为一个文件,驱动代码为一个文件。
相当于一个设备就要两个驱动文件,但这样子存在一个隐患,就是如果一个开发板上有很多设备,那么就要写很多的设备描述文件代码,过于冗余。
设备树文件的出现就是解决这个冗余问题
内核
copy_from_user(void *to, const void __user *from, unsigned long n)
参数:
to = kernel memory
from = user memory
返回值:
成功:0
失败:未复制过去的字节数
copy_to_user(void __user *to, const void *from, unsigned long n)
Params:
to = user memory
from = kernel memory
n = number of bytes to copy
Return value:
Number of bytes NOT copied.
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (access_ok(VERIFY_READ, from, n))
n = __copy_from_user(to, from, n);
else /* security hole - plug it */
memset(to, 0, n);
return n;
}
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (access_ok(VERIFY_WRITE, to, n))
n = __copy_to_user(to, from, n);
return n;
}
GPIO
头文件:
#include <linux/gpio.h>
Ps
:
一定要留意pinctrl的复用功能配置没有被别的节点使用,一个引脚的复用在设备树里面只能设置一个
Linux的GPIO设计分为两部分负责:IOMUXC控制器和GPIO
IOMUXC
作用:
负责设置引脚的复用功能(选择用哪个外设),电气特性(内部上下拉、速率、驱动能力)
IOMUXC节点和引脚配置信息
pinctrl_xxx(别名) : xxxgrp(节点名,不需要带寄存器地址的节点名)
{
fsl,pins = <
引脚功能复用定义 电气属性
>
}
pinctrl-names
:
作用
:用于定义一组引脚控制状态的名称。
1、属性中的名字并不是随便写的,它们应该与设备驱动程序中的状态名称相对应。
2、状态通常对应于设备的不同操作模式,例如"default"、“sleep”、"idle"等。
3、设备可以在运行时切换到这些状态,以改变引脚的配置。
4、如果为一个状态定义多个pinctrl-x属性,那么只有索引最小的那个pinctrl-x属性会被使用。其他的pinctrl-x属性会被忽略
5、pinctrl-names 属性一个状态(值)对应一个的 pinctrl-x
pinctrl-0
:用于指定与pinctrl-names中的第一个状态对应的引脚控制组。
例如,如果你有两个状态,"default"和"sleep",你可以这样定义:
pinctrl-names = "default", "sleep";
pinctrl-0 = <&pinctrl_default>;
pinctrl-1 = <&pinctrl_sleep>;
"default"状态对应的引脚控制组是pinctrl_default
"sleep"状态对应的引脚控制组是pinctrl_sleep。
注意,pinctrl-x属性的索引必须从0开始,且连续,不能跳过。
例如,你不能只定义pinctrl-0和pinctrl-2,而不定义pinctrl-1。
GPIO
作用:
用于初始化GPIO具体引脚、选择输入输出模式
设备树添加gpio节点
节点名字 {
compatiblr = "xxx";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_xxx>;
gpios = <&gpioX(第几组gpio) NUM(组的第几个引脚) 电平值>;
status = "okey";
};
电气特性:
HYS[ bit 16 ] : 迟滞比较器,仅输入的时候有效,用于对输入波形整形为方波
迟滞比较器会设置两个电压值,高于或低于设置的电压值才能进行高低电平转换
PUS[ 15:14 ] :用来设置上下拉电阻
电阻值越大,上拉能力越弱
上拉能力:引脚电平变换到高电压的快慢
值 | 意义 |
---|---|
00 | 100K下拉 |
01 | 47K上拉 |
10 | 100K上拉 |
11 | 22K上拉 |
PUE[ Bit 13 ] : 仅输入有效
0:状态保持器,当外部电路断电后,IO口仍然保持之前电平状态
1:使用上下拉
PKE[ bit 12 ] : 上下拉/状态保持器功能的使能或禁止
ODE[ bit 11 ]: 使能或禁止 开漏输出
SPEED[ bit 7:6 ] :引脚输出速率
bit值 | 速度 |
---|---|
00 | 50M |
01 | 100M |
10 | 100M |
11 | 200M |
DSE[ bit 5:3 ] : 调整芯片内部与引脚串联电阻 R0 的阻值,从而改变输出驱动能力
bit | 阻值 |
---|---|
000 | 输出关闭 |
001 | R0本身阻值 |
010 | R0 / 2 |
011 | R0 / 3 |
100 | R0 / 4 |
101 | R0 / 5 |
110 | R0 / 6 |
111 | R0 / 7 |
SRE[ Bit 0 ] : 压摆率 ,用于改变输出电压上升的时间
0:低压摆率
1:高压摆率
压摆率越高,上升需要时间越少,但波形越陡
高速通信使用高压摆率
想要输出波形缓和使用低压摆率
API
int gpio_request(unsigned gpio, const char *label)
参数:
gpio:gpio的号码
label:名字。
返回值:
成功:0
失败:其他值
void gpio_free(unsigned gpio)
//设置GPIO为输入模式
int gpio_direction_input(unsigned gpio)
返回值:
成功:0
失败:负值
//GPIO设置为输出和输出值
int gpio_direction_output(unsigned gpio, int value)
参数:
value GPIO默认输出值
返回值:
成功:0
失败:负值
//获取GPIO电平值
int gpio_get_value(unsigned gpio)
返回值:
成功:0或1
失败:负值
//设置GPIO输出值
void gpio_set_value(unsigned gpio, int value)
获取GPIO号码
像某些属性的值为<&gpio5 7 GPIO_ACTIVE_LOW>,通过这个函数就能获取到GPIO5的第7个引脚编号
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
参数:
np:设备节点。
propname:设备节点的属性名
index:GPIO索引
一个属性里面可能包含多个GPIO,此参数用于指定要获取哪个GPIO的编号。
如果只有一个GPIO,此参数配置为0。
返回值:
成功:获取到的GPIO号码(正值)
失败:负值
struct gpio {
unsigned gpio;
unsigned long flags;
const char *label; ->标签,没啥用
};
flag:
1、GPIOF_IN - configure as input
2、GPIOF_OUT_INIT_LOW - configured as output, initial level LOW
3、GPIOF_OUT_INIT_HIGH - configured as output, initial level HIGH
ARRAY_SIZE(struct *gpio)用于获取gpio组有多少个成员,放在第二个参数num里面
//申请多个gpio
int gpio_request_array(struct gpio *array, size_t num);
返回值:
成功:0
失败:非0
//释放多个gpio
void gpio_free_array(struct gpio *array, size_t num);
platform
/sys/bus/platform目录下,devices对应设备树节点,drivers对应驱动文件
匹配方式
1、设备树匹配 ← IMX6UL的匹配机制
if (of_driver_match_device(dev, drv))
return 1;
node → compatible值 匹配 (platform_driver → device_driver → of_device_id → compatible)
struct platform_driver
{
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
struct device_driver
{
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
struct of_device_id
{
char name[32];
char type[32];
char compatible[128];
const void *data;
};
2、ACPI匹配
if (acpi_driver_match_device(dev, drv))
return 1;
3、id_table匹配
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
4、name字段匹配
return ( strcmp(pdev->name, drv->name) == 0 );
//平台驱动注册
int platform_driver_register (struct platform_driver *driver)
返回值:
成功:0
失败:负数
//平台驱动卸载
void platform_driver_unregister(struct platform_driver *drv)
平台驱动框架
/* 驱动与设备匹配成功以后此函数就会执行 */
static int xxx_probe(struct platform_device *dev)
{
......
return 0;
}
static int xxx_remove(struct platform_device *dev)
{
/* 函数具体内容 */
return 0;
}
/* 匹配列表 */
static const struct of_device_id xxx_of_match[] =
{
{ .compatible = "xxx" },
{ /* Sentinel */ } <-of_device_id表最后一个匹配项必须是空的
};
/* platform平台驱动结构体 */
static struct platform_driver xxx_driver =
{
.driver = {
.name = "xxx", <-用于传统的驱动与设备匹配机制(上面讲述的第四种)
.of_match_table = xxx_of_match,
},
.probe = xxx_probe,
.remove = xxx_remove,
};
Ps:一个完整的驱动应该同时包含有设备树和无设备树两个匹配方式,这样才能兼容更多内核
/* 驱动模块加载 */
static int __init xxxdriver_init(void)
{
return platform_driver_register(&xxx_driver);
}
/* 驱动模块卸载 */
static void __exit xxxdriver_exit(void)
{
platform_driver_unregister(&xxx_driver);
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
MISC驱动
1、所有的混杂驱动 主设备号都是 10
2、在 /sys/class/misc目录下存在着misc这个类所有的设备
3、驱动核心就是实现 struct miscdevice
里面的minor、name、fops即可创建一个混杂设备,name为创建后在/dev目录下显示的混杂设备名字
struct file_operations
{
struct module *owner; <---所有者,一般设置为 "THIS_MODULE"
loff_t (*llseek) (struct file *, loff_t, int); //光标偏移函数
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读操作
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写操作
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *); //打开操作
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *); //关闭操作,close函数关闭设备时候执行该函数
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset, 1616 loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};
struct miscdevice
{
int minor; /* 子设备号 */ ->使用MISC_DYNAMIC_MINOR可以动态分配子设备号
const char *name; /* 设备名字 */
const struct file_operations *fops; /* 设备操作集 */
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
注册混杂设备
int misc_register(struct miscdevice * misc)
返回值:
成功:0
失败:负数
卸载混杂设备
int misc_deregister(struct miscdevice *misc)
返回值:
成功:0
失败:负数
核心示例代码
全局变量定义
static struct file_operations xxx_fops = {
.open = xxx_open,
.release = xxx_release,
};
static struct miscdevice xxx_miscdev = {
.name = "xxx",
.minor = MISC_DYNAMIC_MINOR,
.fops = &xxx_fops,
};
在相应的初始化函数里面执行
misc_register(&xxx_miscdev);
在相应的卸载化函数里面执行
misc_deregister(&xxx_miscdev);
中断
获取gpio对应的中断号
int gpio_to_irq(unsigned int gpio)
返回值:
成功 :大于0 ,获取对应的中断编号
失败 :小于0
request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
函数参数和返回值含义如下:
irq:中断号。
handler:中断处理函数
flags:中断标志
IRQF_ONESHOT:单次中断,中断执行一次就 结束
IRQF_TRIGGER_NONE:无触发
IRQF_TRIGGER_RISING:上升沿触发
IRQF_TRIGGER_FALLING:下降沿触发
IRQF_TRIGGER_HIGH:高电平触发
IRQF_TRIGGER_LOW:低电平触发
name:中断名字,设置以后可以在/proc/interrupts文件中看到对应的中断名字
dev:用于区别 共享中断线 时候的区别
返回值:
申请成功:0
申请失败: 负值
-EBUSY:中断已经被申请了
void free_irq(unsigned int irq, void *dev)
参数:
irq:中断号
dev:共享中断只有在释放最后中断处理函数的时候才会被禁止掉
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
中断函数
irqreturn_t (*irq_handler_t) (int, void *)
参数:
int类型为中断号
void指针为申请中断函数的dev参数
void enable_irq(unsigned int irq)
disable_irq需要在中断不执行的时候才能关闭,如果有中断正在执行则需要执行完才能关闭,因此要确保
不会一直产生新中断请求从而导致中断无法关闭
void disable_irq(unsigned int irq)
立刻关闭中断,就算中断执行也不会等待
void disable_irq_nosync(unsigned int irq)
驱动核心代码
//中断服务函数
static irqreturn_t xxx_handler(int irq,void *dev)
{
/* 具体内容 */
return 0;
}
//在初始化函数里面加入中断请求
request_irq(irq,xxx_handler,xxx,"xxx",NULL);
//在卸载函数里面释放中断请求
free_irq(key_irqnum,NULL);
上、下半部
上半部:request_irq注册的中断函数,要求处理过程尽量快,不要占用长时间
下半部:上半部需要费时的代码部分放在下半部执行,实现上半部(中断处理函数)快进快出
上半部考虑的点:
1、中断处理内容不想被打断
2、对时间敏感
3、与硬件相关
中断下半部执行方式:
1、tasklet
2、工作队列
tasklet
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个tasklet */
unsigned long state; /* tasklet状态 */
atomic_t count; /* 计数器,记录对tasklet的引用数 */
void (*func)(unsigned long); /* tasklet执行的函数 */
unsigned long data; /* 函数func的参数 */
};
tasklet_init和DECLARE_TASKLET宏定义都可以用于对tasklet进行初始化
void tasklet_init( struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
参数:
t:要初始化的tasklet
func:处理函数
data:传递给处理函数的参数
DECLARE_TASKLET(name, func, data)
//tasklet运行
void tasklet_schedule(struct tasklet_struct *t)
工作队列
工作队列会将要推后的工作交给一个内核线程去执行,
工作队列工作在进程上下文,因此工作队列中允许睡眠或重新调度,因此推后的工作也可以选择睡眠
工作结构体
struct work_struct
{
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
#define INIT_WORK(_work, _func) _work:初始化的工作,_func:工作处理函数
//工作调度函数
bool schedule_work(struct work_struct *work)
返回值:
成功:0
失败:其他值
//取消工作
bool cancel_work_sync(struct work_struct *work)
返回值:
工作被成功取消或者已经完成:返回 false
工作正在执行并且被成功停止:返回 true
核心部分
struct work_struct xxx_work;
void keywork_fun(struct work_struct *work)
{
*********
}
//中断服务函数
static irqreturn_t xxx_handler(int irq,void *dev)
{
/* 具体内容 */
schedule_work(&xxx_work);
return 0;
}
//在初始化函数里面加入中断请求
request_irq(irq,xxx_handler,xxx,"xxx",NULL);
INIT_WORK(&xxx_work,xxxwork_fun);
//在卸载函数里面释放中断请求
free_irq(key_irqnum,NULL);
阻塞与非阻塞
阻塞的好处:当设备文件不可操作的时候,使进程进入休眠态让出CPU资源。当设备文件可以操作的时候,在中断函数里面进行唤醒进程的操作。
struct __wait_queue_head
{
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
//初始化 等待队列头
void init_waitqueue_head(wait_queue_head_t *q);
struct __wait_queue
{
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
//给 当前进程 初始化 等待队列项
DECLARE_WAITQUEUE(name, tsk)
name:等待队列项
tsk:等待队列项属于的任务(进程),一般设置为current
在Linux内核中 current相当于一个全局变量,表示当前进程
//有待开发具体用法,目前暂时用不上
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
//唤醒函数,用于唤醒等待队列头中的所有进程
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
wake_up可以唤醒处于"TASK_INTERRUPTIBLE"和"TASK_UNINTERRUPTIBLE"状态的进程
wake_up_interruptible只能唤醒处于"TASK_INTERRUPTIBLE"状态的进程
等待队列被唤醒的时候会检测condition的值是不是为true
"TASK_INTERRUPTIBLE"状态可以被信号中断
唤醒方式:
1、信号打断
2、condition为true
"TASK_UNINTERRUPTIBLE"状态不可以被信号中断,只能condition为true才可以唤醒
//TASK_UNINTERRUPTIBLE状态,休眠,直到condition为true
wait_event(wq, condition)
没有返回值
//TASK_UNINTERRUPTIBLE状态,condition为true或者超时 则 结束休眠
wait_event_timeout(wq, condition, timeout)
timeout:超时,单位为jiffies
返回值:
0 : 超时后,condition为 false
1: 超时后,condition为 true
>=1 : 超时之前,condition为true成功唤醒,则为剩余的jiffies
//休眠,直到condition为true
wait_event_interruptible(wq, condition)
返回值:
-ERESTARTSYS : 被信号中断
0 : condition为true唤醒
将进程设置为 "TASK_INTERRUPTIBLE"状态
唤醒方式:
1、信号打断(返回值为 "-ERESTARTSYS" )
2、condition为true
//TASK_INTERRUPTIBLE状态,condition为true或者超时 则 结束休眠
wait_event_interruptible_timeout(wq, condition, timeout)
timeout:超时,单位为jiffies
返回值:
0 : 超时后,condition为 false
1: 超时后,condition为 true
>=1 : 超时之前,condition为true成功唤醒,则为剩余的jiffies
-ERESTARTSYS : 被信号打断
四、时钟
clock-names与clocks两个属性的索引值是一一对应的,即 clock-names 中的第一个元素与 clocks 中的第一个元素对应,clock-names 中的第二个元素与 clocks 中的第二个元素对应,以此类推。
五、零碎知识点
1、属性值是引用
phandle属性是设备树中用于引用其他节点的一种属性。它是一个32位的整数值,用于唯一标识设备树中的节点。其他节点可以使用phandle属性来引用该节点,从而建立节点之间的关联关系。
parent_node {
phandle_property = <&child_node>;
// 其他属性...
};
phandle_property = <&child_node>;
这个引用可以被视作类似于程序中的指针。
它指向了名为"child_node"的设备树节点,允许其他节点通过phandle属性引用并获取对应的节点指针