Linux驱动开发(2)–应用与内核、杂项设备
前言
- 该章节介绍 应用层与驱动层, 参数传输 、物理/虚拟地址、杂项设备 ;
- 以迅为IMX6ULL开发板为基础,编写蜂鸣器驱动以及LED驱动;
应用程和驱动层
首先,要明确一个概念,Linux系统中–一切皆文件;驱动文件最终通过文件操作相关的系统调用或者函数来被访问,而设备驱动的结构最终也是为了提供应用程序API ;
- 应用层和内核层是不能直接进行数据传输的,如果需要在驱动中进行数据传输,可以使用下面两个函数进行数据互传;PS: 下列函数都是在内核层中使用,以内核层为对象,来看到数据的走向 ;
static inline long copy_from_user(void * to ,const void __user *from,unsigned long n ) ;
参数 | 内容 |
---|---|
to | 目标地址 (内核空间) |
from | 源地址 (用户空间) |
n | 拷贝的字节数 |
返回值 | 成功返回0 ,失败则返回没有拷贝成功的字节数 |
功能 | 将用户空间的数据拷贝到内核空间 |
static inline long copy_to_user(void __user * to ,const void *from,unsigned long n ) ;
参数 | 内容 |
---|---|
to | 目标地址 (用户空间) |
from | 源地址 (内核空间) |
n | 拷贝的字节数 |
返回值 | 成功返回0 ,失败则返回没有拷贝成功的字节数 |
功能 | 将内核空间的数据拷贝到用户空间 |
物理/虚拟地址
-
大多数的嵌入式微控制器(ARM等)中并不提供I/O空间,仅存在内存空间。内存空间可以直接通过地址、指针来访问,程序及在程序运行中使用的变量和其数据都存在于内存空间中,内存地址可以直接有C语言指针操作。
-
内存管理单元(MMU - memory mangement unit ) ,该单元辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射、内存访问权限保护和Cache缓存控制等硬件支持 。
-
MMU具有虚拟地址和物理地址 转换、内存访问权限保护等功能。这使得Linux操作系统能单独为系统的每个用户进程分配独立的内存空间并保证用户空间不能访问内核空间的地址,为操作系统的虚拟内存管理模块提供硬件基础。
-
把物理地址转换成为虚拟地址(映射):
内容 说明 函数原型 static inline void * ioremap(phys_addr_t offset ,size_t size ) phys_addr_t offset 映射物理地址的起始地址 size_t size 所映射空间的大小 返回值 成功–返回虚拟地址的首地址,失败–返回NULL 功能 把物理地址转换为虚拟地址,映射 内容 说明 函数原型 static inline void * iounmap(void *addr ) addr 虚拟地址的首地址 功能 释放ioremap 所映射的虚拟地址 -
可以在设备中查看物理地址映射情况
cat /proc/iomem
杂项设备
Linux的三大设备驱动 ,字符设备、块设备、网络设备
-
杂项设备是一个典型的字符设备,其主设备号固定为10(内部实现是调用register_chrdev())。
-
杂项设备实现简单
实现步骤:
- 构造一个file_operations结构体,其中包含对硬件的所有操作;
- 实现file_operations结构体的成员函数(例:read ,write);
- 构造一个杂项设备驱动(miscdevice)实体,并且赋值前面定义的file_operations实体;
- 在入口函数处调用**misc_register()**向系统注册杂项设备;
- 在出口函数处调用**misc_unregister()**从系统注销杂项设备;
- 内核的入口和出口函数;
-
misc 设备用miscdevice结构体表示,该结构体定义在include/linux/miscdevice.h中
struct miscdevice { int 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 ; }
创建一个miscdevice结构体时需要初始化 minor、name、fpos这三个成员变量,其中minor表示次设备号(misc的主设备号是10),在include/linux/miscdevice.h中定义了次设备号的宏定义:
#define PSMOUSE_MINOR 1 #define MS_BUSMOUSE_MINOR 2 #define ATIXL_BUSMOUSE_MINOR 3 ...
-
结构体创建号后,需要函数对其进行操作:
内容 说明 函数原型 int misc_register(struct miscdevice * misc) 参数misc 创建好的miscdevice 结构体实体 返回值 成功返回0 , 失败返回负数 功能 向系统注册一个misc设备 内容 说明 函数原型 void misc_deregister(struct miscdevice * misc) 参数misc 需要注销的misc结构体 返回值 void 功能 注销misc设备 -
代码示例
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #include <linux/miscdevice.h> ssize_t misc_write(struct file* file ,char * userbuf, size_t size ,loff_t * loff) { printk("hello write\n"); return 0 ; } struct file_operations ops = { .owner = THIS_MODULE, .write = misc_write , }; struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = "hello", .fops = &ops, }; static int hello_init(void) { int ret ; ret = misc_register(&misc); if(ret < 0 ) printk("misc register fail\n"); printk("hello world\r\n"); return 0; } static void hello_exit(void) { misc_deregister(&misc) ; printk("hello byebye\r\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
-
对应的头文件包含
#include <linux/miscdevice.h> //对应miscdeivce结构体 #include <linux/fs.h> //对应file_operations结构体
-
创建miscdevice 和file_operations 实体
struct file_operations ops = { .owner = THIS_MODULE }; struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = "hello" , .fops = &ops , };
-
在出入口函数加入misc对应的操作函数
-
学会查看芯片手册
- 在进行编写实际的驱动程序之前,要先学会查看原理图,以及根据芯片手册来找到对应的引脚或接口功能;
- 以蜂鸣器为例;
- 查看硬件原理图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PEHbgHtw-1681031461750)(C:\Users\ly\AppData\Roaming\Typora\typora-user-images\image-20230409091655793.png)]
上图显示 SNVS_TAMPER1引脚来控制蜂鸣器的通断;
-
查看芯片手册
总结查看步骤:
- 找到SNVS_TAMPER1对应的功能表,对应的复用MUX功能;
- 复用功能的GPIO脚位,查看该脚位的Memory Map
- 查看GPIO映射表
- 找到SNVS_TAMPER1对应的功能表,对应的复用MUX功能;
蜂鸣器–杂项设备驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/kernel.h>
#define GPIO5_DR 0x020AC000
unsigned int * vir_addr ;
int misc_open(struct inode * inode,struct file * file)
{
printk("misc open\n");
return 0 ;
}
ssize_t misc_write(struct file* file ,char * userbuf, size_t size ,loff_t * loff)
{
char kbuf[64] = {0} ;
if(copy_from_user(kbuf,userbuf,size)!=0)
{
printk("copy fail\n");
return -1;
}
printk("kbuf[0] = %d\n",kbuf[0]);
if(kbuf[0] == 1)
*vir_addr |= (1<<1) ;
else
*vir_addr &= ~(1<<1) ;
return 0 ;
}
struct file_operations ops = {
.owner = THIS_MODULE,
.open = misc_open ,
.write = misc_write ,
};
struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello",
.fops = &ops,
};
static int hello_init(void)
{
int ret ;
ret = misc_register(&misc);
if(ret < 0 )
printk("misc register fail\n");
vir_addr = ioremap(GPIO5_DR,4);
if(vir_addr == NULL)
{
printk("map error\n");
return -1 ;
}
printk("map ok\n");
printk("hello world\r\n");
return 0;
}
static void hello_exit(void)
{
misc_deregister(&misc) ;
iounmap(vir_addr) ;
printk("hello byebye\r\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
编码问题
-
Linux下的open 函数
-
头文件
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
-
模式常量
三种常用的操作模式
- O_RDONLY : 只读模式
- O_WRONLY : 只写模式
- O_RDWR :读写模式
-
-
文件流操作下,打开一个文件后,结束时一定要close 关闭文件 ;
-
对于指针直接操作存在的问题
上述
vir_addr
指针,直接进行赋值与位操作赋值存在区别*vir_addr = 1; *vir_addr = 0;
*vir_addr |=(1<<1); *vir_addr &= ~(1<<1);
这两个种操作方式,第一种是直接赋值,把vir_addr指向的对象直接赋值为0/1 ;
第二种方式是位操作,针对需要操作的位进行赋值;显而易见,第二种操作方式是正确的的;