linux驱动


设备树

设备树文件机制创建原因

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 ] :用来设置上下拉电阻
电阻值越大,上拉能力越弱
上拉能力:引脚电平变换到高电压的快慢

意义
00100K下拉
0147K上拉
10100K上拉
1122K上拉

PUE[ Bit 13 ] : 仅输入有效
0:状态保持器,当外部电路断电后,IO口仍然保持之前电平状态
1:使用上下拉

PKE[ bit 12 ] : 上下拉/状态保持器功能的使能或禁止

ODE[ bit 11 ]: 使能或禁止 开漏输出

SPEED[ bit 7:6 ] :引脚输出速率

bit值速度
0050M
01100M
10100M
11200M

DSE[ bit 5:3 ] : 调整芯片内部与引脚串联电阻 R0 的阻值,从而改变输出驱动能力

bit阻值
000输出关闭
001R0本身阻值
010R0 / 2
011R0 / 3
100R0 / 4
101R0 / 5
110R0 / 6
111R0 / 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)
返回值: 
成功:01
失败:负值

//设置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属性引用并获取对应的节点指针
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值