- [驱动](#驱动) - [驱动基础知识](#驱动基础知识) - [内核源码编译过程](#内核源码编译过程) - [第一步:配置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 #include #include #include #include #include #include #include #include #include #include #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 #include #include #include #include #include #include #include #include #include #include #include #include /* 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驱动 >- i2c,spi等都属于此类驱动,i2c控制器驱动和spi控制器驱动就相当于platform驱动,通过core注册。 i2c和spi设备分别可通过内核态或者用户态控制。platform驱动匹配过程->driver_register->bus_add_driver ->driver_attach->__driver_attach->device_driver_attach->driver_probe_device->really_probe-> drv->probe(dev) 或者 dev->bus->probe(dev) ``` #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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下实现