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

四. 总结

头信息对应如下

整个汇编代码的流程如下






评论
  •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
    电子知识打边炉 2025-01-04 20:04 98浏览
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 83浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 125浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 80浏览
  • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
    米尔电子嵌入式 2025-01-03 17:04 55浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 180浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 141浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 170浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 87浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 167浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 103浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 63浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 66浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 113浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 40浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦