研究RISCV D1芯片已经一个月有余了,中间为了方便开发,也开发了一个烧写工具xfel,为方便大家研究学习,这里编写了一个简化的裸机程序,供大家参考,实现比较简单,但该有的都有了。(从xboot精简而来,想要研究高阶功能,就去参考xboot源码树)
在DDR中运行d1-baremetal.bin,这种方式比较适合快速开发调试。借助xfel工具就可以实现。
sudo xfel ddr ddr3;sudo xfel write 0x40000000 d1-baremetal.bin;sudo xfel exec 0x40000000;
烧写d1-baremetal.bin到spi nor flash,固化程序到spi nor flash, 然后上电,自动实现初始化ddr,并自拷贝到ddr中运行。
sudo xfel spinor write 0 d1-baremetal.bin
程序正确运行后,会打印如下信息;
D1 baremetal
count = 0
count = 1
count = 2
count = 3
count = 4
count = 5
count = 6
count = 7
count = 8
count = 9
count = 10
count = 11
count = 12
count = 13
count = 14
count = 15
count = 16
点击下载: d1-baremetal.7z
离线
la t0, _start
la t1, _image_start
LREG t1, (t1)
beq t0, t1, _avoid
/* Initial system jtag, uart and clock */
call sys_jtag_init
call sys_uart_init
call sys_clock_init
/* Copy ddr bin to 0x00030000 */
la t1, _ddr_bin_start
LREG t1, (t1)
la t2, _ddr_bin_end
LREG t2, (t2)
sub a2, t2, t1
la t1, _image_start
LREG t1, (t1)
la t2, _ddr_bin_start
LREG t2, (t2)
sub t0, t2, t1
la a1, _start
add a1, a1, t0
li a0, 0x00030000
call memcpy
/* Initial ddr controller */
call sys_dram_init
_avoid:
nop
/* Copyself to link address */
la t0, _start
la t1, _image_start
LREG t1, (t1)
beq t0, t1, 1f
call sys_copyself
1: nop
核心就这个地方,判断当前运行地址是否是链接地址,nor flash启动的话,肯定不是链接地址,那么就会多做很多事,初始化时钟,DDR,自拷贝等,如果是链接地址,那么就跳过这些动作,直接在DDR中运行。
离线
目前支持所有sfdp协议 nor flash,不支持sfdp协议的,就需要自己填入flash信息,w25x40是不支持sfdp的
离线
你这边既然能正确获取flash容量,应该是支持sfdp协议的,但还是烧不进去,有点奇怪,但可以做个测试,重新上电后,仅写入64KB,然后再读出来,看是否一致。不应该出现这种现象的。
离线
你在虚拟机里跑,有没有可能是虚拟机相关问题了,可以真机或者windows烧录实验下。
离线
请问楼主,关于中断向量表这部分的汇编代码,有点不太懂,主要是vectors:csrw mscratch, sp addi sp, sp, -(37 * REGSZ) SREG x1, 1 * REGSZ(x2)...这部分。
就是中断现场保护,没什么特别的。
离线
楼主,还有这个问题: https://whycan.com/t_6706.html ,有时候DDR信息能输出完整,有时候输出又不完整,很奇怪。
这个估计你哪里异常了,DDR不要初始化两次,还有要注意cache类问题,需要你自己仔细对比自己的操作。
离线
看xboot代码里面都有的
离线
你这是在搞笑吗?中断控制器都没有初始化,还想触发中断
离线
先尝试用定时器做中断源吧,至少这个xboot是验证通过的,而且gpio中断,属于子中断,需要先开启父中断的
最近编辑记录 xboot (2021-06-30 15:26:17)
离线
父中段就是一个中断被多个子中段共享,gpio一般是一组对应一个父中断
离线
这是中断编号,手册里有的
#define D1_IRQ_GPIOB_NS (85)
#define D1_IRQ_GPIOC_NS (87)
#define D1_IRQ_GPIOD_NS (89)
#define D1_IRQ_GPIOE_NS (91)
#define D1_IRQ_GPIOF_NS (93)
#define D1_IRQ_GPIOG_NS (95)
离线
@woolen
这个测试demo仅支持spi nor flash引导,如果要支持spi nand引导,需要做两个工作,一个是实现spi nand自拷贝,另一个是,一个page只能使用前2k,后面的需要空着。
spi nand flash 引导是有点特殊的。但只要满足这两个要求,就可以实现spi nand 自举了。
离线
离线
spl的大小不一定就是24k,大小由链接脚本中自动计算的__spl_size来决定,但mksunxi工具会将spl按8k的整数倍来填充,这是为了兼容各种引导介质。
xfel现在是没有做任何写1k跳1k的,后面会增加一个命令行参数来做这件事。
同样跟你有一样的疑惑,现在没有按1k跳为何能有ddr打印信息,checksum肯定是不正确的,理论上应该是禁止运行的才对。除非,对于nand 引导,checksum根本就不起作用,要确认这个问题,就需要去逆向brom了。
假设checksum不起作用,或者歪打正着,碰对了,那么后面的现象就是可解释的。
同样关注这个问题,争取有个明确的结论。
这里提供一个之前做的工作,用于判断启动的引导介质,已验证。
enum {
BOOT_DEVICE_SPINOR,
BOOT_DEVICE_SPINAND,
BOOT_DEVICE_SDCARD,
};
static int get_boot_device(void)
{
uint8_t s = *((volatile uint8_t *)(0x00020000 + 0x28));
if(s == 0x3)
return BOOT_DEVICE_SPINOR;
else if(s == 0x4)
return BOOT_DEVICE_SPINAND;
else if(s == 0x0)
return BOOT_DEVICE_SDCARD;
return BOOT_DEVICE_SPINOR;
}
离线
@woolen
那很有可能brom spi nand flash 引导时的地址不是0,而是64KB,还有一个可能的地方,就是每个page使用的是2k,而不是1k。
两个方向查找原因:
1,spi nand flash引导地址
2,每page采用2k还是1k,checksum运算应该是必须的。
离线
猜测,brom里面自动搜索spi nand flash 有效spl,这个是合理的,而且也很有可能就是这样,但很有可能,D1 的brom是每个page读取2k,这样对于2k page的spi nand flash就无需留空了。
离线
那就写一个sys-spinand.c,测试下,是否能完成引导,既然是多个引导地址,那就从0开始写了。
关于spinand自拷贝,可以参考xfel里面的代码,当然,全志的boot0也可以参考。
离线
已实现spinand引导,确认每个page使用2k,自拷贝函数实现。
/*
* sys-spinand.c
*
* Copyright(c) 2007-2021 Jianjun Jiang <8192542@qq.com>
* Official site: http://xboot.org
* Mobile phone: +86-18665388956
* QQ: 8192542
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include <xboot.h>
/*
* Default spi nand page size: 2048(11), 4096(12)
*/
#define SPINAND_PAGE_BITS (11)
#define SPINAND_PAGE_MASK ((1 << SPINAND_PAGE_BITS) - 1)
#define SPINAND_PAGE_SIZE (1 << SPINAND_PAGE_BITS)
enum {
SPI_GCR = 0x04,
SPI_TCR = 0x08,
SPI_IER = 0x10,
SPI_ISR = 0x14,
SPI_FCR = 0x18,
SPI_FSR = 0x1c,
SPI_WCR = 0x20,
SPI_CCR = 0x24,
SPI_MBC = 0x30,
SPI_MTC = 0x34,
SPI_BCC = 0x38,
SPI_TXD = 0x200,
SPI_RXD = 0x300,
};
void sys_spinand_init(void)
{
virtual_addr_t addr;
u32_t val;
/* Config GPIOC2, GPIOC3, GPIOC4 and GPIOC5 */
addr = 0x02000060 + 0x00;
val = read32(addr);
val &= ~(0xf << ((2 & 0x7) << 2));
val |= ((0x2 & 0xf) << ((2 & 0x7) << 2));
write32(addr, val);
val = read32(addr);
val &= ~(0xf << ((3 & 0x7) << 2));
val |= ((0x2 & 0xf) << ((3 & 0x7) << 2));
write32(addr, val);
val = read32(addr);
val &= ~(0xf << ((4 & 0x7) << 2));
val |= ((0x2 & 0xf) << ((4 & 0x7) << 2));
write32(addr, val);
val = read32(addr);
val &= ~(0xf << ((5 & 0x7) << 2));
val |= ((0x2 & 0xf) << ((5 & 0x7) << 2));
write32(addr, val);
/* Deassert spi0 reset */
addr = 0x0200196c;
val = read32(addr);
val |= (1 << 16);
write32(addr, val);
/* Open the spi0 gate */
addr = 0x02001940;
val = read32(addr);
val |= (1 << 31);
write32(addr, val);
/* Open the spi0 bus gate */
addr = 0x0200196c;
val = read32(addr);
val |= (1 << 0);
write32(addr, val);
/* Select pll-periph0 for spi0 clk */
addr = 0x02001940;
val = read32(addr);
val &= ~(0x3 << 24);
val |= 0x1 << 24;
write32(addr, val);
/* Set clock pre divide ratio, divided by 1 */
addr = 0x02001940;
val = read32(addr);
val &= ~(0x3 << 8);
val |= 0x0 << 8;
write32(addr, val);
/* Set clock divide ratio, divided by 6 */
addr = 0x02001940;
val = read32(addr);
val &= ~(0xf << 0);
val |= (6 - 1) << 0;
write32(addr, val);
/* Set spi clock rate control register, divided by 2 */
addr = 0x04025000;
write32(addr + SPI_CCR, 0x1000);
/* Enable spi0 and do a soft reset */
addr = 0x04025000;
val = read32(addr + SPI_GCR);
val |= (1 << 31) | (1 << 7) | (1 << 1) | (1 << 0);
write32(addr + SPI_GCR, val);
while(read32(addr + SPI_GCR) & (1 << 31));
val = read32(addr + SPI_TCR);
val &= ~(0x3 << 0);
val |= (1 << 6) | (1 << 2);
write32(addr + SPI_TCR, val);
val = read32(addr + SPI_FCR);
val |= (1 << 31) | (1 << 15);
write32(addr + SPI_FCR, val);
}
void sys_spinand_exit(void)
{
virtual_addr_t addr = 0x04025000;
u32_t val;
/* Disable the spi0 controller */
val = read32(addr + SPI_GCR);
val &= ~((1 << 1) | (1 << 0));
write32(addr + SPI_GCR, val);
}
static void sys_spi_select(void)
{
virtual_addr_t addr = 0x04025000;
u32_t val;
val = read32(addr + SPI_TCR);
val &= ~((0x3 << 4) | (0x1 << 7));
val |= ((0 & 0x3) << 4) | (0x0 << 7);
write32(addr + SPI_TCR, val);
}
static void sys_spi_deselect(void)
{
virtual_addr_t addr = 0x04025000;
u32_t val;
val = read32(addr + SPI_TCR);
val &= ~((0x3 << 4) | (0x1 << 7));
val |= ((0 & 0x3) << 4) | (0x1 << 7);
write32(addr + SPI_TCR, val);
}
static void sys_spi_write_txbuf(u8_t * buf, int len)
{
virtual_addr_t addr = 0x04025000;
int i;
write32(addr + SPI_MTC, len & 0xffffff);
write32(addr + SPI_BCC, len & 0xffffff);
if(buf)
{
for(i = 0; i < len; i++)
write8(addr + SPI_TXD, *buf++);
}
else
{
for(i = 0; i < len; i++)
write8(addr + SPI_TXD, 0xff);
}
}
static int sys_spi_transfer(void * txbuf, void * rxbuf, int len)
{
virtual_addr_t addr = 0x04025000;
int count = len;
u8_t * tx = txbuf;
u8_t * rx = rxbuf;
u8_t val;
int n, i;
while(count > 0)
{
n = (count <= 64) ? count : 64;
write32(addr + SPI_MBC, n);
sys_spi_write_txbuf(tx, n);
write32(addr + SPI_TCR, read32(addr + SPI_TCR) | (1 << 31));
while((read32(addr + SPI_FSR) & 0xff) < n);
for(i = 0; i < n; i++)
{
val = read8(addr + SPI_RXD);
if(rx)
*rx++ = val;
}
if(tx)
tx += n;
count -= n;
}
return len;
}
static int sys_spi_write_then_read(void * txbuf, int txlen, void * rxbuf, int rxlen)
{
if(sys_spi_transfer(txbuf, NULL, txlen) != txlen)
return -1;
if(sys_spi_transfer(NULL, rxbuf, rxlen) != rxlen)
return -1;
return 0;
}
static void sys_spinand_wait(void)
{
u8_t tx[2];
u8_t rx[1];
tx[0] = 0x0f;
tx[1] = 0xc0;
do {
sys_spi_select();
sys_spi_write_then_read(tx, 2, rx, 1);
sys_spi_deselect();
} while((rx[0] & 0x1) == 0x1);
}
void sys_spinand_read(int addr, void * buf, int count)
{
u8_t tx[4];
u32_t pa, ca;
int n;
while(count > 0)
{
pa = addr >> SPINAND_PAGE_BITS;
ca = addr & SPINAND_PAGE_MASK;
n = count > (SPINAND_PAGE_SIZE - ca) ? (SPINAND_PAGE_SIZE - ca) : count;
tx[0] = 0x13;
tx[1] = (u8_t)(pa >> 16);
tx[2] = (u8_t)(pa >> 8);
tx[3] = (u8_t)(pa >> 0);
sys_spi_select();
sys_spi_write_then_read(tx, 4, 0, 0);
sys_spi_deselect();
sys_spinand_wait();
tx[0] = 0x03;
tx[1] = (u8_t)(ca >> 8);
tx[2] = (u8_t)(ca >> 0);
tx[3] = 0x0;
sys_spi_select();
sys_spi_write_then_read(tx, 4, buf, n);
sys_spi_deselect();
sys_spinand_wait();
addr += n;
buf += n;
count -= n;
}
}
其中,因spl代码需要实现成位置无关,就不支持芯片型号检测了,一般的spi nand flash page 为2k,4k,这些通过宏定义来设置了,不动态识别。
/*
* Default spi nand page size: 2048(11), 4096(12)
*/
#define SPINAND_PAGE_BITS (11)
#define SPINAND_PAGE_MASK ((1 << SPINAND_PAGE_BITS) - 1)
#define SPINAND_PAGE_SIZE (1 << SPINAND_PAGE_BITS)
这个自拷贝函数,为连续拷贝,不做任何间隔拷贝,间隔拷贝函数仅BROM中的才是如此,xboot中,spl代码实现成位置无关的,且为一个独立的镜像。其镜像最前面就是spl代码。
所以烧录时,将xboot的前64k,烧录到0地址,这个烧录过程需要做间隔处理,再将完整xboot烧录到256k的位置,这个位置无需做任何间隔处理,由上面的函数来实现自拷贝。
2k page大小的flash时,spl代码烧写无需特殊处理。
离线
离线
离线
结合搜索的各种资料,大概明白了,spl的整个过程,brom引导时会从block0开始依次检测合法spl代码,spl会存在多个备份,直到找到合法的为止,各个备份spl按block来对齐,如果 SPL 超过 1 个 block, 单个备份起始 block 地址为偶数。最大可以有8个备份,一个page是2kb有效,但这个现在不敢肯定,手上没有4kpage的nand来测试,估计大概率是还是2k有效。
离线
将spl保留区域调整为1MB,并扩展了xfel,支持烧录spl镜像到spi nand flash,可以烧录普通镜像,以及压缩镜像,并能正常引导启动。
烧写普通镜像到SPI Nand Flash
sudo xfel spinand splwrite 2048 1048576 xboot.bin
烧写压缩镜像到SPI Nand Flash
sudo xfel spinand splwrite 2048 1048576 xboot.bin.z
xfel 1.2.2版本支持的命令
xfel(v1.2.2) - https://github.com/xboot/xfel
usage:
xfel version - Show chip version
xfel hexdump <address> <length> - Dumps memory region in hex
xfel dump <address> <length> - Binary memory dump to stdout
xfel exec <address> - Call function address
xfel read32 <address> - Read 32-bits value from device memory
xfel write32 <address> <value> - Write 32-bits value to device memory
xfel read <address> <length> <file> - Read memory to file
xfel write <address> <file> - Write file to memory
xfel reset - Reset device using watchdog
xfel sid - Show sid information
xfel jtag - Enable jtag debug
xfel ddr [type] - Initial ddr controller with optional type
xfel spinor - Detect spi nor flash
xfel spinor read <address> <length> <file> - Read spi nor flash to file
xfel spinor write <address> <file> - Write file to spi nor flash
xfel spinand - Detect spi nand flash
xfel spinand read <address> <length> <file> - Read spi nand flash to file
xfel spinand write <address> <file> - Write file to spi nand flash
xfel spinand splwrite <valid-page-size> <address> <file> - Write file to spi nand flash with spl mode
离线