ARM嵌入式学习(二十)--- 杂项设备、Platfrom总线和设备树源文件(dts)

目录

一、杂项设备

1、杂项设备的作用

2.杂项设备示例(led举例):

1.修改上次的主设备号led文件:

[杂项设备 vs 普通字符设备(LED 场景对比)](#杂项设备 vs 普通字符设备(LED 场景对比))

2.在Kconfig中添加misc_led:

[3.在内核顶层使用make modules命令:](#3.在内核顶层使用make modules命令:)

4.把模块拷到根文件系统下,开发板输入指令

二、Platform总线

1.Paltform总线实现示例:

(1)先写devices部分把资源分配了,物理地址引脚等等

函数解释:

(2)driver部分

函数解释:

注意:

三、设备树源文件(dts)

1.编写设备树源文件(led示例)

2.led_driver.c驱动

函数解释:

四、总结与补充

遇到的问题:

1.加载驱动模块时候,报错,加载不进去

[2.make dtb要到内核顶层目录make](#2.make dtb要到内核顶层目录make)

补充:

[1. / 是根的意思](#1. / 是根的意思)

2.在内核中调用函数


一、杂项设备

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 resourcestruct 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其他代码才能调用这个函数

类似汇编

相关推荐
知识分享小能手1 小时前
MongoDB入门学习教程,从入门到精通,MongoDB 安全完全指南(19)
学习·安全·mongodb
_李小白2 小时前
【OSG学习笔记】Day 39: NodeCallback(帧回调机制)
java·笔记·学习
浮芷.2 小时前
开源鸿蒙跨平台Flutter开发:校园问答互助社区应用
学习·flutter·华为·开源·harmonyos·鸿蒙
小陈phd2 小时前
CCPD数据集全解析:中文车牌识别的“双黄金标准“
笔记·学习·生成对抗网络
吃着火锅x唱着歌2 小时前
深度探索C++对象模型 学习笔记 第三章 Data语意学(2)
c++·笔记·学习
_李小白2 小时前
【OSG学习笔记】Day 35: Material(材质)
笔记·学习·材质
ZhiqianXia2 小时前
Pytorch 学习笔记(21) : PyTorch Profiler
pytorch·笔记·学习
炽烈小老头2 小时前
【每天学习一点算法 2026/04/10】Excel表列序号
学习·算法
渡我白衣2 小时前
运筹帷幄——在线学习与实时预测系统
人工智能·深度学习·神经网络·学习·算法·机器学习·caffe