嵌入式Linux驱动开发 03:平台(platform)总线驱动模型

news2025/1/16 14:00:15

文章目录

  • 目的
  • 基础说明
  • 开发准备
  • 在驱动中获取资源
  • 单驱动使用多个资源
  • 总结

目的

前面文章 《嵌入式Linux驱动开发 01:基础开发与使用》 和 《嵌入式Linux驱动开发 02:将驱动程序添加到内核中》 介绍了驱动开发最基础的内容,这篇文章将在前面基础上更进一步,引入平台(platform)总线驱动模型。

这篇文章中内容均在下面的开发板上进行测试:
《新唐NUC980使用记录:自制开发板(基于NUC980DK61YC)》

这篇文章是在下面文章基础上进行的:
《新唐NUC980使用记录(5.10.y内核):在用户应用中使用GPIO》

基础说明

在前面的文章中只是最简单的驱动编写和使用的逻辑,并没有涉及到真正需要驱动的外设等。实际开发中驱动程序是通常是为了操作有些外设的,你可以在前面的基础上在驱动程序中通过寄存器或者厂家提供的库(如果有的话)来操作外设。

通常对于同一类型的外设通常除了寄存器或引脚等位置不同外,功能都是一样的。所以通常会把驱动的功能逻辑和具体的寄存器或引脚等数据分开来,分成 driverdevice 两部分。 driver 是真正驱动逻辑部分,而 device 用于存放设备寄存器或引脚等信息。这两部分信息通过标签来关联。

这两部分比较低级的组织方式就是全部以 .c 或者 .h 文件的形式来组织,放在内核工程中一起编译,这就是平台( platform )总线模型。这种方式下如果具体使用的外设的位置和引脚等有变化的话只需要修改 device 部分代码文件即可。

平台总线驱动模型中不管是驱动还是资源都是一个内核模块。作为资源的模块中定义并注册 platform_device 内容,作为驱动的模块中定义并注册 platform_driver 。两者通过一定的字符串进行匹配。一个驱动可以匹配多个资源来创建多个设备节点。

开发准备

本文中演示中涉及目录与文件结构初始组织如下:
在这里插入图片描述

进入源码目录:

cd ~/nuc980-sdk/NUC980-linux-5.10.y/

在源码目录下建立相关目录和Kconfig和Makefile参考 《嵌入式Linux驱动开发 02:将驱动程序添加到内核中》 这个篇文章,需要改动的内容如下:

drivers/user/char_dev 目录下的 Makefile 文件,其内容改为如下:

obj-$(CONFIG_USER_CHAR_DEV) += char_dev.o
obj-$(CONFIG_USER_CHAR_DEV) += char_drv.o

新增 char_drv.c 文件:

touch drivers/user/char_dev/char_drv.c

在驱动中获取资源

这里演示一个驱动和一个资源的情况,两者代码分别如下:

platform_device(char_dev.c)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/platform_device.h>

/* 定义一个资源 */
static struct resource resources[] = {
    {
            .start = 22,		
			.end = 33,	
            .name = "naisu",
			.flags = IORESOURCE_BITS, // 这个宏定义在 include/linux/ioport.h 中,还有更多选项可选
                                      // .flags这个字段用于标识资源类型
                                      // 使用platform_get_resource方法获取资源时就会通过该字段进行筛选
			.desc = 666,
    },
};

// static void char_dev_release(struct device *dev)
// {
// 	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);
// }

/* 定义platform_device,绑定资源 */
static struct platform_device char_dev = {
        .name = "naisu_char_dev", // 资源名称
		.num_resources = ARRAY_SIZE(resources),
		.resource = resources,
        // .dev = {
        //         .release = char_dev_release,
        //  },
};

/* 模块加载操作 */
static int __init char_dev_init(void)
{
    int err;
    
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

    err = platform_device_register(&char_dev); // 注册platform_device
    
    return err;
}

/* 模块退出操作 */
static void __exit char_dev_exit(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

    platform_device_unregister(&char_dev); // 释放资源
}

module_init(char_dev_init); // 模块入口
module_exit(char_dev_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

platform_driver(char_drv.c)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/platform_device.h>

static int major = 0;
static const char *char_drv_name = "char_drv";
static struct class *char_drv_class;
static struct device *char_drv_device;

/* 探测到资源操作 */
static int char_probe(struct platform_device *pdev)
{
    struct resource *res;

	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

    res = &pdev->resource[0];

    printk("NX modlog: %d %d %s %ld %ld\n", res->start, res->end, res->name, res->flags, res->desc);

    return 0;
}

/* 移除资源操作 */
static int char_remove(struct platform_device *pdev)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

    return 0;
}

/* 定义platform_driver,用于探测和获取资源等 */
static struct platform_driver char_driver = {
    .probe      = char_probe,
    .remove     = char_remove,
    .driver     = {
        .name   = "naisu_char_dev", // 通过资源名称进行探测匹配
    },
};

/* 驱动文件操作接口集合 */
static const struct file_operations char_drv_fops = {
	.owner = THIS_MODULE,
};

/* 模块加载操作 */
static int __init char_drv_init(void)
{
	int err;

	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

	major = register_chrdev(0, char_drv_name, &char_drv_fops); // 注册字符设备,第一个参数0表示让内核自动分配主设备号

	char_drv_class = class_create(THIS_MODULE, "char_drv_class"); 
	if (IS_ERR(char_drv_class))
	{
		unregister_chrdev(major, char_drv_name);
		return -1;
	}

	char_drv_device = device_create(char_drv_class, NULL, MKDEV(major, 0), NULL, char_drv_name); // 创建设备节点创建设备节点,成功后就会出现/dev/char_drv_name的设备文件
	if (IS_ERR(char_drv_device))
	{
		device_destroy(char_drv_class, MKDEV(major, 0));
		unregister_chrdev(major, char_drv_name);
		return -1;
	}

    err = platform_driver_register(&char_driver); // 注册platform_driver
    
    return err;
}

/* 模块退出操作 */
static void __exit char_drv_exit(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&char_driver); // 释放platform_driver

	device_destroy(char_drv_class, MKDEV(major, 0)); // 销毁设备节点,销毁后/dev/下设备节点文件就会删除
	class_destroy(char_drv_class);

	unregister_chrdev(major, char_drv_name); // 注销字符设备
}

module_init(char_drv_init); // 模块入口
module_exit(char_drv_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

编译测试
在内核配置界面启用自定义的驱动:

make menuconfig

在这里插入图片描述

编译然后拷贝到开发板上进行测试:

# 设置编译工具链
# export ARCH=arm; export CROSS_COMPILE=arm-buildroot-linux-gnueabi-
# export PATH=$PATH:/home/nx/nuc980-sdk/buildroot-2023.02/output/host/bin

# 编译生成内核镜像
make uImage
# 可以根据电脑配置使用make -jx等加快编译速度

# 编译完成后拷贝到电脑上再拷贝到SD卡中
# sudo cp arch/arm/boot/uImage /media/sf_common/

# 我这里开发环境和开发板在同一局域网中,所以可以直接通过网络将dtb文件拷贝到开发板上
# 在开发板中挂载boot分区
# mount /dev/mmcblk0p1 /mnt/
# 在ubuntu中使用scp命令拷贝dtb文件到开发板上
# scp arch/arm/boot/uImage root@192.168.31.142:/mnt/
# 拷贝完成后重启开发板即可测试
# reboot

在这里插入图片描述

单驱动使用多个资源

拷贝一份资源文件:

cp drivers/user/char_dev/char_dev.c drivers/user/char_dev/char_dev2.c

文件中对资源内容稍作修改:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/platform_device.h>

/* 定义一个资源 */
static struct resource resources[] = {
    {
            .start = 1234567,		
			.end = 7654321,	
            .name = "naisu222",
			.flags = IORESOURCE_BITS, 
			.desc = 777,
    },
};

/* 定义platform_device,绑定资源 */
static struct platform_device char_dev = {
        .name = "naisu_char_dev2", // 不同的platform_device,该字段不能重复
		.num_resources = ARRAY_SIZE(resources),
		.resource = resources,
        .driver_override = "naisu_char_dev", // 该字段将资源强制与platform_driver.driver.name相同的驱动匹配
};

/* 模块加载操作 */
static int __init char_dev_init(void)
{
    int err;
    
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

    err = platform_device_register(&char_dev); // 注册platform_device
    
    return err;
}

/* 模块退出操作 */
static void __exit char_dev_exit(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

    platform_device_unregister(&char_dev); // 释放资源
}

module_init(char_dev_init); // 模块入口
module_exit(char_dev_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

然后修改 drivers/user/char_dev 目录下的 Makefile 文件,其内容改为如下:

obj-$(CONFIG_USER_CHAR_DEV) += char_dev.o
obj-$(CONFIG_USER_CHAR_DEV) += char_dev2.o
obj-$(CONFIG_USER_CHAR_DEV) += char_drv.o

接着修改驱动文件 char_drv.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/platform_device.h>

static int major = 0;
static const char *char_drv_name = "char_drv_";
static struct class *char_drv_class;

static int device_node_minor = 0;

struct device_node {
	int minor;
	char data[128];
};

static struct device_node device_node_arr[2];

/* 探测到资源操作 */
static int char_probe(struct platform_device *pdev)
{
	int minor = device_node_minor;
    struct resource *res;

	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

	res = &pdev->resource[0];

    printk("NX modlog: %d %d %s %ld %ld\n", res->start, res->end, res->name, res->flags, res->desc);

	sprintf(device_node_arr[minor].data, "NX modlog: %d %d %s %ld %ld\n", res->start, res->end, res->name, res->flags, res->desc);

	device_create(char_drv_class, NULL, MKDEV(major, minor), NULL, "%s%d", char_drv_name, minor); // 创建设备节点创建设备节点,成功后就会出现/dev/char_drv_name的设备文件

	platform_set_drvdata(pdev, &device_node_arr[minor]); // 保存节点自定义数据到原始资源上

	device_node_minor++;

    return 0;
}

/* 移除资源操作 */
static int char_remove(struct platform_device *pdev)
{
	struct device_node *node = platform_get_drvdata(pdev); // 获取保存的节点自定义数据

	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(char_drv_class, MKDEV(major, node->minor)); // 销毁设备节点,销毁后/dev/下设备节点文件就会删除

    return 0;
}

/* 定义platform_driver,用于探测和获取资源等 */
static struct platform_driver char_driver = {
    .probe      = char_probe,
    .remove     = char_remove,
    .driver     = {
        .name   = "naisu_char_dev", // 通过资源名称进行探测匹配
    },
};


static int char_drv_open(struct inode *node, struct file *file)
{
	return 0;
}

static int char_drv_close(struct inode *node, struct file *file)
{
	return 0;
}


static ssize_t char_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int minor = iminor(file_inode(file)); // 获取当前设备节点minor号
	// 读取设备数据,可以使用 cat /dev/char_drv_x 来读取
	return simple_read_from_buffer(buf, size, offset, device_node_arr[minor].data, strlen(device_node_arr[minor].data));
}

/* 驱动文件操作接口集合 */
static const struct file_operations char_drv_fops = {
	.owner = THIS_MODULE,
	.open = char_drv_open,
	.release = char_drv_close,
	.read = char_drv_read,
};

/* 模块加载操作 */
static int __init char_drv_init(void)
{
	int err;

	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

	major = register_chrdev(0, char_drv_name, &char_drv_fops); // 注册字符设备,第一个参数0表示让内核自动分配主设备号

	char_drv_class = class_create(THIS_MODULE, "char_drv_class"); 
	if (IS_ERR(char_drv_class))
	{
		unregister_chrdev(major, char_drv_name);
		return -1;
	}

    err = platform_driver_register(&char_driver); // 注册platform_driver
    
    return err;
}

/* 模块退出操作 */
static void __exit char_drv_exit(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&char_driver); // 释放platform_driver

	class_destroy(char_drv_class);

	unregister_chrdev(major, char_drv_name); // 注销字符设备
}

module_init(char_drv_init); // 模块入口
module_exit(char_drv_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

修改完成后重新编译内核拷贝测试:
在这里插入图片描述
在这里插入图片描述

总结

平台总线这种资源和驱动分离的结构是发展到一定阶段必然出现的产物。平台总线本身虽然从代码上面进行了分离,但每次调整资源还是会涉及到内核,所以现在更加流行的是设备树方式。设备树和平台总线内容是相关联的,接下来的文章将在平台总线的基础上介绍基于设备树方式驱动的开发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/597787.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3 小兔鲜4:Layout-静态模版结构搭建

Vue3 小兔鲜4&#xff1a;Layout-静态模版结构搭建 Date: May 31, 2023 目标效果&#xff1a; 分成Nav、Heade、二级路由出口、Footer区域 组件结构快速搭建 Nav <script setup></script><template><nav class"app-topnav"><div clas…

如何用VS2019创建并调用动态库

如何用VS2019创建并调用动态库 创建动态库调用动态库 创建动态库 网上查了相关资料&#xff0c;创建动态库主要有两种方式&#xff0c;一种是通过空项目创建动态库&#xff0c;一种是直接创建动态链接库&#xff0c;本文所总结的就是第二种&#xff0c;直接创建动态链接库。 …

B树(C语言描述)

一.概念 B树是一种多路平衡查找树&#xff0c;不同于二叉平衡树&#xff0c;他不只是有两个分支&#xff0c;而是有多个分支&#xff0c;一棵m阶B树(balanced tree of order m)是一棵平衡的m路搜索树&#xff0c;B树用于磁盘寻址&#xff0c;它是一种高效的查找算法。 二.性质…

【Kubernetes 入门实战课】Day03——容器的本质

系列文章目录 【Kubernetes 入门实战课】Day01——虚拟机创建及安装 【Kubernetes 入门实战课】Day02——初识容器 文章目录 系列文章目录前言一、容器到底是什么&#xff1f;二、为什么要隔离三、与虚拟机的区别是什么四、隔离是怎么实现的 前言 上一节中我们完成了在Linux虚…

Anaconda下载安装及使用方法汇总

软件说明: Anaconda是Red Hat Linux和Fedora的安装管理程式。它以Python及C语言写成&#xff0c;以图形的PyGTK和文字的python-newt介面写成。它可以用来自动安装配置&#xff0c;使用户能够以最小的监督运行。Anaconda安装管理程式应用在RHEL&#xff0c;Fedora和其他一些项目…

IMX6ULL裸机篇之I2C实验-

一. I2C 实验简介 I2C实验&#xff0c;我们就来学习如何使用 I.MX6U 的 I2C 接口来驱动 AP3216C&#xff0c;读取 AP3216C 的传感器数据。 AP3216C是一个三合一的环境光传感器&#xff0c;ALSPSIRLED&#xff0c;ALS是环境光&#xff0c;PS是接近传感器&#xff0c;IR是红外L…

MANTOO车联网RSU终端助您畅享智慧出行!

一、案例背景 随着社会经济的飞速发展&#xff0c;汽车逐渐走进了千家万户&#xff0c;目前我国家庭乘用汽车保有量在2.6亿辆&#xff0c;平均每6个人就拥有一辆汽车。汽车保有量的上涨同时也给道路交通安全带来了极大的挑战&#xff0c;为了降低交通事故发生&#xff0c;保障…

牛客网项目—开发社区首页

视频连接&#xff1a;开发社区首页_哔哩哔哩_bilibili 代码地址&#xff1a;Community: msf begin 仿牛客论坛项目 (gitee.com) 本文是对仿牛客论坛项目的学习&#xff0c;学习本文之前需要了解Java开发的常用框架&#xff0c;例如SpringBoot、Mybatis等等。如果你也在学习牛…

遗传算法讲解

遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09; 是模拟生物在自然环境中的遗传和进化的过程而形成的自适应全局优化搜索算法。它借用了生物遗传学的观点&#xff0c;通过自然选择、遗传和变异等作用机制&#xff0c;实现各个个体适应性的提高。 基因型 (G…

文件阅览功能的实现(适用于word、pdf、Excel、ppt、png...)

需求描述&#xff1a; 需要一个组件&#xff0c;同时能预览多种类型文件&#xff0c;一种类型文件可有多个的文件。 看过各种博主的方案&#xff0c;其中最简单的是利用第三方地址进行预览解析&#xff08;无需任何插件&#xff09;&#xff1b; 这里推荐三个地址&#xff1a…

EasyExcel实现excel区域三级联动(模版下载)

序号 前言需求不通过excel,实现省市区级联实战pom.xml配置controller配置service类业务处理类测试 前言 首先&#xff0c;我们先来了解一下java实现模板下载的几种方式 1、使用poi实现2、使用阿里的easyexcel实现 今天社长就给大家说一下easyexcel的实现模板下载的之旅。在这里…

phpword使用整理

目录 介绍 安装 创建文档 设置默认字体和字号 设置文本样式 编号标题 换行符 分页符 超链接 创建表格 添加图片 文件保护 加载word文件 内容转化为html 保存 模板替换 格式 加载模板 替换字符串 替换图片 替换表格 总结 参考 介绍 PHPWord 是一个用纯 …

Vue3 过渡动画效果

文章目录 Vue3 过渡动画效果概述<Transition>组件简单使用为过渡效果命名自定义过渡classJavaScript动画效果元素间过渡 <transition-group>组件列表动画状态动画 Vue3 过渡动画效果 概述 Vue 提供了两个内置组件&#xff0c;可以帮助你制作基于状态变化的过渡和…

中服云设备全生命周期管理系统4.0全新升级,震撼登场!

6月2日&#xff0c;中服云设备全生命周期管理系统4.0将在中服云官方视频号线上直播震撼发布。在此次线上直播发布会上&#xff0c;中服云将详细地介绍设备全生命周期管理系统4.0版本的全新特性和创新功能。同时邀请了业内权威售前顾问、设备管理工程师和合作伙伴&#xff0c;共…

降低FTP服务器速度的解决方案(Filezilla等)

我最近发现&#xff0c;尽管有70Mbps&#xff08;8.75MB / s&#xff09;的互联网连接和1Gbps&#xff08;125MB / s&#xff09;的专用服务器可以从中下载&#xff0c;但我似乎只能从FTP服务器上以大约16.8Mbps&#xff08;2.1MB / s&#xff09;的速度下载。在一个线程上。但…

深入篇【Linux】学习必备:理解文件权限

深入篇【Linux】学习必备&#xff1a;理解文件权限 Ⅰ.Linux权限的概念Ⅱ.Linux权限管理①.文件访问者的分类(访问者的身份)②.文件类型和访问权限(文件本身的事物属性)1.文件类型&#xff1a;2.基本权限: ③.文件权限值的表示方法1.字符表示方法2.八进制数值表示法 ④.文件访问…

【活动回顾】Databend 数据库表达式框架设计与实现 @GOTC

5月28日&#xff0c;“全球开源技术峰会 GOTC 2023 ”圆满落幕。在本次会上&#xff0c;Databend 数据库的 优化器 研发工程师 骆迪安作为嘉宾中的一员&#xff0c;在 rust 专题专区分会场进行了一次主题为《 Rust 实现的先进 SQL Parser 与高效表达式执行框架 — Databend 数…

多语言跨境商城源码,出海跨境商城软件开发模式平台

一、多语言跨境商城模式 多商家模式&#xff1a;容纳不同的商家 多用户模式&#xff1a;用户之社区&#xff0c;用户交互&#xff0c;分享和推广 支持扩展&#xff1a;使用现代化的技术架构和设计&#xff0c;包括支持并发访问、分布式数据存储等功能。 采用常用技术&#x…

合工大Java大作业1:货物进销管理系统

问题描述 编写一个Inventory.java完成以下功能&#xff08;没有学过Java文件处理之前&#xff0c;各位同学可以使用硬编码将数据放进两个Vector变量里。等学过Java文件处理之后&#xff0c;再补充数据文件读取部分&#xff09;&#xff1a; 1&#xff0e;程序首先打开并读取In…

SpringMVC源码分析:SpringMVC九大组件分析(三)

一、概述 SpringMVC九大组件如下图&#xff0c;我将一个个进行介绍。各个组件使用的入口DispatcherServlet.doDispatch是各个组件使用的入口&#xff0c;我们大部分代码都是从这里开始进入的。 二、MultipartResolver 下面是MultipartResolver组件具体的使用流程&#xff0c;…