目录
[杂项设备 vs 普通字符设备(LED 场景对比)](#杂项设备 vs 普通字符设备(LED 场景对比))
[3.在内核顶层使用make modules命令:](#3.在内核顶层使用make modules命令:)
[2.make dtb要到内核顶层目录make](#2.make dtb要到内核顶层目录make)
[1. / 是根的意思](#1. / 是根的意思)
一、杂项设备
1、杂项设备的作用
在 Linux 中,设备驱动主要分为三类:字符设备 、块设备 、网络设备。其中字符设备是最常见的一类,但注册一个普通的字符设备需要:
自己分配或申请主设备号
注册设备类(class)
手动创建
/dev/下的设备节点
这对于功能简单的设备来说过于繁琐。
杂项设备(miscdevice) 是 Linux 内核提供的一种简化版字符设备,它:
主设备号固定为 10
只需指定次设备号(可以用
MISC_DYNAMIC_MINOR自动分配)自动在
/dev/下生成设备节点注册接口
misc_register()简单,代码量小
一句话总结:杂项设备是给"功能简单、不复杂"的字符设备使用的快捷方式
2.杂项设备示例(led举例):
1.修改上次的主设备号led文件:
与上次我们写的主设备号led对比,杂项设备明显简单很多:
(1)填充次设备结构体
(2)注册次设备号
杂项设备 vs 普通字符设备(LED 场景对比)
| 特性 | 普通字符设备 | 杂项设备 |
|---|---|---|
| 主设备号 | 需动态或静态分配 | 固定 10 |
| 次设备号 | 自己管理 | 自动或指定 |
/dev/ 节点 |
手动或 class 创建 | 自动创建 |
| 代码量 | 较多 | 简洁 |
| 适合设备 | 复杂设备(如串口、USB) | 简单设备(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>
#define DEV_NAME "led"
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 0x20e0068U
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 0x20E02F4U
#define GPIO1_DR 0x209C000U
#define GPIO1_GDIR 0x209C004U
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
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int __init led1_init(void)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc;
sw_mux = ioremap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03, 4);
sw_pad = ioremap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03, 4);
gpio1_dr = ioremap(GPIO1_DR, 4);
gpio1_gdir = ioremap(GPIO1_GDIR, 4);
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);
2.在Kconfig中添加misc_led:

这里解释一下tristate类型:

3.在内核顶层使用make modules命令:

.ko文件就是我们编译好的模块,可以直接放到根文件系统进行动态加载,可以理解为热插拔
4.把模块拷到根文件系统下,开发板输入指令

这里的信息意思是misc_led:模块许可证'未指定'污染了内核。
由于内核污染,禁用锁定调试。
解决办法:代码末尾添加:

GPL:通用公共许可证,一种广泛使用的开源协议。
其他命令:
History历史命令
Ctrl +R
Vim -- t 文件名 (查找打开)
Lsmod 可以查看动态加载的所有模块
二、Platform总线

是一种虚拟总线。管理那些不依靠传统硬件总线(如PCI、USB、I2C、SPI)来被CPU发现,而是直接集成在SoC(片上系统)内部或固定在特定内存地址上的设备。
比如,UART(串口)、I2C控制器、GPIO控制器、看门狗定时器、RTC(实时时钟)、以及许多嵌入式系统中的"按键"和"LED"等,都属于Platform设备。
你可以把Platform总线理解为Linux内核为嵌入式SoC设备搭建的一个"虚拟插槽",驱动程序写好一个"驱动插件",内核根据设备树的描述,自动把插件插到正确的插槽上,然后运行
和我们之前的方法对比:
| 方面 | 传统方式 | Platform总线方式 |
|---|---|---|
| 设备号分配 | 驱动里静态定义dev_t(比如100) |
驱动中动态分配(alloc_chrdev_region),不依赖固定编号 |
| 硬件地址 | 驱动里写死:#define LED_BASE 0xE2900000 |
从platform_device资源中获取:res = platform_get_resource(pdev, IORESOURCE_MEM, 0) |
| 中断号 | 写死:#define LED_IRQ 79 |
同上,从资源获取 |
| 多实例支持 | 困难(需要多个主设备号或次设备号) | 天然支持:一个驱动probe多次,每次创建不同的设备节点(/dev/led0, /dev/led1) |
| 更换板卡 | 需要修改驱动源码,重编内核 | 只需修改设备树(.dts),驱动二进制不变 |
| 代码复用 | 每类设备写一个驱动,代码重复 | LED、KEY、UART驱动各自独立,但都遵循platform模型,框架统一 |
1.Paltform总线实现示例:

步骤:
(1)先写devices部分把资源分配了,物理地址引脚等等
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#define DEV_NAME "led"
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 0x20e0068U
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 0x20E02F4U
#define GPIO1_GDIR 0x209C004U
#define GPIO1_DR 0x209C000U
static struct resource res[4] =
{
[0] =
{
.start = IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03,
.end = IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 + 4 -1,
.name = "iomuxc_sw_mux"
},
[1] =
{
.start = IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03,
.end = IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 + 4 -1,
.name = "iomuxc_sw_pad"
},
[2] =
{
.start = GPIO1_GDIR,
.end = GPIO1_GDIR + 4 - 1,
.name = "gpio1__gdir"
},
[3] =
{
.start = GPIO1_DR,
.end = GPIO1_DR + 4 - 1,
.name = "gpio1_dr"
}
};
static void release(struct device *dev){}
static struct platform_device dev =
{
.name = DEV_NAME,
.id = -1,
.num_resources = sizeof(res)/ sizeof(res[0]),
.resource = res,
.dev =
{
.release = release
}
};
static int __init led_init(void)
{
int ret = platform_device_register(&dev);
if(ret < 0)
goto err_reg;
printk("platform_device_register ...\n");
return 0;
err_reg:
printk("platform_device_register failed... ret = %d\n", ret);
platform_device_unregister(&dev);
return ret;
}
static void __exit led_exit(void)
{
platform_device_unregister(&dev);
printk("platform_device_unregister ...\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
函数解释:
(1)static struct resource res[4] 分配资源,填充好我们的物理地址
(2)static void release(struct device *dev){} 直接编译会提示缺少relaease函数,这里写个空函数解决警告提示
(3)static struct platform_device dev 填充虚拟总线platform_device结构体
(4)static int __init led_init(void)初始化,在里面注册我们的device结构体
(2)driver部分
#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/platform_device.h>
#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
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int probe(struct platform_device * pdev)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc;
//sw_mux = ioremap(pdev->resource[0].start, sizeof *sw_mux);
sw_mux = ioremap(pdev->resource[0].start, pdev->resource[0].end - pdev->resource[0].start + 1);
sw_pad = ioremap(pdev->resource[1].start, pdev->resource[1].end - pdev->resource[1].start + 1);
gpio1_gdir = ioremap(pdev->resource[2].start, pdev->resource[2].end - pdev->resource[2].start + 1);
gpio1_dr = ioremap(pdev->resource[3].start, pdev->resource[3].end - pdev->resource[3].start + 1);
printk("probe led misc_register ##############\n");
return 0;
err_misc:
misc_deregister(&misc);
printk("probe led misc_register failed ret = %d\n", ret);
return ret;
return 0;
}
static int remove(struct platform_device * pdev)
{
iounmap(gpio1_gdir);
iounmap(gpio1_dr);
iounmap(sw_pad);
iounmap(sw_mux);
misc_deregister(&misc);
printk("remove led misc_deregister ##############\n");
return 0;
}
static struct platform_driver drv =
{
.probe = probe,
.remove = remove,
.driver =
{
.name = DEV_NAME
}
};
static int __init led1_init(void)
{
int ret = platform_driver_register(&drv);
if(ret < 0)
goto err_reg;
printk("platform_driver_register ...\n");
return 0;
err_reg:
platform_driver_unregister(&drv);
printk("platform_driver_register failed\n");
return ret;
}
static void __exit led1_exit(void)
{
platform_driver_unregister(&drv);
printk("platform_driver_unregister ...\n");
}
module_init(led1_init);
module_exit(led1_exit);
MODULE_LICENSE("GPL");
函数解释:
(1)实现操作方法
(2)static int probe(struct platform_device * pdev)
probe是 Platform 驱动中真正"把硬件管起来"的函数:当内核找到与驱动匹配的硬件设备时,它负责初始化该设备并让它对操作系统可用。相当于匹配driver和device的(3)static struct platform_driver drv 填充虚拟总线platform_driver 结构体
(4)static int __init led_init(void)初始化,在里面注册我们的platform_driver 结构体
注意:
添加device.c和driver.c后记得Kconfig和Makefile要添加
然后make modules
进入内核加载下这两个模块,在应用层写一个ledapp测试一下即可
三、设备树源文件(dts)
其实上面我们编写的device.c是过时的操作了
而设备树源文件(DTS)就是现代 Linux 内核中用来替代我们所写的 devices.c 的方式。
在我们的 devices.c 中,这些信息是通过 C 代码静态定义 struct resource 和 struct platform_device 并调用 platform_device_register() 提交给内核的。
而在设备树方式下,这些信息写在 .dts 文本文件里,由内核启动时解析,然后动态创建出完全相同的 struct platform_device 对象。
所以这里,我们把devices.c的信息都写到.dts中
然后把driver.c改写一下:
1.编写设备树源文件(led示例)
这里我们从内核中找到默认的设备树源文件进行修改
最好是把默认的dts拷贝一份,这里我拷贝出来命名的3.dts:
cp arch/arm/boot/dts/3.dtb ~/tftpboot/

在中间添加一个我们写的led设备树描述
修改设备树的makefile,让我们写的dts能够被编译进去

内核顶层目录输入make 3.dtb(指定编译dts)
然后把编译出来的设备树拷到tftp中启动即可
2.led_driver.c驱动
设备描述写到设备树后,现在写我们的驱动:
#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>
#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
};
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);
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);
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");
函数解释:
#include <linux/of.h>包头文件,我们需要用到查找设备树描述信息的函数
struct device_node * pnode;
pnode = of_find_node_by_path("/pt_led"); 找设备节点
of_property_read_string()读设备节点字符串
of_property_read_u32_array()读设备节点的数组里的信息
写好驱动后,在根文件系统中加载一下模块,最后使用应用层写好的ledapp测试即可
四、总结与补充
遇到的问题:
1.加载驱动模块时候,报错,加载不进去
解决办法:注意!设备树源文件的设备节点不要写成别人的子节点(写到了别人的大括号里面,成了子节点所以无法加载模块)
2.make dtb要到内核顶层目录make
补充:
1. / 是根的意思
这两个处于不同文件,但是依然是同一个根,相当于在同一个文件写
2.在内核中调用函数

在内核中,需要加这个EXPORT其他代码才能调用这个函数
类似汇编
