delphi linux arm_Linux设备树专有名词及语法规则详解(下)

每个节点都有 compatible 属性,根节点“/”也不例外,imx6ull-alientek-emmc.dts 文件中根节点的 compatible 属性内容如下所示:

14 / {15 model = "Freescale i.MX6 ULL 14x14 EVK Board";16 compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";......148 }

可以看出,compatible 有两个值:“fsl,imx6ull-14x14-evk”和“fsl,imx6ull”。前面我们说了,设备节点的compatible 属性值是为了匹配Linux 内核中的驱动程序,那么根节点中的 compatible属性是为了做什么工作的? 通过根节点的compatible 属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是“imx6ull-14x14-evk”这个设备,第二个值描述了设备所使用的SOC,比如这里使用的是“imx6ull”这颗 SOC。Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。接下来我们就来学习一下Linux 内核在使用设备树前后是如何判断是否支持某款设备的。

1、使用设备树之前设备匹配方法

在没有使用设备树以前,uboot 会向 Linux 内核传递一个叫做 machine id 的值,machine id也就是设备 ID,告诉 Linux 内核自己是个什么设备,看看 Linux 内核是否支持。Linux 内核是支持很多设备的,针对每一个设备(板子),Linux 内核都用MACHINE_START 和MACHINE_END来定义一个 machine_desc 结构体来描述这个设备, 比如在文件 arch/arm/mach-imx/mach-mx35_3ds.c 中有如下定义:

613 MACHINE_START(MX35_3DS, "Freescale MX35PDK")614 /* Maintainer: Freescale Semiconductor, Inc */ 615 .atag_offset = 0x100,616 .map_io = mx35_map_io,617 .init_early = imx35_init_early, 618 .init_irq = mx35_init_irq,619 .init_time = mx35pdk_timer_init, 620 .init_machine = mx35_3ds_init, 621 .reserve = mx35_3ds_reserve,622 .restart = mxc_restart, 623 MACHINE_END

上述代码就是定义了“ Freescale MX35PDK ” 这个设备, 其中 MACHINE_START 和MACHINE_END 定义在文件 arch/arm/include/asm/mach/arch.h 中,内容如下:

#define MACHINE_START(_type,_name) static const struct machine_desc mach_desc_##_type  used  attribute (( section (".arch.info.init"))) = { .nr = MACH_TYPE_##_type, .name = _name,#define MACHINE_END };

根据 MACHINE_START 和 MACHINE_END 的宏定义,将代码展开后如下所示:

1 static const struct machine_desc mach_desc_MX35_3DS 2 used 3 attribute (( section (".arch.info.init"))) = {4 .nr = MACH_TYPE_MX35_3DS,5 .name = "Freescale MX35PDK",6 /* Maintainer: Freescale Semiconductor, Inc */7 .atag_offset = 0x100,8 .map_io = mx35_map_io,9 .init_early = imx35_init_early,10 .init_irq = mx35_init_irq,11 .init_time = mx35pdk_timer_init,12 .init_machine = mx35_3ds_init,13 .reserve = mx35_3ds_reserve,14 .restart = mxc_restart, 15 }

从代码中可以看出,这里定义了一个 machine_desc 类型的结构体变量mach_desc_MX35_3DS , 这 个 变 量 存 储 在 “ .arch.info.init ” 段 中 。 第 4 行的 MACH_TYPE_MX35_3DS 就 是 “ Freescale MX35PDK ” 这 个 板 子 的 machine id 。MACH_TYPE_MX35_3DS 定义在文件 include/generated/mach-types.h 中,此文件定义了大量的machine id,内容如下所示:

15 #define MACH_TYPE_EBSA110 016 #define MACH_TYPE_RISCPC 117 #define MACH_TYPE_EBSA285 418 #define MACH_TYPE_NETWINDER 519 #define MACH_TYPE_CATS 620 #define MACH_TYPE_SHARK 1521 #define MACH_TYPE_BRUTUS 1622 #define MACH_TYPE_PERSONAL_SERVER 17......287 #define MACH_TYPE_MX35_3DS 1645......1000 #define MACH_TYPE_PFLA03 4575

第 287 行就是 MACH_TYPE_MX35_3DS 的值,为 1645。

前面说了,uboot 会给 Linux 内核传递 machine id 这个参数,Linux 内核会检查这个 machineid,其实就是将 machine id 与示例代码中的这些 MACH_TYPE_XXX 宏进行对比,看看有没有相等的,如果相等的话就表示 Linux 内核支持这个设备,如果不支持的话那么这个设备就没法启动Linux 内核。

2、使用设备树以后的设备匹配方法

当 Linux 内核引入设备树以后就不再使用 MACHINE_START 了, 而是换为了DT_MACHINE_START。DT_MACHINE_START 也定义在文件arch/arm/include/asm/mach/arch.h里面,定义如下:

#define DT_MACHINE_START(_name, _namestr) static const struct machine_desc mach_desc_##_name  used  attribute (( section (".arch.info.init"))) = { .nr = ~0, .name = _namestr,

可以看出,DT_MACHINE_START 和 MACHINE_START 基本相同,只是.nr 的设置不同,在 DT_MACHINE_START 里面直接将.nr 设置为~0。说明引入设备树以后不会再根据 machineid 来检查 Linux 内核是否支持某个设备了。

打开文件 arch/arm/mach-imx/mach-imx6ul.c,有如下所示内容:

208 static const char *imx6ul_dt_compat[] initconst = {209 "fsl,imx6ul",210 "fsl,imx6ull",211 NULL,212 };213214 DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")215 .map_io = imx6ul_map_io,216 .init_irq = imx6ul_init_irq,217 .init_machine = imx6ul_init_machine,218 .init_late = imx6ul_init_late,219 .dt_compat = imx6ul_dt_compat,220 MACHINE_END

machine_desc 结构体中有个.dt_compat 成员变量,此成员变量保存着本设备兼容属性,示例代码 中设置.dt_compat = imx6ul_dt_compat,imx6ul_dt_compat 表里面有"fsl,imx6ul"和"fsl,imx6ull" 这两个兼容值。只要某个设备( 板子) 根节点“/”的 compatible 属性值与imx6ul_dt_compat 表中的任何一个值相等,那么就表示 Linux 内核支持此设备。imx6ull-alientek-emmc.dts 中根节点的 compatible 属性值如下:

compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

其中“fsl,imx6ull”与 imx6ul_dt_compat 中的“fsl,imx6ull”匹配,因此 I.MX6U-ALPHA 开发板可以正常启动Linux 内核。如果将 imx6ull-alientek-emmc.dts 根节点的compatible 属性改为其他的值,比如:compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ullll"重新编译DTS,并用新的DTS 启动 Linux 内核,结果如图所示的错误提示:

1e912e97a32acdd45c8a6c9f108a4d56.png

系统启动信息

当我们修改了根节点 compatible 属性内容以后,因为 Linux 内核找不到对应的设备,因此Linux 内核无法启动。在 uboot 输出 Starting kernel…以后就再也没有其他信息输出了。接下来我们简单看一下 Linux 内核是如何根据设备树根节点的 compatible 属性来匹配出对应的 machine_desc,Linux 内核调用 start_kernel 函数来启动内核,start_kernel 函数会调用setup_arch 函数来匹配 machine_desc,setup_arch 函数定义在文件 arch/arm/kernel/setup.c 中,函数内容如下(有缩减):

913 void init setup_arch(char **cmdline_p)914 {915 const struct machine_desc *mdesc;916917 setup_processor();918 mdesc = setup_machine_fdt( atags_pointer);919 if (!mdesc)920 mdesc = setup_machine_tags( atags_pointer, machine_arch_type);921 machine_desc = mdesc;922 machine_name = mdesc->name;......986 }

第 918 行,调用 setup_machine_fdt 函数来获取匹配的 machine_desc,参数就是 atags 的首地址,也就是 uboot 传递给 Linux 内核的dtb 文件首地址,setup_machine_fdt 函数的返回值就是找到的最匹配的machine_desc。

函数 setup_machine_fdt 定义在文件 arch/arm/kernel/devtree.c 中,内容如下(有缩减):

204 const struct machine_desc * init setup_machine_fdt(unsigned intdt_phys)205 {206 const struct machine_desc *mdesc, *mdesc_best = NULL;......214215 if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))216 return NULL;217218 mdesc = of_flat_dt_match_machine(mdesc_best,arch_get_next_mach);219......247 machine_arch_type = mdesc->nr;248249 return mdesc;250 }

第 218 行,调用函数 of_flat_dt_match_machine 来获取匹配的machine_desc,参数 mdesc_best是默认的 machine_desc ,参数 arch_get_next_mach 是个函数, 此函数定义在定义在arch/arm/kernel/devtree.c 文件中。找到匹配的 machine_desc 的过程就是用设备树根节点的compatible 属性值和 Linux 内核中保存的所以 machine_desc 结构的. dt_compat 中的值比较,看看那个相等,如果相等的话就表示找到匹配的 machine_desc,arch_get_next_mach 函数的工作就是获取 Linux 内核中下一个 machine_desc 结构体。

最后在来看一下 of_flat_dt_match_machine 函数,此函数定义在文件 drivers/of/fdt.c 中,内容如下(有缩减):

705 const void * init of_flat_dt_match_machine(const void*default_match,706 const void * (*get_next_compat)(const char * const**))707 {708 const void *data = NULL;709 const void *best_data = default_match; 710 const char *const *compat;711 unsigned long dt_root;712 unsigned int best_score = ~1, score = 0; 713714 dt_root = of_get_flat_dt_root();715 while ((data = get_next_compat(&compat))) { 716 score = of_flat_dt_match(dt_root, compat); 717 if (score > 0 && score < best_score) {718 best_data = data;719 best_score = score; 720 }721 }......739740 pr_info("Machine model: %s", of_flat_dt_get_machine_name());741742 return best_data;743 }

第 714 行,通过函数 of_get_flat_dt_root 获取设备树根节点。

第 715~720 行,此循环就是查找匹配的 machine_desc 过程,第 716 行的 of_flat_dt_match 函数会将根节点compatible 属性的值和每个machine_desc 结构体中. dt_compat 的值进行比较,直至找到匹配的那个 machine_desc。

总结一下,Linux 内核通过根节点 compatible 属性找到对应的设备的函数调用过程,如图所示:

0ca222987f31671e28d143ee4a748387.png

查找匹配设备的过程

产品开发过程中可能面临着频繁的需求更改,比如第一版硬件上有一个 IIC 接口的六轴芯片 MPU6050,第二版硬件又要把这个 MPU6050 更换为 MPU9250 等。一旦硬件修改了,我们就要同步的修改设备树文件,毕竟设备树是描述板子硬件信息的文件。假设现在有个六轴芯片fxls8471,fxls8471 要接到 I.MX6U-ALPHA 开发板的 I2C1 接口上,那么相当于需要在 i2c1 这个节点上添加一个 fxls8471 子节点。先看一下 I2C1 接口对应的节点,打开文件 imx6ull.dtsi 文件,找到如下所示内容:

937 i2c1: i2c@021a0000 {938 #address-cells = <1>;939 #size-cells = <0>;940 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; 941 reg = <0x021a0000 0x4000>;942 interrupts = ; 943 clocks = ;944 status = "disabled"; 945 };

上面代码就是 I.MX6ULL 的 I2C1 节点,现在要在 i2c1 节点下创建一个子节点,这个子节点就是 fxls8471,最简单的方法就是在 i2c1 下直接添加一个名为 fxls8471 的子节点,如下所示:

937 i2c1: i2c@021a0000 {938 #address-cells = <1>;939 #size-cells = <0>;940 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; 941 reg = <0x021a0000 0x4000>;942 interrupts = ; 943 clocks = ;944 status = "disabled";945946 //fxls8471 子节点947 fxls8471@1e {948 compatible = "fsl,fxls8471";  949 reg = <0x1e>;950 };951 };

第 947~950 行就是添加的 fxls8471 这个芯片对应的子节点。但是这样会有个问题!i2c1 节点是定义在 imx6ull.dtsi 文件中的,而 imx6ull.dtsi 是设备树头文件,其他所有使用到 I.MX6ULL这颗 SOC 的板子都会引用 imx6ull.dtsi 这个文件。直接在 i2c1 节点中添加 fxls8471 就相当于在其他的所有板子上都添加了 fxls8471 这个设备,但是其他的板子并没有这个设备啊!因此,按照示例代码这样写肯定是不行的。

这里就要引入另外一个内容,那就是如何向节点追加数据,我们现在要解决的就是如何向i2c1 节点追加一个名为 fxls8471 的子节点,而且不能影响到其他使用到 I.MX6ULL 的板子。 I.MX6U-ALPHA 开发板使用的设备树文件为 imx6ull-alientek-emmc.dts, 因此我们需要在 imx6ull-alientek-emmc.dts 文件中完成数据追加的内容,方式如下:

1 &i2c1 {2 /* 要追加或修改的内容 */3 };

第 1 行,&i2c1 表示要访问 i2c1 这个 label 所对应的节点,也就是 imx6ull.dtsi 中的“i2c1: i2c@021a0000”。

第 2 行,花括号内就是要向 i2c1 这个节点添加的内容,包括修改某些属性的值。打开 imx6ull-alientek-emmc.dts,找到如下所示内容:

224 &i2c1 {225 clock-frequency = <100000>;226 pinctrl-names = "default";227 pinctrl-0 = ;228 status = "okay"; 229230 mag3110@0e {231 compatible = "fsl,mag3110";232 reg = <0x0e>;233 position = <2>;234 };235236 fxls8471@1e {237 compatible = "fsl,fxls8471";238 reg = <0x1e>;239 position = <0>;240 interrupt-parent = ;241 interrupts = <0 8>;242 };243 };

代码就是向 i2c1 节点添加/修改数据,比如第 225 行的属性“clock-frequency”就表示 i2c1 时钟为 100KHz。“clock-frequency”就是新添加的属性。

第 228 行,将 status 属性的值由原来的disabled 改为 okay。

第 230~234 行,i2c1 子节点 mag3110,因为 NXP 官方开发板在 I2C1 上接了一个磁力计芯片 mag3110,正点原子的 I.MX6U-ALPHA 开发板并没有使用 mag3110。

第 236~242 行,i2c1 子节点 fxls8471,同样是因为 NXP 官方开发板在 I2C1 上接了 fxls8471这颗六轴芯片。

因为代码中的内容是 imx6ull-alientek-emmc.dts 这个文件内的,所以不会对使用 I.MX6ULL 这颗SOC 的其他板子造成任何影响。这个就是向节点追加或修改内容,重点就是通过&label 来访问节点,然后直接在里面编写要追加或者修改的内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux平台上使用timer_create函数可以创建一个定时器。在Delphi中,可以通过调用Linux系统库头文件来实现该函数的调用。下面是一个简单的示例代码: ```delphi unit LinuxTimer; interface uses LinuxApi; type TTimer = class private FTimerID: timer_t; FInterval: Cardinal; FOnTimer: TNotifyEvent; FIsStarted: Boolean; FTimerSpec: itimerspec; procedure SetInterval(const Value: Cardinal); public constructor Create; destructor Destroy; override; procedure Start; procedure Stop; property Interval: Cardinal read FInterval write SetInterval; property OnTimer: TNotifyEvent read FOnTimer write FOnTimer; property IsStarted: Boolean read FIsStarted; end; implementation uses SysUtils; { TTimer } constructor TTimer.Create; begin inherited; FTimerID := 0; FInterval := 1000; FIsStarted := False; end; destructor TTimer.Destroy; begin Stop; inherited; end; procedure TTimer.SetInterval(const Value: Cardinal); begin if FInterval <> Value then begin FInterval := Value; if IsStarted then begin Stop; Start; end; end; end; procedure TTimer.Start; begin if not IsStarted then begin FillChar(FTimerSpec, SizeOf(FTimerSpec), 0); FTimerSpec.it_interval.tv_sec := FInterval div 1000; FTimerSpec.it_interval.tv_nsec := (FInterval mod 1000) * 1000000; FTimerSpec.it_value.tv_sec := FInterval div 1000; FTimerSpec.it_value.tv_nsec := (FInterval mod 1000) * 1000000; if timer_create(CLOCK_REALTIME, nil, @FTimerID) = 0 then begin if timer_settime(FTimerID, 0, @FTimerSpec, nil) = 0 then FIsStarted := True else timer_delete(FTimerID); end; end; end; procedure TTimer.Stop; begin if IsStarted then begin FTimerSpec.it_interval.tv_sec := 0; FTimerSpec.it_interval.tv_nsec := 0; FTimerSpec.it_value.tv_sec := 0; FTimerSpec.it_value.tv_nsec := 0; timer_settime(FTimerID, 0, @FTimerSpec, nil); timer_delete(FTimerID); FIsStarted := False; end; end; end. ``` 这是一个简单的封装,使用时只需要创建一个TTimer对象,并调用Start方法就可以启动一个定时器。在该代码中,使用了Linux API中的timer_create和timer_settime函数来实现定时器功能。同时,该封装使用了Delphi中的事件模型,通过设置OnTimer事件,在定时器到期时,会触发该事件,从而完成定时器任务。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值