嵌入式Linux驱动开发 04:基于设备树的驱动开发

news2024/12/28 4:59:22

文章目录

  • 目的
  • 基础说明
  • 开发准备
  • 设备树调整
  • 驱动程序与测试
  • 总结
  • 设备树文件内容

目的

前面文章 《嵌入式Linux驱动开发 03:平台(platform)总线驱动模型》 引入了资源和驱动分离的概念,这篇文章将在前面基础上更进一步,引入设备树的概念。

基础说明

在平台总线驱动模型中资源和驱动已经从逻辑上和代码组织上进行了分离,但每次调整资源还是会涉及到内核,所以现在更加流行的是设备树方式。设备树的好处是通过独立于内核存在,这样如果设备上外设功能启用与否以及位置变动的话很多时候不用修改与编译内核,只要重新处理设备树文件即可。

设备树代码(或者都算不上代码)只是一些树状的数据,有点像JSON。通常每个系列的芯片厂家都会编写好后缀为 .dtsi 的设备树文件,里面把芯片基本上的功能资源都定义了。而对于某个具体的电路板来说,只要编写后缀为 .dts 的文件,在其中引入前述的 .dtsi 文件,然后在 .dts 文件中选择性启用 .dtsi 中已经定义好的并且电路中需要用到的功能。当然在 .dts 文件中也可以自定义新的功能。

.dts 文件最终可以编译为 .dtb 文件,系统在启动的时候会通过Bootloader将该文件传递给内核,内核就会解析取用其中的资源并与驱动进行匹配。如果资源需要调整,通常只需要调整 .dts 文件生成新的 .dtb 文件即可。

开发准备

本文中演示中涉及目录与文件结构组织如下:
在这里插入图片描述
基本上和前文相同,只需稍作修改。

进入源码目录:

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

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

obj-$(CONFIG_USER_CHAR_DEV) += char_drv.o

设备树调整

在这篇文章中将在设备树中创建一个自己的节点供下面的驱动程序使用:

# cd ~/nuc980-sdk/NUC980-linux-5.10.y/
gedit arch/arm/boot/dts/nuc980-dev-v1.0.dts

自定义节点内容如下:

	nx_node@0 {
		compatible = "nx_dts_node";
		str = "Naisu 233!";
		num = <0x00000000 0x00000020>;
	};
	
	nx_node@1 {
		compatible = "nx_dts_node";
		str = "Hello Naisu!";
		num = <0x000000 0x00000040>;
	};

完整的设备树文件内容见文章结尾。

上面节点中 compatible 字段的内容用于驱动程序查找匹配; str 字段后面的形式是字符串; num 字段后面是数值,可以用来表示u32或u64(需要用两个u32,中间用空格隔开)。

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

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

# 编译生成设备树文件
make dtbs

# 编译完成后拷贝到电脑上再拷贝到SD卡中
# sudo cp arch/arm/boot/dts/nuc980-dev-v1.0.dtb /media/sf_common/

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

可以在 /sys/firmware/devicetree/base/ 或者 /proc/device-tree/ 目录下看到被内核解析后设设备树节点信息:
在这里插入图片描述

驱动程序与测试

驱动文件 char_drv.c 内容如下:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of_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)
{
	const char *tmp_str;
	u64 tmp_num;

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

    of_property_read_string(pdev->dev.of_node, "str", &tmp_str); // 读取字符串数据
	of_property_read_u64(pdev->dev.of_node, "num", &tmp_num); // 读取u64内容

    printk("NX modlog: %s %llu\n", tmp_str, tmp_num);

    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;
}

static const struct of_device_id dts_device_ids[] = { 
    { .compatible = "nx_dts_node", } // 通过设备树 compatible 字段进行匹配
};

/* 定义platform_driver,用于探测和获取资源等 */
static struct platform_driver char_driver = {
    .probe      = char_probe,
    .remove     = char_remove,
    .driver     = {
        .name   = "naisu_char_dev", // 没有该字段启动时会崩溃
		.of_match_table = dts_device_ids,
    },
};

/* 驱动文件操作接口集合 */
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"); // 模块许可

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

# 设置编译工具链
# 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

在这里插入图片描述

总结

这篇文章简单试了下通过设备树创建资源节点,然后在驱动程序中获取这些节点的数据。实时上关于设备树以及设备树下驱动程序资源类型和资源获取还有很多细节的内容和操作函数,这些内容更多的可以通过使用过程中参考各种已有的驱动代码来了解,这里就不进行展开了。

设备树文件内容

/*
 * Device Tree Source for NUC980 DEV board
 *
 * Copyright (C) 2018 Nuvoton Technology Corp.
 *
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */
/dts-v1/;

#include "nuc980.dtsi"

/ {
	model = "Nuvoton NUC980 DEV V1.0";
	compatible = "nuvoton,nuc980-dev-v1.0", "nuvoton,nuc980";

	chosen {
		bootargs = "console=ttyS0,115200n8 noinitrd rootfstype=ext4 root=/dev/mmcblk0p2 rw rootwait mem=64M";
	};

	apb {
		uart1: serial@b0071000 {
			status = "disabled";
		};

		uart2: serial@b0072000 {
			status = "disabled";
		};

		uart3: serial@b0073000 {
			status = "disabled";
		};

		uart4: serial@b0074000 {
			status = "disabled";
		};

		uart5: serial@b0075000 {
			status = "disabled";
		};

		uart6: serial@b0076000 {
			status = "disabled";
		};

		uart7: serial@b0077000 {
			status = "disabled";
		};

		uart8: serial@b0078000 {
			status = "disabled";
		};

		uart9: serial@b0079000 {
			status = "disabled";
		};

		can0: can@b00a0000 {
			status = "disabled";
		};

		can1: can@b00a1000 {
			status = "disabled";
		};

		rtc: rtc@b0041000 {
			status = "disabled";
		};

		gpio: gpio@b0004000 {
			pinctrl-0 = <>;
			eint2-config = <0 0 0>;
			eint3-config = <0 0 0>;
		};

		nadc: nadc@b0043000 {
			status = "disabled";
		};

		pwm0: pwm@b0058000 {
			status = "disabled";
		};

		pwm1: pwm@b0059000 {
			status = "disabled";
		};


		etimer0: etimer0@b0050000 {
			status = "disabled";
		};

		etimer1: etimer1@b0050100 {
			status = "disabled";
		};

		etimer2: etimer2@b0051000 {
			status = "disabled";
		};

		etimer3: etimer3@b0051100 {
			status = "disabled";
		};

		i2c0: i2c0@b0080000 {
			status = "disabled";
		};

		i2c1: i2c1@b0081000 {
			status = "disabled";
			pinctrl-0 = <&pinctrl_i2c1_PB>;
		};


		i2c2: i2c2@b0082000 {
			status = "disabled";
			pinctrl-0 = <&pinctrl_i2c2_PB>;
		};

	};

	ahb {

		usbh_ehci@b0015000 {
			pinctrl-0 = <>; /*disable PWREN and OVC*/
			ov_active = <1>;/*disable PWREN and OVC*/
			status = "okay";
		};
		usbh_ohci@b0017000{
			status = "okay";
		};

		usbdev@b0016000 {
			status = "okay";
		};

		fmi@b0019000 {
			status = "disabled";
		};

		sdh@b0018000 {
			status = "okay";
		};

		emac0@b0012000 {
			status = "okay";
		};
		emac1@b0022000 {
			status = "disabled";
		};
		ccap0@b0024000 {
			status = "disabled";
		};
		i2c_gpio0: i2c-gpio-0 {
			status = "disabled";
		};
		ccap1@b0014000 {
			status = "disabled";
		};
		i2c_gpio1: i2c-gpio-1 {
			status = "disabled";
		};
		dma@b0008000 {
			status = "okay";
		};

		i2s: i2s@b0020000 {
			status = "disabled";
		};

		i2s_pcm: i2s_pcm {
			status = "disabled";
		};

		sound {
			compatible = "nuvoton,nuc980-audio";
			i2s-controller = <&i2s>;
			i2s-platform = <&i2s_pcm>;
			status = "disabled";
		};
		ebi: ebi@b0010000 {
			status = "disabled";
		};
	};

	nx_node@0 {
		compatible = "nx_dts_node";
		str = "Naisu 233!";
		num = <0x00000000 0x00000020>;
	};
	
	nx_node@1 {
		compatible = "nx_dts_node";
		str = "Hello Naisu!";
		num = <0x000000 0x00000040>;
	};
};

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

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

相关文章

内网或者没有公网IP,如何轻松通过公网访问?

很多网友都面临一个普遍的问题&#xff0c;就是作为家庭宽带用户没有公网IP&#xff0c;即没有IP v4的公网IP&#xff0c;我们天天盼着有一天IP v6能够普及。IP v4一共有2的32次方个地址&#xff0c;约43亿&#xff0c;中国仅为7000多万个&#xff0c;IP v6一共有2的128次方个地…

MySQL8.0版本在CentOS系统的配置教程

1.安装MySQL MySQL安装完成后&#xff0c;会自动配置为名称叫做&#xff1a;mysqld的服务&#xff0c;可以被systemctl所管理&#xff0c;我们在进行系统的配置时&#xff0c;主要修改root密码和允许root远程登录。 # 通过grep命令&#xff0c;在/var/log/mysqld.log文件中&a…

iOS开发:__weak __strong解决Block嵌套

Block使用会存在循环引用的问题&#xff0c;多个Block嵌套使用的情况更复杂&#xff0c;还会出现对象nil的问题。 为什么会循环引用&#xff1f; 现在iOS开发都是在ARC引用计数管理模式下的&#xff0c;参考另一篇文章《Block底层原理》&#xff0c;我们知道Block访问外部变量…

文心一言 VS 讯飞星火 VS chatgpt (29)-- 算法导论5.2 1题

一、在 HIRE-ASSISTANT 中&#xff0c;假设应聘者以随机顺序出现&#xff0c;你正好雇用一次的概率是多少&#xff1f;正好雇用 n 次的概率是多少? 文心一言&#xff1a; 讯飞星火&#xff1a; 华为盘古&#xff1a; 在HIRE-ASSISTANT中&#xff0c;假设应聘者以随机顺序出现…

仙境传说RO:从零开始编译服务端开发环境搭建安装Pandas-rAthena

仙境传说RO&#xff1a;从零开始编译服务端开发环境搭建安装Pandas-rAthena 大家好&#xff0c;我是艾西今天跟大家分享下仙境传说RO 开发脚本环境搭建。 Pandas是RO的模拟器实际上是开源的一个叫做rAthenna的一个开源项目 准备工具&#xff1a; Git 2.25.0 Setup建议放入D盘…

【消息队列】| 队列的优势介绍及应用场景

目录 &#x1f981; 前言&#x1f981; 那么MQ的优势在哪里&#xff1f;&#x1f981; 应用场景&#x1f981; 最后 &#x1f981; 前言 消息队列&#xff1a;MQ全称Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中保存消息的容器。多用于系统…

javaweb课程设计——商城项目

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;项目专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

2.2 动态范围的常用计算方法

1. 动态范围的常用计算方法 动态范围(Dynamic Range)指的是输入数据中数值的范围&#xff0c;计算动态范围是为了确定量化时使用的比特位数(还是抽象&#x1f602;)。个人理解:考虑到输入数据可能存在数据分布不均&#xff0c;即有些数据偏离过大。而过大的偏离值&#xff0c;会…

Ansys Zemax | NSC 非序列矢高图用户分析

本文介绍如何使用 NSC 矢高图用户分析功能在非序列模式下测量和显示对象的矢高。了解此功能的基础知识&#xff0c;包括如何设置复杂 CAD 零件的文件以获取特定面的矢高值。&#xff08;联系我们获取文章附件&#xff09; 介绍 OptocStudio 的序列模式具有表面矢高分析功能&…

硬件系统工程师宝典(28)-----关于LDO,应该知道的事

各位同学大家好&#xff0c;欢迎继续做客电子工程学习圈&#xff0c;今天我们继续来讲这本书&#xff0c;硬件系统工程师宝典。上篇我们说到BJT配合MOSFET控制电源开关的四种电路以及MOSFET的均流电路。今天我们来讲讲LDO的应用分析。 LDO的结构 LDO&#xff08;Low Dropout R…

Linux内核源码的配置和编译

目录 配置交叉编译工具链 读README 配置内核源码支持当前的硬件平台 驱动配置 内核编译 编译&#xff1a; 问题&#xff1a; 解决问题的方法&#xff1a; 测试内核 配置交叉编译工具链 打开内核源码顶层目录的Makefile&#xff0c; hqUbuntu:~/fs6818_uboot/kernel-3.4.39$ vi …

IP地址、子网划分

目录 一、IP地址1.IP地址表示2.分类IP地址3.无分类编址 CIDR4.特殊IP地址 二、子网划分1.子网、子网掩码、子网划分VLSM2.网络地址、广播地址3.示例1&#xff1a;等分为两个子网3.1 划分前&#xff1a;3.2 划分后&#xff1a; 4.示例2&#xff1a;等分为四个子网3.1 划分前&…

五种经典IO模型详解

目录 同步和异步同步阻塞IO模型基本概念应用场景优缺点 同步非阻塞IO模型基本概念应用场景优缺点 IO多路复用模型信号驱动IO模型回顾复习1.信号2.产生信号的条件3.可重入函数4.为什么中断处理函数不能直接调用不可重入函数5.如何写出可重入的函数 基本概念应用场景优缺点 异步I…

【操作系统】 1、计算机系统概述

1.1 操作系统的基本概念 从操作系统的角度上来划分计算机体系结构&#xff1a; 这里注意一点&#xff1a; 编译器属于应用程序。 操作系统&#xff1a;是指控制和管理计算机系统的 硬件 和软件 资源&#xff0c;合理的组织、调度计算机的工作与资源分配&#xff0c;进而为用…

对象数组练习案例

定义一个长度为3的数组&#xff0c;数组存储1~3名学生对象作为初始数据&#xff0c;学生对象的学号&#xff0c;姓名各不相同。 * 学生的属性&#xff1a;学号、姓名、年龄 * 要求1&#xff1a;再次添加一个学生对象&#xff0c;并在添加的时候进行学号的唯一性判断 * 要求2&am…

Nautilus Chain:我们将支持EIP6969

在今年 5 月初&#xff0c;以太坊核心开发者、Slingshot 的 CTO zkCole 提出了一个通用的协议标准 EIP-6969 &#xff0c;其旨在实现合约保护收入&#xff08;在以太坊 L2 上引入 / 标准化 CSR &#xff09;&#xff0c;该提案可以看作是之前 EIP-1559的改进版&#xff0c;并在…

Record类浅喽一眼~

Record类的一点小概念嗷。 一. 基本使用 java19 的新特性: 我们先构造一个student的Record类. 默认构造几个属性. public record Student(Integer id,String name, String email,Integer age) {} 然后简单搞一点例子 public static void main(String[] args) { St…

2023年上半年系统分析师上午真题及答案解析

1.信息系统的构成包括( )。 A.计算机硬件、计算机软件、网络和通信设备、系统分析人员、系统设计人员、系统开发人员 B.计算机硬件、计算机软件、系统分析人员、系统设计人员、系统开发人员 C.计算机硬件、计算机软件、系统设计人员、系统开发人员、信息用户 D.计算机硬件…

【C++】类和对象——友元函数和友元类的概念、初始化列表、explicit关键字、static成员

文章目录 1.友元函数和友元类的概念1.1友元函数1.2友元类 2.构造函数知识补充2.1初始化列表2.2explicit关键字 3.static成员3.1static成员概念3.2static成员特性 1.友元函数和友元类的概念 在C中&#xff0c;友元函数和友元类是指允许非成员函数或非成员类访问某个类中的私有成…

LeetCode面向运气之Javascript—第13题-罗马数字转整数-99.21%

LeetCode第13题-罗马数字转整数 题目要求 给定一个罗马数字&#xff0c;将其转换成整数。 罗马数字 罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M 分别代表1&#xff0c;5&#xff0c;10&#xff0c;50&#xf…