函数重定位:基于I.MX6ULL

前言

基于 100ask_imx6ull_pro 开发板

使用 100ASK_IMX6ULL_Flashing_tool 工具进行程序烧写

参考资料为《嵌入式 Linux 应用开发完全手册(第 2 版)》 -> 裸机开发 -> 重定位部分

手册中已经介绍了如何重定位 data 数据段以及全部代码的示例。

因此,本文不再赘述这些基础知识,而是专注于手册中未提到的内容:如何实现函数的重定位

为什么需要函数重定位

  1. 由于 MCU 的 SRAM 资源有限,代码通常直接从 norFlash 中读取并运行

    然而,在执行 OTA 升级时,需要对 Flash 进行擦写操作,而 Flash 通常不支持 RWW(Read While Write)

    为了解决这一冲突,需要将擦写 Flash 的函数搬运到 SRAM 中执行

  2. 由于 SRAM 速度更快,可以将某些热点函数搬运至 SRAM 中运行,以提高程序的执行效率。

启动分析

在 IMX6ULL 芯片手册中,我们了解到,芯片在上电时,内部固化的 boot ROM 程序会通过外部引脚确定启动方式(如 USB、NAND、eMMC、SD 等)。接着,boot ROM 通过相应的启动接口获取所需的信息,例如如何初始化 DDR、如何搬运程序、以及将程序搬运到哪个内存位置等。随后,boot ROM 会自动将应用的二进制数据(如 app.bin​)从存储介质(如 NAND、eMMC、SD 等)搬运到 DDR 的指定位置,并在该位置开始执行程序。

整个过程由芯片自动完成,但需要提前生成并烧录到存储介质的映像文件。首先,通过编译得到应用的二进制文件 app.bin​(这可以是裸机应用固件或 Linux 固件等)。然后,使用 mkimage​ 工具(该工具附带在 gcc-arm-linux-gnueabihf-6.2.1​ 编译器中)根据 imximage.cfg.cfgtmp​ 文件中的信息生成头部信息,再将头部信息与 app.bin​ 组合生成 .imx​ 文件。

如果使用 SD 卡启动,则根据下表所示,.imx​ 文件的头部还需要添加 1KB 的偏移量,生成 .img​ 文件,才能将其烧录到 SD 卡中。

请添加图片描述

由于本次实验采用的是 USB 启动,因此不需要添加偏移量。

代码示例

为了程序简化,便于分析,以下只保留必要的代码。通过对延时函数 delay 的重定位与否来观察 LED 的闪烁频率变化

工程结构

│   ├── tools
│   │   ├── 1k.bin
│   │   ├── imx6ull.lds
│   │   ├── imximage.cfg.cfgtmp
│   ├── main.c
│   ├── main.dis
│   ├── main.imx
│   ├── Makefile
│   ├── start.S

启动文件 start.S

这里只设置了 堆栈指针,为 C 语言提供运行环境,随后跳转到 main 函数

.text
.align 2      
.global _start 

_start:
	ldr  sp,=0x80100000
	bl main

主程序文件 main.c

delay​ 函数通过大量循环来实现延时。函数前的 __attribute__((section(".delay")))​ 修饰符将 delay​ 函数归入 .delay​ 段,使其与 .text​、.data​ 等段并列,供链接脚本使用。

copy_data​ 函数负责将 delay​ 函数的代码段从加载地址(存储地址)复制到链接地址(运行地址),其中 _delay_start_addr​ 等符号是在链接脚本中定义的。

led_init​ 函数用于初始化 LED,其具体实现可以参考手册的相关章节。在主循环中,通过不断改变 LED 的亮灭状态,可以观察 delay​ 函数的运行快慢。

#define CCM_CCGR1 (volatile unsigned long *)0x20C406C
#define IOMUXC_SW_MUX_CTL_PAD_GPIO5_IO03 (volatile unsigned long *)0x2290014
#define IOMUXC_SW_PAD_CTL_PAD_GPIO5_IO03 (volatile unsigned long *)0x2290058
#define GPIO5_GDIR (volatile unsigned long *)0x020AC004
#define GPIO5_DR (volatile unsigned long *)0x020AC000

#define uint32_t unsigned int

void copy_data();
void led_init();
void __attribute__((section(".delay"))) delay(uint32_t second);

int main()
{

    copy_data();
    led_init();

    while (1)
    {
        *(GPIO5_DR) = 0x0;
        delay(50);
        *(GPIO5_DR) = (1 << 3);
        delay(50);
    }
    return 0;
}

void led_init()
{
    *(CCM_CCGR1) = 0xFFFFFFFF;                     // 开启GPIO1的时钟
    *(IOMUXC_SW_MUX_CTL_PAD_GPIO5_IO03) = 0x5;     // 设置PAD复用功能为GPIO
    *(IOMUXC_SW_PAD_CTL_PAD_GPIO5_IO03) = 0x1F838; // 设置PAD属性
    *(GPIO5_GDIR) = (1 << 3);                      // 设置GPIO为输出模式
    *(GPIO5_DR) = 0x0;                             // 设置输出电平为低电平
}

void __attribute__((section(".delay"))) delay(uint32_t second)
{
    uint32_t count = 0xFFFFF * second;
    volatile uint32_t i = 0;
    for (i = 0; i < count; ++i)
    {
        __asm("NOP"); /* 调用nop空指令 */
    }
}

void copy_data()
{
    extern unsigned long _delay_start_addr;
    extern unsigned long _delay_end_addr;
    extern unsigned long _delay_at_start_addr;
    unsigned long *src, *dst;

    src = &_delay_at_start_addr;
    dst = &_delay_start_addr;
    while (dst < &_delay_end_addr)
    {
        *dst++ = *src++;
    }
}

链接脚本 imx6ull.lds

请添加图片描述

IMX6ULL 具有 128KB 的片内 RAM,在本次实验中,我们将 delay​ 函数从 DDR 搬运到 OCRAM。

_delay_at_start_addr​ 保存的是延迟段的加载地址(存储地址),即搬运操作的起始位置。

.delay 0x00900000:
.delay​ 是一个自定义的段名。在 main​ 文件中,我们将 delay​ 函数分配到了 .delay​ 段。因此,编译后 delay​ 函数会被放入 .delay​ 段中。0x00900000​ 是该段在内存中的运行时地址。程序正常执行时,只有当这段代码或数据放置在此地址处才会运行正确。

AT(_delay_at_start_addr):
AT​ 是一个关键字,用于指定段的加载地址。加载地址是指程序被加载到内存时该段将被放置的位置。_delay_at_start_addr​ 是一个变量,表示 .delay​ 段的加载地址,定义了该段在程序加载到内存时应该被放置的位置。

SECTIONS {
    . = 0x80100000;  /* 起始地址 */

    . = ALIGN(4);
    .text : 
    {
        *(.text)  /* 主程序代码 */
    }

    . = ALIGN(4);
    .data : 
    {
        *(.data)  /* 已初始化的数据 */
    }

    . = ALIGN(4);
    .bss : 
    {
        *(.bss)  /* 未初始化的数据 */
    }

    . = ALIGN(4);
    _delay_at_start_addr = .;  /* 记录延迟段的装载地址 */
  
    .delay 0x00900000 : AT(_delay_at_start_addr) 
    {
        _delay_start_addr = .;  /* 延迟段的起始地址 */
        *(.delay)               /* 延迟段内容 */
        _delay_end_addr = .;    /* 延迟段的结束地址 */
    }
}

Makefile

mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d main.bin main.imx

这一条命令使用 mkimage ​工具,将 boot ROM ​所需的信息与 main.bin ​结合起来生成符合 imx6ull ​启动规范的 imx ​文件

其中 -e 0x80100000 ​参数指示 boot ROM ​将 main.bin ​的内容搬运至 DDR ​的 0x80100000 ​地址处,同时 CPU ​从该地址处开始运行用户程序

PREFIX=arm-linux-gnueabihf-
CC=$(PREFIX)gcc
LD=$(PREFIX)ld
AR=$(PREFIX)ar
OBJCOPY=$(PREFIX)objcopy
OBJDUMP=$(PREFIX)objdump

main.imx : main.c start.S ./tools/imx6ull.lds
	$(CC) -nostdlib -g -c -o start.o start.S
	$(CC) -nostdlib -g -c -o main.o main.c

	$(LD) -T ./tools/imx6ull.lds -g start.o main.o -o main.elf 

	$(OBJCOPY) -O binary -S main.elf  main.bin
	$(OBJCOPY) -O ihex -S main.elf main.hex
	$(OBJDUMP) -D -m arm  main.elf  > main.dis

	mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d main.bin main.imx
	rm -f *.o main.elf main.bin

clean:
	rm -f main.dis main.imx main.hex

调试分析

在工程文件准备完毕后,执行 make​ 命令生成了 main.dis​、main.imx​ 和 main.hex​ 文件。

  • main.imx​ 是目标文件,通过 USB 启动方式下载到开发板后可进行测试运行。
  • main.hex​ 文件包含程序的二进制数据及地址信息。使用工具查看该文件,可以发现段与段之间的存储地址是连续的。

请添加图片描述

  • main.dis​ 是反汇编文件,以下我们将截取关键部分进行分析:

程序的第一条指令位于 0x80100000​,对应于 start.S​ 中定义的 _start​ 函数。设置好堆栈后,程序跳转到 main​ 函数。

main​ 函数中,首先执行了 copy_data​ 函数,跳转至 0x8010008c​ 处。这里需要注意的是,在函数重定位之前只能执行地址无关码(即编译成汇编代码后不依赖绝对地址的指令,因此需要使用地址无关码进行重定位搬运操作)

通过 copy_data​ 函数,我们可以得知:

  • _delay_start_addr​ 的地址为 0x90000000
  • _delay_end_addr​ 的地址为 0x90000038
  • _delay_at_start_addr​ 的地址为 0x8010e0

也就是说,.delay​ 段的长度为 0x38​。我们的目标是将 .delay​ 段的代码搬运至 OCRAM 的 0x90000000-0x90000038​ 区域,这正是 copy_data​ 函数所完成的任务。

请添加图片描述

接下来是 LED 的初始化,通过设置 DR 寄存器来控制输出电平的高低,从而实现 LED 的亮灭。

main​ 函数调用 delay​ 函数时,程序首先跳转到 __delay_veneer​,再从 __delay_veneer​ 跳转至 0x00900000​ 处执行搬运后的 delay​ 函数。

如果没有进行搬运操作,当程序运行到此处时会跑飞出错。

请添加图片描述
请添加图片描述
请添加图片描述

main.elf:     file format elf32-littlearm


Disassembly of section .text:

80100000 <_start>:
80100000:	e51fd000 	ldr	sp, [pc, #-0]	; 80100008 <_start+0x8>
80100004:	fa000000 	blx	8010000c <main>
80100008:	80100000 	andshi	r0, r0, r0

8010000c <main>:
8010000c:	b580      	push	{r7, lr}
8010000e:	af00      	add	r7, sp, #0
80100010:	f000 f83c 	bl	8010008c <copy_data>
80100014:	f000 f813 	bl	8010003e <led_init>
80100018:	f44f 4340 	mov.w	r3, #49152	; 0xc000
8010001c:	f2c0 230a 	movt	r3, #522	; 0x20a
80100020:	2200      	movs	r2, #0
80100022:	601a      	str	r2, [r3, #0]
80100024:	2032      	movs	r0, #50	; 0x32
80100026:	f000 e858 	blx	801000d8 <__delay_veneer>
8010002a:	f44f 4340 	mov.w	r3, #49152	; 0xc000
8010002e:	f2c0 230a 	movt	r3, #522	; 0x20a
80100032:	2208      	movs	r2, #8
80100034:	601a      	str	r2, [r3, #0]
80100036:	2032      	movs	r0, #50	; 0x32
80100038:	f000 e84e 	blx	801000d8 <__delay_veneer>
8010003c:	e7ec      	b.n	80100018 <main+0xc>

8010003e <led_init>:
8010003e:	b480      	push	{r7}
80100040:	af00      	add	r7, sp, #0
80100042:	f244 036c 	movw	r3, #16492	; 0x406c
80100046:	f2c0 230c 	movt	r3, #524	; 0x20c
8010004a:	f04f 32ff 	mov.w	r2, #4294967295
8010004e:	601a      	str	r2, [r3, #0]
80100050:	2314      	movs	r3, #20
80100052:	f2c0 2329 	movt	r3, #553	; 0x229
80100056:	2205      	movs	r2, #5
80100058:	601a      	str	r2, [r3, #0]
8010005a:	2358      	movs	r3, #88	; 0x58
8010005c:	f2c0 2329 	movt	r3, #553	; 0x229
80100060:	f64f 0238 	movw	r2, #63544	; 0xf838
80100064:	f2c0 0201 	movt	r2, #1
80100068:	601a      	str	r2, [r3, #0]
8010006a:	f24c 0304 	movw	r3, #49156	; 0xc004
8010006e:	f2c0 230a 	movt	r3, #522	; 0x20a
80100072:	2208      	movs	r2, #8
80100074:	601a      	str	r2, [r3, #0]
80100076:	f44f 4340 	mov.w	r3, #49152	; 0xc000
8010007a:	f2c0 230a 	movt	r3, #522	; 0x20a
8010007e:	2200      	movs	r2, #0
80100080:	601a      	str	r2, [r3, #0]
80100082:	bf00      	nop
80100084:	46bd      	mov	sp, r7
80100086:	f85d 7b04 	ldr.w	r7, [sp], #4
8010008a:	4770      	bx	lr

8010008c <copy_data>:
8010008c:	b480      	push	{r7}
8010008e:	b083      	sub	sp, #12
80100090:	af00      	add	r7, sp, #0
80100092:	f240 03e0 	movw	r3, #224	; 0xe0
80100096:	f2c8 0310 	movt	r3, #32784	; 0x8010
8010009a:	607b      	str	r3, [r7, #4]
8010009c:	f240 0300 	movw	r3, #0
801000a0:	f2c0 0390 	movt	r3, #144	; 0x90
801000a4:	603b      	str	r3, [r7, #0]
801000a6:	e007      	b.n	801000b8 <copy_data+0x2c>
801000a8:	683b      	ldr	r3, [r7, #0]
801000aa:	1d1a      	adds	r2, r3, #4
801000ac:	603a      	str	r2, [r7, #0]
801000ae:	687a      	ldr	r2, [r7, #4]
801000b0:	1d11      	adds	r1, r2, #4
801000b2:	6079      	str	r1, [r7, #4]
801000b4:	6812      	ldr	r2, [r2, #0]
801000b6:	601a      	str	r2, [r3, #0]
801000b8:	683a      	ldr	r2, [r7, #0]
801000ba:	f240 0338 	movw	r3, #56	; 0x38
801000be:	f2c0 0390 	movt	r3, #144	; 0x90
801000c2:	429a      	cmp	r2, r3
801000c4:	d3f0      	bcc.n	801000a8 <copy_data+0x1c>
801000c6:	bf00      	nop
801000c8:	370c      	adds	r7, #12
801000ca:	46bd      	mov	sp, r7
801000cc:	f85d 7b04 	ldr.w	r7, [sp], #4
801000d0:	4770      	bx	lr
801000d2:	0000      	movs	r0, r0
801000d4:	0000      	movs	r0, r0
	...

801000d8 <__delay_veneer>:
801000d8:	e51ff004 	ldr	pc, [pc, #-4]	; 801000dc <__delay_veneer+0x4>
801000dc:	00900001 	addseq	r0, r0, r1

Disassembly of section .delay:

00900000 <delay>:
  900000:	b480      	push	{r7}
  900002:	b085      	sub	sp, #20
  900004:	af00      	add	r7, sp, #0
  900006:	6078      	str	r0, [r7, #4]
  900008:	687a      	ldr	r2, [r7, #4]
  90000a:	4613      	mov	r3, r2
  90000c:	051b      	lsls	r3, r3, #20
  90000e:	1a9b      	subs	r3, r3, r2
  900010:	60fb      	str	r3, [r7, #12]
  900012:	2300      	movs	r3, #0
  900014:	60bb      	str	r3, [r7, #8]
  900016:	2300      	movs	r3, #0
  900018:	60bb      	str	r3, [r7, #8]
  90001a:	e003      	b.n	900024 <delay+0x24>
  90001c:	bf00      	nop
  90001e:	68bb      	ldr	r3, [r7, #8]
  900020:	3301      	adds	r3, #1
  900022:	60bb      	str	r3, [r7, #8]
  900024:	68ba      	ldr	r2, [r7, #8]
  900026:	68fb      	ldr	r3, [r7, #12]
  900028:	429a      	cmp	r2, r3
  90002a:	d3f7      	bcc.n	90001c <delay+0x1c>
  90002c:	bf00      	nop
  90002e:	3714      	adds	r7, #20
  900030:	46bd      	mov	sp, r7
  900032:	f85d 7b04 	ldr.w	r7, [sp], #4
  900036:	4770      	bx	lr

实验结果

将生成的 main.imx​ 文件通过 USB 烧录到开发板后,可以观察到板载 LED 灯大约每 45 秒切换一次亮灭。

如果去除代码搬运部分,直接使用存放在 DDR 中的 delay​ 函数,并将其烧录到开发板后,板载 LED 灯大约每 50 秒切换一次亮灭。

经过重定位的 delay​ 函数使 LED 切换速度加快了约 5 秒,由于 DDR 和片内 RAM 的速度差异并不显著,而且延迟函数中读写次数的敏感性较低,因此表现出来的效果并不十分明显。

撰写一篇原创文章不容易,还请大家多多点赞收藏支持一下!有什么问题也可以在评论区讨论,共同进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值