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

目的

前面文章 《嵌入式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/

# 我这里开发环境和开发板在同一局域网中,所以可以直接通过网络将文件拷贝到开发板上
# 在开发板中挂载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的设备文件
	device_node_arr[minor].minor = device_node_minor; // 保存子设备号,供后面卸载使用
	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"); // 模块许可

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

总结

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

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naisu Xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值