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

515 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

- [驱动](#驱动)
- [驱动基础知识](#驱动基础知识)
- [内核源码编译过程](#内核源码编译过程)
- [第一步配置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下实现