草庐IT

基于设备树的驱动编写

寒听雪落 2023-09-24 原文

和平台总线实现的驱动不同的地方在于,设备树将平台设备,也就是 platform_device 进行了完全的抽象,对于不同的设备并不需要去写一个个平台设备。而只是需要修改设备树就行了。设备树是一个用节点描述系统中设备的树状结构。

一,设备树相关定义

1,设备树文件定义

•  DTS :dts 文件是对 Device Tree 的描述,放置在内核的/arch/arm64/boot/dts 目录,描述了一个板子的硬件
资源。以前写在 mach-xxx 文件中的内容被转成了 dts 文件。
•  DTC :编译工具,存放在目录 scripts/dtc 位置,它可以将.dts 文件编译成.dtb 文件。
•  DTB :DTC 编译*.dts 生成的二进制文件(.dtb),bootloader 在引导内核时,会预先读取.dtb 到内存,进而由内核解析。
•  DTSI :由于同一系列 SOC 很多相同的地方,为了减少代码的冗余,设备树将这些共同部分提炼保存在.dtsi 文件中,供不同的 dts 共同使用。

2,设备树文件类型

一根节点多个子节点,子节点又可以包含多个子节点,以树状的结构散开。 在编写驱动程序时,我们只需关心设备树和平台驱动两样东西,使用平台驱动直接调用设备树的内容。

3, dts 节点node和文件结构

设备树节点是用节点名和单元地址和一个用大括号作为节点定义的开始和结尾来定义的。
•  [lable:]:设备树文件允许标签附加在任何节点或者属性上。
•  node-name:是指节点的名字。
•  [@unit-address]:是指节点所在的基地址。
•  [properties definitions]:是指相关属性的定义。
•  [child nodes]:是指相关的子节点。
如果父节点中有子节点相同的属性,那么以设备树的父节点的属性为主。所有的 dts 文件都需要有一个 root 节点,并且 root 节点内必须有一个 cpus 节点和至少一个的 memory 节点。

 

4,aliases节点

设备树文件有一个 root 节点,所有的其他设备节点都是它的子节点。下面展示的是它的一些必须属性和可选属性。 设备树可以有一个别名节点,也就是 aliases 节点。用来定义一个或者更多的属性。别名节点只能存在于设备树的root节点中,并且节点名字为 aliaes。客户端程序会通过 alias 的属性名来引用设备的全路径,或者部分路径。

5,chosen节点和memory节点

/chosen 节点并不代表系统中的一个真实的设备,但是,描述了当系统固件在运行的时候会被选择或者指定的参数。这个节点一定得在 root 节点下。所有的设备树文件都需要内存设备节点,用来描述系统物理内存的布局。。。。如果一个系统有多个范围的内存,多个内存节点将会被创建。或者可以在一个单独的内存节点的 reg 属性中指定多个范围的内存。chosen节点和memory节点可选和必须属性如下:

 6,cpus节点

设备树所必须的 cpus 节点,它不代表系统中一个真实的设备。它作为一个系统 cpus 的子 cpu 节点的容器存在。一个 cpu 节点就代表一个硬件的可执行块。能够运行操作系统而不会受到运行其他操作系统 cpu 的干扰。

二,设备树节点分析:在设备树中添加 gpio-led节点

	gpio-led 
    {
		compatible = "xlnx,zynqmp-led-1.0";
		gpios = <&gpio0 0x33 0x0>;
	};

具体分析:
•  gpio-led:设备节点名,也就是 node-name。
•  compatible:兼容名,驱动将会通过这个在设备树中找到该节点。
•  gpio:定义的 led 的管脚,MIO51。 

三,驱动程序分析

1,int platform_driver_register(struct platform_driver *driver)  //向平台总线注册平台驱动。

•  pdrv:注册的平台驱动。
•  返回值:0,设置成功;负值,设置失败。

2,of_get_gpio

static inline int of_get_gpio(struct device_node *np, int index)  //从节点出通过index获取GPIO号
 {
                return of_get_gpio_flags(np, index, NULL);  //np:从设备树处获得的节点
}

3,platform_driver结构体:构建平台注册的结构体

当驱动在设备中找到 name 之后,进行配对获取 resource 资源,进入 probe 函数
struct platform_driver gpio_drv =

{
        .driver =

        {
         .name = "zynqmp_led",
         .of_match_table = gpio_of_match_table,  //用来在设备树中找到对应 node 的函数。
         },
         .probe = gpio_pdrv_probe,    //gpio 初始化函数。
         .remove = gpio_pdrv_remove,   //当移除 gpio 时会执行的函数。
 };
4,int gpio_request(unsigned gpio, const char *label)

含义:gpio_request 函数用来向系统申请一个 GPIO 管脚,每一个管脚在使用前都需要使用这个函数初始化,若不使用这个函数直接使用 GPIO,则 GPIO 不会反应。
•  参数:gpio,需要申请的 gpio 标号;lable,gpio 的名字,可自由设定。
•  返回值:0,申请成功;其他值,申请失败。

5,static inline int gpio_direction_output(unsigned gpio, int value) //gpio号和0 为灭灯/1 为开灯

//设置 gpio 为输出模式,并设置 0 或 1,来控制输出的开关。

6,static inline void gpio_set_value(unsigned gpio, int value)//设置 gpio 的引脚值。

7,makefile文件

# 已经编译过的内核源码路径
KERNEL_DIR = /home/.............xlnx/sources/kernel

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# 当前路径
CURRENT_DIR = $(shell pwd)
MODULE = platform_gpio_drv

all :
# 进入并调用内核源码目录中 Makefile 的规则 ,  将当前的目录中的源码编译成模块
make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
rm -rf *.mod.c *.mod.o *.symvers *.order *.o

ifneq ($(APP), )
$(CROSS_COMPILE)gcc $(APP).c -o $(APP)
endif

clean :
make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean

# 指定编译哪个文件
obj-m += $(MODULE).o

8,驱动代码 

//1、添加头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>

static unsigned int led_major;
static struct class *led_class;

struct led_driver
{
	int gpio1;
	// int gpio2;
	int irq;
	struct device dev;
};
struct led_driver *led_dri = NULL;

const struct file_operations led_fops = {

};

//在probe函数中打印获取数据包里面的名字及GPIO
int gpio_pdrv_probe(struct platform_device *pdev)
{
	struct device_node *node;
	// unsigned int gpio1, gpio2;
	unsigned int gpio1;
	unsigned int ret = 0;

	printk("gpio pdrv probe!\n");
	printk("pdrv name = %s!\n", pdev->name);

	//申请主设备号
	led_major = register_chrdev(0, "led_drv", &led_fops);
	if (led_major < 0)
	{
		printk("register chrdev led major error!\n");
		return -ENOMEM;
	}
	//创建类
	led_class = class_create(THIS_MODULE, "led_class");
	//创建设备
	device_create(led_class, NULL, MKDEV(led_major, 0), NULL, "led_device%d", 0);
	//硬件初始化
	//申请空间
	led_dri = devm_kmalloc(&pdev->dev, sizeof(struct led_driver), GFP_KERNEL);
	if (led_dri == NULL)
	{
		printk("devm kmalloc led_driver error!\n");
		return -1;
	}
	//获取从设备节点传过来的pdev中的dev及node节点
	led_dri->dev = pdev->dev;
	node = pdev->dev.of_node;

	//从node节点处获得GPIO号
	gpio1 = of_get_gpio(node, 0);
	printk("of get gpio1 number = %d\n", gpio1);
	if (gpio1 < 0)
	{
		printk("of get gpio error!\n");
		return -1;
	}

	//申请GPIO
	ret = gpio_request(gpio1, "plattree_led");
	if (ret < 0)
	{
		printk("plattree led gpio request error!\n");
		return ret;
	}

	//设置GPIO为输出模式,并设备为0,灭灯
	gpio_direction_output(gpio1, 0);
	// gpio_direction_output(gpio2, 0);

	led_dri->gpio1 = gpio1;
	// led_dri->gpio2 = gpio2;

	return 0;
}

int gpio_pdrv_remove(struct platform_device *pdev)
{
	printk("led pdrv remove!\n");

	gpio_set_value(led_dri->gpio1, 1);
	// gpio_set_value(led_dri->gpio2, 1);

	gpio_free(led_dri->gpio1);
	// gpio_free(led_dri->gpio2);
	device_destroy(led_class, MKDEV(led_major, 0));
	class_destroy(led_class);
	unregister_chrdev(led_major, "led_drv");

	return 0;
}

//of_match_table实现
const struct of_device_id gpio_of_match_table[] = {
	{
		.compatible = "xlnx,zynqmp-led-1.0",
	},
	{}};

//当驱动在设备中找到name之后,进行配对获取resource资源,进入probe函数
struct platform_driver gpio_drv = {
	.driver = {
		.name = "zynqmp_led",
		.of_match_table = gpio_of_match_table,
	},
	.probe = gpio_pdrv_probe,
	.remove = gpio_pdrv_remove,
};

//实现装载入口函数和卸载入口函数
static __init int platform_gpio_drv_init(void)
{
	//创建pdrv,并且注册到总线中
	return platform_driver_register(&gpio_drv);
}
static __exit void platform_gpio_drv_exit(void)
{
	//注销设备
	platform_driver_unregister(&gpio_drv);
}

//声明装载入口函数和卸载入口函数
module_init(platform_gpio_drv_init);
module_exit(platform_gpio_drv_exit);


//添加GPL协议
MODULE_LICENSE("GPL");
MODULE_AUTHOR("subomb");

四,驱动验证

先加载insmod platform_gpio_drv.ko,然后输入dmesg

 

有关基于设备树的驱动编写的更多相关文章

  1. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  2. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  3. ruby-on-rails - 如何在 Ruby on Rails 中实现由 JSF 2.0 (Primefaces) 驱动的 UI 魔法 - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。问题1)我想知道ruby​​onrails是否有功能类似于primefaces的gem。我问的原因是如果您使用primefaces(http://www.primefaces.org/showcase-labs/ui/home.jsf),开发人员无需担心javascript或jquery的东西。据我所知,JSF是一个规范,基于规范的各种可用实现,prim

  4. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  5. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  6. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  7. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  8. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  9. ruby-on-rails - 禁用设备的 :confirmable on-the-fly to batch-generate users - 2

    Devise是一个Ruby库,它为我提供了这个User类:classUser当写入:confirmable时,注册时会发送一封确认邮件。上周我不得不批量创建300个用户,所以我在恢复之前注释掉了:confirmable几分钟。现在我正在为用户批量创建创建一个UI,因此我需要即时添加/删除:confirmable。(我也可以直接修改Devise的源码,但我宁愿不去调和它)问题:如何即时添加/删除:confirmable? 最佳答案 WayneConrad的解决方案:user=User.newuser.skip_confirmation

  10. ruby-on-rails - 如何为空白字段编写 rspec? [Rails3.1] - 2

    我使用rails3.1+rspec和factorygirl。我对必填字段(validates_presence_of)的验证工作正常。我如何让测试将该事实用作“成功”而不是“失败”规范是:describe"Addanindustrywithnoname"docontext"Unabletocreatearecordwhenthenameisblank"dosubjectdoind=Factory.create(:industry_name_blank)endit{shouldbe_invalid}endend但是我失败了:Failures:1)Addanindustrywithnona

随机推荐