当前位置: 首页 > article >正文

Linux platform子系统和设备树

1 Linux platform子系统

在Linux 2.6内核中,提出了总线、设备、驱动的架构,目的是让我们写出来的驱动通用性更强。

arm核内部总线结构:
在这里插入图片描述

1.1 核心思想

  • 将设备的信息从驱动中分离出来,我们需要在操作系统中,添加设备和驱动两部分。

  • 设备中包含是设备的信息(资源),驱动中包含的是操作设备函数接口。

  • 为了能让驱动最终能操作我们的硬件设备,我们在驱动中必须获取设备的信息(资源)。驱动是如何获取具体设备信息呢?

  • 设备和驱动都会注册到总线上,当注册设备的时候,会去寻找同名的驱动,当注册驱动的时候,也会去找同名的设备。相互查找。一旦匹配成功,操作系统就会自动调用驱动提供的probe函数。我们只需要在probe函数中,使用操作系统提供的通用API获取硬件的资源即可

在这里插入图片描述

1.2 Linux总线的理解

  • 总线在操作系统中本质就是两个链表: 挂载设备的链表和挂载驱动的链表。
  • 在操作系统中总线种类可以分成两大类:
  • 平台总线:平台总线挂载都是控制器设备,用于CPU核与硬件控制器之间的通信。在Linux系统中用"platform bus"表示。
  • 边缘设备之间通信的总线:边缘设备之间通信的总线挂载是符合总线时序的外围设备如:i2c , spi ,usb , uart 等。不同的边缘设备之间通信的总线,总线时序是不一样的,
  • 对于这些总线,Linux 内核是单独实现的

1.3 基于总线写驱动的思路

  • 根据自己的设备,确定总线的类型
  • 根据总线的类型, 确定设备在总线上如何描述
  • 根据总线的类型, 确定驱动在总线上如何描述
  • 根据总线的类型,确定在总线上如何注册设备
  • 根据总线的类型,确定在总线上如何注册驱动
  • 根据总线的类型, 确定设备和驱动匹配原则

设备和驱动匹配后,操作系统就会调用驱动提供的probe函数。在这个函数中,一般需要做两件事情:

  • (1)获取匹配的硬件资源
  • (2)向上层提供硬件设备的操作函数接口(如:注册字符设备)

案例代码

  • 驱动程序:
#include "linux/export.h"
#include "linux/platform_device.h"
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>

#define PLATFORM_LED_NAME "imx_led"



static int imx_led_probe(struct platform_device *pdev)
{
    int err = 0;
    struct resource	*led_ccm_reg;
    struct resource	*led_mux_reg;
    struct resource	*led_gpio_reg;

    printk("imx led probe\n");
    /* probe函数
     * 1、获取平台设备资源
       2、注册设备驱动,向上层提供接口
    */

    /* 1、获取平台设备资源 */
    led_ccm_reg = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!led_ccm_reg) {
        err = -ENODEV;
        printk("platform_get_resource ccm\n");
        goto err_platform_get_resource;
    }
    printk("ccm addr: %#x\n", (unsigned int)led_ccm_reg->start);

    led_mux_reg = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (!led_mux_reg) {
        err = -ENODEV;
        printk("platform_get_resource mux\n");
        goto err_platform_get_resource;
    }
    printk("mux addr: %#x\n", (unsigned int)led_mux_reg->start);

    led_gpio_reg = platform_get_resource(pdev, IORESOURCE_MEM, 2);
    if (!led_gpio_reg) {
        err = -ENODEV;
        printk("platform_get_resource gpio\n");
        goto err_platform_get_resource;
    }
    printk("gpio addr: %#x\n", (unsigned int)led_gpio_reg->start);

    /* 2、注册字符设备驱动:向应用层提供接口 todo */

    return 0;

err_platform_get_resource:
    return err;
}

static int imx_led_remove(struct platform_device *pdev)
{
    printk("imx led remove\n");
    return 0;
}

/* 3、关联平台设备 */
static const struct of_device_id imx_lex_dt_ids[] = {
	{ .compatible = PLATFORM_LED_NAME },
	{ }
};
MODULE_DEVICE_TABLE(of, imx_lex_dt_ids);

/* 1、描述平台驱动 */
static struct platform_driver drv = {
    .probe = imx_led_probe,
    .remove = imx_led_remove,
    .driver = {
        .name = PLATFORM_LED_NAME,
        .owner = THIS_MODULE,
        .of_match_table = imx_lex_dt_ids,
    }
};

static int __init led_init(void)
{
    int err = 0;
    /* 2、注册平台驱动 */
    err = platform_driver_register(&drv);
    if (err < 0) {
        printk("platform_driver_register failed\n");
        goto err_platform_driver_register;
    }

    printk("driver init success\n");
    return 0;

err_platform_driver_register:
    return err;
}

static void __exit led_exit(void)
{
    platform_driver_unregister(&drv);
    printk("driver exit\n");
}

MODULE_AUTHOR("jk luo");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is platform driver");

module_init(led_init);
module_exit(led_exit);

  • 设备程序:
#include "linux/kernel.h"
#include "linux/platform_device.h"
#include <linux/init.h>
#include <linux/module.h>

#define PLATFORM_LED_NAME "imx_led"
#define LED_CCM_REG 0x20C406C  // 26、27管脚高电平
#define LED_CCM_REG_SIZE 0x4
#define LED_MUXCTL_REG 0x20E00B0  // 低四位设置为0101
#define LED_MUXCTL_REG_SIZE 0x4
#define LED_GPIO_BASE_REG 0x209C000
#define LED_GPIO_REG_SIZE 0x8
#define LED_GPIO_DAT_REG_OFFSET 0x0  // gpio1的27号管脚
#define LED_GPIO_DIR_REG_OFFSET 0x4  // 高电平输出,低电平输入

void led_device_release(struct device *dev)
{
    printk("led_device_release\n");
}

static struct resource led_device_resource[] = {
    [0] = {
        .start = LED_CCM_REG,
        .end = LED_CCM_REG + LED_CCM_REG_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = LED_MUXCTL_REG,
        .end = LED_MUXCTL_REG + LED_MUXCTL_REG_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
    [2] = {
        .start = LED_GPIO_BASE_REG,
        .end = LED_GPIO_BASE_REG + LED_GPIO_REG_SIZE - 1,
        .flags = IORESOURCE_MEM,
    }
};

static struct platform_device pdev = {
    .name = PLATFORM_LED_NAME,
    .resource = led_device_resource,
    .num_resources = ARRAY_SIZE(led_device_resource),
    .dev = {
        .release = led_device_release,
    }
};

static int __init led_init(void)
{
    int err = 0;
    err = platform_device_register(&pdev);
    if (err < 0) {
        printk("platform_device_register failed\n");
        goto err_platform_device_register;
    }

    printk("device init success\n");
    return 0;

err_platform_device_register:
    return err;
}

static void __exit led_exit(void)
{
    platform_device_unregister(&pdev);
    printk("unregister device\n");
}

MODULE_AUTHOR("jk luo");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is platform device");

module_init(led_init);
module_exit(led_exit);


2 Linux设备树

  • Linux内核在启动的时候,要求把设备树文件传递给它。它拿到设备树之后,会解析设备树文件,从而识别设备信息

  • 因为设备的信息是针对于特定平台的,如果我们在Linux内核中包含太多设备信息,则Linux内核移植性就会变差。引入设备树之后,设备的信息的描述不再在是以代码的形式存在于Linux内核源代码中

  • dtc,device tree compiler,是将.dts 编译为.dtb需要用到的**编译工具,**是编译设备树的小工具

  • dts,device tree source,是设备树**源码文件,**是描述硬件信息的asiII文本文件

  • dtsi,device tree source include,是设备树源码文件要用到的头文件,类似头文件,描述平台共性

  • dtb,device tree binary,是将DTS 编译以后得到的二进制文件是编译后的二进制文件,可被bootloader/kernel识别并解析

一般开发的时候,把这些设备树文件拷贝出来,方便查找:

  • imx6ull-14x14-smartcar.dts:外围设备相关设备树文件
  • imx6ull.dtsi:芯片相关设备树文件

将dtb文件反编译成dts文件C.将test.dtb文件反编译成test.dts文件:dtc -O dts -I dtb -o test.dts test.dtb

2.1 设备树语法规则

一个节点就是用来描述一个设备的信息,每个节点必须有一个<名称>[@<设备地址>]形式的名字。

  • 1、名称就是一个ascii字符串,节点的命名应该根据设备的功能来命名。例如一个3com以太网适配器的节点就应该命名为ethernet,而不应该是3com509
  • 2、如果存在reg属性:则设备地址是reg属性的第一个数字。
  • 3、每个设备的节点都应该有一个compatible属性

2.2 节点属性

属性有两种格式:

  • 格式1(没有值) : property-name
  • 格式2(键值对) : property-name = value

2.3 添加设备树节点

在设备树文件的首节点的最后一个大括号之前添加新节点: 每个设备树节点必须要有compatible属性。compatible用于与驱动匹配,reg表示寄存器地址和大小。

	};
	// 在首节点的最后一个大括号之前添加新节点。节点名称格式为:名称@reg中第一个数字
	smartcar-led@20c406c {
		compatible = "imx-led"; // 这个名称与驱动匹配
		reg = <0x20c406c 0x4>,<0x20e00b0 0x4>,<0x209c000 0x8>; // 设备的寄存器地址,多个寄存器,用逗号隔开
	};
};

在开发板的/sys/firemware/devicetree/base目录下可以查看设备树是否添加成功。

2.4 在驱动中匹配设备树节点

通过struct device_driver结构体中的of_match_table成员,来匹配设备树节点。

static struct platform_driver imx_led_driver = {
	.driver = {
           .of_match_table = led_dt_ids, // 匹配设备树节点
		  },
};

3 pinctrl和GPIO子系统

所谓的子系统,实质上就是设备树中,对SOC芯片不同寄存器进行了定义和配置。驱动开发时,仅需了解了SOC芯片厂商是如何在设备树中定义这些寄存器的,然后将这些子系统配置,以节点属性的方式添加到我们新增的设备树节点中;最后编译设备树,并在驱动程序中调用各子系统的系统API,操作寄存器。

比如:使用IMX6ULL,通过GPIO管脚控制LED流水灯亮、灭。

  • 控制流水灯,需要根据硬件电路图,找到对应的控制管脚,设置管脚IO复用模式IOMUX为GPIO的工作模式(IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA)
  • 配置好IOMUX后,还需要配置管脚PAD属性,这些属性配置比较复杂,需要根据SOC厂商的芯片说明(IOMUXC_SW_PAD_CTL_PAD_UART1_TX_DATA),进行配置,通常参考设备树中其他pinctrl的配置值即可。
  • 最后要配置管脚的输入输出工作模式。如果为输出工作模式,还需要配置高低电平。

基于以上流程:

  • SOC芯片厂商,在设备树中,对IOMUX和输入工作模式配置,通过一个宏定义直接做好了,我们只需找到对应的宏即可
  • 管脚PAD属性:参考设备树中,同一IOMUX工作模式下,厂商的配置。
  • 最后,将这些配置,在pinctrl子系统中,新增子节点。
		pinctrl_rgb_led: rgb_led {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO04__GPIO1_IO04  0x1b0b1
				MX6UL_PAD_CSI_VSYNC__GPIO4_IO19  0x1b0b1
				MX6UL_PAD_CSI_HSYNC__GPIO4_IO20  0x1b0b1
			>;
		};

GPIO子系统:

  • 在设备树文件imx6ull.dtsi中,定义了各gpioX组的设备信息,只需在我们新增的设备树节点中,以属性的方式引用对应的gpioX组;最后在驱动程序中,调用系统提供的API操作GPIO管脚。
	};
	// 在首节点的最后一个大括号之前添加新节点
	smartcar-led@20c406c {
		compatible = "imx-led"; // 这个名称与驱动匹配
		reg = <0x20c406c 0x4>,<0x20e00b0 0x4>,<0x209c000 0x8>; // 设备的寄存器地址
		status = okay;
		pinctrl-0 = <&pinctrl_rgb_led>;
		rgb_led_red = <gpio1 4 GPIO_ACTIVE_LOW>;
		rgb_led_green = <gpio4 20 GPIO_ACTIVE_LOW>;
		rgb_led_red = <gpio4 19 GPIO_ACTIVE_LOW>;
	};
};

http://www.kler.cn/a/350507.html

相关文章:

  • C#,入门教程(02)—— Visual Studio 2022开发环境搭建图文教程
  • jlatexmath-android如何实现自定义渲染字符
  • 什么是三高架构?
  • KubeSphere部署安装,接入KubeKey安装的k8s集群
  • 用户中心项目教程(四)---Vue脚手架完成前端初始化
  • CSS 合法颜色值
  • 知识篇:(五)JavaScript 数组进阶操作:对象属性操作、数组转换与求和
  • 在uniapp中实现即时通讯中的【发送语音】
  • 不同数据类型转换与转义的对比差异
  • HarmonyOS NEXT 开发之ArkTS基础入门
  • 搭建`mongodb`副本集-开启权限认证 mongo:7.0.5
  • 单片机输出方波
  • 若依框架篇-若依框架搭建具体过程、后端源代码分析、功能详解(权限控制、数据字典、定时任务、代码生成、表单构建、接口测试)
  • AI测试之 TestGPT
  • 如何解决与kernel32.dll相关的常见错误:详细指南解析kernel32.dll文件缺失、损坏或错误加载问题
  • 仓库管理系统
  • AD9361 的 TX 输出中添加前置放大器,并在 RX 输入中添加 LNA。
  • 深度解析计数排序:原理、特性与应用
  • Shiro认证 -- (Authentication)
  • TCP Analysis Flags 之 TCP Window Update
  • 鸡兔同笼(贪心)
  • 【spring ai】java 实现RAG检索增强,超快速入门
  • Unity URP shader ———魔系符文宝石是如何练成的
  • C# Attribute 介绍
  • 微信小程序后台搭建—node+mysql
  • 农业机器人综述:技术现状、应用场景及未来展望