IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 2021-07-28嵌入式学习---驱动 -> 正文阅读

[嵌入式]2021-07-28嵌入式学习---驱动

一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设
备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各
设备。
主设备号用来区分不同种类的设备(华为),而次设备号用来区分同一类型的多个设备(华为mate40)。

从用户态空间贯穿到底层驱动

Linux系统中对于文件的所有操作离不开三个通用的API函数:open、read、wirte,
例如使用函数open打开一个引脚4文件 pin4 到与底层硬件交互时,一共会经过以下三步:

  • 在用户态空间调用函数 OPEN 触发进程 open("/dev/pin4",O_RDWR),产生一个软中断(中断号
    0x80)进入内核空间;
  • 内核会调用函数 sys_call(System call interface),从sys_call到sys_open中间实际上经过一层虚拟文件系统VFS,做到了不同文件系统但有了统一的接口, 根据设备的文件名在内核的驱动链表中寻找设备的设备号(分为主设备号、次设备号);
  • 在寻找到设备号之后调用函数sys_open,再调用对应的驱动文件里的open,最终实现寄存器的电平操作。「在最终的硬件动作方面,单片机与Linux本质上是一样的

内核驱动 pin4driver.c

#include <linux/fs.h>       //file_operations声明
#include <linux/module.h>   //module_init module_exit 声明
#include <linux/init.h>     //_init _exit 宏定义声明
#include <linux/device.h>    //class device声明
#include <linux/uaccess.h>   //copy_from_user的头文件
#include <linux/types.h>     //设备号dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件

static struct class *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;     //设备名
static int major = 231; //主设备号
static int minor = 0;   //次设备号
static char *module_name = "pin4"; //模块名

static int pin4_read(struct file *file, char _user *buf, size_t count, loft_t *ppos)
{
	printfk("pin4_read\n");  //内核的打印函数,和printf类似
	return 0;
}
//led_open函数
static int pin4_open(struct inode *inode, struct file *file)
{
	printf("pin4_open\n");
	return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file, const char _user *buf, size_t count, loff_t *ppos)
{
	printk("pin4_write\n");
	return 0;
}

static struct file_operations pin4_fops = {
	.owner = THIS_MODULE,
	.open = pin4_open,
	.write = pin4_write,
	.read = pin4_read,
};

int _init pin_drv_init(void)//真实驱动入口
{
	int ret;
	devno = MKDEV(major, minor);
	ret = register_chrdev(major, module_name, &pin4_fops);//3.注册驱动,告诉内核,把这个驱动加入到内核的链表中

	pin4_class = class_create(THIS_MODULE, "myfirstdemo");//让代码在Dev生成设备
	pin_class_dev = device_create(pin4_class, NULL, devno, NULL, module_name);//创建设备文件

	return 0;
}

void _exit pin4_drv_exit(void)
{
	device_destory(pin4_class, devno);
	class_destroy(pin4_class);
	unregister_chrdev(major, module_name);  //卸载驱动
}

module_init(pin4_drv_init);  //入口,内核加载驱动的时候,这个宏被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

编写代码,需要调用驱动 pin4test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd;
	fd = open("/dev/pin4", O_RDWR);
	write(fd, '1', 1);

	return 0;
} 

注意:

  1. 内核代码的编写与上层代码的编写有不同之处。内核里的文件代码是一个异常巨大的结构,若你希望在内核里的驱动链表里添加驱动文件,那就必须遵循链表的操作规则,即
    驱动框架。
  2. 上层与底层的代码实际上是一一对应的,上层opne("/dev/pin4",O_RDWR)底层一定有pin4_open,操作函数read、write同理。
  3. 对于其他底层的驱动文件也是类似的,open、read、write都是被放在大的结构体之后才加入到驱动链表中。
  4. 不同于系统的上层文件,底层的驱动框架文件中对于变量的声明有大量的static。之所以大量充斥着该函数,是因为整个内核文件数量庞大足足有上万个之多,容易与其他文件的函数命名冲突,函数static限定了变量的作用域仅在该文件中。

编译两个文件

  1. 引脚驱动文件编译。 将引脚的驱动文件放在系统的字符驱动文件目录下home/xxx/linux-rpi-4.14.y/drivers/char,直接 vi 修改引导配置文件Makefile,在文件中直接插入字段 obj-m += pin4Driver.o(前者代表编译的方式,后者代表编译生成的文件。)
  2. 模块化编译。 在第一步的前提下跳转回到系统源码目录下 home/xxx/linux-rpi-4.14.y/
    ,输入指令进行交叉编译:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
    KERNEL=kernel7 make -j4 modules。等待编译完成后再次打开字符设备驱动文件,编译成功则目录底下会生成新的文件pin4Driver.ko。“j4”表示使用4个核进行编译工作,“modules”表示生成的驱动模块
  3. 上层应用测试代码编译。上层的文件处理比较简单,直接对其进行交叉编译处理即可,输入指令:arm-linux-gnueabihf-gcc pin4Test.c -o pin4Test。

在树莓派内加载驱动

  1. 驱动加载
    输入指令:sudo insmod pin4Driver.ko。查看驱动文件是否加载成功有两个方面:
    输入指令:ls /dev/pin4 -l ,查看设备文件下是否有设备文件;
    输入指令:lsmod ,查看内核是否挂载设备驱动文件 pin4Driver.ko。
  2. 设备权限设置
    输入指令:sudo chmod 666 /dev/pin4
    【666 ,表示所有人都可以访问该设备。】
  3. 测试
    在树莓派主目录 pi@home 下运行文件 ./pin4Test。注意,此时上层界面是没有任何显示的,真正的运行现场是在内核空间。输入指令dmesg | grep pin4 ,可以查看内核中调用函数 printk() 成功,完成了从最上层到最底层的逻辑调用。

驱动

两个部分,第一部分为驱动的加载部分,第二部分为驱动加载完成后,驱动的使用时的调用过程。

驱动加载:

1、将驱动源代码编译后,生成ko文件,这是将要加载的驱动模块。2、调用命令insmod加载模块,首先会找代码里边固定的宏moudle_init()来找到驱动中的初始化函数这里是s5pv210_led_init()和退出函数s5pv210_led_exit()。

3、调用s5pv210_led_init()来进行设备号注册,和设备添加。MKDEV是一个宏,可以通过移位把主设备号和次设备号进行处理,生成一个32位的数据。调用register_chrdev_region()注册设备号,linux驱动根据散列hash表来建立设备描述cdev结构体的索引,当hash表的index冲突时,采用链表的方式避免冲突,这样可以通过设备号快速找到cdev结构体的地址。

4、调用cdev_init()来初始化结构体cdev,最重要的是将file_operations保存在cdev中,file_oerations里边有本地实现的open release ioctl等具体功能的函数指针,这样可以在使用驱动的时候找到相应的实现函数。

5、调用cdev_add()来添加设备结构体cdev到hash表中,根据参数设备号可以找到注册设备号时在hash表中的位置,然后将cdev结构体地址添加进去

6、映射io端口,即映射io端口的物理地址为虚拟地址,因为在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定,可以在数据手册中找到。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内,然后才能根据映射所得到的核心虚地址范围,通过访问内存指令访问这些I/O内存资源。

7、使用命令mknod 添加/dev目录下设备描述文件,其实主要就是描述了我们输入的三个参数,首先c代表字符型设备,500代表主设备号,0代表次设备号。三个参数的用法在下边的流程描述。

8、调用rmmod命令后,卸载驱动,找到驱动中moudle_exit()宏来找到卸载驱动的退出函数,在这个里边调用cdev_del()和unregister_chrdev_region()删除设备并且去掉设备号的注册,相反的过程,不用赘言。

驱动使用:

1、在驱动测试文件中,首先打开了/dev目录下的设备文件,但是这个文件只是设备基本信息的描述,没有实质的动作,具体的作用可以看作为设备的索引,通过打开文件可以找到设备驱动的位置。这里分析我们输入的三个参数,c表示字符型设备open系统调用中拿到c就知道要去找字符型设备的结构体。主设备号和次设备号用来索引hash散列表,找到cdev结构体的地址,而cdev中保存有file_operations的地址,就可以找到驱动的具体实现函数,就是驱动加载的逆过程。而open执行完之后,返回一个文件描述符,这个描述符中就带有找到的cdev地址。

2、Open函数根据得到的cdev找到file_operations 中的.open对应的的函数指针,调用这个函数来初始化驱动,这里可以做io端口控制寄存器的设置,将相关的端口设置为输出。

3、调用ioctl通信,ioctl是io管道管理函数,是linux系统封装用来给驱动用的通信函数,方便用户使用,不用关心通信的实现方式,也不用考虑通信是否跨线程或者跨进程,可以看作是一个通道,在使用时塞入数据,在驱动里边写好拿出数据并做相应的处理及可以,感觉非常像socket套接字。

4、测试文件中调用ioctl传入数据,ioctl根据传入的文件描述符参数中的cdev找到file_operations 中的. unlocked_ioctl对应的的函数指针,在这个函数里边调用copy_from_user(),可以取出传入的参数,根据参数做驱动对应的动作。

5、退出驱动后,跟2-4同样的道理找到.release执行,释放驱动。

学习链接:https://blog.csdn.net/weixin_46959681/article/details/117962761

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-29 11:48:50  更:2021-07-29 11:49:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 9:37:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计