在这篇博文中,我将解释如何使用 VxWorks 7 内核控制台(shell)读写硬件设备中的寄存器。对于任何一个开发设备驱动程序或其他直接与内存映射设备进行交付的代码的人来说,这是一个非常有用的功能。

VxWorks 6.9内核Shell控制台

如果您使用过 VxWorks 6.9,你可能常常使用内核 shell 访问目标硬件中的内存映射寄存器。 例如,你可以使用 d 命令(实际上是一个函数)转储映射到内存中已知基地址的一组寄存器:


-> d 0xffd02000, 8, 4
NOTE: memory values are displayed in hexadecimal.
0xffd02000: 00000001 000000ee 3fe449ea 00000000 *.........I.?....*
0xffd02010: 00000000 00000000 3130362a 00000000 *........*601....*
value = 0 = 0x0
->

以上命令可以转储从地质 0xffd02000 开始, 宽度为4个字节的8个寄存器。

或者使用 m 命令修改寄存器:


-> m ffd02000, 4
0xffd02000: 00000001-
0xffd02004: 000000ee-ff
0xffd02008: 3e63054e-.
value = 0 = 0x0
->

同样,上述命令中的4,指定了寄存器以字节为单位的宽度

你还可以使用如下指针语法来修改寄存器:


-> *0xffd02000 = 0
value = 0 = 0x0
->

顺便说一句,我在这些示例中访问的硬件是 Intel (Altera) Cyclone V 硬处理器系统 (HPS) 中的 L4 看门狗定时器。

VxWorks 7内核Shell控制台

如果你使用 VxWorks 7 内核 shell 来尝试同样的访问,你可能会惊讶地看到不一样的情况:


-> d 0xffd02000, 32, 4
NOTE: memory values are displayed in hexadecimal.
0xffd02000:
Data abort

Exception address: 0x003736a8
Current Processor Status Register: 0x60000013
Data Fault Address Register: 0xffd02000
Data Fault Status Register : 0x00000005
0x00358ea0 shellInterpCparse+0x1328: 0x0035f364 ()
0x0035f450 shellInterpClex+0x6074: shellInternalFunctionCall ()
0x00355854 shellInternalFunctionCall+0x88 : memoryDump ()

Shell task 'tShell0' restarted...

->

那里出了什么问题? 简而言之,读取设备寄存器的操作失败了,导致了异常,因为 VxWorks 7 以与 VxWorks 6.9 截然不同的方式来管理地址映射。

内存管理

要了解发生了什么,我们必须谈谈内存管理。 VxWorks 通常配置为使用集成到现代处理器(微控制器除外)中的内存管理单元来控制程序如何访问内存和其他内存映射硬件。 这意味着有两种地址:

  • 虚拟地址:软件用虚拟地址来初始化内存访问——本质上等同于 C 语言中的指针
  • 物理地址:硬件用于将“内存”访问定向到正确的硬件(内存、I/O 设备等)——这就是您将

在硬件文档的地址映射部分看到的内容

无需深入细节,当 VxWorks 运行时,它会设置并维护从虚拟地址到物理地址的映射。 维基百科的这张图给出了地址映射如何工作的一般图:

VxWorks Memory Management Unit

虽然该图不是特定于 VxWorks,但它显示了适用于 VxWorks 7 的地址映射的几个特性:

  • 虚拟地址的数值可能与它映射到的物理地址的数值不匹配(也就是说,它不是“恒等映射”或“一对一转换”)
  • 可能存在不映射到任何物理地址的虚拟地址空间区域
  • 可能存在未被任何虚拟地址映射的物理地址空间区域

VxWorks 6.9 和 VxWorks 7 之间的一个关键区别是大多数虚拟地址到物理地址的映射不是“一对一”的,因此通常虚拟地址在数值上与它们映射的物理地址不同。

虚拟内存上下文

如果你想查看内核设置的地址映射,可以通过从内核 shell 控制台调用函数 vmContextShow 来实现:


-> vmContextShow
VIRTUAL ADDR BLOCK LENGTH PHYSICAL ADDR PROT (S/U) CACHE SPECIAL
------------ ------------ ------------- ---------- -------- ------------
0x00000000 0x00050000 0x00000000 RW- / --- CB-/CO/- --/S3
0x00050000 0x00001000 0x00050000 R-X / --- CB-/CO/- --/S3
0x00051000 0x000af000 0x00051000 RW- / --- CB-/CO/- --/S3
0x00100000 0x00418000 0x00100000 R-X / --- CB-/CO/- --
...
0x00943000 0x00001000 0x00943000 R-- / --- CB-/CO/- --/S3
0x00944000 0x00cb9000 0x00944000 RW- / --- CB-/CO/- --/S3
0x20000000 0x00008000 0x015fd000 RW- / --- CB-/CO/- --/S3
0x20008000 0x00002000 0x01605000 RWX / --- CB-/CO/- --/S3
0x2000a000 0x0006f000 0x01607000 RW- / --- CB-/CO/- --/S3
0x20079000 0x00002000 0x01676000 RWX / --- OFF/CO/G --
0x2007b000 0x01f85000 0x01678000 RW- / --- CB-/CO/- --/S3
0x22000000 0x00004000 0xffd08000 RW- / --- OFF/CO/G --
0x22004000 0x00001000 0xffd05000 RW- / --- OFF/CO/G --
0x22005000 0x00001000 0xff708000 RW- / --- OFF/CO/G --
0x22006000 0x00002000 0xffc03000 RW- / --- OFF/CO/G --
0x22008000 0x00001000 0xffd05000 RW- / --- OFF/CO/G --
0x22009000 0x00001000 0xffd04000 RW- / --- OFF/CO/G --
0x2200a000 0x00002000 0xfffec000 RW- / --- OFF/CO/G --
0x2200c000 0x00001000 0xfffec000 RW- / --- OFF/CO/G --
0x2200d000 0x00001000 0xfffef000 RW- / --- OFF/CO/G --
0x2200e000 0x00002000 0xffc08000 RW- / --- OFF/CO/G --
...
0x22d32000 0x00002000 0x040c3000 RW- / --- CB-/CO/- --/S3
0x22d35000 0x00001000 0x040c6000 RW- / --- CB-/CO/- --/S3
0x22d37000 0x00002000 0x040c8000 RW- / --- CB-/CO/- --/S3
0x22d3a000 0x00001000 0x040cb000 RW- / --- CB-/CO/- --/S3
0x22d3c000 0x00002000 0x040cd000 RW- / --- CB-/CO/- --/S3
0x22d3f000 0x00f01000 0x040d0000 RW- / --- CB-/CO/- --/S3
value = 0 = 0x0
->

此函数显示整个虚拟内存上下文,通常会产生多行输出,其中大部分是我从该输出中截取的。 它只显示有效的虚拟地址区域,也就是说,映射到物理地址。

你可以看到这个命令输出按虚拟地址递增的顺序排序,你可以发现我们刚才在寄存器转储的操作中使用的虚拟地址 (0xffd02000) 未映射到任何物理地址。 这正是我们得到数据中止异常的原因:该虚拟地址不访问任何硬件。

你还可以看到,我们感兴趣的硬件附近有一些物理地址空间区域被映射到虚拟地址空间。 例如,虚拟地址 0x22008000 映射到物理地址 0xffd05000,它恰好是 Cyclone V HPS Reset Manager。

如果你想知道为什么某些硬件设备映射到了虚拟地址空间而其他硬件设备不被映射,简短的回答,这是由设备树定义的,它描述了硬件并指定了它在物理地址空间中的布局方式。 当 VxWorks 7 启动时,它会解析设备树并找到硬件设备的驱动程序。 当这些驱动程序被初始化时,它们会映射与其设备关联的物理内存区域。

手动映射

也可以在操作系统启动后映射物理地址空间区域。 有几种方法可以做到这一点,但我发现最方便的是使用带有三个参数的函数 pmapGlobalMap :

--- 要映射的区域的物理地址 --- 要映射的区域的字节长度 -- 该区域应该映射的属性 pmapGlobalMap 返回映射的虚拟地址。 在继续之前,这里有一个例子:


-> l4wd0 = pmapGlobalMap (0xffd02000ULL, 0x1000, 0x483)
New symbol "l4wd0" added to kernel symbol table.
l4wd0 = 0x22bea338: value = 579825664 = 0x228f7000
->

物理地址是你可以在硬件数据表中找到的内容,以及你在 VxWorks 6.9 中使用的内容:例如,要访问 Cyclone V HPS L4 看门狗定时器,它将是 0xffd02000。

请注意,VxWorks 使用 64 位表示物理地址,因此为了确保 shell 将 64 位值传递给 pmapGlobalMap,我添加了 ULL 后缀。 有时你可以不用它就可以逃脱,但是添加它可以确保不会出现任何意外。

长度是你感兴趣的一组寄存器所需要被映射的任何长度。(VxWorks 将默默地四舍五入 tje 长度,以便映射至少是一个虚拟内存页面,通常为 4 KiB。)

这些属性将定义您可以通过虚拟地址使用的访问类型。 要访问硬件寄存器,你通常需要读写访问权限,但你可能还需要一些额外的属性来确保您的访问绕过缓存。

通过在 VxWorks vmLibCommon.h 中找到 MMU_ATTR_* 定义,我设定了属性值 0x483。 分解一下:0x3 提供读/写访问权限,0x80 使访问不可缓存,0x400 使访问受保护以保留访问顺序(在 ARM 架构上)。

在示例中,我还将返回的虚拟地址存储在新变量 l4wd0 中。 这不是必需的,它只是在引用虚拟地址时更方便。<

最终结果

所以,最后,我们可以从 VxWorks 7 内核 shell 中转储 HPS L4 看门狗寄存器:


-> d l4wd0, 32, 4
NOTE: memory values are displayed in hexadecimal.
0x228f7000: 00000001 000000ff 7b02c5be 00000000 *...........{....*
0x228f7010: 00000000 00000000 3130362a 00000000 *........*601....*
0x228f7020: 00000000 00000000 00000000 00000000 *................*
0x228f7030: 00000000 00000000 00000000 00000000 *................*
0x228f7040: 00000000 00000000 00000000 00000000 *................*
0x228f7050: 00000000 00000000 00000000 00000000 *................*
0x228f7060: 00000000 00000000 00000000 00000000 *................*
0x228f7070: 00000000 00000000 00000000 00000000 *................*
value = 0 = 0x0
->

你还可以确认映射显示在 vmContextShow 的输出中:


-> vmContextShow
VIRTUAL ADDR BLOCK LENGTH PHYSICAL ADDR PROT (S/U) CACHE SPECIAL
------------ ------------ ------------- ---------- -------- ------------
0x00000000 0x00050000 0x00000000 RW- / --- CB-/CO/- --/S3
0x00050000 0x00001000 0x00050000 R-X / --- CB-/CO/- --/S3
0x00051000 0x000af000 0x00051000 RW- / --- CB-/CO/- --/S3
0x00100000 0x00418000 0x00100000 R-X / --- CB-/CO/- --
...
0x228f7000 0x00001000 0xffd02000 RW- / --- OFF/CO/G --
...
0x22d32000 0x00002000 0x040c3000 RW- / --- CB-/CO/- --/S3
0x22d35000 0x00001000 0x040c6000 RW- / --- CB-/CO/- --/S3
0x22d37000 0x00002000 0x040c8000 RW- / --- CB-/CO/- --/S3
0x22d3a000 0x00001000 0x040cb000 RW- / --- CB-/CO/- --/S3
0x22d3c000 0x00002000 0x040cd000 RW- / --- CB-/CO/- --/S3
0x22d3f000 0x00f01000 0x040d0000 RW- / --- CB-/CO/- --/S3
value = 0 = 0x0
->

结论

虽然这看起来是一个冗长的过程,但实际上只有两个步骤:

  • 使用 pmapGlobalMap 映射硬件以获得虚拟地址
  • 使用与 VxWorks 6.9 相同的方法访问它,但使用的是新的虚拟地址

我希望这对你有所帮助,如果你希望我在以后的博文中涵盖任何其他主题,请告诉我。