ZYNQ(PS) 动态加载APP之一

这里主要涉及ZYNQ加载流程及裸机下的动态加载

平台 : zedboard
vivado : vivado 2018.3

1. IAP简介

IAP,全称In Application Programing, 在应用编程。简单理解,即在程序运行过程中进行编程(升级程序,更新固件)

由ZYNQ的启动特性可知,上电后会执行BootROM,然后跳转到FSBL,由FSBL跳转到用户程序。这里实现两个用户程序(基于同一个FSBL,都是默认配置),分别是bootloader和app

ZYNQ动态加载主要参考UG821,常见的方法使用Multiboot。

在bootloader里面实现两个步骤

1 >  往Multiboot寄存器(地址0xF800702C)写便偏移地址,单位32K,比如写0x8, 实际QSPI地址为 32K * 8 = 0x40000 (256K)
2 > 软复位 

2. 本地IAP

本地IAP即提前固化好两段用户程序,bootloader和app,通过在bootloader里面写multiboot来切换到app

本地IAP是后面远程IAP的基础。

以上BSP, HW和FSBL是共用的,系统默认生成(仅仅改名了), FW为bootloader, APP为app

2.1 zynq软件复位

仅仅往SLCR寄存器组里面的PSS_RST_CTRL(地址0xF8000200)写1即可,注意SLCR寄存器写操作前要先unlock,即往SLCR_UNLOCK(地址0xF8000008)写0xDF0D

 

参考代码如下:

#define BASE_SLCR_ADDR		0xF8000000
#define BASE_XDCFG_ADDR		0xF8007000

/*
 * SLCR LOCK
 *  base : BASE_SLCR_ADDR
 */
#define SLCR_LOCK_OFFSET		0x00000004
#define LOCK_KEY_SHIFT			0x0
#define LOCK_KEY_MASK			0xFFFF

/*
 * SLCR UNLOCK
 *  base : BASE_SLCR_ADDR
 */
#define SLCR_UNLOCK_OFFSET		0x00000008
#define UNLOCK_KEY_SHIFT		0x0
#define UNLOCK_KEY_MASK			0xFFFF

/*
 * RESET
 *  base : BASE_SLCR_ADDR
 */
#define PSS_RST_CTRL_OFFSET		0x00000200
#define SOFT_RST_SHIFT			0
#define SOFT_RST_MASK			0x1

static void SLCR_Lock(void)
{
	REG32_WRITE(BASE_SLCR_ADDR + SLCR_LOCK_OFFSET , 0x767B);
}

static void SLCR_Unlock(void)
{
	REG32_WRITE(BASE_SLCR_ADDR + SLCR_UNLOCK_OFFSET , 0xDF0D);
}
c
int SYS_Reset(void)
{
	SLCR_Unlock();
	REG32_WRITE(BASE_SLCR_ADDR + PSS_RST_CTRL_OFFSET , 1<<SOFT_RST_SHIFT);
	SLCR_Lock();

	return 0;
}

2.2 修改multiboot

仅仅往XDCFG寄存器组里面的XDCFG_MULTIBOOT_ADDR_OFFSET(地址0xF800702C)写offset即可,注意XDCFG寄存器写操作前要先unlock,即往XDCFG_UNLOCK_OFFSET(地址0xF8007034)写0x757BDF0D。注意offset是32K最小单位。

由于multiboot只有13位, 因此multiboot最大访问的空间 =  8192 * 32K = 256M

这里设置multiboot = 8, 即8 * 32K = 256K(0x40000)

参考代码如下:

/*
 * XDCFG LOCK
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_LOCK_OFFSET 		0x4
#define LOCK_SHIFT				0x0
#define LOCK_MASK				0x1F

/*
 * XDCFG UNLOCK
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_UNLOCK_OFFSET 		0x34
#define UNLOCK_SHIFT				0x0
#define UNLOCK_MASK				0xFFFFFFFF
#define UNLOCK_KEY				0x757BDF0D

/*
 * multiboot
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_MULTIBOOT_ADDR_OFFSET 	0x0000002C
#define MULTIBOOT_SHIFT					0x0
#define MULTIBOOT_MASK					0x1FFF

int SYS_Upgrade(void)
{
	// unlock XDCFG
	REG32_WRITE(BASE_XDCFG_ADDR + XDCFG_UNLOCK_OFFSET, UNLOCK_KEY);

	// update the multiboot
	REG32_WRITE(BASE_XDCFG_ADDR + XDCFG_MULTIBOOT_ADDR_OFFSET, 0x8);

	SYS_Reset();
	return 0;
}

2.3 bootloader

bootloader和FSBL生成bootloader.bin烧到偏移offset = 0的位置

2.4 App

app和FSBL生成App.bin烧到偏移offset = 0x40000的位置

2.5 操作

上电后,系统默认进入bootloader

输入up,实际上执行SYS_Upgrade即可以跳到app

注意这里跳到app的过程

1. bootloader先往multiboot 寄存器写0x8 ,表示下个image在0x8*32K=0x40000
2. bootloader紧接着软件复位,使得BootROM被再次执行
3. 在BootROM里面会去读取multiboot寄存器的offset,然后在offset读取image header并判断是否合法
4. 以上判断合法后跳到offset指向的应用程序,即app

以上两段程序分别烧写到flash不同的偏移,其具体的操作可以从program flash的日志里面看到,比如第二段app, offset=0x40000

cmd /C program_flash -f D:\02_Code\zedboard\SW\APP\bootimage\App.bin -offset 0x40000 \
-flash_type qspi-x4-single -fsbl D:\02_Code\zedboard\SW\FSBL\Debug\FSBL.elf -cable type \
xilinx_tcf url TCP:127.0.0.1:3121 

U-Boot 2018.01-00073-g63efa8c-dirty (Oct 04 2018 - 08:22:22 -0600)
Model: Zynq CSE QSPI Board
Board: Xilinx Zynq
Silicon: v3.1
DRAM:  256 KiB
WARNING: Caches not enabled

Using default environment

In:    dcc
Out:   dcc
Err:   dcc

Zynq> sf probe 0 10000000 0
SF: Detected s25fl256s_64k with page size 256 Bytes, erase size 64 KiB, total 32 MiB

Zynq> Sector size = 65536.
f probe 0 10000000 0

Performing Erase Operation...
sf erase 40000 40000              // 从偏移0x40000擦除256K(0x40000)

SF: 262144 bytes @ 0x40000 Erased: OK
Zynq> Erase Operation successful.
INFO: [Xicom 50-44] Elapsed time = 0 sec.
Performing Program Operation...
0%...sf write FFFC0000 40000 20000  // 写操作, OCM被重定位到0xFFFC0000, 写128K

device 0 offset 0x40000, size 0x20000
SF: 131072 bytes @ 0x40000 Written: OK

Zynq> 100%
sf write FFFC0000 60000 15750       // 写剩余的size

device 0 offset 0x60000, size 0x15750

SF: 87888 bytes @ 0x60000 Written: OK

Zynq> Program Operation successful.
INFO: [Xicom 50-44] Elapsed time = 2 sec.

Flash Operation Successful

3 QSPI的操作

可以参考mss文件提供的实例代码

4 Dump/Restore data file

vivado-sdk里面有个工具Dump/Restore可以实现加载/读取内存(DDR)数据

 其使用必须在Debug As或者Run As之后

最后配置如下

其中Start Address是DDR地址,注意不要覆盖当前运行区(一般为0x1000000), DDR范围0x00100000~0x3FFFFFFF,还有板载DDR实际容量,比如我的是512M,那么有效范围是0x00100000~0x20000000, 这里选0x10000000, 应该是合理的。

Size(in bytes)是App.bin实际大小,不能随便填

与App.bin对比可知正常

5 从DDR更新到QSPI

从第4节的Restore,可以把PC机的bin文件加载到DDR,这里讨论从DDR数据固化到QSPI。

从第二章本地IAP的固化log可以简化如下

Downloading FSBL...
sf probe 0 10000000 0
sf erase 40000 40000              // 从偏移0x40000擦除256K(0x40000)
sf write FFFC0000 40000 20000  // 写操作, OCM被重定位到0xFFFC0000, 写128K
sf write FFFC0000 60000 15750       // 写剩余的size

这里bootloader和app的FSBL采用同一个,先不管FSBL,看下FSBL.elf和App.bin的文件属性

FSBL    : 613K 627864   0x99498
App.bin : 213K  218960  0x35750

根据log, flash擦除0x40000 ,size=0x40000(256K),但是却写到0xFFFC0000(很费解???),大小刚好等于App.bin文件的大小。

我们这里直接操作0x40000这个地址

5.1 擦除

bootloader # se 0x40000 0x40000
void FlashEraseSectors(u32 addr, u32 len)
{
	u8 writeEnableCmd = { WRITE_ENABLE_CMD };
	u8 readStatusCmd[] = { READ_STATUS_CMD, 0 };  /* must send 2 bytes */
	u8 flashStatus[2];
	int sector;

	for (sector = 0; sector < ((len / SECTOR_SIZE) + 1); sector++) {
		/*
		 * Send the write enable command to the SEEPOM so that it can be
		 * written to, this needs to be sent as a seperate transfer
		 * before the write
		 */
		XQspiPs_PolledTransfer(QspiInstancePtr, &writeEnableCmd, NULL,
					sizeof(writeEnableCmd));

		/*
		 * Setup the write command with the specified address and data
		 * for the FLASH
		 */
		writeBuffer[COMMAND_OFFSET]   = SEC_ERASE_CMD;
		writeBuffer[ADDRESS_1_OFFSET] = (u8)(addr >> 16);
		writeBuffer[ADDRESS_2_OFFSET] = (u8)(addr >> 8);
		writeBuffer[ADDRESS_3_OFFSET] = (u8)(addr & 0xFF);

		/*
		 * Send the sector erase command and address; no receive buffer
		 * is specified since there is nothing to receive
		 */
		XQspiPs_PolledTransfer(QspiInstancePtr, writeBuffer, NULL,
					SEC_ERASE_SIZE);


		while (1) {
			XQspiPs_PolledTransfer(QspiInstancePtr, readStatusCmd,
						flashStatus,
						sizeof(readStatusCmd));

			if ((flashStatus[1] & 0x01) == 0) {
				break;
			}
		}

		addr += SECTOR_SIZE;
	}	
}   

REGISTER_CMD(
        se,
        3,
        ARG_TYPE_U32,
		CLI_FlashErase,
        "<addr> <len> flash erase"
);

// se 0x40000 0x40000  三个参数分别是命令字se, addr=0x40000, len=0x40000 (256K) 足够

5.2 Restore App.bin

根据第4节加载App.bin,首先Debug as当前FW项目

然后

其中0x35750是App.bin文件的实际大小

在终端里面输入

可以读到0x10000000地址的值,和App.bin一致

5.3 写回QSPI

以上通过Vivado的Restore把App.bin写到DDR,现在看下怎么烧写到QSPI地址0x40000

命令

sload 0x40000 0x10000000 0x35750

其实现

REGISTER_CMD(
        sload,
        4,
        ARG_TYPE_U32,
		CLI_Upgrade,
        "<dest> <src> <len> load data to wb from ddr"
);
// 三个参数,第一个命令字,第二个src,这里是QSPI的偏移地址0x40000, src这里是DDR里面App.bin加载地址,0x10000000 , 最后一个len是App.bin实际长度

CLI_Upgrade

#define COMMAND_OFFSET		0 /* FLASH instruction */
#define ADDRESS_1_OFFSET	1 /* MSB byte of address to read or write */
#define ADDRESS_2_OFFSET	2 /* Middle byte of address to read or write */
#define ADDRESS_3_OFFSET	3 /* LSB byte of address to read or write */
#define DATA_OFFSET			4 /* Start of Data for Read/Write */
#define DUMMY_OFFSET		4 /* Dummy byte offset for fast, dual and quad */
#define PAGE_SIZE		256
u8 writeBuffer[PAGE_SIZE + DATA_OFFSET];

void FlashWrite(u32 addr, u32 len, u8 cmd)
{
	u8 writeEnableCmd = { WRITE_ENABLE_CMD };
	u8 readStatusCmd[] = { READ_STATUS_CMD, 0 };  /* must send 2 bytes */
	u8 flashStatus[2] ;

	/*
	 * Send the write enable command to the FLASH so that it can be
	 * written to, this needs to be sent as a seperate transfer before
	 * the write
	 */
	XQspiPs_PolledTransfer(QspiInstancePtr, &writeEnableCmd, NULL,
				sizeof(writeEnableCmd));

	/*
	 * Setup the write command with the specified address and data for the
	 * FLASH
	 */
	writeBuffer[COMMAND_OFFSET]   = cmd;
	writeBuffer[ADDRESS_1_OFFSET] = (u8)((addr & 0xFF0000) >> 16);
	writeBuffer[ADDRESS_2_OFFSET] = (u8)((addr & 0xFF00) >> 8);
	writeBuffer[ADDRESS_3_OFFSET] = (u8)(addr & 0xFF);

	/*
	 * Send the write command, address, and data to the FLASH to be
	 * written, no receive buffer is specified since there is nothing to
	 * receive
	 */
	XQspiPs_PolledTransfer(QspiInstancePtr, writeBuffer, NULL,
				len + OVERHEAD_SIZE);

	// TODO : add timeout
	while (1) {

		XQspiPs_PolledTransfer(QspiInstancePtr, readStatusCmd, flashStatus,
					sizeof(readStatusCmd));

		if ((flashStatus[1] & 0x01) == 0) {
			break;
		}
	}
	
}


int CLI_Upgrade(u32 destAddr, u32 srcAddr, u32 len)
{
	int i = 0;
	u32 ddrValue = 0;
	u32 cnt = 0;
	int page_cnt = len / PAGE_SIZE;
	int last_page = len - page_cnt * PAGE_SIZE;

	if(len & 0x3)
	{
		debug(PR_LEVEL_ERROR, "not aligned\r\n");
		return -1;
	}

	do{	
		for(i = DATA_OFFSET; i < PAGE_SIZE + DATA_OFFSET; i += 4, srcAddr+=4)
		{
			ddrValue = REG32_READ(srcAddr);
			writeBuffer[i] = ddrValue & 0xFF;
			writeBuffer[i + 1] = (ddrValue >> 8) & 0xFF;
			writeBuffer[i + 2] = (ddrValue >> 16) & 0xFF;
			writeBuffer[i + 3] = (ddrValue >> 24) & 0xFF;
		}

		FlashWrite(destAddr, PAGE_SIZE, WRITE_CMD);

		destAddr += PAGE_SIZE;
		
		cnt ++;
	}while(cnt < page_cnt);

	/* last page */
	for(i = DATA_OFFSET; i < last_page + DATA_OFFSET; i += 4, srcAddr+=4)
	{
		ddrValue = REG32_READ(srcAddr);
			writeBuffer[i] = ddrValue & 0xFF;
			writeBuffer[i + 1] = (ddrValue >> 8) & 0xFF;
			writeBuffer[i + 2] = (ddrValue >> 16) & 0xFF;
			writeBuffer[i + 3] = (ddrValue >> 24) & 0xFF;
	}
	
	FlashWrite(destAddr, last_page, WRITE_CMD);
	
	return 0;
}

最后

 

其中up命令

#define BASE_XDCFG_ADDR		0xF8007000
/*
 * XDCFG UNLOCK
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_UNLOCK_OFFSET 		0x34
#define UNLOCK_SHIFT				0x0
#define UNLOCK_MASK				0xFFFFFFFF
#define UNLOCK_KEY				0x757BDF0D

/*
 * multiboot
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_MULTIBOOT_ADDR_OFFSET 	0x0000002C
#define MULTIBOOT_SHIFT					0x0
#define MULTIBOOT_MASK					0x1FFF

int SYS_Upgrade(void)
{
	// unlock XDCFG
	REG32_WRITE(BASE_XDCFG_ADDR + XDCFG_UNLOCK_OFFSET, UNLOCK_KEY);

	// update the multiboot
	REG32_WRITE(BASE_XDCFG_ADDR + XDCFG_MULTIBOOT_ADDR_OFFSET, 0x8);

	SYS_Reset();
	return 0;
}


REGISTER_CMD(
        up,
        1,
        ARG_TYPE_NONE,
		SYS_Upgrade,
        "<addr> load the kernel"
);

很简单,把新的image地址/32K写到multiboot寄存器即可。

6 从其他方式升级

常用方法有基于uart的ymodem协议传输升级,基于udp的tftp协议传输升级,基于usb设备的传输升级等,原理一样,不再赘述。

### Zynq PS 加载地址配置方法及参数 #### 配置加载地址的方式 对于Zynq系列器件,在PS(处理系统)加载过程中,可以通过多种途径设置加载地址。通常情况下,加载过程涉及从外部存储器读取数据并将其传输至内部内存或DDR中特定的位置。 当通过SD卡或者Flash启动时,FSBL(First Stage Boot Loader)负责初始化必要的硬件资源,并将后续阶段所需的代码复制到指定的目标地址[^1]。具体来说: - **初始引导加载程序(Boot ROM)**:在上电复位之后自动运行,它会依据预定义的规则去寻找合法的启动源。一旦找到合适的启动设备,则继续执行相应的操作来加载第二级引导加载程序即FSBL。 - **FSBL的作用**:此部分主要职责在于完成基本外围接口初始化工作以及准备用于传递给应用程序处理器核(Cortex-A9) 的上下文环境;同时也会把PL比特流映射进系统的物理地址空间内以便于稍后的下载动作能够顺利开展。 为了使PS能正确解析这些指令和数据,必须确保它们被放置在一个已知且固定的起始位置之上。例如,在某些应用场景下,可能会采用如下所示的方法来进行设定: ```bash make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm zynq_zc702_defconfig ``` 上述命令中的`zynq_zc702_defconfig`选项指定了目标板的具体型号及相关配置细节,而编译工具链前缀则表明了交叉编译所针对的操作系统架构类型[^2]。 至于实际的加载地址,这取决于具体的项目需求和技术文档的规定。一般而言,Linux内核镜像会被安排存放在较低范围内的RAM区域里,比如0x8000处开始的一段连续区间;而对于用户自定义的应用程序或其他固件组件,则可以根据实际情况灵活调整其安置地点。 另外值得注意的是,在涉及到动态重配PL的情况下,还需要考虑如何有效地管理不同版本之间可能存在的冲突问题。此时可借助于专门设计好的API函数库如`xdecfg_polled_example.c`所提供的功能来简化这一流程,从而允许开发者更加便捷地控制整个更新过程。 最后,有关bit文件转换成适合加载的形式,可以使用带有`-split`参数的相关工具,该参数将会生成`.bit.bin`这样的二进制格式文件,便于进一步部署到目标平台上[^3]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值