RISCVlinuxkernel启动代码分析之一:启动汇编部分代码

原创 嵌入式Lee 2024-11-12 08:01

一. 前言

本文主要分析RISCV linux kernel的启动汇编部分代码。先结合链接脚本和汇编代码介绍镜像头和启动汇编代码部分的执行过程。后面文章再详细分析重定向,mmu设置等相关重点内容。

代码路径arch/riscv/kernel/head.S, 不同版本内核可能有点差异。

二. 根据链接脚本查找入口与镜像头介绍

2.1 链接脚本

内核根目录Makefile

export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds

指定了链接脚本

实际来自于arch/riscv/kernel/vmlinux.lds.S

include/asm-generic/vmlinux.lds.h

scripts/link-vmlinux.shld链接时指定-T参数,指定该链接脚本

  local lds="${objtree}/${KBUILD_LDS}"

2.2 通过链接脚本对应汇编入口

链接脚本vmlinux.lds最开始处

  /* Beginning of code and text segment */  . = LOAD_OFFSET;  _start = .;  HEAD_TEXT_SECTION  . = ALIGN(PAGE_SIZE);

即对应的入口

查找_start即可搜索到对应的代码位于

arch/riscv/kernel/head.S

来看该处汇编代码

首先宏

__HEAD

include/linux/init.h中定义

#define __HEAD .section ".head.text","ax"

即后续代码放在段.head.text,ax表示可执行。

然后是

ENTRY(_start)

...

END(_start)
ENTRY,END的宏在include/linux/linkage.h中定义
#ifndef ENTRY
/* deprecated, use SYM_FUNC_START */
#define ENTRY(name) \
SYM_FUNC_START(name)
#endif
#ifndef END
/* deprecated, use SYM_FUNC_END, SYM_DATA_END, or SYM_END */
#define END(name) \
.size name, .-name
#endif

SYM_FUNC_START又定义如下

/* SYM_FUNC_START -- use for global functions */
#ifndef SYM_FUNC_START
/*
* The same as SYM_FUNC_START_ALIAS, but we will need to distinguish these two
* later.
*/
#define SYM_FUNC_START(name) \
SYM_START(name, SYM_L_GLOBAL, SYM_A_ALIGN)
#endif

SYM_START又定义如下

/* SYM_START -- use only if you have to */
#ifndef SYM_START
#define SYM_START(name, linkage, align...) \
SYM_ENTRY(name, linkage, align)
#endif

SYM_ENTRY又定义如下

/* SYM_ENTRY -- use only if you have to for non-paired symbols */
#ifndef SYM_ENTRY
#define SYM_ENTRY(name, linkage, align...) \
linkage(name) ASM_NL \
align ASM_NL \
name:
#endif

SYM_L_GLOBAL定义如下

#define SYM_L_GLOBAL(name)          .globl name

ASM_NL定义如下

#ifndef ASM_NL
#define ASM_NL ;
#endif

最终展开如下,即定义了标签_start:

.globl _start;

align ;

_start:

......

.size _start, .-_start

其中.size指定符号的大小.-_start表示当前位置减去_start标签的位置,_start到该处的大小。

arch/riscv/kernel/vmlinux.lds.S

SECTIONS{  /* Beginning of code and text segment */  . = LOAD_OFFSET;  _start = .;  HEAD_TEXT_SECTION
HEAD_TEXT_SECTION在include/asm-generic/vmlinux.lds.h

中定义

/* Section used for early init (in .S files) */
#define HEAD_TEXT KEEP(*(.head.text))
#define HEAD_TEXT_SECTION \
.head.text : AT(ADDR(.head.text) - LOAD_OFFSET) { \
HEAD_TEXT \
}

head.S最开始的代码放在了

.head.text,LOAD_OFFSET开始处。

LOAD_OFFSET

#define LOAD_OFFSET PAGE_OFFSET

arch/riscv/Kconfig中可menuconfig配置

config PAGE_OFFSET
hex
default 0xC0000000 if 32BIT && MAXPHYSMEM_2GB
default 0x80000000 if 64BIT && !MMU
default 0xffffffff80000000 if 64BIT && MAXPHYSMEM_2GB
default 0xffffffe000000000 if 64BIT && MAXPHYSMEM_128GB

最后根据menuconfig配置生成的.config

CONFIG_PAGE_OFFSET=0xffffffe000000000

arch/riscv/Makefile中最终会使用该宏

KBUILD_CFLAGS += -DCONFIG_PAGE_OFFSET=$(CONFIG_PAGE_OFFSET)

arch/riscv/include/asm/page.h中使用该宏

/*
* PAGE_OFFSET -- the first address of the first page of memory.
* When not using MMU this corresponds to the first free page in
* physical memory (aligned on a page boundary).
*/
#define PAGE_OFFSET _AC(CONFIG_PAGE_OFFSET, UL)

_AC宏定义在include/uapi/linux/const.h

汇编时不变(定义了__ASSEMBLY__)

#ifdef __ASSEMBLY__
#define _AC(X,Y) X
#define _AT(T,X) X
#else
#define __AC(X,Y) (X##Y)
#define _AC(X,Y) __AC(X,Y)
#define _AT(T,X) ((T)(X))
#endif

否则为__AC, X##Y即加后缀ul, 0xffffffe000000000ul

使用以下命令查看vmlinux中的段对应

riscv64-unknown-linux-gnu-readelf -S -l output/vmlinux

Section Headers:  [Nr] Name              Type             Address           Offset       Size              EntSize          Flags  Link  Info  Align  [ 0]                   NULL             0000000000000000  00000000       0000000000000000  0000000000000000           0     0     0  [ 1] .head.text        PROGBITS         ffffffe000000000  00001000       0000000000001ea4  0000000000000000  AX       0     0     4096

可以看到 MAXPHYSMEM_128GB,64BIT,链接地址是0xffffffe000000000(虚拟地址),实际会加载到0x80200000处执行,MMU配置之后重定向使用0xffffffe000000000(虚拟地址)

2.3镜像头

Output/vmlinuxelf格式输出文件,而arch/riscv/boot/Image是二进制镜像,完全和内核代码二进制对应。Image的开始处即head.s处的代码。最开始的一段字节即镜像头

头格式见arch/riscv/include/asm/image.h

对于EFI4K对齐的头,对于非EFI64字节。

/**
* struct riscv_image_header - riscv kernel image header
* @code0: Executable code
* @code1: Executable code
* @text_offset: Image load offset (little endian)
* @image_size: Effective Image size (little endian)
* @flags: kernel flags (little endian)
* @version: version
* @res1: reserved
* @res2: reserved
* @magic: Magic number (RISC-V specific; deprecated)
* @magic2: Magic number 2 (to match the ARM64 'magic' field pos)
* @res3: reserved (will be used for PE COFF offset)
*
* The intention is for this header format to be shared between multiple
* architectures to avoid a proliferation of image header formats.
*/
struct riscv_image_header {
u32 code0;
u32 code1;
u64 text_offset;
u64 image_size;
u64 flags;
u32 version;
u32 res1;
u64 res2;
u64 magic;
u32 magic2;
u32 res3;
};

看汇编代码head.S

/*   * Image header expected by Linux boot-loaders. The image header data   * structure is described in asm/image.h.   * Do not modify it without modifying the structure and all bootloaders   * that expects this header format!!   */#ifdef CONFIG_EFI  /*   * This instruction decodes to "MZ" ASCII required by UEFI.   */  c.li s4,-13  j _start_kernel#else  /* jump to start kernel */  j _start_kernel  /* reserved */  .word 0#endif  .balign 8

.balign 8表示后面从8字节开始对齐。前面8字节就对应code0code1.

前面如果支持EFI(配置了CONFIG_EFI)则是

c.li s4,-13

j _start_kernel

否则是

j _start_kernel

.word 0

即配置支持EFI则最开始两字节必须是MZ,这里通过指令c.li s4,-13的指令码凑出来。

然后是跳转到_start_kernel处的指令。

如果是不支持EFI则直接是跳转指令,后面填充0.

riscv64-unknown-linux-gnu-objdump -l -S vmlinux > a.s可以查看到汇编指令
ffffffe000000000 <_start>:_start():arch/riscv/kernel/head.S:29 */#ifdef CONFIG_EFI /* * This instruction decodes to "MZ" ASCII required by UEFI. */ c.li s4,-13ffffffe000000000: 5a4d li s4,-13arch/riscv/kernel/head.S:30 j _start_kernelffffffe000000002: 7ff0106f j ffffffe000002000 <_start_kernel>ffffffe000000006: 0001 nopffffffe000000008: 0000 unimpffffffe00000000a: 0020 addi s0,sp,8ffffffe00000000c: 0000 unimpffffffe00000000e: 0000 unimpffffffe000000010: e000 sd s0,0(s0)ffffffe000000012: 01bc addi a5,sp,200

也可以直接查看Image的二进制值

hexdump arch/riscv/boot/Image -C -n 64

00000000  4d 5a 6f 10 f0 7f 01 00  00 00 20 00 00 00 00 00  |MZo....... .....|00000010  00 e0 1c 01 00 00 00 00  00 00 00 00 00 00 00 00  |................|00000020  02 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|00000030  52 49 53 43 56 00 00 00  52 53 43 05 40 00 00 00  |RISCV...RSC.@...|00000040

可以看到这里配置支持EFI,MZ开头。

然后接下来是text_offset,如果配置了CONFIG_RISCV_M_MODE则偏移为0,否则32位偏移0x400000, 64位偏移0x200000.

#ifdef CONFIG_RISCV_M_MODE  /* Image load offset (0MB) from start of RAM for M-mode */  .dword 0#else#if __riscv_xlen == 64  /* Image load offset(2MB) from start of RAM */  .dword 0x200000#else  /* Image load offset(4MB) from start of RAM */  .dword 0x400000#endif#endif

然后是镜像大小,image_size

/* Effective size of kernel image */

.dword _end - _start

这里通过两个符号_end - _start获取,

arch/riscv/kernel/vmlinux.lds.S中的符号,分别代表镜像的开始结束位置。

_start = .;

_end = .;

这里看到的值是

00 e0 1c 01 00 00 00 00小端, 0x11CE000=18669568

查看镜像大小

ls -al arch/riscv/boot/Image

-rw-r--r-- 1 18314240 Oct 31 22:15 arch/riscv/boot/Image

(??这里为什么实际大小和头中大小对应不上)

然后是

.dword __HEAD_FLAGS

.word RISCV_HEADER_VERSION

.word 0

.dword 0

.ascii RISCV_IMAGE_MAGIC

.balign 4

.ascii RISCV_IMAGE_MAGIC2

u64 flags;
u32 version;
u32 res1;
u64 res2;
u64 magic;
u32 magic2;

00000000  4d 5a 6f 10 f0 7f 01 00  00 00 20 00 00 00 00 00  |MZo....... .....|00000010  00 e0 1c 01 00 00 00 00  00 00 00 00 00 00 00 00  |................|00000020  02 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|00000030  52 49 53 43 56 00 00 00  52 53 43 05 40 00 00 00  |RISCV...RSC.@...|00000040

__HEAD_FLAGSarch/riscv/include/asm/image.h中定义

这里是小端,展开后为0

#define RISCV_IMAGE_FLAG_BE_SHIFT   0
#define RISCV_IMAGE_FLAG_BE_MASK 0x1
#define RISCV_IMAGE_FLAG_LE 0
#define RISCV_IMAGE_FLAG_BE 1
#ifdef CONFIG_CPU_BIG_ENDIAN
#error conversion of header fields to LE not yet implemented
#else
#define __HEAD_FLAG_BE RISCV_IMAGE_FLAG_LE
#endif
#define __HEAD_FLAG(field) (__HEAD_FLAG_##field << \
RISCV_IMAGE_FLAG_##field##_SHIFT)
#define __HEAD_FLAGS (__HEAD_FLAG(BE))

Version2

magicmagic2RISCV_IMAGE_MAGICRISCV_IMAGE_MAGIC2

arch/riscv/include/asm/image.h中定义

#define RISCV_IMAGE_MAGIC "RISCV\0\0\0"

#define RISCV_IMAGE_MAGIC2 "RSC\x05"

接下来

#ifdef CONFIG_EFI  .word pe_head_start - _startpe_head_start:
__EFI_PE_HEADER#else .word 0#endif

如果配置支持EFI.word pe_head_start - _start,即后面pe_head_start位置到_start的偏移,

即包括这个.word本身前面的字节数,这里是是64字节40 00 00 00

如果不支持EFI则这里是0

00000000 4d 5a 6f 10 f0 7f 01 00  00 00 20 00 00 00 00 00  |MZo....... .....|

00000010 00 e0 1c 01 00 00 00 00  00 00 00 00 00 00 00 00  |................|

00000020 02 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

00000030 52 49 53 43 56 00 00 00  52 53 43 05 40 00 00 00  |RISCV...RSC.@...|

00000040

如果支持EFI__EFI_PE_HEADER后面放EFI, EFI后面头按照0x1000对齐。

.balign 0x1000

efi_header_end:

arch/riscv/kernel/efi-header.S中定义,EFI头这里不再详叙。

.macro  __EFI_PE_HEADER  .long  PE_MAGICcoff_header:#ifdef CONFIG_64BIT  .short  IMAGE_FILE_MACHINE_RISCV64    // Machine#else  .short  IMAGE_FILE_MACHINE_RISCV32    // Machine#endif  .short  section_count        // NumberOfSections  .long  0           // TimeDateStamp  .long  0          // PointerToSymbolTable  .long  0          // NumberOfSymbols  .short  section_table - optional_header    // SizeOfOptionalHeader  .short  IMAGE_FILE_DEBUG_STRIPPED | \    IMAGE_FILE_EXECUTABLE_IMAGE | \    IMAGE_FILE_LINE_NUMS_STRIPPED    // Characteristics
optional_header:#ifdef CONFIG_64BIT .short PE_OPT_MAGIC_PE32PLUS // PE32+ format#else .short PE_OPT_MAGIC_PE32 // PE32 format#endif .byte 0x02 // MajorLinkerVersion .byte 0x14 // MinorLinkerVersion .long __pecoff_text_end - efi_header_end // SizeOfCode .long __pecoff_data_virt_size // SizeOfInitializedData .long 0 // SizeOfUninitializedData .long __efistub_efi_pe_entry - _start // AddressOfEntryPoint .long efi_header_end - _start // BaseOfCode#ifdef CONFIG_32BIT .long __pecoff_text_end - _start // BaseOfData#endif
extra_header_fields: .quad 0 // ImageBase .long PECOFF_SECTION_ALIGNMENT // SectionAlignment .long PECOFF_FILE_ALIGNMENT // FileAlignment .short 0 // MajorOperatingSystemVersion .short 0 // MinorOperatingSystemVersion .short LINUX_EFISTUB_MAJOR_VERSION // MajorImageVersion .short LINUX_EFISTUB_MINOR_VERSION // MinorImageVersion .short 0 // MajorSubsystemVersion .short 0 // MinorSubsystemVersion .long 0 // Win32VersionValue
.long _end - _start // SizeOfImage
// Everything before the kernel image is considered part of the header .long efi_header_end - _start // SizeOfHeaders .long 0 // CheckSum .short IMAGE_SUBSYSTEM_EFI_APPLICATION // Subsystem .short 0 // DllCharacteristics .quad 0 // SizeOfStackReserve .quad 0 // SizeOfStackCommit .quad 0 // SizeOfHeapReserve .quad 0 // SizeOfHeapCommit .long 0 // LoaderFlags .long (section_table - .) / 8 // NumberOfRvaAndSizes
.quad 0 // ExportTable .quad 0 // ImportTable .quad 0 // ResourceTable .quad 0 // ExceptionTable .quad 0 // CertificationTable .quad 0 // BaseRelocationTable
// Section tablesection_table: .ascii ".text\0\0\0" .long __pecoff_text_end - efi_header_end // VirtualSize .long efi_header_end - _start // VirtualAddress .long __pecoff_text_end - efi_header_end // SizeOfRawData .long efi_header_end - _start // PointerToRawData
.long 0 // PointerToRelocations .long 0 // PointerToLineNumbers .short 0 // NumberOfRelocations .short 0 // NumberOfLineNumbers .long IMAGE_SCN_CNT_CODE | \ IMAGE_SCN_MEM_READ | \ IMAGE_SCN_MEM_EXECUTE // Characteristics
.ascii ".data\0\0\0" .long __pecoff_data_virt_size // VirtualSize .long __pecoff_text_end - _start // VirtualAddress .long __pecoff_data_raw_size // SizeOfRawData .long __pecoff_text_end - _start // PointerToRawData
.long 0 // PointerToRelocations .long 0 // PointerToLineNumbers .short 0 // NumberOfRelocations .short 0 // NumberOfLineNumbers .long IMAGE_SCN_CNT_INITIALIZED_DATA | \ IMAGE_SCN_MEM_READ | \ IMAGE_SCN_MEM_WRITE // Characteristics
.set section_count, (. - section_table) / 40
.balign 0x1000efi_header_end: .endm

三. 启动汇编代码介绍

入口调用j _start_kernel跳到_start_kernel执行,对应代码如下


__INITENTRY(_start_kernel) /* Mask all interrupts */ csrw CSR_IE, zero csrw CSR_IP, zero
#ifdef CONFIG_RISCV_M_MODE /* flush the instruction cache */ fence.i
/* Reset all registers except ra, a0, a1 */ call reset_regs
/* * Setup a PMP to permit access to all of memory. Some machines may * not implement PMPs, so we set up a quick trap handler to just skip * touching the PMPs on any trap. */ la a0, pmp_done csrw CSR_TVEC, a0
li a0, -1 csrw CSR_PMPADDR0, a0 li a0, (PMP_A_NAPOT | PMP_R | PMP_W | PMP_X) csrw CSR_PMPCFG0, a0.align 2pmp_done:
/* * The hartid in a0 is expected later on, and we have no firmware * to hand it to us. */ csrr a0, CSR_MHARTID#endif /* CONFIG_RISCV_M_MODE */
/* Load the global pointer */.option push.option norelax la gp, __global_pointer$.option pop
/* * Disable FPU to detect illegal usage of * floating point in kernel space */ li t0, SR_FS | SR_VS csrc CSR_STATUS, t0
#ifdef CONFIG_SMP li t0, CONFIG_NR_CPUS blt a0, t0, .Lgood_cores tail .Lsecondary_park.Lgood_cores:#endif
/* Pick one hart to run the main boot sequence */ la a3, hart_lottery li a2, 1 amoadd.w a3, a2, (a3) bnez a3, .Lsecondary_start
/* Clear BSS for flat non-ELF images */ la a3, __bss_start la a4, __bss_stop ble a4, a3, clear_bss_doneclear_bss: REG_S zero, (a3) add a3, a3, RISCV_SZPTR blt a3, a4, clear_bssclear_bss_done:
/* Save hart ID and DTB physical address */ mv s0, a0 mv s1, a1 la a2, boot_cpu_hartid REG_S a0, (a2)
/* Initialize page tables and relocate to virtual addresses */ la sp, init_thread_union + THREAD_SIZE mv a0, s1 call setup_vm#ifdef CONFIG_MMU la a0, early_pg_dir call relocate#endif /* CONFIG_MMU */
call setup_trap_vector /* Restore C environment */ la tp, init_task sw zero, TASK_TI_CPU(tp) la sp, init_thread_union + THREAD_SIZE
#ifdef CONFIG_KASAN call kasan_early_init#endif /* Start the kernel */ call soc_early_init tail start_kernel
.Lsecondary_start:#ifdef CONFIG_SMP /* Set trap vector to spin forever to help debug */ la a3, .Lsecondary_park csrw CSR_TVEC, a3
slli a3, a0, LGREG la a1, __cpu_up_stack_pointer la a2, __cpu_up_task_pointer add a1, a3, a1 add a2, a3, a2
/* * This hart didn't win the lottery, so we wait for the winning hart to * get far enough along the boot process that it should continue. */.Lwait_for_cpu_up: /* FIXME: We should WFI to save some energy here. */ REG_L sp, (a1) REG_L tp, (a2) beqz sp, .Lwait_for_cpu_up beqz tp, .Lwait_for_cpu_up fence
tail secondary_start_common#endif
END(_start_kernel)

首先

__INIT

include/linux/init.h中定义

即后面代码放在了.init.text,可执行

#define __INIT      .section    ".init.text","ax"

ENTRY(_start_kernel)

END(_start_kernel)

类似前面的ENTRY(_start) END(_start)

定义了标签_start_kernel

3.1 关闭中断

接着清除总中断和标志

/* Mask all interrupts */

csrw CSR_IE, zero

csrw CSR_IP, zero

CSR_IE,CSR_IParch/riscv/include/asm/csr.h中根据运行在M模式还是S模式分别定义为对应的CSR寄存器。

M模式

# define CSR_IE     CSR_MIE
# define CSR_IP CSR_MIP

S模式

# define CSR_IE     CSR_SIE
# define CSR_IP CSR_SIP

3.2 M模式配置

以下代码配置CONFIG_RISCV_M_MODE才执行

#ifdef CONFIG_RISCV_M_MODE  /* flush the instruction cache */  fence.i
/* Reset all registers except ra, a0, a1 */ call reset_regs
/* * Setup a PMP to permit access to all of memory. Some machines may * not implement PMPs, so we set up a quick trap handler to just skip * touching the PMPs on any trap. */ la a0, pmp_done csrw CSR_TVEC, a0
li a0, -1 csrw CSR_PMPADDR0, a0 li a0, (PMP_A_NAPOT | PMP_R | PMP_W | PMP_X) csrw CSR_PMPCFG0, a0.align 2pmp_done:
/* * The hartid in a0 is expected later on, and we have no firmware * to hand it to us. */ csrr a0, CSR_MHARTID#endif /* CONFIG_RISCV_M_MODE */

fence.i flush指令cache

然后是call reset_regs清除所有寄存器,除了(ra a0 a1, ra是返回地址,a0记录了hardida1记录了设备树地址)

#ifdef CONFIG_RISCV_M_MODEENTRY(reset_regs)  li  sp, 0  li  gp, 0  li  tp, 0  li  t0, 0  li  t1, 0  li  t2, 0  li  s0, 0  li  s1, 0  li  a2, 0  li  a3, 0  li  a4, 0  li  a5, 0  li  a6, 0  li  a7, 0  li  s2, 0  li  s3, 0  li  s4, 0  li  s5, 0  li  s6, 0  li  s7, 0  li  s8, 0  li  s9, 0  li  s10, 0  li  s11, 0  li  t3, 0  li  t4, 0  li  t5, 0  li  t6, 0  csrw  CSR_SCRATCH, 0
#ifdef CONFIG_FPU csrr t0, CSR_MISA andi t0, t0, (COMPAT_HWCAP_ISA_F | COMPAT_HWCAP_ISA_D) beqz t0, .Lreset_regs_done
li t1, SR_FS csrs CSR_STATUS, t1 fmv.s.x f0, zero fmv.s.x f1, zero fmv.s.x f2, zero fmv.s.x f3, zero fmv.s.x f4, zero fmv.s.x f5, zero fmv.s.x f6, zero fmv.s.x f7, zero fmv.s.x f8, zero fmv.s.x f9, zero fmv.s.x f10, zero fmv.s.x f11, zero fmv.s.x f12, zero fmv.s.x f13, zero fmv.s.x f14, zero fmv.s.x f15, zero fmv.s.x f16, zero fmv.s.x f17, zero fmv.s.x f18, zero fmv.s.x f19, zero fmv.s.x f20, zero fmv.s.x f21, zero fmv.s.x f22, zero fmv.s.x f23, zero fmv.s.x f24, zero fmv.s.x f25, zero fmv.s.x f26, zero fmv.s.x f27, zero fmv.s.x f28, zero fmv.s.x f29, zero fmv.s.x f30, zero fmv.s.x f31, zero csrw fcsr, 0 /* note that the caller must clear SR_FS */#endif /* CONFIG_FPU */.Lreset_regs_done: retEND(reset_regs)#endif /* CONFIG_RISCV_M_MODE */

然后设置PMP,设置所有区域都可读可写可执行。

  /*   * Setup a PMP to permit access to all of memory.  Some machines may   * not implement PMPs, so we set up a quick trap handler to just skip   * touching the PMPs on any trap.   */  la a0, pmp_done  csrw CSR_TVEC, a0
li a0, -1 csrw CSR_PMPADDR0, a0 li a0, (PMP_A_NAPOT | PMP_R | PMP_W | PMP_X) csrw CSR_PMPCFG0, a0.align 2pmp_done:

这里有个特别处理,即先设置异常处理入口CSR_TVEC(根据运行在M模式还是S模式分别是CSR_MTVEC,CSR_STVEC)pmp_done:, 这样如果不支持PMP则操作CSR_PMPADDR0时进入异常地址,直接跳到pmp_done:后继续执行,pmp_done:处2字节对齐。

la a0, pmp_done

csrw CSR_TVEC, a0

li a0, -1

csrw CSR_PMPADDR0, a0

即设置PMPADDR0寄存器为全F,这里写-1,对于32位即0xFFFFFFFF,对于64位即0xFFFFFFFFFFFFFFFF

然后设置PMPCFG0的属性,可读可写可执行

li a0, (PMP_A_NAPOT | PMP_R | PMP_W | PMP_X)

csrw CSR_PMPCFG0, a0

PMP的设置参考规格书he RISC-V Instruction Set Manual: Volume II的《3.7. Physical Memory Protection

3.3获取当前ID

/*

 * The hartid in a0 is expected later on, and we have no firmware

 * to hand it to us.

 */

csrr a0, CSR_MHARTID

hartida0

3.4设置GP

/* Load the global pointer */

.option push

.option norelax

la gp, __global_pointer$

.option pop

push配置,然后修改为norelax配置,因为还未配置gp所以告诉编译器norelax不使用gp,最后pop恢复配置。

3.5关闭FPU/VECTOR

/*

 * Disable FPU to detect illegal usage of

 * floating point in kernel space

 */

li t0, SR_FS | SR_VS

csrc CSR_STATUS, t0

arch/riscv/include/asm/csr.h中定义

#define SR_FS       _AC(0x00006000, UL) /* Floating-point Status */
#if (defined(CONFIG_VECTOR_1_0) && defined(__THEAD_VERSION__))
#define SR_VS _AC(0x00000600, UL) /* Vector Status */
#else
#define SR_VS _AC(0x01800000, UL) /* Vector Status */

同样CSR_STATUS根据M模式还是S模式对应

CSR_MSTATUSCSR_SSTATUS

3.6同构多核启动参数检查

#ifdef CONFIG_SMP  li t0, CONFIG_NR_CPUS  blt a0, t0, .Lgood_cores  tail .Lsecondary_park.Lgood_cores:#endif

对于M模式,a0是前面读出来的当前hardid

csrr a0, CSR_MHARTID

对于非M模式,a0opensbi传入进来的参数,表示启动的hardid

检查下a0要小于CONFIG_NR_CPUS

CONFIG_NR_CPUSKconfig中配置。

.config

CONFIG_NR_CPUS=8

如果a0大于核数则进入以下,死循环。

.Lsecondary_park:

/* We lack SMP support or have too many harts, so park this hart */

wfi

j .Lsecondary_park

3.7选择核启动

/* Pick one hart to run the main boot sequence */

la a3, hart_lottery

li a2, 1

amoadd.w a3, a2, (a3)

bnez a3, .Lsecondary_start

hart_lottery是全局变量, 将其地址加载到a3

然后通过 amoadd.w a3, a2, (a3)

hart_lottery的值读出到a3,并将hart_lottrry递增1.

如果读出到a3的值不为0,说明别的核心先给其递增了,本核心没有抢占到,则跳到Lsecondary_start等待其他核心初始化。

读出a30则本核心抢占到,继续初始化。

3.8非启动核

.Lsecondary_start:#ifdef CONFIG_SMP  /* Set trap vector to spin forever to help debug */  la a3, .Lsecondary_park  csrw CSR_TVEC, a3
slli a3, a0, LGREG la a1, __cpu_up_stack_pointer la a2, __cpu_up_task_pointer add a1, a3, a1 add a2, a3, a2
/* * This hart didn't win the lottery, so we wait for the winning hart to * get far enough along the boot process that it should continue. */.Lwait_for_cpu_up: /* FIXME: We should WFI to save some energy here. */ REG_L sp, (a1) REG_L tp, (a2) beqz sp, .Lwait_for_cpu_up beqz tp, .Lwait_for_cpu_up fence
tail secondary_start_common#endif

先初始化异常入口为Lsecondary_park

la a3, .Lsecondary_park

csrw CSR_TVEC, a3

然后

获取a1__cpu_up_stack_pointer[hartid]的地址

a2__cpu_up_task_pointer[hartid]的地址,

64位则LGREG332为为2slli左移,

所以64位则偏移地址位haridx832位位haridx4

slli a3, a0, LGREG  la a1, __cpu_up_stack_pointer  la a2, __cpu_up_task_pointer  add a1, a3, a1  add a2, a3, a2

然后不断读__cpu_up_stack_pointer[harid]

__cpu_up_task_pointer[hartid]直到两者不为0

注意fence flush i d cache

/*   * This hart didn't win the lottery, so we wait for the winning hart to   * get far enough along the boot process that it should continue.   */.Lwait_for_cpu_up:  /* FIXME: We should WFI to save some energy here. */  REG_L sp, (a1)  REG_L tp, (a2)  beqz sp, .Lwait_for_cpu_up  beqz tp, .Lwait_for_cpu_up  fence

跳入

.global secondary_start_commonsecondary_start_common:
#ifdef CONFIG_MMU /* Enable virtual memory and relocate to virtual address */ la a0, swapper_pg_dir call relocate#endif call setup_trap_vector tail smp_callin#endif /* CONFIG_SMP */

支持MMU则调用relocate,否则设置异常入口,调用smp_callin

重定向和smp_callin后续再分析。

3.9启动核清除bss

/* Clear BSS for flat non-ELF images */  la a3, __bss_start  la a4, __bss_stop  ble a4, a3, clear_bss_doneclear_bss:  REG_S zero, (a3)  add a3, a3, RISCV_SZPTR  blt a3, a4, clear_bssclear_bss_done:

这里没什么特殊的从连接脚本中获取bss段开始结束地址

__bss_start__bss_stop遍历将这段内存清除为0.

3.10保存hardid和设备树地址

/* Save hart ID and DTB physical address */  mv s0, a0  mv s1, a1  la a2, boot_cpu_hartid  REG_S a0, (a2)

a0 hardid

a1 设备树地址

保存到s0 s1

并将a0 hardid值保存到全局变量boot_cpu_hartid.

这里保存a0 a1是因为后面要调用c函数要使用这两个寄存器所以要保存。

3.11初始化栈指针

/* Initialize page tables and relocate to virtual addresses */

la sp, init_thread_union + THREAD_SIZE

arch/riscv/include/asm/thread_info.h

/* thread information allocation */
#ifdef CONFIG_64BIT
#define THREAD_SIZE_ORDER (2)
#else
#define THREAD_SIZE_ORDER (1)
#endif
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)

所以这里THREAD_SIZE4k<<2=16k.

init_thread_union来源于

include/asm-generic/vmlinux.lds.h

预留的初始化栈大小空间

#define INIT_TASK_DATA(align)                       \
. = ALIGN(align); \
__start_init_task = .; \
init_thread_union = .; \
init_stack = .; \
KEEP(*(.data..init_task)) \
KEEP(*(.data..init_thread_info)) \
. = __start_init_task + THREAD_SIZE; \
__end_init_task = .;

3.12 调用setup_vm

前面配置了sp栈准备好之后可以调用c

mv a0, s1

call setup_vm

参数a0为设备树地址

后续再分析该部分mmu配置。

3.13 重定向

#ifdef CONFIG_MMU

la a0, early_pg_dir

call relocate

#endif /* CONFIG_MMU */

后面再分析重定向部分。

3.14 设置异常入口

call setup_trap_vector

设置异常入口为handle_exception

.align 2setup_trap_vector:  /* Set trap vector to exception handler */  la a0, handle_exception  csrw CSR_TVEC, a0
/* * Set sup0 scratch register to 0, indicating to exception vector that * we are presently executing in kernel. */ csrw CSR_SCRATCH, zero ret

3.15重新设置栈准备c环境

  /* Restore C environment */  la tp, init_task  sw zero, TASK_TI_CPU(tp)  la sp, init_thread_union + THREAD_SIZE

全局变量init_task偏移TASK_TI_CPU处设置为0

#define TASK_TI_CPU 32 /* offsetof(struct task_struct, thread_info.cpu) */

struct task_struct

init/init_task.c中的

struct task_struct init_task

重新设置sp指针到init_thread_union + THREAD_SIZE

前面已经使用了sp所以这里可再重新设置。

3.16 Kasan

#ifdef CONFIG_KASAN

call kasan_early_init

#endif

后续再分析。

3.17 早期初始化

call soc_early_init

后续再分析

3.18 启动内核

tail start_kernel

后续再分析

3.19 对齐

__PAGE_ALIGNED_BSS

/* Empty zero page */

.balign PAGE_SIZE

include/linux/linkage.h,

#define __PAGE_ALIGNED_BSS  .section ".bss..page_aligned", "aw"

对齐大小为PAGE_SIZE

四. 总结

头信息对应如下

整个汇编代码的流程如下






评论 (0)
  • 职场之路并非一帆风顺,从初入职场的新人成长为团队中不可或缺的骨干,背后需要经历一系列内在的蜕变。许多人误以为只需努力工作便能顺利晋升,其实核心在于思维方式的更新。走出舒适区、打破旧有框架,正是让自己与众不同的重要法宝。在这条道路上,你不只需要扎实的技能,更需要敏锐的观察力、不断自省的精神和前瞻的格局。今天,就来聊聊那改变命运的三大思维转变,让你在职场上稳步前行。工作初期,总会遇到各式各样的难题。最初,我们习惯于围绕手头任务来制定计划,专注于眼前的目标。然而,职场的竞争从来不是单打独斗,而是团队协
    优思学院 2025-04-01 17:29 198浏览
  • 探针本身不需要对焦。探针的工作原理是通过接触被测物体表面来传递电信号,其精度和使用效果取决于探针的材质、形状以及与检测设备的匹配度,而非对焦操作。一、探针的工作原理探针是检测设备中的重要部件,常用于电子显微镜、坐标测量机等精密仪器中。其工作原理主要是通过接触被测物体的表面,将接触点的位置信息或电信号传递给检测设备,从而实现对物体表面形貌、尺寸或电性能等参数的测量。在这个过程中,探针的精度和稳定性对测量结果具有至关重要的影响。二、探针的操作要求在使用探针进行测量时,需要确保探针与被测物体表面的良好
    锦正茂科技 2025-04-02 10:41 69浏览
  • 引言在语音芯片设计中,输出电路的设计直接影响音频质量与系统稳定性。WT588系列语音芯片(如WT588F02B、WT588F02A/04A/08A等),因其高集成度与灵活性被广泛应用于智能设备。然而,不同型号在硬件设计上存在关键差异,尤其是DAC加功放输出电路的配置要求。本文将从硬件架构、电路设计要点及选型建议三方面,解析WT588F02B与F02A/04A/08A的核心区别,帮助开发者高效完成产品设计。一、核心硬件差异对比WT588F02B与F02A/04A/08A系列芯片均支持PWM直推喇叭
    广州唯创电子 2025-04-01 08:53 188浏览
  • 退火炉,作为热处理设备的一种,广泛应用于各种金属材料的退火处理。那么,退火炉究竟是干嘛用的呢?一、退火炉的主要用途退火炉主要用于金属材料(如钢、铁、铜等)的热处理,通过退火工艺改善材料的机械性能,消除内应力和组织缺陷,提高材料的塑性和韧性。退火过程中,材料被加热到一定温度后保持一段时间,然后以适当的速度冷却,以达到改善材料性能的目的。二、退火炉的工作原理退火炉通过电热元件(如电阻丝、硅碳棒等)或燃气燃烧器加热炉膛,使炉内温度达到所需的退火温度。在退火过程中,炉内的温度、加热速度和冷却速度都可以根
    锦正茂科技 2025-04-02 10:13 64浏览
  • 引言随着物联网和智能设备的快速发展,语音交互技术逐渐成为提升用户体验的核心功能之一。在此背景下,WT588E02B-8S语音芯片,凭借其创新的远程更新(OTA)功能、灵活定制能力及高集成度设计,成为智能设备语音方案的优选。本文将从技术特性、远程更新机制及典型应用场景三方面,解析该芯片的技术优势与实际应用价值。一、WT588E02B-8S语音芯片的核心技术特性高性能硬件架构WT588E02B-8S采用16位DSP内核,内部振荡频率达32MHz,支持16位PWM/DAC输出,可直接驱动8Ω/0.5W
    广州唯创电子 2025-04-01 08:38 158浏览
  • 提到“质量”这两个字,我们不会忘记那些奠定基础的大师们:休哈特、戴明、朱兰、克劳士比、费根堡姆、石川馨、田口玄一……正是他们的思想和实践,构筑了现代质量管理的核心体系,也深远影响了无数企业和管理者。今天,就让我们一同致敬这些质量管理的先驱!(最近流行『吉卜力风格』AI插图,我们也来玩玩用『吉卜力风格』重绘质量大师画象)1. 休哈特:统计质量控制的奠基者沃尔特·A·休哈特,美国工程师、统计学家,被誉为“统计质量控制之父”。1924年,他提出世界上第一张控制图,并于1931年出版《产品制造质量的经济
    优思学院 2025-04-01 14:02 145浏览
  • 在智能交互设备快速发展的今天,语音芯片作为人机交互的核心组件,其性能直接影响用户体验与产品竞争力。WT588F02B-8S语音芯片,凭借其静态功耗<5μA的卓越低功耗特性,成为物联网、智能家居、工业自动化等领域的理想选择,为设备赋予“听得懂、说得清”的智能化能力。一、核心优势:低功耗与高性能的完美结合超低待机功耗WT588F02B-8S在休眠模式下待机电流仅为5μA以下,显著延长了电池供电设备的续航能力。例如,在电子锁、气体检测仪等需长期待机的场景中,用户无需频繁更换电池,降低了维护成本。灵活的
    广州唯创电子 2025-04-02 08:34 148浏览
  • 文/Leon编辑/cc孙聪颖‍步入 2025 年,国家进一步加大促消费、扩内需的政策力度,家电国补政策将持续贯穿全年。这一利好举措,为行业发展注入强劲的增长动力。(详情见:2025:消费提振要靠国补还是“看不见的手”?)但与此同时,也对家电企业在战略规划、产品打造以及市场营销等多个维度,提出了更为严苛的要求。在刚刚落幕的中国家电及消费电子博览会(AWE)上,家电行业的竞争呈现出胶着的态势,各大品牌为在激烈的市场竞争中脱颖而出,纷纷加大产品研发投入,积极推出新产品,试图提升产品附加值与市场竞争力。
    华尔街科技眼 2025-04-01 19:49 206浏览
  • 据先科电子官方信息,其产品包装标签将于2024年5月1日进行全面升级。作为电子元器件行业资讯平台,大鱼芯城为您梳理本次变更的核心内容及影响:一、标签变更核心要点标签整合与环保优化变更前:卷盘、内盒及外箱需分别粘贴2张标签(含独立环保标识)。变更后:环保标识(RoHS/HAF/PbF)整合至单张标签,减少重复贴标流程。标签尺寸调整卷盘/内盒标签:尺寸由5030mm升级至**8040mm**,信息展示更清晰。外箱标签:尺寸统一为8040mm(原7040mm),提升一致性。关键信息新增新增LOT批次编
    大鱼芯城 2025-04-01 15:02 196浏览
  • 随着汽车向智能化、场景化加速演进,智能座舱已成为人车交互的核心承载。从驾驶员注意力监测到儿童遗留检测,从乘员识别到安全带状态判断,座舱内的每一次行为都蕴含着巨大的安全与体验价值。然而,这些感知系统要在多样驾驶行为、复杂座舱布局和极端光照条件下持续稳定运行,传统的真实数据采集方式已难以支撑其开发迭代需求。智能座舱的技术演进,正由“采集驱动”转向“仿真驱动”。一、智能座舱仿真的挑战与突破图1:座舱实例图智能座舱中的AI系统,不仅需要理解驾驶员的行为和状态,还要同时感知乘员、儿童、宠物乃至环境中的潜在
    康谋 2025-04-02 10:23 95浏览
  • 文/郭楚妤编辑/cc孙聪颖‍不久前,中国发展高层论坛 2025 年年会(CDF)刚刚落下帷幕。本次年会围绕 “全面释放发展动能,共促全球经济稳定增长” 这一主题,吸引了全球各界目光,众多重磅嘉宾的出席与发言成为舆论焦点。其中,韩国三星集团会长李在镕时隔两年的访华之行,更是引发广泛热议。一直以来,李在镕给外界的印象是不苟言笑。然而,在论坛开幕前一天,李在镕却意外打破固有形象。3 月 22 日,李在镕与高通公司总裁安蒙一同现身北京小米汽车工厂。小米方面极为重视此次会面,CEO 雷军亲自接待,小米副董
    华尔街科技眼 2025-04-01 19:39 209浏览
  • REACH和RoHS欧盟两项重要的环保法规有什么区别?适用范围有哪些?如何办理?REACH和RoHS是欧盟两项重要的环保法规,主要区别如下:一、核心定义与目标RoHS全称为《关于限制在电子电器设备中使用某些有害成分的指令》,旨在限制电子电器产品中的铅(Pb)、汞(Hg)、镉(Cd)、六价铬(Cr6+)、多溴联苯(PBBs)和多溴二苯醚(PBDEs)共6种物质,通过限制特定材料使用保障健康和环境安全REACH全称为《化学品的注册、评估、授权和限制》,覆盖欧盟市场所有化学品(食品和药品除外),通过登
    张工13144450251 2025-03-31 21:18 140浏览
  • 北京贞光科技有限公司作为紫光同芯授权代理商,专注于为客户提供车规级安全芯片的硬件供应与软件SDK一站式解决方案,同时配备专业技术团队,为选型及定制需求提供现场指导与支持。随着新能源汽车渗透率突破40%(中汽协2024数据),智能驾驶向L3+快速演进,车规级MCU正迎来技术范式变革。作为汽车电子系统的"神经中枢",通过AEC-Q100 Grade 1认证的MCU芯片需在-40℃~150℃极端温度下保持μs级响应精度,同时满足ISO 26262 ASIL-D功能安全要求。在集中式
    贞光科技 2025-04-02 14:50 120浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦