0%

MIPS 杂项

MIPS 相关理解

协处理器

在MIPS体系结构中,最多支持4个协处理器(Co-Processor)。其中,协处理器CP0是体系结构中必须实现的。它起到控制CPU的作用。MMU、异常处理、乘除法等功能,都依赖于协处理器CP0来实现。它是MIPS的精髓之一,也是打开MIPS特权级模式的大门。

   - Register 0: Index,作为MMU的索引用。将来讨论MMU和TLB时会详解之。
   - Register 2, EntryLo0,访问TLB Entry偶数页中的地址低32Bit用。同上,在MMU和TLB的相关章节中详解。
   - Register 3, EntryLo1,访问TLB Entry奇数页中的地址低32Bit用。
   - Register 4, Context,用以加速TLB Miss异常的处理。
   - Register 5, PageMask,用以在MMU中分配可变大小的内存页。
   - Register 8, BadVAddr,在系统捕获到TLB Miss或Address Error这两种Exception时,发生错误的虚拟地址会储存在该寄存器中。对于引发Exception的Bug的定位来说,这个寄存器非常重要。
   - `Register 9`, Count,这个寄存器是R4000以后的MIPS系统引入的。它是一个计数器,`计数频率是系统主频的1/2`。BCM1125/1250,RMI XLR系列以及Octeon的Cavium处理器均支持该寄存器。对于操作系统来说,`可以通过读取该寄存器的值来获取tick的时基`。`在系统性能测试中,利用该寄存器也可以实现打点计数`。
   - Register 10,EntryHi,这个寄存器同EntryLo0/1一样,用于MMU中。以后会详述。
   - Register 11,Compare,配合Count使用。当Compare和Count的值相等的时候,会触发一个硬件中断(Hardware Interrupt),并且总是使用Cause寄存器的IP7位。  
   - Register 12,Status,用于处理器状态的控制。
   - Register 13,Cause,这个寄存器体现了处理器异常发生的原因。
   -   Register 14,EPC,这个寄存器存放异常发生时,系统正在执行的指令的地址。
   -   Register 15,PRID,这个寄存器是只读的,标识处理器的版本信息。向其中写入无意义。
  •   Register 18/19,WatchLo/WatchHi,这对寄存器用于设置硬件数据断点(Hardware Data Breakpoint)。该断点一旦设定,当CPU存取这个地址时,系统就会发生一个异常。这个功能广泛应用于调试定位内存写坏的错误。
      Register 28/29,TagLo和TagHi,用于高速缓存(Cache)管理。

相关链接 协处理器CP0

常用汇编指令

MFC0、MTC0、DMFC0、DMTC0可以完成通用寄存器和CP0寄存器之间的数据传送。比如我们可以通过下面指令来获取epc寄存器的值。

             mfc0 t1,$14           # 从CP0 取epc寄存器($14)数据,存到通用寄存器t0

mfc0完成从cp0寄存器获取一个32位数据(如果cp0寄存器是64位数据则只取其低32位。)到通用寄存器,如果要获取的是64位数据则需要使用dmfc0指令。

注意:和CP0相关的指令执行都需要特权模式,比如上面的mfc0指令的运行需要root权限或者sudo才可以运行。所以在一般用户程序中很少见到这些CP0相关的控制指令。

相关理解总结在 linux 中链接脚本 ld 文件详解

这里总结下用到的大概率用到的知识点:

LMA VMA

目标文件的每个 section 至少包含两个信息: 名字和大小. 大部分 section 还包含与它相关联的一块数据, 称为 section contents(section 内容). 一个 section 可被标记为 “loadable(可加载的)” 或“allocatable(可分配的)”.

  • loadable section: 在输出文件运行时, 相应的 section 内容将被载入进程地址空间中.

  • allocatable section: 内容为空的 section 可被标记为 “可分配的”. 在输出文件运行时, 在进程地址空间中空出大小同 section 指定大小的部分. 某些情况下, 这块内存必须被置零.

如果一个 section 不是 “可加载的” 或“可分配的”, 那么该 section 通常包含了调试信息. 可用 objdump -h 命令查看相关信息.

每个 “可加载的” 或“可分配的”输出 section 通常包含两个地址: VMA(virtual memory address 虚拟内存地址或程序地址空间地址)和 LMA(load memory address 加载内存地址或进程地址空间地址). 通常 VMA 和 LMA 是相同的.

在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的 flash 中 (由 LMA 指定), 而在运行时将位于 flash 中的输出文件复制到 SDRAM 中 (由 VMA 指定).

LMA是加载地址(镜像执行之前位于存储器中的地址, 称为加载时地址), 需要将该地址的数据拷贝到对应的VMA段下. VMA是实际映射到内存中的地址, 可以通过map文件查看

-M 或者 -Map 指令指定输出到map文件, map 应该类似于进程执行时 map 内存映射 (刨除基地址)

定位符号 .

. 是一个特殊的符号,它是定位器,一个位置指针, 指向程序地址空间内的某位置 (如果它在 SECTIONS 命令内的某 section 描述内, 指的是在该section 内的偏移)

该符号只能在 SECTIONS 命令内使用 .始终指的是运行时的地址 VMA, 与LMA无关

SECTIONS 命令

SECTIONS 命令告诉 ld 如何把输入文件的 sections 映射到输出文件的各个 section: 如何将输入 section 合为输出 section; 如何把输出 section 放入程序地址空间 (VMA) 和进程地址空间(LMA). 该命令格式如下:

1
2
3
4
5
6
SECTIONS
{
SECTIONS-COMMAND
SECTIONS-COMMAND

}

SECTION-COMMAND 有四种:

  • (1) ENTRY 命令

  • (2) 符号赋值语句

  • (3) 一个输出 section 的描述 (output section description)

  • (4) 一个 section 叠加描述 (overlay description)

如果整个连接脚本内没有 SECTIONS 命令, 那么 ld 将所有同名输入 section 合成为一个输出 section 内, 各输入 section 的顺序为它们被连接器发现的顺序.

如果某输入 section 没有在 SECTIONS 命令中提到, 那么该 section 将被直接拷贝成输出 section。

输出 section 描述

输出 section 描述具有如下格式:

1
2
3
4
5
6
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND

} [>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP]

[ ] 内的内容为可选选项, 一般不需要.

每个OUTPUT-SECTION-COMMAND为以下四种之一,

  • 符号赋值语句
  • 一个输入 section 描述
  • 直接包含的数据值
  • 一个特殊的输出 section 关键字
前面部分 SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
输出 section 名字 (SECTION)

输出 section 名字必须符合输出文件格式要求,比如:a.out 格式的文件只允许存在. text、.data 和. bss section 名。而有的格式只允许存在数字名字,那么此时应该用引号将所有名字内的数字组合在一起;另外,还有一些格式允许任何序列的字符存在于 section 名字内,此时如果名字内包含特殊字符 (比如空格、逗号等),那么需要用引号将其组合在一起。

输出 section 地址 (ADDRESS)

ADDRESS 是一个表达式,它的值用于设置 VMA

  • 如果没有该选项且有 REGION 选项,那么连接器将根据 REGION 设置 VMA;

  • 如果也没有 REGION 选项,那么连接器将根据定位符号 . 的值设置该 section 的 VMA,将定位符号的值调整到满足输出 section 对齐要求后的值,

输出 section 的对齐要求为:该输出 section 描述内用到的所有输入 section 的对齐要求中最严格的。

例子:

1
2
1   .text . : {*(.text) }
2 .text : {*(.text) }

第一个 . 定义了输出VMA的地址,说明是根据当前的定位指针的偏移设置 VMA的

第二个 没有定义 输出DMA,也没有REGION, 则根据定位符号 . 对齐后的值 输出 VMA

ADDRESS 可以是一个任意表达式,比如 ALIGN(0×10) 这将把该 section 的 VMA 设置成定位符号的修调值,满足 16 字节对齐后的。

注意:设置 ADDRESS 值,将更改定位符号的值。

TYPE

每个输出 section 都有一个类型,如果没有指定 TYPE 类型,那么连接器根据输出 section 引用的输入 section 的类型设置该输出 section 的类型。

它可以为以下五种值,

  1. NOLOAD :该 section 在程序运行时,不被载入内存。
  2. DSECT
  3. COPY
  4. INFO
  5. OVERLAY 支持这些类型名称以实现向后兼容,并且很少使用。 它们都具有相同的效果:该段应标记为不可分配,以便在程序运行时不为该段分配内存
[AT(LMA)]

默认情况下,LMA 等于 VMA,但可以通过关键字 AT() 指定 LMA。

用关键字 AT() 指定,括号内包含表达式,表达式的值用于设置 LMA。如果不用 AT() 关键字,那么可用 AT>LMA_REGION 表达式设置指定该 section 加载地址的范围。 这个属性主要用于构件 ROM 境象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SECTIONS
{
.text 0×1000 :
{
*(.text);
_etext = . ;
}
.mdata 0×2000 : AT (ADDR (.text) + SIZEOF (.text) )
{
_data = . ;
*(.data);
_edata = . ;
}
.bss 0×3000 :
{
bstart = . ;
*(.bss) *(COMMON) ;
_bend = . ;
}
}
1
2
3
4
5
6
7
8
extern char etext, data, edata, bstart, _bend;
char *src = &_etext;
char *dst = &_data;
while (dst < &_edata) {
dst++ = src++;
}
for (dst = &bstart; dst< &_bend; dst++)
*dst = 0;

此程序将处于 ROM内的已初始化数据拷贝到 mdata段的相应区域,并将未初始化数据置零

中间部分 OUTPUT-SECTION-COMMAND

每个OUTPUT-SECTION-COMMAND为以下四种之一,

  • 符号赋值语句

  • 一个输入 section 描述

  • 直接包含的数据值

  • 一个特殊的输出 section 关键字
输入 section 描述

最常见的输出 section 描述命令是输入 section 描述。

输入 section 描述是最基本的连接脚本描述。

语法 FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)

  • FILENAME 文件名,可以是一个特定的文件的名字,也可以是一个字符串模式。

  • SECTION 名字,可以是一个特定的 section 名字,也可以是一个字符串模式

    例子:

    • *(.text) :表示所有输入文件的. text section

    • (*(EXCLUDE_FILE ( *crtend.o *otherfile.o) .ctors)) :表示除 crtend.o、otherfile.o 文件外的所有输入文件的. ctors section。

    • data.o(.data) :表示 data.o 文件的. data section

    • data.o :表示 data.o 文件的所有 section

    • *(.text .data) :表示所有文件的. text section 和. data section,顺序是:第一个文件的. text section,第一个文件的. data section, 第二个文件的. text section,第二个文件的. data section,…

    • *(.text) * (.data) :表示所有文件的. text section 和. data section,顺序是:第一个文件的. text section,第二个文件的. text section,…,最后一个文件的. text section,第一个文件的. data section,第二个文件的. data section,…,最后一个文件的. data section

前面部分 SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
后半部分 [>REGION] [AT>LMA_REGION] [:PHDR :FILEHDR …] [=FILLEXP]
[>REGION]

可以将输出 section 放入预先定义的内存区域内

1
2
MEMORY { rom : ORIGIN = 0×1000, LENGTH = 0×1000 }
SECTIONS {ROM : { *(.text) } >rom }

这个地方应该是放在VMA中, 注意这里输出到 预定的内存区域后, 连带会把 . 改为 预定内存区域的地址(第一次出现), 然后执行section 内的语句

ld脚本section 是按顺序解析的, 第一次出现时 会将 .定界, 后面的section 如果都放在和该section相同的内存区域时, . 会累加 section的size.

[:PHDR :FILEHDR … ] 这里显示的是段名

不需要深究, 详细见https://sourceware.org/binutils/docs/ld/PHDRS.html

linker脚本中的PHDRS命令需要与SECTIONS命令结合起来看,否则不容易理解.

1
2
3
4
5
6
7
8
9
10
PHDRS
{
text PT_LOAD FILEHDR PHDRS ;
data PT_LOAD ;
}
SECTIONS
{
.text : { *(.text) } :text // :text 是 PHDRS 里定义的段名
.data : { *(.data) } :data // :data 是 PHDRS 里定义的段名
}

ld以该脚本对输入文件进行链接,输出文件中将会包含两个section:.text和.data
以SECTIONS命令中的第一行为例: .text : { *(.text) } :text
这句话是说将输入文件中所有的.text section (中间的 *(.text)) 都链接到输出文件的.text section中.
并将输出文件中的.text section分配到PHDRS命令中定义的名为text的segment中.

PHDRS命令中,
text PT_LOAD FILEHDR PHDRS ;
该语句定义了名为text的一个segment
它的属性为PT_LOAD, 表示它是一个可以被加载的segment
FILEHDRPHDRS是它的追加属性,分别表示在这个segment中必须包含ELF文件头必须包含ELF程序文件头

You may use the FILEHDR and PHDRS keywords after the program header type to further describe the contents of the segment. The FILEHDR keyword means that the segment should include the ELF file header. The PHDRS keyword means that the segment should include the ELF program headers themselves. If applied to a loadable segment (PT_LOAD), all prior loadable segments must have one of these keywords.

The ELF object file format uses program headers, also knows as segments. The program headers describe how the program should be loaded into memory. You can print them out by using the objdump -p

The linker will create reasonable program headers by default. However, in some cases, you may need to specify the program headers more precisely. You may use the PHDRS command for this purpose. When the linker sees the PHDRS command in the linker script, it will not create any program headers other than the ones specified.

[=FILLEXP] 这里显示的是段名

= FILLEXP 属性作用于整个输出 section 区域

在当前输出 section 内可能存在未描述的存储区域 (比如由于对齐造成的空隙),可以用 FILL(EXPRESSION) 命令决定这些存储区域的内容

它的作用如同 FILL()命令,但是 FILL 命令只作用于该 FILL 指令之后的 section 区域,而 = FILLEXP 属性作用于整个输出 section 区域,且 FILL 命令的优先级更高!!!

1
SECTIONS { .text : { *(.text) } =0x90909090 } // 此处=0x90909090 为 [=FILLEXP]

(for example, gaps left due to the required alignment of input sections) will be filled with the value, repeated as necessary. If the fill expression is a simple hex number, ie. a string of hex digit starting with ‘0x’ and without a trailing ‘k’ or ‘M’, then an arbitrarily long sequence of hex digits can be used to specify the fill pattern; Leading zeros become part of the pattern too. For all other cases, including extra parentheses or a unary +, the fill pattern is the four least significant bytes of the value of the expression.

In all cases, the number is big-endian. 见 https://sourceware.org/binutils/docs/ld/Output-Section-Fill.html

输入 section 和垃圾回收:

在连接命令行内使用了选项–gc-sections 后,连接器可能将某些它认为没用的 section 过滤掉,此时就有必要强制连接器保留一些特定的 section,可用 KEEP() 关键字达此目的。如 KEEP((.text)) 或 KEEP(SORT()(.text))

硬件寄存器

即使有tlb mmu的情况下, 硬件寄存器地址所在的地址段 就是真实的物理地址, 是一一映射的

这里注意的一点是 地址的快速运算, 看代码时, 利用gdb 将地址寄存器的内容快速打出, 不需要频繁加log.

DMA

流控制器

决定DMA块传输长度并终止它的设备被称为流控制器(DW_ahb_dmac或源/目标外围设备)

  • 如果在发起传输前就知道传输长度,则dma自己是作为流控制器
  • 传送长度未知时,源外设或目标外设是流控制器模式。外设需要在流程中主动发起结束请求, 这种情况下只能选择软件握手。
  • 内存不可能是流控制器, 仅限能够发出传输结束信号的外设支持此功能

源、目标和传输模式

存储器到外设模式

FIFO 模式

这种模式,只要使能数据流(DMA_SxCR 寄存器中的位 EN 置 1),存储器数据就会传输到 FIFO 中,发生外设请求,FIFO 数据会移出并存储到目标地址。

当 FIFO 小于阈值,存储器的数据会重新装载 FIFO

提高带宽的特殊模式。当启用时,通道会等到目标设备的FIFO小于半满时才从源外设获取数据,等到源设备的FIFO大于或等于半满时才会向目的外设发送数据。正因为如此,通道可以使用突发方式传输数据,这样就不需要在每一次AHB传输中对AHB主接口进行仲裁。当不启用该模式时,通道只等待FIFO可以传输或接受单次AHB传输后才会请求主总线接口。

直连模式

使能数据流时,DMA 传输存储器的第一个数据到内部 FIFO,发生外设请求时,DMA 把预装在值发送的目标地址,然后进行下一个数据的传输。预装载的数据大小为 DMA_SxCR 寄存器中 PSIZE 位字段的值

传输停止条件,下列满足一条即可:

  • DMA_SxNDTR block_ts 寄存器达到零
  • 外设请求传输终止
  • DMA_SxCR 寄存器中的 EN chen 位由软件清零

存储器到外设模式和外设到存储器模式一样,同样需要对应数据流赢得仲裁,才会启动传输

握手接口

DEST_PER

HS_SEL_DST字段为0时(即配置的硬件仲裁), 分配一个硬件握手接口(0 - 15)到通道n的目的地。如配置为1(软件仲裁), 该字段将被忽略。

然后,通道可以通过指定的硬件握手接口与连接到该接口的目标外围设备通信。

为了正确的DMA操作,只有一个外围设备(源或目标)应该被分配到同一个握手接口。

SRC_PER

同上, 与HS_SEL_SRC字段配合

? 对于外设来说, 握手接口的类型取决与其是否是流控制器, 如果是流控制器, 是软件握手接口; 不是流控制器, 即在发起dma的传输之前, 已经知道了传输大小, 需要将大小配置给dma 控制器, 此时外设只能选择硬件握手接口. ? XXXXXX 理解错误

参考dma 设计pdf 3.8 章节 3.8.1 3.8.2

这个地方有疑问, 如果外设不是流控制器,又配置的软件握手, 需要另外的软件逻辑同步外设的状态与dma的传输状态是一致的, 即额外的软件握手通信的流程。否则只配置软件握手, 是无法正常work的。