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

17 KiB
Raw Blame History

驱动

驱动基础知识

内核源码编译过程

1.遍历每个源码目录或配置指定的源码目录Makefile
2.每个目录的Makefile 会根据Kconfig来定制要编译对象
3.回到顶层目录的Makeifle执行编译

Kconfig ---> (每个源码目录下)提供选项 (决定哪些需要编译那些不需要)
.config ---> (源码顶层目录下)保存选择结果 (默认配置)
Makefile---> (每个源码目录下)根据.config中的内容来告知编译系统如何编译

举例

第一步配置Kconfig

在driver目录下新建一个目录
mkdir driver/test
进入test目录创建Kconfig文件 

这里定义了一个TEST的句柄Kconfig可以通过这个句柄来控制Makefile中是否编译”Test driver”是显示在终端的名称

第二步配置Makefile

Obj-$(CONFIG_选项名) += xxx.o /当CONFIG_选项名=y时表示对应目录下的xxx.c将被编译进内核 当CONFIG_选项名=m时对应目录下的xxx.c将被编译成模块/

第三步 配置上层目录的Makefile与Kconfig

在上一层目录的Kconfig中

在上一层目录的Makefile中

结果,运行根目录的.config查看结果

驱动程序分析过程

插入驱动

  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() - 注册输入设备

字符设备

字符设备框架

静态注册设备

#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驱动

  • 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 <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
  1. uboot加载内核
  2. uboot的重要配置文件默认配置在路径uboot/configs下板级头文件在uboot/include/configs下
    头文件由CONFIG_SYS_CONFIG_NAME宏配置。文件也可能没有默认配置文件只需要头文件。
  3. uboot的nand的擦除写入读取命令在uboot/drivers/mtd/nand下实现