xv6-zynq

        这个5.1出不了门,正好重温一下嵌入式操作系统知识,手头有一块Xilinx的开发版,想着试一试将xv6移植到开发板上。

        开发版使用Zynq UltraScale+ MPSoCs EV系列的系列的芯片,型号为 XCZU4EV-1SFVC784I。ZU4EV 芯片的PS 系统集成了 4 个ARM Cortex™-A53处理器,2个Cortex-R5 处理器。其中R5基于 ARMv7R ,是32位的指令集,A53基于ARMv8,是64为指令集,一般R5用于实时任务,A53用作高速任务。xv6 是MIT 开发的一个教学用的完整的类 Unix 操作系统,是  Unix Version 6(v6)操作系统的重新实现。xv6 在一定程度上遵守 v6 的结构和风格,用ANSI C实现,并且是基于 x86多核处理器的。包含了进程管理、内存管理、文件系统等组件,非常适合操作系统的学习。这回就尝试将xv6移植到ZYNQ的A53上运行。

        网上有将xv6移植到qemu虚拟virt上的开源项目xv6-OS-for-arm-v8,就以这个为基础,主要解决如下几个问题。(1)开发环境搭建。 Zynq使用xilix的vitis开发与调试,首先要将xv6引入到vitis中才能进一步使用相关的调试手段。(2)系统引导程序。主要完成A53处理器异常等级配置,建立c语言的栈调用环境,然后跳转到c语言主程序。(3)串口驱动。在系统运行初期提供辅助调试手段和在系统运行时为控制台程序提供输入输出。(4)任务调度进程。(5)SD卡运行程序。

一、开发环境搭建

 1.vitis
        嵌入式 Linux 开发,一般需要一台 Linux 操作系统主机,用来编译 u-boot 或者 Linux-kernel。一般习惯先在windows平台安装虚拟机,然后在虚拟集中安装Linux主机,鉴于ubuntu Linux 桌面操作 系统的安装以及配置较为简单,选择了ubuntu 桌面操作系统。这里使用Ubuntu 18.04.2 LTS 64 位操作系统。在操作系统安装完成后安装Vivado 软件(2020.1,集成vitis)。

2.xv6 环境搭建
        根据ZYNQ的开发流程,首先在vivado中进行硬件设计和配置,导出.design_1_wrapper.xsa文件。在vitis中创建项目xv6p,导入硬件信息,选择A53_0作为运行处理器,生成空项目。取消项目编译时自动生成makefile文件,防止vitis自动覆盖makefile文件。

       导入xv6的源代码,替换debug目录下的makefile文件,这里同时拷贝预先生成的文件系统(initcode和fs.img)到该目录下,替换src目录下的lscript.ld文件,修改makefile和lscript.ld文件中文源文件的相对路径。
        最终目录结构如下图:

这样xv6在zynq下的调试环境就搭建完成。其中xv6的entry、start和内核页表从0X40100000的地址开始,xv6其他部分从0X40300000的地址开始。

二、系统引导程序

        zynq复位后首先运行存储在rom中的boot程序,由该程序加载fsbl,全称为first stage boot loader,再由fsbl进一步引导uboot或目标系统。这里直接使用fsbl加载xv6。

        A53复位后处于EL3异常等级,操作系统内核一般运行在EL1,用户程序运行在EL0。在复位后bootrom,完成ram、flash 的初始化后加载fsbl,fsbl分别加载PL的配置文件(bit),然后加载并跳转到PS部分即xv6。因此xv6首先要返回到EL1,然后初始化C语言的调用栈环境。

        armv8使用eret从高一级的异常状态返回,这里需要配置SCR_EL3的NS=0,使EL1可以访问Secure memory,特别是gic控制器。

.text
.align 16
.global _start

#define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11)
#define SCTLR_EE_LITTLE_ENDIAN (0 << 25)
#define SCTLR_EOE_LITTLE_ENDIAN (0 << 24)
#define SCTLR_I_CACHE_DISABLED (0 << 12)
#define SCTLR_D_CACHE_DISABLED (0 << 2)
#define SCTLR_MMU_DISABLED (0 << 0)
#define SCTLR_MMU_ENABLED (1 << 0)
#define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED)

#define HCR_RW (1 << 31)
#define HCR_VALUE HCR_RW

#define SCR_RESERVED (3 << 4)
#define SCR_RW (1 << 10)
#define SCR_NS (0 << 0)
#define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS)

#define SPSR_MASK_ALL (7 << 6)
#define SPSR_EL1h (5 << 0)
#define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h)
_start:
Loop_el:
	mov x0,0
    mrs	x0, currentEL
    cmp	x0, #0xC
    beq	InitEL3
    cmp	x0, #0x8
    beq	InitEL2
    cmp	x0, #0x4
    beq	InitEL1
InitEL3:
    adr x0,	Loop_el
    msr elr_el3,x0
    ldr x1, =SCTLR_VALUE_MMU_DISABLED
    msr sctlr_el1, x1
    ldr x1, =HCR_VALUE
    msr hcr_el2, x1
    ldr x1, =SCR_VALUE
    msr scr_el3, x1
    ldr x1, =SPSR_VALUE
    msr spsr_el3, x1
    msr elr_el3, x0
    eret

       初始化C语言的栈帧环境及跳转。

Init_el0:
	adr x0,	Loop_el
	msr elr_el1,x0
	mov x0,0
	msr spsr_el1,x0
	eret
entry_0:
	adrp    x0, init_stktop
	mov     sp, x0

	# clear the entry bss section, the svc stack, and kernel page table
	LDR     x1, =edata_entry
	LDR     x2, =end_entry
	MOV     x3, #0x00
1:
	CMP     x1, x2
	B.GT    2f
	STR     x3, [x1]
	ADD     x1, x1, #0x08
	BLT     1b
2:
	BL      start

        其中init_stktop、edata_entry、end_entry 均定义在链接文件lscript.ld 中,sp=init_stktop为栈顶,栈空间大小为0x1000 ,将从edata_entry到end_entry的空间清零,包含了xv6自身引导程序的bss段,栈和内核页表空间。

        最后跳转到C语言入口start函数地址。

三、串口驱动

      在xv6启动初始化阶段,仅用到了UART0输出,用于打印内核信息,该阶段工作在实地址模式。UART0的基址为0XFF000000.

        UART0输出相关寄存器的偏移。

        当判断TXFULL为空时,就可以向TXFIFO写发送数据。使用volatile 关键字定义寄存器变量,防止缓存。重写_uart_putc(int c)函数如下:

#define XUARTPS_SR_OFFSET		0x002CU  /**< Channel Status [14:0] */
#define STDIN_BASEADDRESS 0xFF000000
#define XUARTPS_FIFO_OFFSET		0x0030U  /**< FIFO [7:0] */
#define XUARTPS_SR_TXFULL	0x00000010U /**< TX FIFO full */
void _uart_putc(int c)
{
    while(1)
    {
        uint v_sr=0;
        volatile uint8 * uartsr = (uint8*)(STDIN_BASEADDRESS+XUARTPS_SR_OFFSET);
        v_sr =*uartsr;
        if((v_sr & XUARTPS_SR_TXFULL) == XUARTPS_SR_TXFULL)
        {
            continue;
        }
        else
        {
            break;
        }
    }
    /* Write the byte into the TX FIFO */
    volatile uint8 * uart0 = (uint8*)(STDIN_BASEADDRESS+XUARTPS_FIFO_OFFSET);
    *uart0 = (c&0x00ff);
}

          当xv6完成内核页表初始化,开启mmu后,需要使用虚地址访问UART0,基址经TTBR1_EL1中的页表映射到地址0xFFFFFFFFFF000000,在EL1虚地址模式armv8对大于0x0000ffff_ffffffff的地址使用TTBR1_EL1作为页表基地址寄存器,不大于的使用TTBR0_EL1,一般情况下TTBR0用于存放用户空间的一级页表基址,TTBR1存放内核空间的一级页表基址。

        UART0接受数据使用中断,通过GIC的IRQ中断,中断号53。首先配置GIC中断及对应中断号的处理函数,然后开启UART0接收数据中断。详细可参考UG1085.。

#define V_STDIN_BASEADDRESS  0xFFFFFFFFFF000000
void uart_enable_rx ()
{
	u32 TempMask ;
	volatile u32 *uart_RXWM = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_RXWM_OFFSET);
	*uart_RXWM = 1;
	volatile u32 *uart_TXWM = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_TXWM_OFFSET);
	*uart_TXWM = 1;
	TempMask = XUARTPS_IXR_RXOVR|XUARTPS_IXR_RXEMPTY;
	volatile u32 *uart_IER=(u32 *)(V_STDIN_BASEADDRESS+XUARTPS_IER_OFFSET);
	*uart_IER =TempMask;
	pic_enable(PIC_UART0, isr_uart);
}

        中断号处理函数isr_uart:

int uartgetc (void)
{
	volatile u32 *uartsr = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_SR_OFFSET);
	int CsrRegister = *uartsr;
	if(((CsrRegister & XUARTPS_SR_RXEMPTY) == (u32)0))
	{
		volatile u32 *uartfifo = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_FIFO_OFFSET);
		return *uartfifo;
	}
	else
	{
		volatile u32 *uartisr = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_ISR_OFFSET);
		*uartisr = XUARTPS_IXR_RXEMPTY;
		return -1;
	}
}
void isr_uart (struct trapframe *tf, int idx)
{
	volatile u32 *uartsr = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_SR_OFFSET);
	int CsrRegister = *uartsr;
	if((CsrRegister & XUARTPS_SR_RXOVR) && ((CsrRegister & XUARTPS_SR_RXEMPTY) == (u32)0))
	{
		consoleintr(uartgetc);
	}
	volatile u32 *uartisr = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_ISR_OFFSET);
	*uartisr = XUARTPS_IXR_RXOVR;
}

四、任务调度进程

       在进程调度时需要保存调度出的进程的上下文的环境(一组寄存器),并将要取得cpu的进程的上下文更新到cpu。xv6声明的上下文数据结构如下:

struct context {
    // svc mode registers
	uint64    r3;
    uint64    r4;
    uint64    r5;
    uint64    r6;
    uint64    r7;
    uint64    r8;
    uint64    r9;
    uint64    r10;
    uint64    r11;
    uint64    r12;
    uint64    r13;
    uint64    r14;
    uint64    r15;
    uint64    r16;
    uint64    r17;
    uint64    r18;
    uint64    r19;
    uint64    r20;
    uint64    r21;
    uint64    r22;
    uint64    r23;
    uint64    r24;
    uint64    r25;
    uint64    r26;
    uint64    r27;
    uint64    r28;
    uint64    r29;
    uint64    lr;	// x30
};

        在AARCH64 C语言栈回溯结构中,一般使用x30作为返回地址,x29保存sp。x0用作第一个参数,x1用作第二个参数。xv6的调度程序如下。

void scheduler(void)
{
    struct proc *p;
    for(;;){
        // Enable interrupts on this processor.
        sti();
        // Loop over process table looking for process to run.
        acquire(&ptable.lock);
        for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
            if(p->state != RUNNABLE) {
                //_puts("scheduler3\n");
                continue;
            }
            // Switch to chosen process.  It is the process's job
            // to release ptable.lock and then reacquire it
            // before jumping back to us.
            proc = p;
            switchuvm(p);
            p->state = RUNNING;
            swtch(&cpu->scheduler, proc->context);
            // Process is done running for now.
            // It should have changed its p->state before coming back.
            proc = 0;
        }
        release(&ptable.lock);
    }
}

       这里x0=&cpu->scheduler,x1=proc->context。swtch在swtch.S中实现。

.global swtch

swtch:
	stp	x29, x30, [sp, #-16]!
	stp	x27, x28, [sp, #-16]!
	stp	x25, x26, [sp, #-16]!
	stp	x23, x24, [sp, #-16]!
	stp	x21, x22, [sp, #-16]!
	stp	x19, x20, [sp, #-16]!
	stp	x17, x18, [sp, #-16]!
	stp	x15, x16, [sp, #-16]!
	stp	x13, x14, [sp, #-16]!
	stp	x11, x12, [sp, #-16]!
	stp	x9, x10, [sp, #-16]!
	stp	x7, x8, [sp, #-16]!
	stp	x5, x6, [sp, #-16]!
	stp	x4, x3,	[sp, #-16]!
_sw_1:
	# switch the stack
	mov	x21, sp
	str	x21, [x0]
	mov	sp, x1
_sw_2:
	ldp	x3, x4, [sp], #16
	ldp	x5, x6, [sp], #16
	ldp	x7, x8, [sp], #16
	ldp	x9, x10, [sp], #16
	ldp	x11, x12, [sp], #16
	ldp	x13, x14, [sp], #16
	ldp	x15, x16, [sp], #16
	ldp	x17, x18, [sp], #16
	ldp	x19, x20, [sp], #16
	ldp	x21, x22, [sp], #16
	ldp	x23, x24, [sp], #16
	ldp	x25, x26, [sp], #16
	ldp	x27, x28, [sp], #16
	ldp	x29, x30, [sp], #16
_sw_3:
	# return to the caller
	br	x30

        将所有入栈栈帧sp(x29)进行16字节对齐。

sp &= (~15);

        第一个进程的栈结构如下。

        第一个进程的启动过程参考xv6中文文档

五、SD卡运行程序

         在vitis中通过jtag调试好程序后,可以通过create boot image生成BOOT.bin文件拷贝到U盘根目录下,同时通过拨码开关调整启动方式一般就可以了,但是却遇到了意外,xv6在运行到cprinf函数时产生了同步异常,在jtag调试时没有任何问题,怀疑是系统配置导致。

        通过gcc -S 生成void cprintf (char *fmt, ...)函数的汇编代码:

cprintf:
	stp	x29, x30, [sp, -256]!
	add	x29, sp, 0
	str	x0, [x29, 24]
	str	x1, [x29, 200]
	str	x2, [x29, 208]
	str	x3, [x29, 216]
	str	x4, [x29, 224]
	str	x5, [x29, 232]
	str	x6, [x29, 240]
	str	x7, [x29, 248]
	str	q0, [x29, 64]
	str	q1, [x29, 80]
	str	q2, [x29, 96]
	str	q3, [x29, 112]
	str	q4, [x29, 128]
	str	q5, [x29, 144]
	str	q6, [x29, 160]
	str	q7, [x29, 176]
	adrp	x0, cons

        系统初始化阶段已经配置了CPACR_EL1寄存器。   

// no trapping on FP/SIMD instructions
    val32 = 0x03 << 20;
    asm("MSR CPACR_EL1, %[v]": :[v]"r" (val32):);

        暂时不去深究原因,暴力解决。手工去掉 str q0,~str q7相关代码,重新编译后,生成BOOT.bin,系统正常启动:

================= In Stage 4 ============ 
PMU-FW is not running, certain applications may not be supported.
Protection configuration applied                                 
Exit from FSBL 
starting-xv6-for-ARMv8..
Implementer: ARM Limited
Current EL: EL1
Flushing TLB and Instr Cache
clearing BSS section for the main kernel

**************************************************************************
**                                                                      **
**                                                                      **
**                  xv6 on ARMv8-A (64-bit) Architecture                **
**                                                                      **
**                                                                      **
**************************************************************************
*
init: Starting Shell
$ ls

.              1 1 512
..             1 1 512
cat            2 2 13592
echo           2 3 13088
grep           2 4 15176
info           2 5 13032
init           2 6 13808
kill           2 7 13064
ln             2 8 13064
ls             2 9 14832
mkdir          2 10 13144
rm             2 11 13136
sh             2 12 22048
stressfs       2 13 13552
usertests      2 14 47064
wc             2 15 14024
zombie         2 16 12776
UNIX           2 17 7828
console        3 18 0

        最后的vitis项目文件, 提取码:64pb 。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值