这里主要涉及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设备的传输升级等,原理一样,不再赘述。