IMX6Ull驱动
mount -t nfs -o nolock,vers=3 192.168.1.117:/home/book/nfs_rootfs /mnt
cat /proc/sys/kernel/printk
echo 8 > /proc/sys/kernel/printk
cp led.ko ~/nfs_rootfs/
arm-buildroot-linux-gnueabihf-gcc app.c -o app -static
vi ~/.bashrc
一、C语言LED驱动实验
1.设置处理器模式
设置6ULL处于SVC模式下。设置CPSR寄存器的bit4-0,也就是M[4:0]=0x13。读写状态要用到MRS指令和MSR指令。MRS指令将CPSR寄存器数据读出到通用寄存器里面,MSR指令将通用寄存器的值写入到CPSR里面去。
2.设置SP指针
处理器栈增长方式,对于A7而言是向下增长的。设置SP指向 0x200000+0x80000000=0x80200000。
3.跳转到C语言
使用b指令,跳转到C语言函数,比如main函数
1.怎么进入到make menuconfig图形化界面?
首先进入到内核源码的路径下,然后输入make menuconfig即可打开这个界面。
2.make menuconfig图形化界面的操作
(1)搜索功能
输入 / ,即可弹出搜索界面,然后输入我们想要搜索的内容即可。
(2)配置驱动的状态。
a.把驱动编译成模块
b.把驱动编译到内核里面,用*
c.不编译
3.和make menuconfig有关的文件
Makefile 里面是编译规则,告诉我们在make 的时候要怎么编译,相当于菜的做饭。
Kconfig 内核配置的选项,相当于吃饭的菜单。
.config 配置完内核以后生成的配置选项,相当于我们点完的菜
4.make menuconfig 会读取哪个目录下的Kconfig文件。
arch/ $ARCH /目录下的Kconfig。
/arch/arm/configs#下面有好多的配置文件。相当于饭店的特色菜。
5.为什么要复制成.config而不复制成其他文件呢?
因为内核会默认读取Linux内核根目录下的.config作为默认的配置选项,所以不能改名字。
6.复制的这个默认的配置选项不符合要求咋办?
7.怎么和Makefile文件建立的关系呢?
内核中编译驱动
Kconfig的一个例子
source “drivers/redled/Kconfig” config LED__4412 tristate “Led Support for GPIO Led” depends on LEDS_CLASS help
1.source “drivers/redled/Kconfig”, 他会包含 drivers/redled/这个路径下的驱动文件,方便我们对菜单进行管理
2.config LED__4412 配置选项的名称,CONFIG_LED_4412
3.tristate 表示的驱动的状态, 三种状态是把驱动编译成模块, 把驱动编译到内核, 不编译。 与之对应的还
有 bool 分别是编译到内核, 不编译
4 “Led Support for GPIO Led”make menuconfig 显示的名字
5 A depends on B 表示只有在选择 B 的时候才可以选择 A
比如我想直接去掉 LED 相关的驱动, 我们直接改.config 文件可以吗? 可以, 但是不推荐。 如果有依赖的话,
直接修改.config 是不成功的。
6.select 反向依赖, 该选项被选中时, 后面的定义也会被选中。
7.help
我们输入“ vim Kconfig” 命令编辑 Kconfig, Kconfig 写入以下内容:
config HELLO tristate "helloworld" help hello hello
config HELLO 里就是Makefile里的名字。
Linux三大设备驱动
字符设备:IO的传输过程是以字符位单位的,没有缓冲的。比如 I2C,SPI 都是字符设备
块设备:IO的传输过程是以块为单位的。跟存储相关的,都属于块设备,比如 tf 卡
网络设备:与前两个不一样,是以socket套接字来访问的。
1.杂项设备是字符设备的一种,可以自动生成设备节点。
我们的系统里面有很多杂项设备。我们可以输入 cat /proc/misc 命令来查看。
2.杂项设备除了比字符设备简单,杂项设备的主设备号是相同的,均为10次,次设备号是不同的。主设备号相同就可以节省内核的资源。
3.主设备号和次设备号的概念
设备号包含主设备号和次设备号, 设备号是计算机识别设备的一种方式, 主设备号相同的就被视为同
一类设备, 主设备号在 Linux 系统里面是唯一的, 次设备号不一定唯一。
主设备可以比作电话号码的区号。比如北京区号010。次设备号相当于电话号码。
主设备号可以通过以下命令来查看, 前面的数字就是主设备号, 如下图所示:cat /proc/devices
4.杂项设备的描述
misc 设备用 miscdevice 结构体表示, 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; };
file_operations 文件操作集在定义在 include/linux/fs.h 下面
里面的一个结构体成员都对应一个调用。
extern int misc_register(struct miscdevice *misc);注册杂项设备
extern void misc_deregister(struct miscdevice *misc);注销杂项设备
5 注册杂项设备的流程。
(1)填充 miscdevice 这个结构体
(2)填充 file_operations 这个结构体
(3)注册杂项设备并生生成设备节点
#include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> /* for kernel specific devices */ struct file_operations misc_fops = { //文件操作集 .owner = THIS_MODULE }; struct miscdevice misc_dev = { //杂项设备结构体 .minor = MISC_DYNAMIC_MINOR, //动态申请的次设备号 .name = "hello_misc", //杂项设备名字是hello_misc .fops = &misc_fops, //文件操作集 }; static int misc_init(void) //注册杂项设备 { int ret; ret = misc_register(&misc_dev); if (ret < 0) { printk("misc registe is error\n"); return -1; } printk("misc registe is succed\n"); return 0; } static void misc_exit(void) { misc_deregister(&misc_dev); printk("misc bye bye\n"); } module_init(misc_init); module_exit(misc_exit); MODULE_LICENSE("GPL");
应用层和内核层数据传输
Linux一切接文件!
文件对应的操作有打开,关闭,读写。
设备节点对应的操作有打开,关闭,读写
1.如果我们在应用层使用系统IO对设备节点进行打开,关闭,读写等操作会发生啥???
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int);
//当我们在应用层read设备节点的时候,就会触发我们驱动里面read这个函数。
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
//当我们在应用层read设备节点的时候,就会触发我们驱动里面read这个函数
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//当我们在应用层 poll/select设备节点的时候,就会触发我们驱动里面poll这个函数
unsigned int (*poll) (struct file *, struct poll_table_struct *);
//当我们在应用层ioctl设备节点的时候,就会触发发我们驱动里面ioctl这个函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
//当我们在应用层open设备节点的时候,就会触发我们驱动里面open这个函数
int (*open) (struct inode *, struct file *);
//当我们在应用层close设备节点的时候,就会触发我们驱动里面close这个函数
int (*release) (struct inode *, struct file *);
通过框图我们可以知道:
上层应用 设备节点 底层驱动
设备节点就是连接上层应用和底层驱动的桥梁
2.假如我们的file_operations里面没有read,我们在应用层read设备
3.我们的应用层和内核层是不能直接进行数据传输的。
copy to
open read write close
应用层从内核读数据
从设备中读取数据, 当用户层调用函数 read 时, 对应的, 内核驱动就会调用这个函数。
应用层从内核写数据
Linux物理地址到虚拟地址映射
1.操作一个寄存器, 可以定义一个指针来操作寄存器
unsighted int *p = 0x12345678;
*p=0x87654321;
但是在linux上不行,在Linux上,如果想要操作硬件,需要先把物理地址转换成虚拟地址。
因为Linux使能了MMU,所以我们在Linux上不能直接操作物理地址。
2.使能了MMU的好处???
(1)让虚拟地址成了可能
(2)可以让系统更加安全,因为有了MMU,我们上层应用看到的内存都是虚拟内存,我们的应用就不能直接访问硬件,所以这样就保证了系统安全。
3.MMU非常复杂,如何完成物理地址到虚拟地址的转换呢??
内核提供了函数
ioremap: 把物理地址转换成虚拟地址
iounmap: 释放掉 ioremap 映射的地址
phys_addr_t offset:映射物理地址的起始地址。
size_t size:要映射多大的内存空间
返回值:成功返回虚拟地址的首地址,失败返回NULL
static inline void iounmap(void __iomem *addr)*addr:要取消映射的虚拟地址的首地址。
注意: 物理地址只能被映射一次, 多次映射会失败。
4.如何查看哪些物理地址被映射过了呢??
可以用 cat /proc/iomem
驱动模块传参
1.什么是驱动传参
驱动传参就是传递参数给我们的驱动。
ex:
insmod beep.ko a=1
2.驱动传参的作用??
(1)设置驱动的相关参数,比如设置缓冲区的大小
(2)设置安全校验,防止我们写的驱动被人盗用
3.怎么给驱动传参数?
(1)传递普通的参数,比如char ,int 类型的
函数:
module_param(name, type, perm);
参数:
name 要传递进去的参数的名称
type 类型
perm 参数读写的权限
(2)传递数组
module_param_array(name, type, nump, perm);
参数:
name 要传递进去的参数的名称
type 类型
nump 实际传入进去的参数的个数
perm 参数读写的权限
#include <linux/init.h> #include <linux/module.h> static int a; static int b[5]; static int count; //传递普通的参数 名字 类型 参数读写的权限 module_param(a, int, S_IRUSR); //传递数组 名字 类型 传入进去参数的个数 参数读写的权限 module_param_array(b, int, &count, S_IRUSR); static int hello_init(void) { int i; for(i=0; i<count; i++) { printk("b[%d] = %d\n", i , b[i]); } printk("count = %d \n", count); printk("a = %d \n", a); return 0; } static void hello_exit(void) { // printk("a=%d \n", a); // printk("bye bye! \n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
(3)如果多传递进去参数,会发生什么?
会报错!!!
1.字符设备和杂项设备的区别
1.杂项设备的主设备号是固定的,固定为10, 那么我们要学习的字符类设备号就需要自己或者系统来分配了。
杂项设备可以自动生成设备节点,字符设备需要我们自己生成设备节点。
2.注册字符类设备号的两个方法。
第一种:静态分配一个设备号,使用的是:
register_chrdev_region(dev_t, unsigned, const char *);
需要明确知道我们系统里面哪些设备号没有用。
参数:
dev_t:设备号的起始值。类型是 dev_t 类型
unsigned:次设备号的个数。
const char *:设备的名称
返回值:成功返回0,失败返回非0
dev_t 类型:
dev_t是用来保存设备号的,是一个32位数。
高12位是用来保存设备号,低12位用来保存次设备的号
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
Linux提供了几个宏定义来操作设备号
#define MINORBITS 20
次设备号的位数,一共是20位 #define MINORMASK ((1U << MINORBITS) - 1) 次设备号的掩码
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
在dev_t 里面获取我们的主设备号 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
在dev_t 里面获取我们的次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
将我们主设备号和次设备号组成一个dev_t类型。第一个参数是主设备号,第二个参数是次设备号。
第二种方法:动态分配
alloc_chrdev_region(dev_t * , unsigned, unsigned, const char *);
参数:
dev_t *:保存生成的设备号
unsigned:我们请求的第一个次设备号,通常是0
unsigned:连续申请的设备号的个数。
const char *:设备名称
返回值:成功返回0,失败返回负数
使用动态分配会优先使用255到234
3.注销设备号
unregister_chrdev_region(dev_t, unsigned)
dev_t 分配设备号的起始地址
unsigned 申请的连续设备号的个数
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/kdev_t.h> #define DEVICE_NUMBER 1 //定义次设备号的个数 #define DEVICE_SNAME "schrdev" //定义静态注册设备的名称 #define DEVICE_ANAME "achrdev" //定义动态注册设备的名称 #define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址 static int major_num, minor_num; //定义主设备号和次设备号 //传递普通的参数 名字 类型 参数读写的权限 module_param(major_num, int, S_IRUSR); module_param(minor_num, int, S_IRUSR); static int hello_init(void) { dev_t dev_num; int ret; /* 如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/ if(major_num) { /** 静态注册设备号 */ printk("major_num = %d \n", major_num); printk("minor_num = %d \n", minor_num); dev_num = MKDEV(major_num, minor_num); //MKDEV 将主设备号和次设备号合并为一个设备号 ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); if(ret < 0) { printk("register_chrdev_region error\n"); } printk("register_chrdev_region OK\n"); } else { //保存申请的设备号,次设备号起始地址,设备号数量,设备名字 ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME); if(ret < 0) { printk("alloc_chrdev_region error\n"); } printk("alloc_chrdev_region OK\n"); major_num = MAJOR(dev_num); minor_num = MINOR(dev_num); printk("major_num = %d \n", major_num); printk("minor_num = %d \n", minor_num); } return 0; } static void hello_exit(void) { unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); printk("bye bye! \n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
- 使用静态分配
- 使用动态分配,不传递参数
建议使用动态申请。如果驱动很多人用,动态不会重复!!!
注册杂项设备:
misc_register(&misc_dev);
注销杂项设备:
misc_deregister(&misc_dev);
cdev结构体:描述字符设备的一个结构体
struct cdev{
struct kobject kobj;
struct module *owner;
const struct file_operation *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
步骤一:定义一个cdev结构体
步骤二:使用cdev_init函数初始化cdev结构体成员变量
void cdev_init(struct cdev *, struct file_operations *);
参数:
struct cdev * 要初始化的cdev
file_operations * 文件操作集
cdev->ops=fops; //实际就是把文件操作集写个ops
步骤三:cdev_add函数注册到内核
cdev_add(struct cdev *, dev_t, unsigned);
第一个参数:cdev 的结构体指针
第二个:设备号
第三个:次设备号的数量
注销字符设备:
void cdev_del(struct cdev *);
字符设备注册完以后自动生成设备节点。
我们需要使用 mknod 命令创建一个设备节点
格式:
mknod 名称 类型 主设备号 次设备号
eg:
mknod /dev/test c 245 0
32
高12 主 低20 次设备号
手动创建
可以通过命令mknod创建设备节点。
mknod命令格式:
mknod 设备节点名称 设备类型(字符设备用c,块设备用b) 主设备号 次设备号
举例: mknod /dev/test c236 0
2.在注册设备的时候自动创建。
可以通过mdev机制实现设备节点的自动创建与删除。
自动创建设备节点
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #include <linux/device.h> #define DEVICE_NUMBER 1 //定义次设备号的个数 #define DEVICE_SNAME "schrdev" //定义静态注册设备的名称 #define DEVICE_ANAME "achrdev" //定义动态注册设备的名称 #define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址 #define DEVICE_CLASS_NAME "chrdev_class" //宏定义类名 static int major_num, minor_num; //定义主设备号和次设备号 struct cdev cdev; struct class *class; //传递普通的参数 名字 类型 参数读写的权限 module_param(major_num, int, S_IRUSR); module_param(minor_num, int, S_IRUSR); int chrdev_open(struct inode *inode, struct file *file) { printk("chrdev_open\n"); return 0; } struct file_operations chrdev_ops = { .owner = THIS_MODULE, .open = chrdev_open}; static int hello_init(void) { dev_t dev_num; int ret; /* 如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/ if (major_num) { /** 静态注册设备号 */ printk("major_num = %d \n", major_num); printk("minor_num = %d \n", minor_num); dev_num = MKDEV(major_num, minor_num); // MKDEV 将主设备号和次设备号合并为一个设备号 ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); if (ret < 0) { printk("register_chrdev_region error\n"); } printk("register_chrdev_region OK\n"); } else { //保存申请的设备号,次设备号起始地址,设备号数量,设备名字 ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME); if (ret < 0) { printk("alloc_chrdev_region error\n"); } printk("alloc_chrdev_region OK\n"); major_num = MAJOR(dev_num); minor_num = MINOR(dev_num); printk("major_num = %d \n", major_num); printk("minor_num = %d \n", minor_num); } cdev.owner = THIS_MODULE; cdev_init(&cdev, &chrdev_ops); cdev_add(&cdev, dev_num, DEVICE_NUMBER); class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
return 0; } static void hello_exit(void) { unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); cdev_del(&cdev); class_destroy(class); printk("bye bye! \n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #include <linux/device.h> #define DEVICE_NUMBER 1 //定义次设备号的个数 #define DEVICE_SNAME "schrdev" //定义静态注册设备的名称 #define DEVICE_ANAME "achrdev" //定义动态注册设备的名称 #define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址 #define DEVICE_CLASS_NAME "chrdev_class" //宏定义类名 #define DEVICE_NODE_NAME "chrdev_test" //宏定义设备节点的名字 static int major_num, minor_num; //定义主设备号和次设备号 dev_t dev_num; //设备号 struct cdev cdev; struct class *class; struct device *device; //传递普通的参数 名字 类型 参数读写的权限 module_param(major_num, int, S_IRUSR); module_param(minor_num, int, S_IRUSR); int chrdev_open(struct inode *inode, struct file *file) { printk("chrdev_open\n"); return 0; } struct file_operations chrdev_ops = { .owner = THIS_MODULE, .open = chrdev_open}; static int hello_init(void) { dev_t dev_num; int ret; /* 如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/ if (major_num) { /** 静态注册设备号 */ printk("major_num = %d \n", major_num); printk("minor_num = %d \n", minor_num); dev_num = MKDEV(major_num, minor_num); // MKDEV 将主设备号和次设备号合并为一个设备号 ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); if (ret < 0) { printk("register_chrdev_region error\n"); } printk("register_chrdev_region OK\n"); } else { //保存申请的设备号,次设备号起始地址,设备号数量,设备名字 ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME); if (ret < 0) { printk("alloc_chrdev_region error\n"); } printk("alloc_chrdev_region OK\n"); major_num = MAJOR(dev_num); minor_num = MINOR(dev_num); printk("major_num = %d \n", major_num); printk("minor_num = %d \n", minor_num); } cdev.owner = THIS_MODULE; cdev_init(&cdev, &chrdev_ops); cdev_add(&cdev, dev_num, DEVICE_NUMBER); class = class_create(THIS_MODULE, DEVICE_CLASS_NAME); //在class类下创建设备 设备号 设备节点的名字 device = device_create(class, NULL, dev_num, NULL, DEVICE_NODE_NAME); return 0; } static void hello_exit(void) { unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); cdev_del(&cdev); device_destroy(class, dev_num); class_destroy(class); printk("bye bye! \n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
平台总线模型介绍
1.什么是平台总线模型?
平台总线模型也叫platform总线模型。是Linux内核虚拟出来的一条总线,不是真实的导线。
平台总线模型就是把原来的驱动C文件分成了两个C文件,一个是
2.为什么会有平台总线模型?
(1)可以提高代码的重用性
(2)减少重复性代码。
设备 总线 驱动
device.c driver.c
3.平台总线的优点
4.怎么编写以平台总线模型设计的驱动?
一个是device.c,一个是driver.c,分别注册device.c和driver.c。
平台总线是以名字来匹配的,实际上就是字符串比较。
注册Platform设备
1.平台总线注册一个device
device.c里面写的是硬件资源,这里的硬件资源是指寄存器的地址,中断号,时钟等硬件资源。
在Linux内核里面,我们是用一个结构体来描述硬件资源的。
struct platform_device { const char *name; //平台总线进行匹配的时候用到的name, /sys/bus...... int id; //设备id,一般写-1 bool id_auto; struct device dev;//内嵌的device结构体
u32 num_resources; //资源的个数 struct resource *resource;//device里面的硬件资源 };
#define IORESOURCE_IO IO 的内存
#define IORESOURCE_MEM 表述一段物理内存
#define IORESOURCE_IRQ 表示中断
注册platform驱动
编写driver.c思路
首先定义一个platform_driver结构体变量,然后去实现结构体中的各个成员变量,那么当我们的driver和device匹配成功的时候,就会执行probe函数,所以匹配成功以后的重点就在于probe函数的编写。
struct platform_driver { /*当 driver 和 device 匹配成功的时候,就会执行 probe 函数*/ int (*probe)(struct platform_device *); /*当 driver 和 device 任意一个 remove 的时候,就会执行这个函数*/ int (*remove)(struct platform_device *); /*当设备收到 shutdown 命令的时候,就会执行这个函数*/ void (*shutdown)(struct platform_device *); /*当设备收到 suspend 命令的时候,就会执行这个函数*/ int (*suspend)(struct platform_device *, pm_message_t state); /*当设备收到 resume 命令的时候,就会执行这个函数*/ int (*resume)(struct platform_device *); //内置的 device_driver 结构体 struct device_driver driver; //该设备驱动支持的设备的列表, 他是通过这个指针去指向 platform_device_id 类型的数组 const struct platform_device_id *id_table;
};
struct device_driver{ const char *name; //这个是我们匹配时用到的名字 struct bus_type *bus; struct module *owner; }
改改
id_table的优先级比driver的.name这个高。
driver.c里的.name的名字要和device.c里面的名字一样
1.driver.c和device.c里面都要定义一个platform_driver和platform_device结构体变量。
2.匹配成功,执行probe函数。。id_table的优先级比driver.name的优先级要高。
平台总线probe函数的编写
编写probe函数的思路:
(1)从 device.c 里面获得硬件资源,因为我们的平台总线将驱动拆成了俩部分,第一部分是 device.c,另一部分是 driver.c。那么匹配成功了之后,driver.c 要从 device.c 中获得硬件资源,那么 driver.c 就是在 probe 函数中获得的。
方法一: 直接获取,不推荐
int beep_probe(struct platform_device *pdev){ printk("beep_probe\n"); return 0; }
方法二:只用函数获得
extern struct resource * platform_get_resource(struct platform_device *, unsigned int ,unsigned int)
(2)获得硬件资源之后,就可以在 probe 函数中注册杂项/字符设备,完善 file_operation 结构体,并生成设备节点。
注册之前要先登记:
request()
ls /sys/bus/platform/devices/
设备树中添加自定义节点
make dtbs
cp arch/arm/boot/dts/100ask_imx6ul l_mini.dtb ~/nfs_rootfs/decp
sudo cp arch/arm/boot/dts/100ask_i mx6ull_mini.dtb ~/nfs_rootfs/demo_hcl/
获得设备树文件节点里面资源的步骤:
步骤一:查找我们要找的节点。
步骤二:获取我们需要的属性值。
与查找节点有关的 OF 函数有 3 个
- inline struct device_node *of_find_node_by_path(const char *path)
- struct device_node *of_get_parent(const struct device_node *node)
- struct device_node *of_get_next_child(const struct device_node *node struct device_node *prev)
获取属性值的 of 函数
- of_find_property 函数 property *of_find_property(const struct device_node *np,const char *name,int *lenp)
设备树下的platform总线
优先级顺序:
Pinctrl 子系统和 GPIO 子系统
匹配成功后,进到了probe函数。查找我们要查找的节点
注册杂项设备和字符设备,GPIO用杂项设备完成
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/miscdevice.h> #include <linux/fs.h> /* for kernel specific devices */ #include <linux/uaccess.h> #include <linux/io.h> #include <linux/gpio.h> #include <linux/of_gpio.h> int size; u32 out_values[2]; struct device_node *test_device_node; struct property *test_node_property; unsigned int *virt_gpio_dr; int led_gpio = 0; int misc_open(struct inode *inode, struct file *file) { printk("hello misc_open\n"); return 0; } int misc_release(struct inode *inode, struct file *file) { printk("hello misc_release bye bye\n"); return 0; } ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t) { char kbuf[64] = "hahaha"; if (copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0) //成功了是0 { printk("copy_to_user error\n"); return -1; } return 0; } ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t) { char kbuf[64] = {0}; if (copy_from_user(kbuf, ubuf, size) != 0) //成功 了是0 { printk("copy_from_user error\n"); return -1; } printk("kbuf is %s\n", kbuf); if (kbuf[0] == 1) gpio_set_value(led_gpio, 1); else if (kbuf[0] == 0) gpio_set_value(led_gpio, 0); return 0; } struct file_operations misc_fops = { .owner = THIS_MODULE, .open = misc_open, .release = misc_release, .read = misc_read, .write = misc_write }; struct miscdevice misc_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "hello_misc", // .fops = &misc_fops}; int led_probe(struct platform_device *pdev) { int ret = 0; printk("led_probe\n"); /*查找我们要的节点*/ test_device_node = of_find_node_by_path("/test"); if (test_device_node == NULL) { printk("of_find_node_by_path is error~ \n"); return -1; } led_gpio = of_get_named_gpio(test_device_node, "led-gpio", 0); if(led_gpio <0){ printk("of_get_named_gpio is error~ \n"); return -1; } printk("led_gpio is %d \n", led_gpio); ret = gpio_request(led_gpio, "led"); if(ret <0){ printk("gpio_request is error~ \n"); return -1; } gpio_direction_output(led_gpio, 1); //杂项设备 ret = misc_register(&misc_dev); if (ret < 0) { printk("misc registe is error\n"); return -1; } return 0; } int led_remove(struct platform_device *pdev) { misc_deregister(&misc_dev); gpio_free(led_gpio); printk("led_remove \n"); return 0; } const struct platform_device_id led_id_table = { .name = "led_test"}; const struct of_device_id of_match_table[]={ {.compatible = "test1234"}, {} }; struct platform_driver led_device = { .probe = led_probe, .remove = led_remove, .driver = { .owner = THIS_MODULE, .name = "hhh", .of_match_table = of_match_table }, // const struct platform_device_id *id_table; //如果id_table的优先级比driver的.name这个高,就不能匹配成功 .id_table = &led_id_table }; static int led_driver_init(void) { int ret = 0; ret = platform_driver_register(&led_device); if (ret < 0) { printk("platform_driver_register error \n"); return ret; } printk("platform_driver_register OK~ \n"); return 0; } static void led_driver_exit(void) { printk("bye bye\n"); platform_driver_unregister(&led_device); } module_init(led_driver_init); module_exit(led_driver_exit); MODULE_LICENSE("GPL");
IOCTL接口
1.什么是unlocked_ioctl接口?
unlocked_ioctl就是ioctl接口,但是功能和对应的系统调用均没有发生变化。
2.unlocked_ioctl 和 read/write 函数有什么异同呢?
相同点:都可以往内核写数据。
不同点:read 函数只能完成读的功能,write 只能完成写的功能。读取大数据的时候效率高。ioctl 既可以读也可以写,读取大数据的时候效率不高。
3.unlocked_ioctl 接口命令规则
第一个分区:0-7, 命令的编号,范围是 0-255.
第二个分区:8-15,命令的幻数。
第一个分区和第二个分区主要作用是用来区分命令的。
第三个分区:16-29 表示传递的数据大小。
第四个分区:30-31 代表读写的方向。
00:表示用户程序和驱动程序没有数据传递
10:表示用户程序从驱动里面读数据
01:表示用户程序向驱动里面写数据
11:先写数据到驱动里面然后在从驱动里面把数据读出来。
中断基础概念
在设备树里面配置中断的时候只需要两个步骤即可:
1.把管脚设置为gpio功能。
2.使用 interrupt-parent 和 interrupts 属性来描述中断。

#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/interrupt.h> struct device_node *test_device_node; struct property *test_node_property; int gpio_nu;//GPIO编号 int irq; //中断号 irqreturn_t test_key(int irq, void *args) { printk("test_key hhh\n"); return IRQ_HANDLED; } int led_probe(struct platform_device *pdev) { int ret = 0; printk("led_probe\n"); //printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/ test_device_node = of_find_node_by_path("/test_key"); // if (test_device_node == NULL) // { // printk("of_find_node_by_path is error~ \n"); // return -1; // } gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); if(gpio_nu < 0){ printk("of_get_named_gpio is error~ \n"); return -1; } gpio_direction_input(gpio_nu); irq = gpio_to_irq(gpio_nu); ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL); if(ret < 0){ printk("request_irq is error~ \n"); return -1; } printk("irq is %d \n", irq); return 0; } int led_remove(struct platform_device *pdev) { printk("led_remove \n"); return 0; } const struct platform_device_id led_id_table = { .name = "led_test"}; const struct of_device_id of_match_table[]={ {.compatible = "keys"}, {} }; struct platform_driver led_device = { .probe = led_probe, .remove = led_remove, .driver = { .owner = THIS_MODULE, .name = "hhh", .of_match_table = of_match_table }, // const struct platform_device_id *id_table; //如果id_table的优先级比driver的.name这个高,就不能匹配成功 .id_table = &led_id_table }; static int led_driver_init(void) { int ret = 0; ret = platform_driver_register(&led_device); if (ret < 0) { printk("platform_driver_register error \n"); return ret; } printk("platform_driver_register OK~ \n"); return 0; } static void led_driver_exit(void) { printk("bye bye\n"); free_irq(irq, NULL); platform_driver_unregister(&led_device); } module_init(led_driver_init); module_exit(led_driver_exit); MODULE_LICENSE("GPL");
中断下文之tasklet
步骤一:定义一个 tasklet 结构体
步骤二:动态初始化 tasklet
步骤三:编写 tasklet 绑定的函数
步骤四:在中断上文调用 tasklet
步骤五:卸载模块的时候删除 tasklet
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/interrupt.h> #include <linux/of_irq.h> struct device_node *test_device_node; struct property *test_node_property; struct tasklet_struct key_test; int gpio_nu;//GPIO编号 int irq; //中断号 void test(unsigned long data) { int i = data; printk("i is %d\n", i); while(i--) printk("test_key is %d\n", i); } irqreturn_t test_key(int irq, void *args) { printk("start~\n"); tasklet_schedule(&key_test); printk("end~\n"); return IRQ_HANDLED; } int led_probe(struct platform_device *pdev) { int ret = 0; printk("led_probe\n"); //printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/ test_device_node = of_find_node_by_path("/test_key"); if (test_device_node == NULL) { printk("of_find_node_by_path is error~ \n"); return -1; } /** 获得GPIO的编号*/ gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); if(gpio_nu < 0){ printk("of_get_named_gpio is error~ \n"); return -1; } /*设置GPIO的方向*/ gpio_direction_input(gpio_nu); /*获得中断号*/ irq = gpio_to_irq(gpio_nu); //irq = irq_of_parse_and_map(test_device_node, 0); printk("irq is %d \n", irq); /*申请中断*/ ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL); if(ret < 0){ printk("request_irq is error~ \n"); return -1; } tasklet_init(&key_test, test, 100); return 0; } int led_remove(struct platform_device *pdev) { printk("led_remove \n"); return 0; } const struct platform_device_id led_id_table = { .name = "led_test"}; const struct of_device_id of_match_table[]={ {.compatible = "keys"}, {} }; struct platform_driver led_device = { .probe = led_probe, .remove = led_remove, .driver = { .owner = THIS_MODULE, .name = "hhh", .of_match_table = of_match_table }, // const struct platform_device_id *id_table; //如果id_table的优先级比driver的.name这个高,就不能匹配成功 .id_table = &led_id_table }; static int led_driver_init(void) { int ret = 0; ret = platform_driver_register(&led_device); if (ret < 0) { printk("platform_driver_register error \n"); return ret; } printk("platform_driver_register OK~ \n"); return 0; } static void led_driver_exit(void) { printk("bye bye\n"); free_irq(irq, NULL); tasklet_kill(&key_test); platform_driver_unregister(&led_device); } module_init(led_driver_init); module_exit(led_driver_exit); MODULE_LICENSE("GPL");
等待队列
阻塞: 操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
非阻塞: 操作的进程在不能进行设备操作时,并不挂起,它要么放弃,要么不停地查询,直至可以进行操作为止。
irqreturn_t test_key(int irq, void *args) { value = !value; wq_flags = 1; wake_up(&key_wq); return IRQ_RETVAL(IRQ_HANDLED); }
唤醒的时候不会立马解除阻塞,先去判断wq_flags是否达成,如果还是0,不会解除,如果标志位为1 解除。
工作队列
工作队列(workqueue)是实现中断下文的机制之一,是一种将工作推后执行的形式。那工作队列和我们之前学的 tasklet 机制有什么不同呢?tasklet 也是实现中断下文的机制。他们俩个最主要的区别是 tasklet不能休眠,而工作队列是可以休眠的。所以,tasklet 可以用来处理比较耗时间的事情,而工作队列可以处理非常复杂并且更耗时间的事情。
Linux 系统在启动期间会创建内核线程,该线程创建以后就处于 sleep 状态,然后这个线程会一直去队列里面读,看看有没有任务,如果有就执行,如果没有就休眠。
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/interrupt.h> #include <linux/of_irq.h> #include <linux/workqueue.h> struct device_node *test_device_node; struct property *test_node_property; //struct tasklet_struct key_test; struct work_struct key_test; int gpio_nu;//GPIO编号 int irq; //中断号 void test(struct work_struct *data) { int i = 100; printk("i is %d\n", i); while(i--) printk("test_key is %d\n", i); } irqreturn_t test_key(int irq, void *args) { printk("start~\n"); //tasklet_schedule(&key_test); //调度 schedule_work(&key_test); printk("end~\n"); return IRQ_HANDLED; } int led_probe(struct platform_device *pdev) { int ret = 0; printk("led_probe\n"); //printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/ test_device_node = of_find_node_by_path("/test_key"); if (test_device_node == NULL) { printk("of_find_node_by_path is error~ \n"); return -1; } /** 获得GPIO的编号*/ gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); if(gpio_nu < 0){ printk("of_get_named_gpio is error~ \n"); return -1; } /*设置GPIO的方向*/ gpio_direction_input(gpio_nu); /*获得中断号*/ //irq = gpio_to_irq(gpio_nu); irq = irq_of_parse_and_map(test_device_node, 0); printk("irq is %d \n", irq); /*申请中断*/ ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL); if(ret < 0){ printk("request_irq is error~ \n"); return -1; } //tasklet_init(&key_test, test, 100); INIT_WORK(&key_test, test); return 0; } int led_remove(struct platform_device *pdev) { printk("led_remove \n"); return 0; } const struct platform_device_id led_id_table = { .name = "led_test"}; const struct of_device_id of_match_table[]={ {.compatible = "keys"}, {} }; struct platform_driver led_device = { .probe = led_probe, .remove = led_remove, .driver = { .owner = THIS_MODULE, .name = "hhh", .of_match_table = of_match_table }, // const struct platform_device_id *id_table; //如果id_table的优先级比driver的.name这个高,就不能匹配成功 .id_table = &led_id_table }; static int led_driver_init(void) { int ret = 0; ret = platform_driver_register(&led_device); if (ret < 0) { printk("platform_driver_register error \n"); return ret; } printk("platform_driver_register OK~ \n"); return 0; } static void led_driver_exit(void) { printk("bye bye\n"); free_irq(irq, NULL); //tasklet_kill(&key_test); platform_driver_unregister(&led_device); } module_init(led_driver_init); module_exit(led_driver_exit); MODULE_LICENSE("GPL");
内核定时器
expires=jiffies+ 1*HZ
#include <linux/init.h> #include <linux/module.h> #include <linux/timer.h> static void timer_function(unsigned long data); DEFINE_TIMER(test_timer, timer_function, 0, 0);
/*现在是10:00,定时了一分钟,到了10:01进入了
超时处理函数,超时处理函数里面又使用了mod_timer()
把它设置了10:02,10:02进来设置成10:03...*/ static void timer_function(unsigned long data) { printk("This is time_function!\n"); mod_timer(&test_timer, jiffies+ 3*HZ); } static int hello_init(void) { printk("hello world!\n"); test_timer.expires=jiffies+ 3*HZ; add_timer(&test_timer);//启动定时器 return 0; } static void hello_exit(void) { printk("bye bye\n"); del_timer(&test_timer); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
按键消抖实验
按键按下,第一次进入中断,会定时20ms,20ms超时后,就会在超时处理函数判断当前电容是否低电平,如果是低电平,就执行想要执行的
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/interrupt.h> #include <linux/of_irq.h> #include <linux/timer.h> static void timer_function(unsigned long data); DEFINE_TIMER(test_timer, timer_function, 0, 0); struct device_node *test_device_node; struct property *test_node_property; int gpio_nu; // GPIO编号 int irq; //中断号 static void timer_function(unsigned long data) { printk("This is tim e_function!\n"); //mod_timer(&test_timer, jiffies + 1 * HZ); //周期性定时 } irqreturn_t test_key(int irq, void *args) { printk("test_key hhh\n"); //20ms test_timer.expires = jiffies + msecs_to_jiffies(20); //定义时间点 add_timer(&test_timer);//添加到内核里面 return IRQ_HANDLED; } int led_probe(struct platform_device *pdev) { int ret = 0; printk("led_probe\n"); // printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/ test_device_node = of_find_node_by_path("/test_key"); if (test_device_node == NULL) { printk("of_find_node_by_path is error~ \n"); return -1; } /** 获得GPIO的编号*/ gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); if (gpio_nu < 0) { printk("of_get_named_gpio is error~ \n"); return -1; } /*设置GPIO的方向*/ gpio_direction_input(gpio_nu); /*获得中断号*/ // irq = gpio_to_irq(gpio_nu); irq = irq_of_parse_and_map(test_device_node, 0); printk("irq is %d \n", irq); /*申请中断*/ ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL); if (ret < 0) { printk("request_irq is error~ \n"); return -1; } // printk("irq is %d \n", irq); return 0; } int led_remove(struct platform_device *pdev) { printk("led_remove \n"); return 0; } const struct platform_device_id led_id_table = { .name = "led_test"}; const struct of_device_id of_match_table[] = { {.compatible = "keys"}, {}}; struct platform_driver led_device = { .probe = led_probe, .remove = led_remove, .driver = { .owner = THIS_MODULE, .name = "hhh", .of_match_table = of_match_table}, // const struct platform_device_id *id_table; //如果id_table的优先级比driver的.name这个高,就不能匹配成功 .id_table = &led_id_table}; static int led_driver_init(void) { int ret = 0; ret = platform_driver_register(&led_device); if (ret < 0) { printk("platform_driver_register error \n"); return ret; } printk("platform_driver_register OK~ \n"); return 0; } static void led_driver_exit(void) { printk("bye bye\n"); free_irq(irq, NULL); platform_driver_unregister(&led_device); } module_init(led_driver_init); module_exit(led_driver_exit); MODULE_LICENSE("GPL");
输入子系统
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/input.h> int main(int argc, char *argv[]) { int fd; struct input_event test_event; fd = open("/dev/input/event1", O_RDWR); if (fd < 0) { perror("open error"); return fd; } while(1) { read(fd, &test_event, sizeof(test_event)); if(test_event.type == EV_KEY){ printf("type is %#x\n", test_event.type); printf("value is %#x\n", test_event.value); } } return 0; }
(二)
应用层实现I2C通信
IO 模型
A钓鱼不干其他的事情,等着
B钓鱼看书,画画...
C拿了几个鱼竿钓鱼
D在鱼竿上系了一个铃铛
A请助手B钓鱼
U-Boot
//uboot 版本号和编译时间,可以看出, //当前的 uboot 版本号是 2016.03,编译时间是 2020 年 8 月 7 日凌晨 20 点 47 分 U-Boot 2016.03-gd3f0479 (Aug 07 2020 - 20:47:37 +0800) //可以看出当前使用的 CPU 是飞思卡尔的 I.MX6ULL(I.MX 以 前属于飞思卡尔,然而飞思卡尔被 NXP 收购了),频率为 792MHz,但是此时运行在 396MHz。 这颗芯片是工业级的,结温为-40°C~105°C。 CPU: Freescale i.MX6ULL rev1.1 792 MHz (running at 396 MHz) CPU: Industrial temperature grade (-40C to 105C) at 51C //当前的复位原因是 POR。I.MX6ULL 芯片上有个 POR_B 引脚,将这 个引脚拉低即可复位 I.MX6ULL。 Reset cause: POR Board: I.MX6U ALPHA|MINI //板子名字 I2C: ready //提示 I2C 准备就绪 //提示当前板子的 DRAM(内存)为 512MB,如果是 NAND 版本的话内存为256MB。 DRAM: 512 MiB //提示当前有两个 MMC/SD 卡控制器:FSL_SDHC(0)和 FSL_SDHC(1)。I.MX6ULL 支持两个 MMC/SD,正点原子的 I.MX6ULL EMMC 核心板上 FSL_SDHC(0)接的 SD(TF)卡, FSL_SDHC(1)接的 EMMC。 MMC: FSL_SDHC: 0, FSL_SDHC: 1 Display: ATK-LCD-7-1024x600 (1024x600)// LCD 型号 Video: 1024x600x24 In: serial //标准输入、标准输出和标准错误所使用的终端 Out: serial Err: serial switch to partitions #0, OK mmc1(part 0) is current device Net: FEC1 //网口信息 Error: FEC1 address not set. Normal Boot Hit any key to stop autoboot: 0 =>
U-Boot启动流程
- 汇编阶段
1.初始化异常向量表
2.切换到SVC模式
在 reset 函 数 中 跳 转 到 save_boot_params , save_boot_params 定 义 在 100 行 , 功能是跳转到save_boot_params_ret,
save_boot_params_ret 主要功能是保证 CPU 在 SVC 模式,并禁止 FIQ 和 IRQ 中断。
往下是向量表重定位和 SPL 相关的设置,然后依次跳转到 cpu_init_cp15,cpu_init_crit,_main。
3.关闭MMU,关闭cache,禁止看门狗
定义了 cpu_init_cp15 函数,此函数主要功能是关闭 cache,MMU,TLB 等,这些都是虚拟内存转化相关功能,uboot 阶段使用的是物理内存。
关闭虚拟内存映射后跳转到 cpu_init_crit 函数,
cpu_init_crit 跳转到 lowlevel_init,查找函数”lowlevel_init”
lowlevel_init 定义在 arch/arm/cpu/armv7/lowlevel_init.S 文件,
4.清除bss段,,跳转到 board_init_f,
然后跳转到 board_init_r
- C语言阶段
1.初始gd结构体
gd 结构体用于存储全局信息,如 CPU 主频、环境变量地址、RAM 地址等。
2. 初始化硬件,然后进行自启动倒计时,计时期间没有按键按下会根据
bootcmd 环境变量启动内核,有按键按下会进入命令行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具