一、动态加载驱动程序

杂项设备驱动:

static struct miscdevice misc= 
{
    .minor=MISC_DYNAMIC_MINOR
    .name=DEV_NAME
    .fops=&fopsint
}
ret = misc_register(&misc);
misc_deregister(&misc);

这是最关键的一行。声明该模块遵守 GNU General Public License (GPL) 协议。❌ 不加则报错:不声明 GPL,内核会被视为 “被污染”(tainted kernel),导致无法调用内核内部核心函(如ioremapregister_chrdev),编译或运行会报错。

MODULE_LICENSE("GPL");

编译内核模块:make modules (生成xxx.ko文件)

内核模块的编译与使用:

编译:

Kconfig中变量类型为tristate---YNM

makee menuconfig        对应选项配置为M

make modules编译内核模(生成的.ko文件和对应的.c同一目录下)

使用:

insmod xxx.ko动态加载内核模块

Ismod        查看动态加载的内核模块

rmmodXXX        卸载内核模块

二、usb driver

①流程:

1. 应用层 → 驱动层

应用程序通过系统调用(如write/ioctl)向 USB 驱动发送控制指令(如 “点亮 LED”)。

2. 驱动层 → USB 总线

USB 驱动(usb driver)接收指令后,通过USB 读写操作,将控制命令封装为 USB 协议数据包,发送给 USB 外设硬件。

3. USB 硬件 → 寄存器操作

USB 外设控制器解析 USB 数据包,操作内部的 GPIO 寄存器:

  1. 先通过gdir配置引脚为输出模式
  2. 再通过dr数据寄存器写入高 / 低电平
  3. pad/sw-mux寄存器确保引脚电气特性和复用功能正确

4. 硬件引脚 → 外设执行

GPIO 引脚输出对应电平,控制外接 LED、按键等外设完成动作。

bus_type抽象父类iic_bus/spi_bus/platform具体实现子类

②总线

  • 子类继承父类的通用接口,同时实现自身的总线特性(如 I2C 的地址匹配、SPI 的片选逻辑)
  • 内核通过 bus_type 统一管理所有总线,实现驱动框架的标准化。

总线类型 匹配方式 适用场景
Platform 总线 name / of_match_table 片上外设(GPIO、UART、ADC、定时器)
I2C 总线 设备地址 / id_table 外接 I2C 传感器、EEPROM
SPI 总线 片选信号 / id_table 外接 SPI Flash、显示屏
USB 总线 VID/PID / id_table 外接 USB 设备

platform_driver() 核心生命周期回调:

函数指针 核心作用 执行时机
match() 设备 - 驱动匹配 总线遍历设备 / 驱动链表,判断是否配对(Platform 总线用 name/of_match_table,I2C 用地址)
uevent() 热插拔事件通知 设备插入 / 拔出时,向用户空间发送 uevent 事件(udev 监听)
probe() 设备探测初始化 匹配成功后,调用驱动的 probe 函数,初始化硬件
remove() 设备移除 驱动卸载 / 设备断开时,释放资源、清理硬件
shutdown() 系统关机 系统关机 / 重启时,关闭设备、保存状态

地址 内容 状态
... ... ...
start 寄存器基地址 开始
start+1 下一个寄存器 覆盖
start+2 再下一个 覆盖
start+3 第四个寄存器 结束 (start+4−1)
EXPOPT_SYMBOL_GPL(platform_driver_unregister)
作用 权限
EXPORT_SYMBOL() 导出符号 所有驱动都能用
EXPORT_SYMBOL_GPL() GPL 专用导出 只有 GPL 驱动能用

三、设备树文件(Linux/of.h)

设备树是 Linux 内核用来「描述硬件资源」的文本文件,彻底把「硬件信息」和「驱动代码」分离开

1.先复制系统的dts文件

   cp arch/arm/boot/dts/imx6ull-alientek-emmc.dts arch/arm/boot/dts/pt.dts

2.编译makefile文件

   vim arch/arm/boot/dts/Makefile 
3.打开你刚才的 pt.dts

   vim arch/arm/boot/dts/pt.dts

   在文件最后面添加一个 LED 节点

4.驱动通过 of_ 函数读取设备树信息

5.内核启动时加载 pt.dtb,自动匹配并执行 probe

  1. 模块一加载,直接手动找设备树节点 /pt_led
  2. 手动映射寄存器
  3. 注册 misc 设备
  4. 应用层控制 LED

它就是一个简单的字符驱动,自己管自己,和内核平台无关

#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>    
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>     设备树操作 API

#define DEV_NAME "led"
static volatile unsigned int * sw_mux;
static volatile unsigned int * sw_pad;
static volatile unsigned int * gpio1_dr;
static volatile unsigned int * gpio1_gdir;

static void led_init(void)
{
    *sw_mux = 0x05;
    *sw_pad = 0x10b0;
    *gpio1_gdir |= (1 << 3);
    *gpio1_dr |= (1 << 3);
}

static void led_on(void)
{
    *gpio1_dr &= ~(1 << 3);
}

static void led_off(void)
{
    *gpio1_dr |= (1 << 3);
}

static int open(struct inode * node, struct file * file)
{
	led_init();
	printk("led  open...\n");
	return 0;
}

static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
	//copy_to_user();
	printk("led  read...\n");
	return 0;
}

static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
	// "ledon"  on       "ledoff"   off
	unsigned char data[10] = {0};
	size_t len_cp = len < sizeof(data) ? len : sizeof data;
	int size_cp = copy_from_user(data, buf, len_cp);
	if(size_cp < 0)
		return size_cp;

	if(!strcmp(buf, "ledon"))
		led_on();
	else if(!(strcmp(buf, "ledoff")))
		led_off();
	else
		return -EINVAL;

	printk("led  write...\n");

	return size_cp;
}

static int close(struct inode * node, struct file * file)
{
	led_off();
	printk("led  close...\n");
	return 0;
}

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = open,
	.read = read,
	.write = write,
	.release = close
};

miscdevice —— 混杂设备(自动创建设备节点)
static struct miscdevice misc = 
{
	.minor = MISC_DYNAMIC_MINOR,    自动分配次设备号
	.name = DEV_NAME,
	.fops = &fops                   关联操作函数
};

static int __init led1_init(void)
{
	struct device_node * pnode;
	const char * pcom;
	const char * pname1;
	u32 led_array[8] = {0};
	int ret = misc_register(&misc);    注册 misc 设备
	if(ret < 0)
		goto err_misc;

	pnode = of_find_node_by_path("/pt_led");    查找设备树节点
	if(pnode == NULL)
	{
		printk("of_find_node_by_path  err\n");
		return -1;
	}

	读取设备树属性
	of_property_read_string(pnode, "compatible", &pcom);
	of_property_read_string(pnode, "name1", &pname1);
	printk("led compatible = %s  name1 = %s\n", pcom, pname1);

    读取 reg 数组
	of_property_read_u32_array(pnode, "reg", led_array, sizeof(led_array) / sizeof(led_array[0]));

	sw_mux = ioremap(led_array[0], led_array[1]);
	sw_pad = ioremap(led_array[2], led_array[3]);
	gpio1_gdir = ioremap(led_array[4], led_array[5]);
	gpio1_dr = ioremap(led_array[6], led_array[7]);

    printk("led_init    ##############\n");
    return 0;

err_misc:
	misc_deregister(&misc);
	printk("led_init failed  ret = %d\n", ret);
	return ret;
}

static void __exit led1_exit(void)
{
	iounmap(gpio1_gdir);
	iounmap(gpio1_dr);
	iounmap(sw_pad);
	iounmap(sw_mux);
	misc_deregister(&misc);
    printk("led_exit    ##############\n");
}

module_init(led1_init);
module_exit(led1_exit);
MODULE_LICENSE("GPL");

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐