data/method/一些思考/系统移植+驱动/driver/driver.md

515 lines
16 KiB
Markdown
Raw Normal View History

2024-01-29 10:44:43 +08:00
- [驱动](#驱动)
- [驱动基础知识](#驱动基础知识)
- [内核源码编译过程](#内核源码编译过程)
- [第一步配置Kconfig](#第一步配置kconfig)
- [第二步配置Makefile](#第二步配置makefile)
- [第三步 配置上层目录的Makefile与Kconfig](#第三步-配置上层目录的makefile与kconfig)
- [驱动程序分析过程](#驱动程序分析过程)
- [插入驱动](#插入驱动)
- [驱动加载进内核或以模块插入后会运行下面的初始化接口](#驱动加载进内核或以模块插入后会运行下面的初始化接口)
- [初始化接口里进行设备驱动的注册](#初始化接口里进行设备驱动的注册)
- [字符设备](#字符设备)
- [字符设备框架](#字符设备框架)
- [静态注册设备](#静态注册设备)
- [新字符设备,引用了字符设备结构体](#新字符设备引用了字符设备结构体)
- [platform驱动](#platform驱动)
- [dts的platform驱动](#dts的platform驱动)
- [驱动程序注册重要函数](#驱动程序注册重要函数)
- [设备树](#设备树)
- [系统](#系统)
- [kernel、rootfs 和 uboot之间的关系](#kernelrootfs-和-uboot之间的关系)
- [uboot代码结构](#uboot代码结构)
- [uboot](#uboot)
# 驱动
## 驱动基础知识
### 内核源码编译过程
1.遍历每个源码目录或配置指定的源码目录Makefile
2.每个目录的Makefile 会根据Kconfig来定制要编译对象
3.回到顶层目录的Makeifle执行编译
Kconfig ---> (每个源码目录下)提供选项 (决定哪些需要编译那些不需要)
.config ---> (源码顶层目录下)保存选择结果 (默认配置)
Makefile---> (每个源码目录下)根据.config中的内容来告知编译系统如何编译
**举例**
#### 第一步配置Kconfig
在driver目录下新建一个目录
mkdir driver/test
进入test目录创建Kconfig文件
![](pic/0.png)
这里定义了一个TEST的句柄Kconfig可以通过这个句柄来控制Makefile中是否编译”Test driver”是显示在终端的名称
#### 第二步配置Makefile
![](pic/1.png)
Obj-$(CONFIG_选项名) += xxx.o
/*当CONFIG_选项名=y时表示对应目录下的xxx.c将被编译进内核 当CONFIG_选项名=m时对应目录下的xxx.c将被编译成模块*/
#### 第三步 配置上层目录的Makefile与Kconfig
在上一层目录的Kconfig中
![](pic/2.png)
在上一层目录的Makefile中
![](pic/3.png)
结果,运行根目录的.config查看结果
![](pic/4.png)
### 驱动程序分析过程
#### 插入驱动
1. insmod chrdevbase.ko
2. 输入“lsmod”命令即可查看当前系统中存在的模块
3. cat /proc/devices 查看当前系统中所有的设备
4. 驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操
作这个设备节点文件来完成对具体设备的操作,使用旧接口注册的驱动需要用命令创建设备节点
- mknod /dev/chrdevbase c 200 0
#### 驱动加载进内核或以模块插入后会运行下面的初始化接口
subsys_initcall(); //用于核心子系统的初始化,初始化时间早于module_init
module_init(); //适用于以模块形式编译的代码insmod加载驱动时会调用这个函数
module_exit();
#### 初始化接口里进行设备驱动的注册
**注册函数**
1. 对于字符设备驱动:
- register_chrdev() - 注册字符设备号 //旧方法,需要手动创建设备节点
- cdev_add() - 添加cdev结构到系统 //新方法, 会自动创建设备节点
2. 对于平台驱动:
- platform_driver_register() - 注册平台驱动
3. 对于I2C和SPI总线驱动:
- i2c_add_driver() - 注册I2C驱动
- spi_register_driver() - 注册SPI驱动
4. 对于USB驱动:
- usb_register() - 注册USB驱动
5. 对于网络设备驱动:
- register_netdev() - 注册网络接口
6. 通用设备驱动注册:
- driver_register() - 注册设备驱动
7. PCI设备驱动:
- pci_register_driver() - 注册PCI设备驱动
8. 输入设备驱动:
- input_register_device() - 注册输入设备
## 字符设备
![](pic/5.png)
### 字符设备框架
#### 静态注册设备
```
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名字 */
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static int __init led_init(void)
{
register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
return 0;
}
static void __exit led_exit(void)
{
unregister_chrdev(LED_MAJOR, LED_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("sakura");
```
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
>major主设备号Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分
>name设备名字指向一串字符串。
>fops结构体 file_operations 类型指针,指向设备的操作函数集合变量。
#### 新字符设备,引用了字符设备结构体
```
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/* newchrled设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct newchrled_dev newchrled; /* led设备 */
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled; /* 设置私有数据 */
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static int __init led_init(void)
{
if (newchrled.major) { /* 定义了设备号 */
newchrled.devid = MKDEV(newchrled.major, 0);
register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */
newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */
newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */
}
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
return 0;
}
static void __exit led_exit(void)
{
cdev_del(&newchrled.cdev);/* 删除cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
```
#### platform驱动
```
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
struct leddev_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
};
struct leddev_dev leddev; /* led设备 */
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &leddev; /* 设置私有数据 */
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
static int led_probe(struct platform_device *dev)
{
if (leddev.major) { /* 定义了设备号 */
leddev.devid = MKDEV(leddev.major, 0);
register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME); /* 申请设备号 */
leddev.major = MAJOR(leddev.devid); /* 获取分配号的主设备号 */
}
leddev.cdev.owner = THIS_MODULE;
cdev_init(&leddev.cdev, &led_fops);
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
return 0;
}
static int led_remove(struct platform_device *dev)
{
cdev_del(&leddev.cdev);/* 删除cdev */
unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
device_destroy(leddev.class, leddev.devid);
class_destroy(leddev.class);
return 0;
}
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
```
#### dts的platform驱动
```
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
struct leddev_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
struct device_node *node; /* LED设备节点 */
int led0; /* LED灯GPIO标号 */
};
struct leddev_dev leddev; /* led设备 */
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &leddev; /* 设置私有数据 */
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
static int led_probe(struct platform_device *dev)
{
if (leddev.major) {
leddev.devid = MKDEV(leddev.major, 0);
register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
} else {
alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
leddev.major = MAJOR(leddev.devid);
}
cdev_init(&leddev.cdev, &led_fops);
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
leddev.node = of_find_node_by_path("/gpioled");
of_get_named_gpio(leddev.node, "led-gpio", 0);
return 0;
}
static int led_remove(struct platform_device *dev)
{
cdev_del(&leddev.cdev); /* 删除cdev */
unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
device_destroy(leddev.class, leddev.devid);
class_destroy(leddev.class);
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "atkalpha-gpioled" },
{ /* Sentinel */ }
};
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
.of_match_table = led_of_match, /* 设备树匹配表 */
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
```
## 驱动程序注册重要函数
## 设备树
1. 设备树的作用:描述板级信息
2. 设备树路径一般在arch目录下 例如kernel_t41/kernel-4.4.94/arch/mips/boot/dts/ingenic
# 系统
## kernel、rootfs 和 uboot之间的关系
1. U-Boot 是引导加载程序,主要完成系统的初期引导工作。它会进行硬件初始化,然后从Flash、SD卡等外部存储中加载 Linux Kernel。
2. Linux Kernel 是系统的内核,提供操作系统的核心功能。U-Boot会把内核映像加载到 RAM 中,并转交给内核控制权。
3. Rootfs 是 root 文件系统,包含了 Linux 系统启动后需要的库、工具等文件。U-Boot同样会把 rootfs 从存储中加载到 RAM,以供内核挂载。
4. 内核初始化完成后,会挂载 rootfs 为根文件系统,然后根据 rootfs 中的配置来启动系统服务,最终完成整个系统的启动。
5. U-Boot主要在系统加电时执行,完成引导工作。内核和 rootfs 在系统运行时提供操作系统环境。
6. U-Boot、内核和 rootfs 三者相互配合,将硬件系统引导起来,构成完整的 Linux 发行版。
7. 开发时,会分别编译这三个组件,然后打包到存储介质中,实现可引导的嵌入式系统。
所以 U-Boot、Kernel 和 Rootfs 在嵌入式系统中有明确的分工,相互配合实现从系统加电到启动完成的整个过程。
### uboot代码结构
1. 启动代码(arch/arm/lib/crt0.S等):实现CPU和板级初始化,构建执行环境。
2. 架构代码(arch/arm/):针对ARM等不同架构的底层支持代码。
3. 公共库(lib/):提供字符串、内存相关的库函数。
4. 驱动模型(drivers/):各种设备驱动,如串口、Ethernet、Flash等。
5. 通用命令(cmd/):U-Boot命令行实现。
6. 网络功能(net/):网络协议栈,如TFTP。
7. 文件系统(fs/):文件系统驱动,如FAT。
8. 加载Image(image/):镜像分析和加载。
9. 板级支持(board/):针对特定开发板的定制代码。
10. 通用核心(common/):初始化参数,环境变量,内存分配等。
11. 入口点(main.c):主程序入口和命令行解析。
### uboot
1. uboot作用启动内核设置一些环境变量
2. uboot添加命令#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)
3. uboot基本命令
>- reset重新启动嵌入式系统。
>- printenv打印当前环境变量。
>- setenv设置环境变量格式setenv name value。
>- saveenv保存环境变量到nand中。
>- sleep延迟执行格式sleep N可以延迟N秒钟执行。
>- bootm可以引导启动存储在内存中的程序映像。
>- nand erase擦除NAND格式nand erase addr1 count。
>- nand write下载的内存数据写入NAND格式nand write addr offset count。
>- sf0 probe
>- sf0 erase
>- sf0 write
4. uboot加载内核
5. uboot的重要配置文件默认配置在路径uboot/configs下板级头文件在uboot/include/configs下
头文件由CONFIG_SYS_CONFIG_NAME宏配置。文件也可能没有默认配置文件只需要头文件。
6. uboot的nand的擦除写入读取命令在uboot/drivers/mtd/nand下实现