├── .gitignore ├── .readthedocs.yaml ├── Makefile ├── README.md ├── autobuild.sh ├── make.bat └── source ├── _static ├── aarch64-qemu.ld ├── cache_asm.S ├── mmu.h ├── os_attr_armv8_external.h ├── prt_asm_cpu_external.h ├── prt_errno.h ├── prt_module.h ├── prt_sem.h ├── prt_task.h ├── prt_task_external.h ├── prt_typedef.h └── vsnprintf_s.c ├── conf.py ├── index.rst ├── intro ├── index.rst └── miniEuler.gif ├── lab1 ├── exec-gdb-cmd.png ├── exp1_debug_on_dots.png ├── index.rst ├── qemu-result.png ├── tree_init.png └── vscode-debug.png ├── lab10 └── index.rst ├── lab2 ├── down-pl011-ref.png ├── index.rst └── lab2-qemu-result.png ├── lab3 ├── dev-tree-example.png └── index.rst ├── lab4 ├── aarch64_exception_levels_2.svg ├── enter_into_os.png └── index.rst ├── lab5 ├── ARMGIC.png ├── gicv2-logic.png └── index.rst ├── lab6 └── index.rst ├── lab7 └── index.rst ├── lab8 ├── index.rst └── v2p-translate.svg ├── lab9 └── index.rst ├── references └── index.rst └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | build/ 3 | os2024_exp_redo/ 4 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.12" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the "docs/" directory with Sphinx 19 | sphinx: 20 | configuration: source/conf.py 21 | 22 | # Optionally build your docs in additional formats such as PDF and ePub 23 | # formats: 24 | # - pdf 25 | # - epub 26 | 27 | # Optional but recommended, declare the Python requirements required 28 | # to build your documentation 29 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 30 | python: 31 | install: 32 | - requirements: source/requirements.txt 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # os2024 -------------------------------------------------------------------------------- /autobuild.sh: -------------------------------------------------------------------------------- 1 | sphinx-autobuild source build/html 2 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /source/_static/aarch64-qemu.ld: -------------------------------------------------------------------------------- 1 | /* demos/hi3093/build/hi3093.ld */ 2 | ENTRY(__text_start) 3 | 4 | _stack_size = 0x10000; 5 | _heap_size = 0x10000; 6 | 7 | MEMORY 8 | { 9 | IMU_SRAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x800000 10 | MMU_MEM (rwx) : ORIGIN = 0x40800000, LENGTH = 0x800000 11 | } 12 | 13 | SECTIONS 14 | { 15 | text_start = .; 16 | .start_bspinit : 17 | { 18 | __text_start = .; 19 | KEEP(*(.text.bspinit)) 20 | } > IMU_SRAM 21 | 22 | .start_text : 23 | { 24 | KEEP(*(.text.startup)) 25 | } > IMU_SRAM 26 | 27 | .text : 28 | { 29 | *(.text) 30 | *(.text.*) 31 | *(*.text) 32 | . = ALIGN(8); 33 | __text_end = .; 34 | } > IMU_SRAM 35 | . = ALIGN(8); 36 | data_copy_start = .; 37 | 38 | .rodata : 39 | { 40 | . = ALIGN(8); 41 | __rodata_start = .; 42 | *(.rodata) 43 | *(.rodata.*) 44 | . = ALIGN(8); 45 | __rodata_end = .; 46 | } > IMU_SRAM 47 | 48 | .eh_frame : 49 | { 50 | . = ALIGN(8); 51 | __os_unwind_table_start = .; 52 | *(.eh_frame) 53 | __os_unwind_table_end = .; 54 | } > IMU_SRAM 55 | 56 | .heap (NOLOAD) : 57 | { 58 | . = ALIGN(8); 59 | PROVIDE (__HEAP_INIT = .); 60 | . = . + _heap_size; 61 | . = ALIGN(8); 62 | PROVIDE (__HEAP_END = .); 63 | } > IMU_SRAM 64 | 65 | .stack (NOLOAD) : 66 | { 67 | . = ALIGN(8); 68 | PROVIDE (__os_sys_sp_start = .); 69 | . = . + _stack_size; 70 | . = ALIGN(8); 71 | PROVIDE (__os_sys_sp_end = .); 72 | } > IMU_SRAM 73 | end = .; 74 | 75 | .percpu.data : 76 | { 77 | __os_per_cpu_start = .; 78 | *(.os.percpu.data) 79 | __os_per_cpu_end = .; 80 | LONG (ALIGNOF(.percpu.data)) 81 | } > IMU_SRAM 82 | 83 | .data : 84 | { 85 | . = ALIGN(8); 86 | __data_start = .; 87 | *(.data) 88 | *(.data.*) 89 | . = ALIGN(8); 90 | __os_text_start = .; 91 | QUAD(__text_start) 92 | QUAD(__text_end) 93 | __os_text_end = .; 94 | __data_end = .; 95 | } > IMU_SRAM 96 | 97 | .llt.bss : 98 | { 99 | _llt_bss_start = .; 100 | *__code_measure_stub*.o(.bss) 101 | *__code_measure_stub*.o(.bss.*) 102 | _llt_bss_end = .; 103 | } > IMU_SRAM 104 | 105 | .bss (NOLOAD) : 106 | { 107 | . = ALIGN(8); 108 | __bss_start__ = .; 109 | *(.bss) 110 | *(.bss.*) 111 | *(COMMON) 112 | . = ALIGN(8); 113 | __bss_end__ = .; 114 | } > IMU_SRAM 115 | 116 | .mmu.table.base : 117 | { 118 | PROVIDE (g_mmu_page_begin = .); 119 | PROVIDE (g_mmu_page_end = g_mmu_page_begin + 0x8000); 120 | } > MMU_MEM 121 | } 122 | -------------------------------------------------------------------------------- /source/_static/cache_asm.S: -------------------------------------------------------------------------------- 1 | .global os_asm_invalidate_dcache_all 2 | .global os_asm_flush_dcache_all 3 | .global os_asm_clean_dcache_all 4 | .global os_asm_invalidate_icache_all 5 | .global os_asm_invalidate_tlb_all 6 | 7 | .type os_asm_dcache_level, "function" 8 | os_asm_dcache_level: 9 | lsl x12, x0, #1 10 | msr csselr_el1, x12 11 | isb 12 | mrs x6, ccsidr_el1 13 | and x2, x6, #7 14 | add x2, x2, #4 15 | mov x3, #0x3ff 16 | and x3, x3, x6, lsr #3 17 | clz w5, w3 18 | mov x4, #0x7fff 19 | and x4, x4, x6, lsr #13 20 | 21 | loop_set: 22 | mov x6, x3 23 | loop_way: 24 | lsl x7, x6, x5 25 | orr x9, x12, x7 26 | lsl x7, x4, x2 27 | orr x9, x9, x7 28 | tbz w1, #1, 3f 29 | dc csw, x9 30 | b 2f 31 | 3: tbz w1, #0, 1f 32 | dc isw, x9 33 | b 2f 34 | 1: dc cisw, x9 35 | 2: subs x6, x6, #1 36 | b.ge loop_way 37 | subs x4, x4, #1 38 | b.ge loop_set 39 | 40 | ret 41 | 42 | .type os_asm_dcache_all, "function" 43 | os_asm_dcache_all: 44 | mov x1, x0 45 | dsb sy 46 | mrs x10, clidr_el1 47 | lsr x11, x10, #24 48 | and x11, x11, #0x7 49 | cbz x11, finished 50 | mov x15, x30 51 | mov x0, #0 52 | 53 | loop_level: 54 | lsl x12, x0, #1 55 | add x12, x12, x0 56 | lsr x12, x10, x12 57 | and x12, x12, #7 58 | cmp x12, #2 59 | b.lt skip 60 | bl os_asm_dcache_level 61 | skip: 62 | add x0, x0, #1 63 | cmp x11, x0 64 | b.gt loop_level 65 | 66 | mov x0, #0 67 | msr csselr_el1, x0 68 | dsb sy 69 | isb 70 | mov x30, x15 71 | 72 | finished: 73 | ret 74 | 75 | .type os_asm_invalidate_dcache_all, "function" 76 | os_asm_invalidate_dcache_all: 77 | mov x0, #0x1 78 | b os_asm_dcache_all 79 | 80 | .type os_asm_flush_dcache_all, "function" 81 | os_asm_flush_dcache_all: 82 | mov x0, #0x0 83 | b os_asm_dcache_all 84 | 85 | .type os_asm_clean_dcache_all, "function" 86 | os_asm_clean_dcache_all: 87 | mov x0, #0x2 88 | b os_asm_dcache_all 89 | 90 | .type os_asm_invalidate_icache_all, "function" 91 | os_asm_invalidate_icache_all: 92 | ic ialluis 93 | isb sy 94 | ret 95 | 96 | .type os_asm_invalidate_tlb_all, "function" 97 | os_asm_invalidate_tlb_all: 98 | tlbi vmalle1 99 | dsb sy 100 | isb 101 | ret 102 | -------------------------------------------------------------------------------- /source/_static/mmu.h: -------------------------------------------------------------------------------- 1 | #ifndef __MMU_H__ 2 | #define __MMU_H__ 3 | 4 | #include "prt_typedef.h" 5 | 6 | #define CACHE_POS 0x2 7 | #define CACHE_ENABLE 0x4 8 | #define CACHE_MASK 0x7 9 | 10 | #define MT_DEVICE_NGNRNE 0 11 | #define MT_DEVICE_NGNRE 1 12 | #define MT_DEVICE_GRE 2 13 | #define MT_NORMAL_NC 3 14 | #define MT_NORMAL 4 15 | 16 | #define MEMORY_ATTRIBUTES ((0x00 << (MT_DEVICE_NGNRNE * 8)) | \ 17 | (0x04 << (MT_DEVICE_NGNRE * 8)) | \ 18 | (0x0c << (MT_DEVICE_GRE * 8)) | \ 19 | (0x44 << (MT_NORMAL_NC * 8)) | \ 20 | (0xffUL << (MT_NORMAL * 8))) 21 | // https://developer.arm.com/documentation/den0024/a/The-Memory-Management-Unit/Translation-tables-in-ARMv8-A/AArch64-descriptor-format 22 | #define PTE_TYPE_FAULT (0 << 0) 23 | #define PTE_TYPE_BLOCK (1 << 0) 24 | #define PTE_TYPE_VALID (1 << 0) 25 | #define PTE_TYPE_MASK (3 << 0) 26 | #define PTE_TYPE_PAGE (3 << 0) 27 | #define PTE_TYPE_TABLE (3 << 0) 28 | 29 | #define PTE_TABLE_PXN (1UL << 59) 30 | #define PTE_TABLE_XN (1UL << 60) 31 | #define PTE_TABLE_AP (1UL << 61) 32 | #define PTE_TABLE_NS (1UL << 63) 33 | 34 | #define PTE_BLOCK_MEMTYPE(x) ((x) << 2) 35 | #define PTE_BLOCK_NS (1 << 5) 36 | #define PTE_BLOCK_AP_R (2 << 6) 37 | #define PTE_BLOCK_AP_RW (0 << 6) 38 | #define PTE_BLOCK_NON_SHARE (0 << 8) 39 | #define PTE_BLOCK_OUTER_SHARE (2 << 8) 40 | #define PTE_BLOCK_INNER_SHARE (3 << 8) 41 | #define PTE_BLOCK_AF (1 << 10) 42 | #define PTE_BLOCK_NG (1 << 11) 43 | #define PTE_BLOCK_PXN (1UL << 53) 44 | #define PTE_BLOCK_UXN (1UL << 54) 45 | 46 | #define PMD_ATTRINDX(t) ((t) << 2) 47 | #define PMD_ATTRINDX_MASK (7 << 2) 48 | #define PMD_ATTRMASK (PTE_BLOCK_PXN | \ 49 | PTE_BLOCK_UXN | \ 50 | PMD_ATTRINDX_MASK | \ 51 | PTE_TYPE_VALID) 52 | 53 | #define TCR_IPS(x) ((x) << 32) // Intermediate Physical Address Size 54 | #define TCR_T0SZ(x) ((64 - (x)) << 0) 55 | #define TCR_IRGN_NC (0 << 8) 56 | #define TCR_IRGN_WBWA (1 << 8) 57 | #define TCR_IRGN_WT (2 << 8) 58 | #define TCR_IRGN_WBNWA (3 << 8) 59 | #define TCR_IRGN_MASK (3 << 8) 60 | #define TCR_ORGN_NC (0 << 10) 61 | #define TCR_ORGN_WBWA (1 << 10) 62 | #define TCR_ORGN_WT (2 << 10) 63 | #define TCR_ORGN_WBNWA (3 << 10) 64 | #define TCR_ORGN_MASK (3 << 10) 65 | #define TCR_SHARED_NON (0 << 12) 66 | #define TCR_SHARED_OUTER (2 << 12) 67 | #define TCR_SHARED_INNER (3 << 12) 68 | #define TCR_TG0_4K (0 << 14) 69 | #define TCR_TG0_64K (1 << 14) 70 | #define TCR_TG0_16K (2 << 14) 71 | #define TCR_EPD1_DISABLE (1 << 23) 72 | 73 | #define TCR_EL1_RSVD (1UL << 35) //原定义错误 74 | #define TCR_EL2_RSVD (1UL << 31 | 1UL << 23) 75 | #define TCR_EL3_RSVD (1UL << 31 | 1UL << 23) 76 | 77 | #define MAX_PTE_ENTRIES_4K 512 78 | #define MAX_PTE_ENTRIES_64K 8192 79 | 80 | #define PTE_TABLE_ADDR_MARK_4K 0x0000FFFFFFFFF000ULL 81 | #define PTE_TABLE_ADDR_MARK_64K 0x0000FFFFFFFF0000ULL 82 | 83 | 84 | #define MMU_ATTR_DEVICE_NGNRNE (PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE)) 85 | #define MMU_ATTR_DEVICE (PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRE)) 86 | #define MMU_ATTR_UNCACHE_UNSHARE (PTE_BLOCK_MEMTYPE(MT_NORMAL_NC) | PTE_BLOCK_NON_SHARE) 87 | #define MMU_ATTR_UNCACHE_SHARE (PTE_BLOCK_MEMTYPE(MT_NORMAL_NC) | PTE_BLOCK_INNER_SHARE) 88 | #define MMU_ATTR_CACHE_UNSHARE (PTE_BLOCK_MEMTYPE(MT_NORMAL) | PTE_BLOCK_NON_SHARE) 89 | #define MMU_ATTR_CACHE_SHARE (PTE_BLOCK_MEMTYPE(MT_NORMAL) | PTE_BLOCK_INNER_SHARE) 90 | #define MMU_ATTR_MASK 0X31CULL 91 | 92 | #define MMU_ACCESS_NONE (PTE_BLOCK_AP_RW) 93 | #define MMU_ACCESS_R (PTE_BLOCK_AF | PTE_BLOCK_UXN | PTE_BLOCK_PXN | PTE_BLOCK_AP_R) 94 | #define MMU_ACCESS_RW (PTE_BLOCK_AF | PTE_BLOCK_UXN | PTE_BLOCK_PXN | PTE_BLOCK_AP_RW) 95 | #define MMU_ACCESS_RWX (PTE_BLOCK_AF | PTE_BLOCK_AP_RW) 96 | #define MMU_ACCESS_RX (PTE_BLOCK_AF | PTE_BLOCK_AP_R) 97 | #define MMU_ACCESS_MASK 0X600000000004C0ULL 98 | 99 | #define MMU_GRANULE_4K 0 100 | #define MMU_GRANULE_64K 1 101 | 102 | #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 103 | #define MIN(a, b) (((a) < (b)) ? (a) : (b)) 104 | 105 | #define CR_M (1 << 0) // MMU enable 106 | #define CR_A (1 << 1) // Alignment check enable 107 | #define CR_C (1 << 2) // Cacheability control 108 | #define CR_SA (1 << 3) // SP(stack pointer) Alignment check enable 109 | #define CR_I (1 << 12) 110 | #define CR_WXN (1 << 19) 111 | #define CR_EE (1 << 25) 112 | 113 | #define SPSR_EL_END_LE (0 << 9) 114 | #define SPSR_EL_DEBUG_MASK (1 << 9) 115 | #define SPSR_EL_ASYN_MASK (1 << 8) 116 | #define SPSR_EL_SERR_MASK (1 << 8) 117 | #define SPSR_EL_IRQ_MASK (1 << 7) 118 | #define SPSR_EL_FIQ_MASK (1 << 6) 119 | #define SPSR_EL_T_A32 (0 << 5) 120 | #define SPSR_EL_M_AARCH64 (0 << 4) 121 | #define SPSR_EL_M_AARCH32 (1 << 4) 122 | #define SPSR_EL_M_SVC (0x3) 123 | #define SPSR_EL_M_HYP (0xa) 124 | #define SPSR_EL_M_EL1H (5) 125 | #define SPSR_EL_M_EL2H (9) 126 | 127 | #define CPUECTLR_EL1_L1PCTL_MASK (7 << 13) 128 | #define CPUECTLR_EL1_L3PCTL_MASK (7 << 10) 129 | 130 | typedef enum { 131 | MMU_LEVEL_0 = 0, 132 | MMU_LEVEL_1, 133 | MMU_LEVEL_2, 134 | MMU_LEVEL_3, 135 | MMU_LEVEL_MAX 136 | } mmu_level_e; 137 | 138 | typedef enum { 139 | MMU_PHY_ADDR_LEVEL_0 = 0, 140 | MMU_PHY_ADDR_LEVEL_1, 141 | MMU_PHY_ADDR_LEVEL_2, 142 | MMU_PHY_ADDR_LEVEL_3, 143 | MMU_PHY_ADDR_LEVEL_4, 144 | MMU_PHY_ADDR_LEVEL_5 145 | } mmu_physical_addr_size_e; 146 | 147 | typedef enum { 148 | MMU_BITS_9 = 9, 149 | MMU_BITS_12 = 12, 150 | MMU_BITS_13 = 13, 151 | MMU_BITS_16 = 16, 152 | MMU_BITS_32 = 32, 153 | MMU_BITS_36 = 36, 154 | MMU_BITS_39 = 39, 155 | MMU_BITS_40 = 40, 156 | MMU_BITS_42 = 42, 157 | MMU_BITS_44 = 44, 158 | MMU_BITS_48 = 48, 159 | } mmu_bits_e; 160 | 161 | typedef struct { 162 | U64 tlb_addr; 163 | U64 tlb_size; 164 | U64 tlb_fillptr; 165 | U32 granule; 166 | U32 start_level; 167 | U32 va_bits; 168 | } mmu_ctrl_s; 169 | 170 | typedef struct { 171 | U64 virt; 172 | U64 phys; 173 | U64 size; 174 | U64 max_level; 175 | U64 attrs; 176 | } mmu_mmap_region_s; 177 | 178 | static inline unsigned long get_sctlr(void) 179 | { 180 | unsigned long val; 181 | 182 | __asm__ __volatile__("mrs %0, sctlr_el1" : "=r" (val) : : "cc"); 183 | 184 | return val; 185 | } 186 | 187 | static inline unsigned long get_cpuectr(void) 188 | { 189 | unsigned long val; 190 | 191 | __asm__ __volatile__("mrs %0, S3_1_C15_C2_1" : "=r" (val) : : "cc"); 192 | 193 | return val; 194 | } 195 | 196 | static inline void set_sctlr(unsigned long val) 197 | { 198 | __asm__ __volatile__("dsb sy"); 199 | __asm__ __volatile__("msr sctlr_el1, %0" : : "r" (val) : "cc"); 200 | __asm__ __volatile__("dsb sy"); 201 | __asm__ __volatile__("isb"); 202 | } 203 | 204 | extern S32 mmu_init(void); 205 | 206 | #endif 207 | -------------------------------------------------------------------------------- /source/_static/os_attr_armv8_external.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * UniProton is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | * See the Mulan PSL v2 for more details. 12 | * Create: 2022-11-22 13 | * Description: 属性宏相关内部头文件 14 | */ 15 | 16 | // src/arch/cpu/armv8/common/os_attr_armv8_external.h 17 | 18 | #ifndef OS_ATTR_ARMV8_EXTERNAL_H 19 | #define OS_ATTR_ARMV8_EXTERNAL_H 20 | 21 | /* 定义操作系统的代码数据分段 */ 22 | #ifndef CONFIG_OPTION_FX_SECTIONS 23 | /* L0表示最高性能内存段 */ 24 | #ifndef OS_SEC_L0_TEXT 25 | #define OS_SEC_L0_TEXT __attribute__((section(".os.text"))) 26 | #endif 27 | 28 | #ifndef OS_SEC_LX0_TEXT 29 | #define OS_SEC_LX0_TEXT __attribute__((section(".os.text"))) 30 | #endif 31 | 32 | /* OS_SEC_L1_TEXT, L1表示性能敏感内存段,如PL2,默认缺省,不显示指定 */ 33 | #ifndef OS_SEC_TEXT 34 | #define OS_SEC_TEXT __attribute__((section(".os.text"))) 35 | #endif 36 | 37 | #ifndef OS_SEC_L2_TEXT 38 | #define OS_SEC_L2_TEXT __attribute__((section(".os.minor.text"))) 39 | #endif 40 | 41 | #ifndef OS_SEC_L4_TEXT 42 | #define OS_SEC_L4_TEXT __attribute__((section(".os.init.text"))) 43 | #endif 44 | 45 | #ifndef OS_SEC_LX_TEXT 46 | #define OS_SEC_LX_TEXT __attribute__((section(".os.init.text"))) 47 | #endif 48 | 49 | #ifndef OS_SEC_DATA 50 | #define OS_SEC_DATA __attribute__((section(".os.data"))) 51 | #endif 52 | 53 | #ifndef OS_SEC_L4_DATA 54 | #define OS_SEC_L4_DATA __attribute__((section(".os.data"))) 55 | #endif 56 | 57 | #ifndef OS_SEC_BSS 58 | #define OS_SEC_BSS __attribute__((section(".os.bss"))) 59 | #endif 60 | 61 | #ifndef OS_SEC_L4_BSS 62 | #define OS_SEC_L4_BSS __attribute__((section(".os.bss"))) 63 | #endif 64 | 65 | #ifndef OS_SEC_L4_INSTSH_DATA 66 | #define OS_SEC_L4_INSTSH_DATA __attribute__((section(".os.data"))) 67 | #endif 68 | #endif /* CONFIG_OPTION_FX_SECTIONS */ 69 | 70 | #endif /* OS_ATTR_ARMV8_EXTERNAL_H */ 71 | -------------------------------------------------------------------------------- /source/_static/prt_asm_cpu_external.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * UniProton is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | * See the Mulan PSL v2 for more details. 12 | * Create: 2009-10-05 13 | * Description: 核架构相关的汇编宏头文件 14 | */ 15 | #ifndef PRT_ASM_CPU_EXTERNAL_H 16 | #define PRT_ASM_CPU_EXTERNAL_H 17 | 18 | /* 有名空间不支持时,从核不能从有名空间获取 */ 19 | #define GLB_NS_ADDR 0 20 | #define GLB_NSM_START 0 21 | 22 | #define OS_EXC_REGINFO_OFFSET 160 23 | #define OS_EXC_MAGIC_WORD 0xEDCAACDC 24 | #define OS_SYS_STACK_MAGIC_WORD 0xCACACACA /* 系统栈魔术字 */ 25 | 26 | /* 内核状态定义 */ 27 | #define OS_FLG_HWI_ACTIVE 0x0001 28 | #define OS_FLG_BGD_ACTIVE 0x0002 29 | #define OS_FLG_TICK_ACTIVE 0x0008 30 | #define OS_FLG_SYS_ACTIVE 0x0010 31 | #define OS_FLG_EXC_ACTIVE 0x0020 32 | #define OS_FLG_TSK_REQ 0x1000 33 | #define OS_FLG_TSK_SWHK 0x2000 /* 任务切换时是否调用切换入口函数 */ 34 | 35 | #endif /* PRT_ASM_CPU_EXTERNAL_H */ 36 | -------------------------------------------------------------------------------- /source/_static/prt_errno.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * UniProton is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | * See the Mulan PSL v2 for more details. 12 | * Create: 2009-12-22 13 | * Description: 通用错误码定义头文件。 14 | */ 15 | #ifndef PRT_ERRNO_H 16 | #define PRT_ERRNO_H 17 | 18 | #include "prt_typedef.h" 19 | 20 | #ifdef __cplusplus 21 | #if __cplusplus 22 | extern "C" { 23 | #endif /* __cpluscplus */ 24 | #endif /* __cpluscplus */ 25 | 26 | /* 27 | * OS错误码标记位,0x00表示OS 28 | * 29 | */ 30 | #define ERRNO_OS_ID (0x00U << 16) 31 | 32 | /* 33 | * 定义错误的等级:提示级别 34 | * 35 | */ 36 | #define ERRTYPE_NORMAL (0x00U << 24) 37 | 38 | /* 39 | * 定义错误的等级:告警级别 40 | * 41 | */ 42 | #define ERRTYPE_WARN (0x01U << 24) 43 | 44 | /* 45 | * 定义错误的等级:严重级别 46 | * 47 | */ 48 | #define ERRTYPE_ERROR (0x02U << 24) 49 | 50 | /* 51 | * 定义错误的等级:致命级别 52 | * 53 | */ 54 | #define ERRTYPE_FATAL (0x03U << 24) 55 | 56 | /* 57 | * @brief 定义OS致命错误。 58 | * 59 | * @par 描述 60 | * 宏定义,定义OS致命错误。 61 | * 62 | * @attention 无 63 | * 64 | * @param mid [IN] 模块ID编号。 65 | * @param errno [IN] 错误码编号。 66 | * 67 | * @retval 无 68 | * @par 依赖 69 | *
  • prt_errno.h:该宏定义所在的头文件。
  • 70 | * @see OS_ERRNO_BUILD_ERROR | OS_ERRNO_BUILD_WARN | OS_ERRNO_BUILD_NORMAL 71 | */ 72 | #define OS_ERRNO_BUILD_FATAL(mid, errno) (ERRTYPE_FATAL | ERRNO_OS_ID | ((U32)(mid) << 8) | (errno)) 73 | 74 | /* 75 | * @brief 定义OS严重错误 76 | * 77 | * @par 描述 78 | * 宏定义,定义OS严重错误 79 | * 80 | * @attention 无 81 | * @param mid [IN] 模块ID编号。 82 | * @param errno [IN] 错误码编号。 83 | * 84 | * @retval 无 85 | * @par 依赖 86 | *
  • prt_errno.h:该宏定义所在的头文件。
  • 87 | * @see OS_ERRNO_BUILD_FATAL | OS_ERRNO_BUILD_WARN | OS_ERRNO_BUILD_NORMAL 88 | */ 89 | #define OS_ERRNO_BUILD_ERROR(mid, errno) (ERRTYPE_ERROR | ERRNO_OS_ID | ((U32)(mid) << 8) | (errno)) 90 | 91 | #ifdef __cplusplus 92 | #if __cplusplus 93 | } 94 | #endif /* __cpluscplus */ 95 | #endif /* __cpluscplus */ 96 | 97 | #endif /* PRT_ERRNO_H */ 98 | -------------------------------------------------------------------------------- /source/_static/prt_module.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * UniProton is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | * See the Mulan PSL v2 for more details. 12 | * Create: 2009-12-22 13 | * Description: 模块ID编号。 14 | */ 15 | #ifndef PRT_MODULE_H 16 | #define PRT_MODULE_H 17 | 18 | #ifdef __cplusplus 19 | #if __cplusplus 20 | extern "C" { 21 | #endif /* __cpluscplus */ 22 | #endif /* __cpluscplus */ 23 | 24 | enum MoudleId { 25 | OS_MID_SYS = 0x0, /* 系统模块 */ 26 | OS_MID_MEM = 0x1, /* 内存模块 */ 27 | OS_MID_FSCMEM = 0x2, 28 | OS_MID_TSK = 0x3, 29 | OS_MID_SWTMR = 0x4, 30 | OS_MID_TICK = 0x5, 31 | OS_MID_CPUP = 0x6, 32 | OS_MID_SEM = 0x7, 33 | OS_MID_HWI = 0x8, 34 | OS_MID_HOOK = 0x9, 35 | OS_MID_EXC = 0xa, 36 | OS_MID_EVENT = 0xb, 37 | OS_MID_QUEUE = 0xc, 38 | OS_MID_TIMER = 0xd, 39 | OS_MID_HARDDRV = 0xe, 40 | OS_MID_APP = 0xf, 41 | OS_MID_SIGNAL = 0x10, 42 | OS_MID_SHELL = 0x11, 43 | OS_MID_BUTT 44 | }; 45 | 46 | #ifdef __cplusplus 47 | #if __cplusplus 48 | } 49 | #endif /* __cpluscplus */ 50 | #endif /* __cpluscplus */ 51 | 52 | #endif /* PRT_MODULE_H */ 53 | -------------------------------------------------------------------------------- /source/_static/prt_sem.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * UniProton is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | * See the Mulan PSL v2 for more details. 12 | * Create: 2009-12-22 13 | * Description: 信号量模块对外头文件。 14 | */ 15 | #ifndef PRT_SEM_H 16 | #define PRT_SEM_H 17 | 18 | #include "prt_module.h" 19 | #include "prt_errno.h" 20 | 21 | #ifdef __cplusplus 22 | #if __cplusplus 23 | extern "C" { 24 | #endif 25 | #endif /* __cplusplus */ 26 | 27 | /* 28 | * 信号量错误码:初始化信号量内存不足。 29 | * 30 | * 值: 0x02000701 31 | * 32 | * 解决方案: 可以将私有的静态内存空间配大。 33 | */ 34 | #define OS_ERRNO_SEM_NO_MEMORY OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x01) 35 | 36 | /* 37 | * 信号量错误码:信号量句柄非法(错误或已删除)。 38 | * 39 | * 值: 0x02000702 40 | * 41 | * 解决方案: 查看输入的信号量句柄值是否有效。 42 | */ 43 | #define OS_ERRNO_SEM_INVALID OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x02) 44 | 45 | /* 46 | * 信号量错误码:输入指针为空。 47 | * 48 | * 值: 0x02000703 49 | * 50 | * 解决方案: 查看指针是否输入为空。 51 | */ 52 | #define OS_ERRNO_SEM_PTR_NULL OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x03) 53 | 54 | /* 55 | * 信号量错误码:没有空闲信号量。 56 | * 57 | * 值: 0x02000704 58 | * 59 | * 解决方案: 查看信号量模块是否打开,或配置更多信号量。 60 | */ 61 | #define OS_ERRNO_SEM_ALL_BUSY OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x04) 62 | 63 | /* 64 | * 信号量错误码:信号量没有可用资源且Pend时设为不等待(等待时间为0)时获取信号量失败。 65 | * 66 | * 值: 0x02000705 67 | * 68 | * 解决方案: 无。 69 | */ 70 | #define OS_ERRNO_SEM_UNAVAILABLE OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x05) 71 | 72 | /* 73 | * 信号量错误码:禁止中断处理函数阻塞于信号量。 74 | * 75 | * 值: 0x02000706 76 | * 77 | * 解决方案: 查看是否在中断中Pend信号量。 78 | */ 79 | #define OS_ERRNO_SEM_PEND_INTERR OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x06) 80 | 81 | /* 82 | * 信号量错误码:任务切换锁定时,禁止任务阻塞于信号量。 83 | * 84 | * 值: 0x02000707 85 | * 86 | * 解决方案: 不要在锁任务时pend没有资源可用的信号量。 87 | */ 88 | #define OS_ERRNO_SEM_PEND_IN_LOCK OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x07) 89 | 90 | /* 91 | * 信号量错误码:信号量等待超时。 92 | * 93 | * 值: 0x02000708 94 | * 95 | * 解决方案: 无。 96 | */ 97 | #define OS_ERRNO_SEM_TIMEOUT OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x08) 98 | 99 | /* 100 | * 信号量错误码:信号量发生溢出。 101 | * 102 | * 值: 0x02000709 103 | * 104 | * 解决方案: 查看输入的信号量计数值是否有效。 105 | */ 106 | #define OS_ERRNO_SEM_OVERFLOW OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x09) 107 | 108 | /* 109 | * 信号量错误码:信号量删除和重设当前值时有阻塞于信号量的任务。 110 | * 111 | * 值: 0x0200070a 112 | * 113 | * 解决方案: 如果当前信号量有任务阻塞,不能进行删除和重设计数值操作。 114 | */ 115 | #define OS_ERRNO_SEM_PENDED OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x0a) 116 | 117 | /* 118 | * 信号量错误码:注册核内信号量个数为0导致注册失败。 119 | * 120 | * 值: 0x0200070b 121 | * 122 | * 解决方案: 查看信号量模块配置的最大个数是否为0。 123 | */ 124 | #define OS_ERRNO_SEM_REG_ERROR OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x0b) 125 | 126 | /* 127 | * 信号量错误码:调用#PRT_SemGetPendList时,指定的内存空间不足,无法存入全部的阻塞任务PID。 128 | * 129 | * 值: 0x0200070c 130 | * 131 | * 解决方案: 建议将数组长度配置为(#OS_TSK_MAX_SUPPORT_NUM + 1) * 4。 132 | */ 133 | #define OS_ERRNO_SEM_INPUT_BUF_NOT_ENOUGH OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x0c) 134 | 135 | /* 136 | * 信号量错误码:调用#PRT_SemGetPendList时,输入指针为空或者bufLen小于4。 137 | * 138 | * 值: 0x0200070d 139 | * 140 | * 解决方案: 出参不能为NULL,指定的缓存长度不能小于4。 141 | */ 142 | #define OS_ERRNO_SEM_INPUT_ERROR OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x0d) 143 | 144 | /* 145 | * 信号量错误码:获取信号量详细信息时出参结构体指针为NULL。 146 | * 147 | * 值: 0x0200070e 148 | * 149 | * 解决方案: 用来保存信号量详细信息的结构体指针不能为NULL。 150 | */ 151 | #define OS_ERRNO_SEM_INFO_NULL OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x0e) 152 | 153 | /* 154 | * 信号量错误码:获取当前信号量计数时传入的出参为NULL。 155 | * 156 | * 值: 0x0200070f 157 | * 158 | * 解决方案: 互斥型信号量的唤醒方式不能为FIFO。 159 | */ 160 | #define OS_ERRNO_SEM_COUNT_GET_PTR_NULL OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x0f) 161 | 162 | /* 163 | * 信号量错误码:非当前互斥信号量的持有者释放该信号量。 164 | * 165 | * 值: 0x02000710 166 | * 167 | * 解决方案: 互斥信号量只能由其持有者释放,即互斥信号量的PV操作必须配对使用。 168 | */ 169 | #define OS_ERRNO_SEM_MUTEX_NOT_OWNER_POST OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x10) 170 | 171 | /* 172 | * 信号量错误码:在中断中释放互斥型信号量。 173 | * 174 | * 值: 0x02000711 175 | * 176 | * 解决方案: 只能在任务中对互斥型信号量进行PV操作。 177 | */ 178 | #define OS_ERRNO_SEM_MUTEX_POST_INTERR OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x11) 179 | 180 | /* 181 | * 尚未实现该功能 182 | */ 183 | #define OS_ERRNO_SEM_FUNC_NOT_SUPPORT OS_ERRNO_BUILD_ERROR(OS_MID_SEM, 0x12) 184 | 185 | /* 186 | * 信号量等待时间设定:表示不等待。 187 | */ 188 | #define OS_NO_WAIT 0 189 | 190 | /* 191 | * 信号量等待时间设定:表示永久等待。 192 | */ 193 | #define OS_WAIT_FOREVER 0xFFFFFFFF 194 | 195 | /* 196 | * 二进制信号量空闲状态,互斥型信号量初始计数值。 197 | */ 198 | #define OS_SEM_FULL 1 199 | 200 | /* 201 | * 二进制信号量占用状态,同步型信号量初始计数值。 202 | */ 203 | #define OS_SEM_EMPTY 0 204 | 205 | /* 206 | * 计数型信号量最大计数值。 207 | */ 208 | #define OS_SEM_COUNT_MAX 0xFFFFFFFE 209 | 210 | /* 211 | * 信号量不被任何任务持有,处于空闲状态。 212 | */ 213 | #define OS_INVALID_OWNER_ID 0xFFFFFFFE 214 | 215 | /* 216 | * 信号量句柄类型定义。 217 | */ 218 | typedef U16 SemHandle; 219 | 220 | /* 221 | * 信号量类型。 222 | */ 223 | /* 计数型信号量 */ 224 | #define SEM_TYPE_COUNT 0 225 | /* 二进制信号量 */ 226 | #define SEM_TYPE_BIN 1 227 | 228 | /* 229 | * 信号量模块被阻塞线程唤醒方式。 230 | */ 231 | enum SemMode { 232 | SEM_MODE_FIFO, // 信号量FIFO唤醒模式 233 | SEM_MODE_PRIOR, // 信号量优先级唤醒模式 234 | SEM_MODE_BUTT // 信号量非法唤醒方式 235 | }; 236 | 237 | /* 238 | * 信号量模块配置信息的结构体定义。 239 | */ 240 | struct SemModInfo { 241 | /* 最大支持的信号量数 */ 242 | U16 maxNum; 243 | /* 保留 */ 244 | U16 reserved; 245 | }; 246 | 247 | /* 248 | * 信号量模块获取信号量详细信息时的信息结构体。 249 | */ 250 | struct SemInfo { 251 | /* 信号量计数 */ 252 | U32 count; 253 | /* 信号量占用者,对于计数型信号量,记录的是最后一次信号量获得者;如果没有被任务获得,则为OS_THREAD_ID_INVALID */ 254 | U32 owner; 255 | /* 信号量唤醒方式,为SEM_MODE_FIFO或SEM_MODE_PRIOR */ 256 | enum SemMode mode; 257 | /* 信号量类型,为SEM_TYPE_COUNT(计数型)或SEM_TYPE_BIN(互斥型) */ 258 | U32 type; 259 | }; 260 | 261 | /* 262 | * @brief 创建一个计数型信号量。 263 | * 264 | * @par 描述 265 | * 根据用户指定的计数值,创建一个计数型信号量,设定初始计数器数值,唤醒方式为FIFO。 266 | * @attention 267 | * 268 | * 269 | * @param count [IN] 类型#U32,计数器初始值,取值范围为[0, 0xFFFFFFFE]。 270 | * @param semHandle [OUT] 类型#SemHandle *,输出信号量句柄。 271 | * 272 | * @retval #OS_OK 0x00000000,操作成功。 273 | * @retval #其它值,操作失败。 274 | * @par 依赖 275 | * 276 | * @see PRT_SemDelete 277 | */ 278 | extern U32 PRT_SemCreate(U32 count, SemHandle *semHandle); 279 | 280 | /* 281 | * @brief 删除一个信号量。 282 | * 283 | * @par 描述 284 | * 删除用户传入的信号量句柄指定的信号量,如果有任务阻塞于该信号量,则删除失败。 285 | * @attention 无 286 | * 287 | * @param semHandle [IN] 类型#SemHandle,信号量句柄,来源于信号量创建成功的输出值。 288 | * 289 | * @retval #OS_OK 0x00000000,操作成功。 290 | * @retval #其它值,操作失败。 291 | * @par 依赖 292 | * 293 | * @see PRT_SemCreate 294 | */ 295 | extern U32 PRT_SemDelete(SemHandle semHandle); 296 | 297 | /* 298 | * @brief 获取信号量计数器数值。 299 | * 300 | * @par 描述 301 | * 根据用户输入信号量句柄和计数值,获取信号量计数器数值。 302 | * @attention 无 303 | * 304 | * @param semHandle [IN] 类型#SemHandle,信号量句柄,来源于信号量创建成功的输出值。 305 | * @param semCnt [OUT] 类型#U32 *,保存信号量计数值指针。 306 | * 307 | * @retval #OS_OK 0x00000000,获取信号量计数器值成功。 308 | * @retval #其它值,获取失败。 309 | * @par 依赖 310 | * 311 | * @see PRT_SemCreate | PRT_SemGetInfo 312 | */ 313 | extern U32 PRT_SemGetCount(SemHandle semHandle, U32 *semCnt); 314 | 315 | /* 316 | * @brief 等待一个信号量。 317 | * 318 | * @par 描述 319 | * 等待用户传入信号量句柄指定的信号量,若其计数器值大于0,则直接减1返回成功。否则任务阻塞, 320 | * 等待其他线程发布该信号量,等待超时时间可设定。 321 | * @attention 322 | * 328 | * 329 | * @param semHandle [IN] 类型#SemHandle,信号量句柄,来源于信号量创建成功的输出值。 330 | * @param timeout [IN] 类型#U32,等待时间限制,单位为tick,取值范围为[0, 0xffffffff]。#OS_NO_WAIT或0表示不等待, 331 | * #OS_WAIT_FOREVER或0xffffffff表示永久等待。 332 | * 333 | * @retval #OS_OK 0x00000000,操作成功。 334 | * @retval #其它值,操作失败。 335 | * @par 依赖 336 | * 337 | * @see PRT_SemPost 338 | */ 339 | extern U32 PRT_SemPend(SemHandle semHandle, U32 timeout); 340 | 341 | /* 342 | * @brief 发布指定的信号量。 343 | * 344 | * @par 描述 345 | * 发布指定的信号量,若没有任务等待该信号量,则直接将计数器加1返回。 346 | * 否则根据唤醒方式唤醒相应的阻塞任务,FIFO方式唤醒最早阻塞的任务,优先级方式唤醒阻塞在此信号量的最高优先级任务。 347 | * @attention 348 | * 354 | * 355 | * @param semHandle [IN] 类型#SemHandle,信号量句柄,来源于信号量创建成功的输出值。 356 | * 357 | * @retval #OS_OK 0x00000000,操作成功。 358 | * @retval #其它值,操作失败。 359 | * @par 依赖 360 | * 361 | * @see PRT_SemPend 362 | */ 363 | extern U32 PRT_SemPost(SemHandle semHandle); 364 | 365 | /* 366 | * @brief 获取阻塞在指定核内信号量上的任务PID清单。 367 | * 368 | * @par 描述 369 | * 根据用户指定的核内信号量句柄,获取阻塞在指定核内信号量上的任务PID清单。 370 | * 若有任务阻塞于该核内信号量,则返回阻塞于该核内信号量的任务数目,以及相应任务的PID。 371 | * 若没有任务阻塞于该核内信号量,则返回阻塞于该核内信号量的任务数目为0。 372 | * @attention 373 | * 375 | * 376 | * @param semHandle [IN] 类型#SemHandle,信号量句柄,来源于信号量创建成功的输出值。 377 | * @param tskCnt [OUT] 类型#U32 *,阻塞于该核内信号量的任务数。 378 | * @param pidBuf [OUT] 类型#U32 *,由用户指定的内存区域首地址,用于存储阻塞于指定核内信号量的任务PID。 379 | * @param bufLen [IN] 类型#U32,用户指定的内存区域的长度(单位:字节)。 380 | * 381 | * @retval #OS_OK 0x00000000,操作成功。 382 | * @retval #其它值,操作失败。 383 | * @par 依赖 384 | * 385 | * @see PRT_TaskGetPendSem | PRT_SemGetInfo 386 | */ 387 | extern U32 PRT_SemGetPendList(SemHandle semHandle, U32 *tskCnt, U32 *pidBuf, U32 bufLen); 388 | 389 | /* 390 | * @brief 获取信号量详细信息:信号量当前计数值,信号量持有者(最后一次pend成功的线程ID),信号量唤醒方式,信号量类型。 391 | * 392 | * @par 描述 393 | * 根据用户输入信号量句柄获取信号量详细信息。 394 | * @attention 无 395 | * 396 | * @param semHandle [IN] 类型#SemHandle,信号量句柄,来源于信号量创建成功的输出值。 397 | * @param semInfo [OUT] 类型#struct SemInfo *,信号量详细信息:count--信号量计数,owner--信号量占用者, 398 | * mode--信号量唤醒方式,type--信号量类型。 399 | * 400 | * @retval #OS_OK 0x00000000,获取信号量计数器值成功。 401 | * @retval #其它值,获取失败。 402 | * @par 依赖 403 | * 404 | * @see PRT_SemGetCount | PRT_SemGetPendList 405 | */ 406 | extern U32 PRT_SemGetInfo(SemHandle semHandle, struct SemInfo *semInfo); 407 | 408 | #ifdef __cplusplus 409 | #if __cplusplus 410 | } 411 | #endif 412 | #endif /* __cplusplus */ 413 | 414 | #endif /* PRT_SEM_H */ 415 | -------------------------------------------------------------------------------- /source/_static/prt_task_external.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * UniProton is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | * See the Mulan PSL v2 for more details. 12 | * Create: 2009-12-22 13 | * Description: 任务模块的内部头文件 14 | */ 15 | #ifndef PRT_TASK_EXTERNAL_H 16 | #define PRT_TASK_EXTERNAL_H 17 | 18 | #include "prt_typedef.h" 19 | 20 | #include "prt_task.h" 21 | #include "list_types.h" 22 | #include "prt_list_external.h" 23 | 24 | // struct TagOsRunQue { 25 | // U32 taskReadyListBitMap; 26 | // /* 优先级bit位表 */ 27 | // U32 tskChildBitMap[OS_GET_WORD_NUM_BY_PRIONUM(OS_TSK_NUM_OF_PRIORITIES)]; 28 | // struct TagListObject readyList[OS_TSK_NUM_OF_PRIORITIES]; 29 | // }; 30 | #define TagOsRunQue TagListObject //简单实现 31 | 32 | /* 33 | * 任务线程及进程控制块的结构体统一定义。 34 | */ 35 | struct TagTskCb { 36 | /* 当前任务的SP */ 37 | void *stackPointer; 38 | /* 任务状态,后续内部全改成U32 */ 39 | U32 taskStatus; 40 | /* 任务的运行优先级 */ 41 | TskPrior priority; 42 | /* 任务栈配置标记 */ 43 | U16 stackCfgFlg; 44 | /* 任务栈大小 */ 45 | U32 stackSize; 46 | TskHandle taskPid; 47 | 48 | /* 任务栈顶 */ 49 | uintptr_t topOfStack; 50 | 51 | /* 任务入口函数 */ 52 | TskEntryFunc taskEntry; 53 | /* 任务Pend的信号量指针 */ 54 | void *taskSem; 55 | 56 | /* 任务的参数 */ 57 | uintptr_t args[4]; 58 | #if (defined(OS_OPTION_TASK_INFO)) 59 | /* 存放任务名 */ 60 | char name[OS_TSK_NAME_LEN]; 61 | #endif 62 | /* 信号量链表指针 */ 63 | struct TagListObject pendList; 64 | /* 任务延时链表指针 */ 65 | struct TagListObject timerList; 66 | /* 持有互斥信号量链表 */ 67 | struct TagListObject semBList; 68 | /* 记录条件变量的等待线程 */ 69 | struct TagListObject condNode; 70 | #if defined(OS_OPTION_LINUX) 71 | /* 等待队列指针 */ 72 | struct TagListObject waitList; 73 | #endif 74 | #if defined(OS_OPTION_EVENT) 75 | /* 任务事件 */ 76 | U32 event; 77 | /* 任务事件掩码 */ 78 | U32 eventMask; 79 | #endif 80 | /* 任务记录的最后一个错误码 */ 81 | U32 lastErr; 82 | /* 任务恢复的时间点(单位Tick) */ 83 | U64 expirationTick; 84 | 85 | #if defined(OS_OPTION_NUTTX_VFS) 86 | struct filelist tskFileList; 87 | #if defined(CONFIG_FILE_STREAM) 88 | struct streamlist ta_streamlist; 89 | #endif 90 | #endif 91 | }; 92 | 93 | typedef void (*TskCoresleep)(void); 94 | typedef void (*TaskNameGetFunc)(U32 taskId, char **taskName); 95 | typedef U32 (*TaskNameAddFunc)(U32 taskId, const char *name); 96 | 97 | extern U16 g_uniTaskLock; 98 | extern TskHandle g_idleTaskId; 99 | extern struct TagOsRunQue g_runQueue; 100 | extern struct TagTskCb *g_runningTask; 101 | extern struct TagTskCb *g_highestTask; 102 | extern struct TagOsTskSortedDelayList g_tskSortedDelay; 103 | 104 | extern U32 g_tskMaxNum; 105 | extern U32 g_tskBaseId; 106 | extern struct TagTskCb *g_tskCbArray; 107 | extern struct TskModInfo g_tskModInfo; 108 | extern struct TagTskMonNode *g_tskMonList; 109 | 110 | extern TskEntryFunc g_tskIdleEntry; 111 | extern TaskNameAddFunc g_taskNameAdd; 112 | extern TaskNameGetFunc g_taskNameGet; 113 | 114 | extern volatile TskCoresleep g_taskCoreSleep; 115 | 116 | #if defined(OS_OPTION_POWEROFF) 117 | typedef void (*PowerOffFuncT)(void); 118 | extern void OsPowerOffSetFlag(void); 119 | extern void OsPowerOffFuncHook(PowerOffFuncT powerOffFunc); 120 | extern void OsCpuPowerOff(void); /* hook之前异常, 需实现该函数 */ 121 | #endif 122 | 123 | #define OS_TSK_PARA_0 0 124 | #define OS_TSK_PARA_1 1 125 | #define OS_TSK_PARA_2 2 126 | #define OS_TSK_PARA_3 3 127 | 128 | /* 定义任务的缺省优先级 */ 129 | #define OS_TSK_DEFAULT_PRIORITY 20 130 | #define OS_TSK_PRIO_BIT_MAP_POW 5 131 | #define OS_TSK_STACK_TOP_MAGIC 0xAAAAAAAA 132 | 133 | #define OS_TSK_PRIO_RDY_BIT 0x80000000U 134 | 135 | #define OS_TASK_LOCK_DATA g_uniTaskLock 136 | #define IDLE_TASK_ID g_idleTaskId 137 | #define RUNNING_TASK g_runningTask 138 | #define TSK_GET_INDEX(taskId) ((taskId) - g_tskBaseId) 139 | 140 | /* 内核进程的进程及线程调度控制块使用同一类型 */ 141 | #define OS_MAX_TCB_NUM (g_tskMaxNum + 1 + 1) // 1个IDLE,1个无效任务 142 | 143 | #define OS_TSK_DELAY_LOCKED_DETACH(task) ListDelete(&(task)->timerList) 144 | #define CHECK_TSK_PID_OVERFLOW(taskId) (TSK_GET_INDEX(taskId) >= (g_tskMaxNum + 1)) 145 | 146 | /* 定义任务的缺省任务栈大小 */ 147 | #define OS_PST_ZOMBIE_TASK (&g_tskCbArray[OS_MAX_TCB_NUM - 1]) 148 | #define TSK_IS_UNUSED(tsk) ((tsk)->taskStatus == OS_TSK_UNUSED) 149 | #define TSK_STATUS_TST(tsk, statBit) (((tsk)->taskStatus & (statBit)) != 0) 150 | #define TSK_STATUS_CLEAR(tsk, statBit) ((tsk)->taskStatus &= ~(statBit)) 151 | #define TSK_STATUS_SET(tsk, statBit) ((tsk)->taskStatus |= (statBit)) 152 | #define OS_TSK_LOCK() (OS_TASK_LOCK_DATA++) 153 | #define GET_TCB_PEND(ptr) LIST_COMPONENT(ptr, struct TagTskCb, pendList) 154 | #define GET_TCB_HANDLE(taskPid) (((struct TagTskCb *)g_tskCbArray) + TSK_GET_INDEX(taskPid)) 155 | // 保留一个idle task。最大任务handle为FE,FF表示硬中断线程。 156 | #define MAX_TASK_NUM ((1U << OS_TSK_TCB_INDEX_BITS) - 2) // 254 157 | #define OS_TSK_BLOCK (OS_TSK_DELAY | OS_TSK_PEND | OS_TSK_SUSPEND | OS_TSK_QUEUE_PEND | \ 158 | OS_TSK_EVENT_PEND | OS_TSK_WAITQUEUE_PEND) 159 | 160 | #if defined(OS_OPTION_LINUX) 161 | #define KTHREAD_TSK_STATE_TST(tsk, tskState) (((tsk)->kthreadTsk != NULL) && ((tsk)->kthreadTsk->state == (tskState))) 162 | #define KTHREAD_TSK_STATE_SET(tsk, tskState) ((tsk)->kthreadTsk != NULL ? (tsk)->kthreadTsk->state = (tskState) : 0) 163 | #endif 164 | 165 | #define OS_TSK_SUSPEND_READY_BLOCK (OS_TSK_SUSPEND) 166 | // 设置任务优先级就绪链表主BitMap中Bit位,每32个优先级对应一个BIT位,即Bit0(优先级0~31),Bit1(优先级32~63),依次类推。 167 | #define OS_SET_RDY_TSK_BIT_MAP(priority) \ 168 | (OS_TSK_PRIO_RDY_BIT >> ((priority) >> OS_TSK_PRIO_BIT_MAP_POW)) 169 | // 清除任务优先级就绪链表主BitMap中Bit位,每32个优先级对应一个BIT位,即Bit0(优先级0~31),Bit1(优先级32~63),依次类推。 170 | #define OS_CLR_RDY_TSK_BIT_MAP(priority) \ 171 | (~(OS_TSK_PRIO_RDY_BIT >> ((priority) >> OS_TSK_PRIO_BIT_MAP_POW))) 172 | 173 | // 设置任务优先级就绪链表子BitMap中Bit位,每个优先级对应一个BIT位。 174 | #define OS_SET_CHILD_BIT_MAP(priority) \ 175 | (OS_TSK_PRIO_RDY_BIT >> ((priority) % OS_WORD_BIT_NUM)) 176 | // 清除任务优先级就绪链表子BitMap中Bit位,每个优先级对应一个BIT位。 177 | #define OS_CLR_CHILD_BIT_MAP(priority) \ 178 | (~(OS_TSK_PRIO_RDY_BIT >> ((priority) % OS_WORD_BIT_NUM))) 179 | 180 | extern void OsTaskScan(void); 181 | extern void OsTskSchedule(void); 182 | extern void OsTskEntry(TskHandle taskId); 183 | extern void OsTaskExit(struct TagTskCb *tsk); 184 | extern void OsTskReadyAdd(struct TagTskCb *task); 185 | extern void OsTskReadyDel(struct TagTskCb *taskCb); 186 | extern void OsTskSwitchHookCaller(U32 prevPid, U32 nextPid); 187 | extern void OsTskTimerAdd(struct TagTskCb *taskCb, uintptr_t timeout); 188 | 189 | extern U32 OsTskMaxNumGet(void); 190 | extern U32 OsTaskDelete(TskHandle taskPid); 191 | extern U32 OsTaskCreateOnly(TskHandle *taskPid, struct TskInitParam *initParam); 192 | 193 | extern void OsTskScheduleFast(void); 194 | extern void OsTskScheduleFastPs(uintptr_t intSave); 195 | 196 | /* 197 | * 模块内内联函数定义 198 | */ 199 | OS_SEC_ALW_INLINE INLINE void OsTskHighestSet(void) 200 | { 201 | struct TagTskCb *taskCb = NULL; 202 | struct TagTskCb *savedTaskCb = NULL; 203 | 204 | // 遍历g_runQueue队列,查找优先级最高的任务 205 | LIST_FOR_EACH(taskCb, &g_runQueue, struct TagTskCb, pendList) { 206 | // 第一个任务,直接保存到savedTaskCb 207 | if(savedTaskCb == NULL) { 208 | savedTaskCb = taskCb; 209 | continue; 210 | } 211 | // 比较优先级,值越小优先级越高 212 | if(taskCb->priority < savedTaskCb->priority){ 213 | savedTaskCb = taskCb; 214 | } 215 | } 216 | 217 | g_highestTask = savedTaskCb; 218 | } 219 | 220 | #endif /* PRT_TASK_EXTERNAL_H */ 221 | -------------------------------------------------------------------------------- /source/_static/prt_typedef.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * UniProton is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | * See the Mulan PSL v2 for more details. 12 | * Create: 2009-12-22 13 | * Description: 定义基本数据类型和数据结构。 14 | */ 15 | #ifndef PRT_TYPEDEF_H 16 | #define PRT_TYPEDEF_H 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #ifdef __cplusplus 23 | #if __cplusplus 24 | extern "C" { 25 | #endif /* __cpluscplus */ 26 | #endif /* __cpluscplus */ 27 | 28 | typedef unsigned char U8; 29 | typedef unsigned short U16; 30 | typedef unsigned int U32; 31 | typedef unsigned long long U64; 32 | typedef signed char S8; 33 | typedef signed short S16; 34 | typedef signed int S32; 35 | typedef signed long long S64; 36 | 37 | typedef void *VirtAddr; 38 | typedef void *PhyAddr; 39 | 40 | #ifndef OS_SEC_ALW_INLINE 41 | #define OS_SEC_ALW_INLINE 42 | #endif 43 | 44 | #ifndef INLINE 45 | #define INLINE static __inline __attribute__((always_inline)) 46 | #endif 47 | 48 | #ifndef OS_EMBED_ASM 49 | #define OS_EMBED_ASM __asm__ __volatile__ 50 | #endif 51 | 52 | /* 参数不加void表示可以传任意个参数 */ 53 | typedef void (*OsVoidFunc)(void); 54 | 55 | #define ALIGN(addr, boundary) (((uintptr_t)(addr) + (boundary) - 1) & ~((uintptr_t)(boundary) - 1)) 56 | #define TRUNCATE(addr, size) ((addr) & ~((uintptr_t)(size) - 1)) 57 | 58 | #ifdef YES 59 | #undef YES 60 | #endif 61 | #define YES 1 62 | 63 | #ifdef NO 64 | #undef NO 65 | #endif 66 | #define NO 0 67 | 68 | #ifndef FALSE 69 | #define FALSE ((bool)0) 70 | #endif 71 | 72 | #ifndef TRUE 73 | #define TRUE ((bool)1) 74 | #endif 75 | 76 | #ifndef NULL 77 | #define NULL ((void *)0) 78 | #endif 79 | 80 | #define OS_ERROR (U32)(-1) 81 | #define OS_INVALID (-1) 82 | 83 | #ifndef OS_OK 84 | #define OS_OK 0 85 | #endif 86 | 87 | #ifndef OS_FAIL 88 | #define OS_FAIL 1 89 | #endif 90 | 91 | #ifndef U8_INVALID 92 | #define U8_INVALID 0xffU 93 | #endif 94 | 95 | #ifndef U12_INVALID 96 | #define U12_INVALID 0xfffU 97 | #endif 98 | 99 | #ifndef U16_INVALID 100 | #define U16_INVALID 0xffffU 101 | #endif 102 | 103 | #ifndef U32_INVALID 104 | #define U32_INVALID 0xffffffffU 105 | #endif 106 | 107 | #ifndef U64_INVALID 108 | #define U64_INVALID 0xffffffffffffffffUL 109 | #endif 110 | 111 | #ifndef U32_MAX 112 | #define U32_MAX 0xFFFFFFFFU 113 | #endif 114 | 115 | #ifndef S32_MAX 116 | #define S32_MAX 0x7FFFFFFF 117 | #endif 118 | 119 | #ifndef S32_MIN 120 | #define S32_MIN (-S32_MAX-1) 121 | #endif 122 | 123 | #ifndef LIKELY 124 | #define LIKELY(x) __builtin_expect(!!(x), 1) 125 | #endif 126 | 127 | #ifndef UNLIKELY 128 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 129 | #endif 130 | 131 | #ifdef __cplusplus 132 | #if __cplusplus 133 | } 134 | #endif /* __cpluscplus */ 135 | #endif /* __cpluscplus */ 136 | 137 | #endif /* PRT_TYPEDEF_H */ 138 | -------------------------------------------------------------------------------- /source/_static/vsnprintf_s.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static void ftoa_fixed(char *buffer, double value); 4 | static void ftoa_sci(char *buffer, double value); 5 | 6 | int strlen(const char *s) 7 | { 8 | const char *sc = (void *)0 ; 9 | 10 | for (sc = s; *sc != '\0'; ++sc) /* nothing */ 11 | ; 12 | 13 | return sc - s; 14 | } 15 | 16 | /* private function */ 17 | #define _ISDIGIT(c) ((unsigned)((c) - '0') < 10) 18 | 19 | /** 20 | * @brief This function will duplicate a string. 21 | * 22 | * @param n is the string to be duplicated. 23 | * 24 | * @param base is support divide instructions value. 25 | * 26 | * @return the duplicated string pointer. 27 | */ 28 | #ifdef RT_KPRINTF_USING_LONGLONG 29 | inline int divide(unsigned long long *n, int base) 30 | #else 31 | inline int divide(unsigned long *n, int base) 32 | #endif /* RT_KPRINTF_USING_LONGLONG */ 33 | { 34 | int res; 35 | 36 | /* optimized for processor which does not support divide instructions. */ 37 | #ifdef RT_KPRINTF_USING_LONGLONG 38 | res = (int)((*n) % base); 39 | *n = (long long)((*n) / base); 40 | #else 41 | res = (int)((*n) % base); 42 | *n = (long)((*n) / base); 43 | #endif 44 | 45 | return res; 46 | } 47 | 48 | int skip_atoi(const char **s) 49 | { 50 | int i = 0; 51 | while (_ISDIGIT(**s)) 52 | i = i * 10 + *((*s)++) - '0'; 53 | 54 | return i; 55 | } 56 | 57 | 58 | #define ZEROPAD (1 << 0) /* pad with zero */ 59 | #define SIGN (1 << 1) /* unsigned/signed long */ 60 | #define PLUS (1 << 2) /* show plus */ 61 | #define SPACE (1 << 3) /* space if plus */ 62 | #define LEFT (1 << 4) /* left justified */ 63 | #define SPECIAL (1 << 5) /* 0x */ 64 | #define LARGE (1 << 6) /* use 'ABCDEF' instead of 'abcdef' */ 65 | 66 | #define RT_PRINTF_PRECISION 67 | 68 | static char *print_number(char *buf, 69 | char *end, 70 | #ifdef RT_KPRINTF_USING_LONGLONG 71 | unsigned long long num, 72 | #else 73 | unsigned long num, 74 | #endif /* RT_KPRINTF_USING_LONGLONG */ 75 | int base, 76 | int qualifier, 77 | int s, 78 | #ifdef RT_PRINTF_PRECISION 79 | int precision, 80 | #endif /* RT_PRINTF_PRECISION */ 81 | int type) 82 | { 83 | char c = 0, sign = 0; 84 | #ifdef RT_KPRINTF_USING_LONGLONG 85 | char tmp[64] = {0}; 86 | #else 87 | char tmp[32] = {0}; 88 | #endif /* RT_KPRINTF_USING_LONGLONG */ 89 | int precision_bak = precision; 90 | const char *digits = (void *)0; 91 | static const char small_digits[] = "0123456789abcdef"; 92 | static const char large_digits[] = "0123456789ABCDEF"; 93 | int i = 0; 94 | int size = 0; 95 | 96 | size = s; 97 | 98 | digits = (type & LARGE) ? large_digits : small_digits; 99 | if (type & LEFT) 100 | { 101 | type &= ~ZEROPAD; 102 | } 103 | 104 | c = (type & ZEROPAD) ? '0' : ' '; 105 | 106 | /* get sign */ 107 | sign = 0; 108 | if (type & SIGN) 109 | { 110 | switch (qualifier) 111 | { 112 | case 'h': 113 | if ((short)num < 0) 114 | { 115 | sign = '-'; 116 | num = (unsigned short)-num; 117 | } 118 | break; 119 | case 'L': 120 | case 'l': 121 | if ((long)num < 0) 122 | { 123 | sign = '-'; 124 | num = (unsigned long)-num; 125 | } 126 | break; 127 | case 0: 128 | default: 129 | if ((int)num < 0) 130 | { 131 | sign = '-'; 132 | num = (unsigned int)-num; 133 | } 134 | break; 135 | } 136 | 137 | if (sign != '-') 138 | { 139 | if (type & PLUS) 140 | { 141 | sign = '+'; 142 | } 143 | else if (type & SPACE) 144 | { 145 | sign = ' '; 146 | } 147 | } 148 | } 149 | 150 | #ifdef RT_PRINTF_SPECIAL 151 | if (type & SPECIAL) 152 | { 153 | if (base == 2 || base == 16) 154 | { 155 | size -= 2; 156 | } 157 | else if (base == 8) 158 | { 159 | size--; 160 | } 161 | } 162 | #endif /* RT_PRINTF_SPECIAL */ 163 | 164 | i = 0; 165 | if (num == 0) 166 | { 167 | tmp[i++] = '0'; 168 | } 169 | else 170 | { 171 | while (num != 0) 172 | tmp[i++] = digits[divide(&num, base)]; 173 | } 174 | 175 | #ifdef RT_PRINTF_PRECISION 176 | if (i > precision) 177 | { 178 | precision = i; 179 | } 180 | size -= precision; 181 | #else 182 | size -= i; 183 | #endif /* RT_PRINTF_PRECISION */ 184 | 185 | if (!(type & (ZEROPAD | LEFT))) 186 | { 187 | if ((sign) && (size > 0)) 188 | { 189 | size--; 190 | } 191 | 192 | while (size-- > 0) 193 | { 194 | if (buf < end) 195 | { 196 | *buf = ' '; 197 | } 198 | 199 | ++ buf; 200 | } 201 | } 202 | 203 | if (sign) 204 | { 205 | if (buf < end) 206 | { 207 | *buf = sign; 208 | } 209 | -- size; 210 | ++ buf; 211 | } 212 | 213 | #ifdef RT_PRINTF_SPECIAL 214 | if (type & SPECIAL) 215 | { 216 | if (base == 2) 217 | { 218 | if (buf < end) 219 | *buf = '0'; 220 | ++ buf; 221 | if (buf < end) 222 | *buf = 'b'; 223 | ++ buf; 224 | } 225 | else if (base == 8) 226 | { 227 | if (buf < end) 228 | *buf = '0'; 229 | ++ buf; 230 | } 231 | else if (base == 16) 232 | { 233 | if (buf < end) 234 | { 235 | *buf = '0'; 236 | } 237 | 238 | ++ buf; 239 | if (buf < end) 240 | { 241 | *buf = type & LARGE ? 'X' : 'x'; 242 | } 243 | ++ buf; 244 | } 245 | } 246 | #endif /* RT_PRINTF_SPECIAL */ 247 | 248 | /* no align to the left */ 249 | if (!(type & LEFT)) 250 | { 251 | while (size-- > 0) 252 | { 253 | if (buf < end) 254 | { 255 | *buf = c; 256 | } 257 | 258 | ++ buf; 259 | } 260 | } 261 | 262 | #ifdef RT_PRINTF_PRECISION 263 | while (i < precision--) 264 | { 265 | if (buf < end) 266 | { 267 | *buf = '0'; 268 | } 269 | 270 | ++ buf; 271 | } 272 | #endif /* RT_PRINTF_PRECISION */ 273 | 274 | /* put number in the temporary buffer */ 275 | while (i-- > 0 && (precision_bak != 0)) 276 | { 277 | if (buf < end) 278 | { 279 | *buf = tmp[i]; 280 | } 281 | 282 | ++ buf; 283 | } 284 | 285 | while (size-- > 0) 286 | { 287 | if (buf < end) 288 | { 289 | *buf = ' '; 290 | } 291 | 292 | ++ buf; 293 | } 294 | 295 | return buf; 296 | } 297 | 298 | /** 299 | * @brief This function will fill a formatted string to buffer. 300 | * 301 | * @param buf is the buffer to save formatted string. 302 | * 303 | * @param size is the size of buffer. 304 | * 305 | * @param fmt is the format parameters. 306 | * 307 | * @param args is a list of variable parameters. 308 | * 309 | * @return The number of characters actually written to buffer. 310 | */ 311 | int vsnprintf_s(char *buf, int size, int no_count, const char *fmt, va_list args) 312 | { 313 | #ifdef RT_KPRINTF_USING_LONGLONG 314 | unsigned long long num = 0; 315 | #else 316 | unsigned long num = 0; 317 | #endif /* RT_KPRINTF_USING_LONGLONG */ 318 | int i = 0, len = 0; 319 | char *str = (void *)0, *end = (void *)0, c = 0; 320 | const char *s = (void *)0; 321 | 322 | unsigned char base = 0; /* the base of number */ 323 | unsigned char flags = 0; /* flags to print number */ 324 | unsigned char qualifier = 0; /* 'h', 'l', or 'L' for integer fields */ 325 | int field_width = 0; /* width of output field */ 326 | 327 | #ifdef RT_PRINTF_PRECISION 328 | int precision = 0; /* min. # of digits for integers and max for a string */ 329 | #endif /* RT_PRINTF_PRECISION */ 330 | 331 | str = buf; 332 | end = buf + size; 333 | 334 | /* Make sure end is always >= buf */ 335 | if (end < buf) 336 | { 337 | end = ((char *) - 1); 338 | size = end - buf; 339 | } 340 | 341 | for (; *fmt ; ++fmt) 342 | { 343 | if (*fmt != '%') 344 | { 345 | if (str < end) 346 | { 347 | *str = *fmt; 348 | } 349 | 350 | ++ str; 351 | continue; 352 | } 353 | 354 | /* process flags */ 355 | flags = 0; 356 | 357 | while (1) 358 | { 359 | /* skips the first '%' also */ 360 | ++ fmt; 361 | if (*fmt == '-') flags |= LEFT; 362 | else if (*fmt == '+') flags |= PLUS; 363 | else if (*fmt == ' ') flags |= SPACE; 364 | else if (*fmt == '#') flags |= SPECIAL; 365 | else if (*fmt == '0') flags |= ZEROPAD; 366 | else break; 367 | } 368 | 369 | /* get field width */ 370 | field_width = -1; 371 | if (_ISDIGIT(*fmt)) 372 | { 373 | field_width = skip_atoi(&fmt); 374 | } 375 | else if (*fmt == '*') 376 | { 377 | ++ fmt; 378 | /* it's the next argument */ 379 | field_width = va_arg(args, int); 380 | if (field_width < 0) 381 | { 382 | field_width = -field_width; 383 | flags |= LEFT; 384 | } 385 | } 386 | 387 | #ifdef RT_PRINTF_PRECISION 388 | /* get the precision */ 389 | precision = -1; 390 | if (*fmt == '.') 391 | { 392 | ++ fmt; 393 | if (_ISDIGIT(*fmt)) 394 | { 395 | precision = skip_atoi(&fmt); 396 | } 397 | else if (*fmt == '*') 398 | { 399 | ++ fmt; 400 | /* it's the next argument */ 401 | precision = va_arg(args, int); 402 | } 403 | if (precision < 0) 404 | { 405 | precision = 0; 406 | } 407 | } 408 | #endif /* RT_PRINTF_PRECISION */ 409 | /* get the conversion qualifier */ 410 | qualifier = 0; 411 | #ifdef RT_KPRINTF_USING_LONGLONG 412 | if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') 413 | #else 414 | if (*fmt == 'h' || *fmt == 'l') 415 | #endif /* RT_KPRINTF_USING_LONGLONG */ 416 | { 417 | qualifier = *fmt; 418 | ++ fmt; 419 | #ifdef RT_KPRINTF_USING_LONGLONG 420 | if (qualifier == 'l' && *fmt == 'l') 421 | { 422 | qualifier = 'L'; 423 | ++ fmt; 424 | } 425 | #endif /* RT_KPRINTF_USING_LONGLONG */ 426 | } 427 | 428 | /* the default base */ 429 | base = 10; 430 | 431 | switch (*fmt) 432 | { 433 | case 'c': 434 | if (!(flags & LEFT)) 435 | { 436 | while (--field_width > 0) 437 | { 438 | if (str < end) *str = ' '; 439 | ++ str; 440 | } 441 | } 442 | 443 | /* get character */ 444 | c = (unsigned char)va_arg(args, int); 445 | if (str < end) 446 | { 447 | *str = c; 448 | } 449 | ++ str; 450 | 451 | /* put width */ 452 | while (--field_width > 0) 453 | { 454 | if (str < end) *str = ' '; 455 | ++ str; 456 | } 457 | continue; 458 | 459 | case 's': 460 | s = va_arg(args, char *); 461 | if (!s) 462 | { 463 | s = "(NULL)"; 464 | } 465 | 466 | for (len = 0; (len != field_width) && (s[len] != '\0'); len++); 467 | #ifdef RT_PRINTF_PRECISION 468 | if (precision > 0 && len > precision) 469 | { 470 | len = precision; 471 | } 472 | #endif /* RT_PRINTF_PRECISION */ 473 | 474 | if (!(flags & LEFT)) 475 | { 476 | while (len < field_width--) 477 | { 478 | if (str < end) *str = ' '; 479 | ++ str; 480 | } 481 | } 482 | 483 | for (i = 0; i < len; ++i) 484 | { 485 | if (str < end) *str = *s; 486 | ++ str; 487 | ++ s; 488 | } 489 | 490 | while (len < field_width--) 491 | { 492 | if (str < end) *str = ' '; 493 | ++ str; 494 | } 495 | continue; 496 | 497 | case 'p': 498 | if (field_width == -1) 499 | { 500 | field_width = sizeof(void *) << 1; 501 | #ifdef RT_PRINTF_SPECIAL 502 | field_width += 2; /* `0x` prefix */ 503 | flags |= SPECIAL; 504 | #endif 505 | flags |= ZEROPAD; 506 | } 507 | #ifdef RT_PRINTF_PRECISION 508 | str = print_number(str, end, 509 | (unsigned long)va_arg(args, void *), 510 | 16, qualifier, field_width, precision, flags); 511 | #else 512 | str = print_number(str, end, 513 | (unsigned long)va_arg(args, void *), 514 | 16, qualifier, field_width, flags); 515 | #endif 516 | continue; 517 | 518 | case '%': 519 | if (str < end) 520 | { 521 | *str = '%'; 522 | } 523 | ++ str; 524 | continue; 525 | 526 | /* integer number formats - set up the flags and "break" */ 527 | case 'b': 528 | base = 2; 529 | break; 530 | case 'o': 531 | base = 8; 532 | break; 533 | 534 | case 'X': 535 | flags |= LARGE; 536 | case 'x': 537 | base = 16; 538 | break; 539 | 540 | case 'd': 541 | case 'i': 542 | flags |= SIGN; 543 | case 'u': 544 | break; 545 | 546 | default: 547 | if (str < end) 548 | { 549 | *str = '%'; 550 | } 551 | ++ str; 552 | 553 | if (*fmt) 554 | { 555 | if (str < end) 556 | { 557 | *str = *fmt; 558 | } 559 | ++ str; 560 | } 561 | else 562 | { 563 | -- fmt; 564 | } 565 | continue; 566 | } 567 | 568 | #ifdef RT_KPRINTF_USING_LONGLONG 569 | if (qualifier == 'L') 570 | { 571 | num = va_arg(args, unsigned long long); 572 | } 573 | else if (qualifier == 'l') 574 | #else 575 | if (qualifier == 'l') 576 | #endif /* RT_KPRINTF_USING_LONGLONG */ 577 | { 578 | num = va_arg(args, unsigned long); 579 | } 580 | else if (qualifier == 'h') 581 | { 582 | num = (unsigned short)va_arg(args, int); 583 | if (flags & SIGN) 584 | { 585 | num = (short)num; 586 | } 587 | } 588 | else 589 | { 590 | num = (unsigned int)va_arg(args, unsigned long); 591 | } 592 | #ifdef RT_PRINTF_PRECISION 593 | str = print_number(str, end, num, base, qualifier, field_width, precision, flags); 594 | #else 595 | str = print_number(str, end, num, base, qualifier, field_width, flags); 596 | #endif 597 | } 598 | 599 | if (size > 0) 600 | { 601 | if (str < end) 602 | { 603 | *str = '\0'; 604 | } 605 | else 606 | { 607 | end[-1] = '\0'; 608 | } 609 | } 610 | 611 | /* the trailing null byte doesn't count towards the total 612 | * ++str; 613 | */ 614 | return str - buf; 615 | } -------------------------------------------------------------------------------- /source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = 'os2024 lab' 10 | copyright = '2024, 湖南大学 李蕊(rui@hnu.edu.cn) ' 11 | author = 'rui@hnu.edu.cn' 12 | 13 | # -- General configuration --------------------------------------------------- 14 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 15 | 16 | extensions = [] 17 | 18 | templates_path = ['_templates'] 19 | exclude_patterns = [] 20 | 21 | language = 'zh_CN' 22 | 23 | # -- Options for HTML output ------------------------------------------------- 24 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 25 | 26 | html_theme = 'sphinx_rtd_theme' 27 | html_static_path = ['_static'] 28 | -------------------------------------------------------------------------------- /source/index.rst: -------------------------------------------------------------------------------- 1 | .. os2024_lab documentation master file, created by 2 | sphinx-quickstart on Wed Jan 3 12:35:50 2024. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | 操作系统课程实验(2024 UniProton版) 7 | ====================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :caption: 实验内容: 12 | 13 | intro/index 14 | lab1/index 15 | lab2/index 16 | lab3/index 17 | lab4/index 18 | lab5/index 19 | lab6/index 20 | lab7/index 21 | lab8/index 22 | lab10/index 23 | lab9/index 24 | errs/index 25 | references/index 26 | 27 | .. Indices and tables 28 | .. ================== 29 | .. 30 | .. * :ref:`genindex` 31 | .. * :ref:`modindex` 32 | .. * :ref:`search` 33 | -------------------------------------------------------------------------------- /source/intro/index.rst: -------------------------------------------------------------------------------- 1 | 前言 2 | ===================== 3 | 4 | - 零拾(010)计划:操作系统理论课程解决从0到1的问题,课程实验解决从1到10的问题。希望同学们可以以此为起点从10再到100,甚至1000。 5 | - 010的名字是受教育部 `101计划 `_ 的启发,教育部101计划是指本科教育教学改革试点工作计划。 6 | - 从象牙塔到产业界,我们拆解的是 `UniProton `_ 操作系统内核,这是华为 OpenEuler 旗下的嵌入式实时内核,未来也将融入鸿蒙生态。 7 | - 我们的目标不是为了构建一个完整的操作系统,而且通过对一款商业内核的拆解、简化,并从0开始构建来了解操作系统中的关键技术和算法等。 8 | - 每个实验需理解的关键代码为数十行到数百行之间,同学们需要自行撰写的代码在数行到数十行之间,完全在同学们可接受范围内。 9 | - 最终我们构建操作系统如下,该系统支持: 10 | 11 | .. image:: ./miniEuler.gif 12 | 13 | - shell 14 | - 时间Tick 15 | - 任务创建 16 | - 任务调度 17 | - 任务延迟 18 | - 信号量 19 | 20 | 21 | 22 | - UniProton 采用木兰协议,其版权声明如下。 23 | 24 | .. code-block:: c 25 | 26 | /* 27 | * Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved. 28 | * 29 | * UniProton is licensed under Mulan PSL v2. 30 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 31 | * You may obtain a copy of Mulan PSL v2 at: 32 | * http://license.coscl.org.cn/MulanPSL2 33 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 34 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 35 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 36 | * See the Mulan PSL v2 for more details. 37 | */ 38 | 39 | - 让我们从0开始吧😄💪🏻 40 | 41 | - 实验设计与指导书编写: 湖南大学 李蕊[`个人页面 `_] 42 | - 贡献者列表(排名不分先后) 43 | - vastina (湖南大学) 44 | -------------------------------------------------------------------------------- /source/intro/miniEuler.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/intro/miniEuler.gif -------------------------------------------------------------------------------- /source/lab1/exec-gdb-cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab1/exec-gdb-cmd.png -------------------------------------------------------------------------------- /source/lab1/exp1_debug_on_dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab1/exp1_debug_on_dots.png -------------------------------------------------------------------------------- /source/lab1/index.rst: -------------------------------------------------------------------------------- 1 | 实验一 环境配置 2 | ===================== 3 | 4 | 安装工具链 5 | -------------------------- 6 | 7 | .. attention:: 8 | 9 | Linux请使用ubuntu 18以上的版本开展本系列实验(本系列实验已在Ubuntu 18 (64位)版本, MacOS 12 (Intel Core i5)版本上验证)。 10 | 11 | 安装交叉编译工具链 (aarch64) 12 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 13 | 14 | 交叉工具链可以从Arm官网的 `GNU Toolchain Downloads `_ 页面下载。 15 | 16 | Linux 17 | 18 | .. code-block:: console 19 | 20 | # 下载工具链,以下载工具链版本为11.2,宿主机为x86 64位 Linux机器为例 21 | $ wget https://developer.arm.com/-/media/Files/downloads/gnu/11.2-2022.02/binrel/gcc-arm-11.2-2022.02-x86_64-aarch64-none-elf.tar.xz 22 | # 解压工具链 23 | $ tar -xf gcc-arm-(按Tab键补全) 24 | # 重命名工具链目录 25 | $ mv gcc-arm-(按Tab键补全) aarch64-none-elf 26 | 27 | 将目录 /path/to/your/aarch64-none-elf/bin (这里对应你自己实际的目录,用 pwd 命令查看)加入到环境变量 PATH 中。 28 | 29 | 测试工具链是否安装成功 30 | 31 | .. code-block:: console 32 | 33 | $ aarch64-none-elf-gcc --version 34 | 35 | 36 | Mac 37 | 38 | 操作方法与Linux相同。但需下载工具链版本(Intel芯片)为: gcc-arm-11.2-2022.02-darwin-x86_64-aarch64-none-elf.tar.xz。 39 | 40 | .. note:: 41 | 下载的交叉工具链版本需选择与你宿主机器和目标机器均对应的版本,本系列实验目标机器为: AArch64 bare-metal target (aarch64-none-elf),再依据你的宿主机器选择相对应的版本。 42 | 43 | 44 | 如果是M1或M2芯片版本的Mac,请使用更新版本的工具链。 45 | 46 | .. note:: 47 | 交叉工具链可能会遇到缺少依赖库的问题,请自行检索解决。 Linux 下的 ldd 命令, Mac 下的 otool -L 命令可以查看动态链接库的依赖关系。 48 | 49 | 安装QEMU模拟器 50 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 51 | 52 | Linux 53 | 54 | .. code-block:: console 55 | 56 | $ sudo apt-get update 57 | $ sudo apt-get install qemu 58 | $ sudo apt-get install qemu-system 59 | 60 | Mac 61 | 62 | 参考QEMU官网 https://wiki.qemu.org/Hosts/Mac 安装。 63 | 64 | 安装CMake 65 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 66 | 67 | Linux 68 | 69 | .. code-block:: console 70 | 71 | $ sudo apt-get install cmake 72 | 73 | Mac 可通过 brew install cmake 或从官网的 `Get the Software `_ 页面下载安装。 74 | 75 | 创建裸机(Bare Metal)程序 76 | -------------------------- 77 | 78 | 由于我们的目标是编写一个操作系统,所以我们需要创建一个独立于操作系统的可执行程序,又称 独立式可执行程序(freestanding executable) 或 裸机程序(bare-metal executable) 。这意味着所有依赖于操作系统的库我们都不能使用。比如 std 中的大部分内容(io, thread, file system, etc.)都需要操作系统的支持,所以这部分内容我们不能使用。 79 | 80 | 但是,不依赖于操作系统的语言特性还是可以继续使用的。 81 | 82 | 创建项目 83 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 84 | 85 | 我们将参照UniProton设计项目的目录层次,但为了理解方便,将会进行相应简化。其初始目录结构如下图所示。 86 | 87 | .. image:: tree_init.png 88 | 89 | 可以看到:在实验一 lab1 目录下有一个 src 目录,所有源代码均放在此处。其下包括 bsp 目录和 include 目录。其中 bsp 目录存放与硬件紧密相关的代码,include 目录中存放项目的大部分头文件。 90 | 91 | .. note:: 92 | src 下目前仅 main.c 文件 93 | 94 | include 目录下有 prt_typedef.h 头文件,它是 UniProton 所使用的基本数据类型和结构的定义,如 U8、U16、U32、U64等。 95 | 96 | bsp 目录下目前包括 CMakeLists.txt 和两个汇编文件 start.S 和 prt_reset_vector.S。 97 | 98 | 99 | 100 | 在src/下创建main.c 101 | 102 | main.c源码 103 | 104 | .. code-block:: c 105 | :linenos: 106 | 107 | #include "prt_typedef.h" 108 | 109 | #define UART_REG_WRITE(value, addr) (*(volatile U32 *)((uintptr_t)addr) = (U32)value) 110 | char out_str[] = "AArch64 Bare Metal"; 111 | 112 | S32 main(void) 113 | { 114 | int length = sizeof(out_str) / sizeof(out_str[0]); 115 | 116 | // 逐个输出字符 117 | for (int i = 0; i < length - 1; i++) { 118 | UART_REG_WRITE(out_str[i], 0x9000000); 119 | } 120 | } 121 | 122 | .. note:: 123 | S32 是在 prt_typedef.h 中定义的基本类型,这是为了屏蔽各硬件系统的区别,方便操作系统移植到多种不同规格的硬件上。 124 | 125 | main 函数的主要功能(L11-L12)是把 out_str 中的字符通过宏 UART_REG_WRITE 逐个写入地址为 0x9000000 的地方。其作用将在 :doc:`../lab2/index` 部分详细解释。 126 | 127 | 在src/include/下创建prt_typedef.h。 `此处 <../\_static/prt_typedef.h>`_ 下载 prt_typedef.h 128 | 129 | 在src/bsp/下创建 start.S 和 prt_reset_vector.S 两个文件 130 | 131 | start.S 源码 132 | 133 | .. code-block:: asm 134 | :linenos: 135 | 136 | .global OsEnterMain 137 | .extern __os_sys_sp_end 138 | 139 | .type start, function 140 | .section .text.bspinit, "ax" 141 | .balign 4 142 | 143 | .global OsElxState 144 | .type OsElxState, @function 145 | OsElxState: 146 | MRS x6, CurrentEL // 把系统寄存器 CurrentEL 的值读入到通用寄存器 x6 中 147 | MOV x2, #0x4 // CurrentEL EL1: bits [3:2] = 0b01 148 | CMP w6, w2 149 | 150 | BEQ Start // 若 CurrentEl 为 EL1 级别,跳转到 Start 处执行,否则死循环。 151 | 152 | OsEl2Entry: 153 | B OsEl2Entry 154 | 155 | Start: 156 | LDR x1, =__os_sys_sp_end // 符号在ld文件中定义 157 | BIC sp, x1, #0xf // 设置栈指针, BIC: bit clear 158 | 159 | B OsEnterMain 160 | 161 | OsEnterReset: 162 | B OsEnterReset 163 | 164 | 165 | .. note:: 166 | L1,L2两行声明 OsEnterMain 和 __os_sys_sp_end 是外部定义的符号,其中 OsEnterMain 在 prt_reset_vector.S 中定义, __os_sys_sp_end 在链接脚本 aarch64-qemu.ld 定义。 167 | 168 | L5 声明这部分代码段(section)的名字是 .text.bspinit 169 | 170 | L10 为系统入口,即系统一启动就会执行从L10开始的代码,其原因在随后的链接脚本中说明。 171 | 172 | L11-L15 检测当前CPU的 Exception Level 是否为 EL1 (将在 :doc:`../lab4/index` 部分详细解释),如果是 EL1 则通过 L15 的 BEQ Start 跳转到标号Start(L20)处开始执行,否则执行 L17 开始的指令,它和 L18 一起构成死循环。 173 | 174 | L11 中的 CurrentEL 是 AArch64 架构的系统寄存器。这些寄存器不能直接操作,需要通过 MRS 指令(把系统寄存器的值读入到通用寄存器)或 MSR 指令(把通用寄存器的值写入到系统寄存器)借助通用寄存器来访问。 175 | 176 | L21-L22 用链接文件定义的地址初始化栈指针 sp,然后 L24 跳转到 prt_reset_vector.S 的 L7 行 OsEnterMain 处开始执行。 177 | 178 | .. tip:: mrs类指令阅读技巧: MRS(Move to Register from System register) 179 | 180 | .. tip:: 完整的寄存器列表及相关描述可访问 `AArch64 System Registers `_ 查看,指令集在 `A64 Instruction Set Architecture `_ 查看。 181 | 182 | prt_reset_vector.S 源码 183 | 184 | .. code-block:: asm 185 | :linenos: 186 | 187 | DAIF_MASK = 0x1C0 // disable SError Abort, IRQ, FIQ 188 | 189 | .global OsVectorTable 190 | .global OsEnterMain 191 | 192 | .section .text.startup, "ax" 193 | OsEnterMain: 194 | BL main 195 | 196 | MOV x2, DAIF_MASK // bits [9:6] disable SError Abort, IRQ, FIQ 197 | MSR DAIF, x2 // 把通用寄存器 x2 的值写入系统寄存器 DAIF 中 198 | 199 | EXITLOOP: 200 | B EXITLOOP 201 | 202 | .. note:: 203 | 目前,完全可以把 start.S 和 prt_reset_vector.S 合成一个文件,但为了将来扩展且与 UniProton 保持一致选择保留2个文件。 204 | 205 | L8 行跳转到通过 BL main 跳转到main.c中的main函数执行,main函数执行完后会回到 L10继续执行。 206 | 207 | L10-L11 禁用了Debug、SError、IRQ和FIQ,因为中断处理尚未设置,详细参见 :doc:`../lab4/index` 208 | 209 | L10 中的 DAIF 是 AArch64 架构的系统寄存器,完整的寄存器列表可参考 Arm 官网的 `AArch64 System Registers `_ 页面。 210 | 211 | L13-L14 进入死循环。 212 | 213 | 在上面两个汇编文件中出现了两种不同的跳转指令 B 和 BL,其中 B 跳转后不返回调用位置, BL 跳转后执行完函数后会回到调用位置继续执行。 214 | 215 | 216 | 217 | 在src/下创建链接文件 aarch64-qemu.ld 218 | 219 | aarch64-qemu.ld 脚本。 该脚本较长,下面展示的仅是目前需了解的部分。完整版可从 `这里 <../\_static/aarch64-qemu.ld>`_ 下载。 220 | 221 | .. code-block:: ld 222 | :linenos: 223 | 224 | ENTRY(__text_start) 225 | 226 | _stack_size = 0x10000; 227 | _heap_size = 0x10000; 228 | 229 | MEMORY 230 | { 231 | IMU_SRAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x800000 /* 内存区域 */ 232 | MMU_MEM (rwx) : ORIGIN = 0x40800000, LENGTH = 0x800000 /* 内存区域 */ 233 | } 234 | 235 | SECTIONS 236 | { 237 | text_start = .; 238 | .start_bspinit : 239 | { 240 | __text_start = .; /* __text_start 指向当前位置, "." 表示当前位置 */ 241 | KEEP(*(.text.bspinit)) 242 | } > IMU_SRAM 243 | 244 | ... ... ... 245 | 246 | .heap (NOLOAD) : 247 | { 248 | . = ALIGN(8); 249 | PROVIDE (__HEAP_INIT = .); 250 | . = . + _heap_size; /* 堆空间 */ 251 | . = ALIGN(8); 252 | PROVIDE (__HEAP_END = .); 253 | } > IMU_SRAM 254 | 255 | .stack (NOLOAD) : 256 | { 257 | . = ALIGN(8); 258 | PROVIDE (__os_sys_sp_start = .); 259 | . = . + _stack_size; /* 栈空间 */ 260 | . = ALIGN(8); 261 | PROVIDE (__os_sys_sp_end = .); 262 | } > IMU_SRAM 263 | end = .; 264 | 265 | ... ... ... 266 | } 267 | 268 | .. Sphinx uses Pygments for highlighting. On a machine that has Pygments installed the command pygmentize -L will list all available lexers. 269 | 270 | .. note:: 271 | L1 的 ENTRY(__text_start)中指明系统入口为 __text_start 。 L17-L18 表明 __text_start 为 .text.bspinit 段的起始位置。而在 start.S 中 L5 处定义了 .text.bspinit 段,其入口为 L10 处的 OsElxState 标号。因此系统的入口实际上就是 start.S 中的 L10 处的 OsElxState 标号处。 272 | 273 | 链接脚本中通过 PROVIDE 定义的符号 __os_sys_sp_end 是全局符号,可以在程序中使用(如 start.s 中),其定义的是栈底的位置。 274 | 275 | L26-L29,L35-L38 处分别定义了堆空间和栈空间。 276 | 277 | .. note:: 278 | 链接脚本中除了组织各个段之外,还可以定义符号,链接脚本中定义的符号被添加到全局符号中 279 | 280 | symbol = expression ; symbol += expression ;第一个表达式表示定义一个符号,第二个表达式对符号值进行操作,中间的空格是必须的 281 | 282 | 当程序和链接脚本中同时定义了变量符号时,链接脚本中的符号会覆盖掉程序中定义的符号 283 | 284 | 定义内存区域后,一个段没有显示地指定将要添加到哪个区域,将会对段的属性和区域的属性进行匹配 285 | 286 | 详情可参考 `The GNU linker `_。此外,这里还有一个简单的 `链接脚本基本介绍 `_ 可参考。 287 | 288 | .. important:: 289 | 链接脚本对理解操作系统的实现非常重要,所以应及早熟悉。 290 | 291 | 工程构建 292 | -------------------------- 293 | 294 | 操作系统是一个复杂的工程。如当前版本的 UniProton 包含了近 500 个文件,超过 10 万行的代码及说明,而 Linux 内核则包含有 6 万多个文件,超过 2700 万行的代码 (2020)。如果纯手工构建这样的系统是不可想象的,所以我们需要构建系统的帮助。 295 | 296 | .. 统计方法 代码行数: find . -type f -print | xargs wc -l 文件个数: ls -lR | grep "^d" | wc -l 297 | 298 | CMake 是一个跨平台的开源构建系统。CMake 通过简单的、与平台和编译器无关的配置文件来控制软件编译过程。 299 | 300 | CMakeLists.txt 301 | ^^^^^^^^^^^^^^^^^^^^^^^^ 302 | 303 | src/下的CMakeLists.txt 304 | 305 | .. code-block:: cmake 306 | :linenos: 307 | 308 | cmake_minimum_required(VERSION 3.12) 309 | 310 | set(CMAKE_SYSTEM_NAME "Generic") # 目标系统(baremental): cmake/tool_chain/uniproton_tool_chain_gcc_arm64.cmake 写的是Linux 311 | set(CMAKE_SYSTEM_PROCESSOR "aarch64") # 目标系统CPU 312 | 313 | set(TOOLCHAIN_PATH "/usr/local/aarch64-none-elf") # 修改为交叉工具链实际所在目录 build.py config.xml中定义 314 | set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-gcc) 315 | set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-g++) 316 | set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-gcc) 317 | set(CMAKE_LINKER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-ld) 318 | 319 | # 定义编译和链接等选项 320 | set(CC_OPTION "-Os -Wformat-signedness -Wl,--build-id=none -fno-PIE -fno-PIE --specs=nosys.specs -fno-builtin -fno-dwarf2-cfi-asm -fomit-frame-pointer -fzero-initialized-in-bss -fdollars-in-identifiers -ffunction-sections -fdata-sections -fno-aggressive-loop-optimizations -fno-optimize-strlen -fno-schedule-insns -fno-inline-small-functions -fno-inline-functions-called-once -fno-strict-aliasing -finline-limit=20 -mlittle-endian -nostartfiles -funwind-tables") 321 | set(AS_OPTION "-Os -Wformat-signedness -Wl,--build-id=none -fno-PIE -fno-PIE --specs=nosys.specs -fno-builtin -fno-dwarf2-cfi-asm -fomit-frame-pointer -fzero-initialized-in-bss -fdollars-in-identifiers -ffunction-sections -fdata-sections -fno-aggressive-loop-optimizations -fno-optimize-strlen -fno-schedule-insns -fno-inline-small-functions -fno-inline-functions-called-once -fno-strict-aliasing -finline-limit=20 -mlittle-endian -nostartfiles -funwind-tables") 322 | set(LD_OPTION " ") 323 | set(CMAKE_C_FLAGS "${CC_OPTION} ") 324 | set(CMAKE_ASM_FLAGS "${AS_OPTION} ") 325 | set(CMAKE_LINK_FLAGS "${LD_OPTION} -T ${CMAKE_CURRENT_SOURCE_DIR}/aarch64-qemu.ld") # 指定链接脚本 326 | set(CMAKE_EXE_LINKER_FLAGS "${LD_OPTION} -T ${CMAKE_CURRENT_SOURCE_DIR}/aarch64-qemu.ld") # 指定链接脚本 327 | set (CMAKE_C_LINK_FLAGS " ") 328 | set (CMAKE_CXX_LINK_FLAGS " ") 329 | 330 | set(HOME_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 331 | 332 | set(APP "miniEuler") # APP变量,后面会用到 ${APP} 333 | project(${APP} LANGUAGES C ASM) # 工程名及所用语言 334 | set(CMAKE_BUILD_TYPE Debug) # 生成 Debug 版本 335 | 336 | include_directories( # include 目录 337 | ${CMAKE_CURRENT_SOURCE_DIR}/include 338 | ${CMAKE_CURRENT_SOURCE_DIR}/bsp 339 | ) 340 | 341 | add_subdirectory(bsp) # 包含子文件夹的内容 342 | 343 | list(APPEND OBJS $) 344 | add_executable(${APP} main.c ${OBJS}) # 可执行文件 345 | 346 | .. hint:: 注意修改 set(TOOLCHAIN_PATH "/usr/local/aarch64-none-elf") 中的目录 347 | 348 | src/bsp/下的CMakeLists.txt 349 | 350 | .. code-block:: cmake 351 | :linenos: 352 | 353 | set(SRCS start.S prt_reset_vector.S ) 354 | add_library(bsp OBJECT ${SRCS}) # OBJECT类型只编译生成.o目标文件,但不实际链接成库 355 | 356 | .. note:: 357 | L36-L37 中指明需链接的目标对象 ${OBJS} 包括 $,而 $ 在src/bsp/下的 CMakeLists.txt 中定义。这样 main.c、prt_reset_vector.S、start.S 都将被包含在可执行文件中。 358 | 359 | CMake 的命令和参数等可参考 `官网文档 `_。此外,这里还有一个很好的入门 `博客文章 `_。 360 | 361 | 可以看到,src/下的 CMakeLists.txt 设置了交叉工具路径、编译和链接选项、项目名称和语言等全局环境,然后设置了需包含头文件的位置和源文件及其子目录。 362 | 363 | 364 | 365 | 366 | 编译运行 367 | ^^^^^^^^^^^^^^^^^^^^^^^^ 368 | 369 | - 编译 370 | 371 | 首先在项目目录 lab1 下创建 build 目录用于编译生成,然后进入 build 目录执行 372 | 373 | .. code-block:: console 374 | 375 | $ cmake ../src 376 | $ cmake --build . 377 | 378 | - 运行 379 | 380 | 在项目目录 lab1 下执行 381 | 382 | .. code-block:: console 383 | 384 | $ qemu-system-aarch64 -machine virt -m 1024M -cpu cortex-a53 -nographic -kernel build/miniEuler -s 385 | 386 | 387 | .. hint:: ctrl-a x 退出 Qemu 388 | 389 | 390 | 调试支持 391 | -------------------------- 392 | 393 | GDB简单调试方法 394 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 395 | 396 | 编译成功后就可以运行,这需要使用前面安装的QEMU模拟器。此外,为了查找并修正bug,我们需要使用调试工具。 397 | 398 | 通过QEMU运行程序并启动调试服务器,默认端口1234 399 | 400 | .. code-block:: console 401 | 402 | $ qemu-system-aarch64 -machine virt,gic-version=2 -m 1024M -cpu cortex-a53 -nographic -kernel build/miniEuler -s -S 403 | 404 | .. note:: 405 | qemu的参数说明: 406 | 407 | -s shorthand for -gdb tcp::1234 408 | 409 | -S freeze CPU at startup (use 'c' to start execution) 410 | 411 | 查看相关参数的作用可在命令行执行: ``qemu-system-aarch64 --help``, 412 | 413 | .. hint:: 414 | 与上面运行程序的差别在于命令中加入了 -S 参数。 415 | 416 | 启动调试客户端 417 | 418 | .. code-block:: console 419 | 420 | $ aarch64-none-elf-gdb build/miniEuler 421 | 422 | 设置调试参数,开始调试 423 | 424 | .. code-block:: 425 | 426 | (gdb) target remote localhost:1234 427 | (gdb) disassemble 428 | (gdb) n 429 | 430 | .. hint:: 可以安装使用 `GDB dashboard `_ 进入可视化调试界面 431 | 432 | 将调试集成到vscode 433 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 434 | 如上通过QEMU运行程序并启动调试服务器 435 | 436 | 打开 main.c 文件,点击 vscode左侧的运行和调试按钮,弹出对话框选择创建 launch.json文件,增加如下配置: 437 | 438 | .. code-block:: json 439 | 440 | { 441 | "version": "0.2.0", 442 | "configurations": [ 443 | { 444 | "name": "aarch64-gdb", 445 | "type": "cppdbg", 446 | "request": "launch", 447 | "program": "${workspaceFolder}/build/miniEuler", 448 | "stopAtEntry": true, 449 | "cwd": "${fileDirname}", 450 | "environment": [], 451 | "externalConsole": false, 452 | "launchCompleteCommand": "exec-run", 453 | "MIMode": "gdb", 454 | "miDebuggerPath": "/usr/local/aarch64-none-elf/bin/aarch64-none-elf-gdb", // 修改成交叉调试器gdb对应位置 455 | "miDebuggerServerAddress": "localhost:1234", 456 | "setupCommands": [ 457 | { 458 | "description": "Enable pretty-printing for gdb", 459 | "text": "-enable-pretty-printing", 460 | "ignoreFailures": true 461 | } 462 | ] 463 | } 464 | ], 465 | 466 | } 467 | 468 | 在左边面板顶部选择刚添加的 aarch64-gdb 选项,点击旁边的绿色 开始调试(F5) 按钮开始调试。 469 | 470 | 在调试时,可以在调试控制台执行gdb命令,如: 471 | 472 | .. image:: exec-gdb-cmd.png 473 | 474 | - 查看指定地址的内存内容。在调试控制台执行 -exec x/20xw 0x40000000 即可,其中 x表示查看命令,20表示查看数量,x表示格式,可选格式包括 Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),t(binary), f(float), a(address), i(instruction), c(char) and s(string).Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).,最后的 w表示字宽,b表示单字节,h表示双字节,w表示四字节,g表示八字节。还可以是指令:-exec x/20i 0x40000000; 字符串:-exec x/20s 0x40000000 475 | - 显示所有寄存器。-exec info all-registers 476 | - 查看寄存器内容。-exec p/x $pc 477 | - 修改寄存器内容。-exec set $x24 = 0x5 478 | - 修改指定内存位置的内容。-exec set {int}0x4000000 = 0x1 或者 -exec set *((int *) 0x4000000) = 0x1 479 | - 修改指定MMIO 寄存器的内容。 -exec set *((volatile int *) 0x08010004) = 0x1 480 | - 退出调试 -exec q 481 | 482 | 总之,可以通过 -exec这种方式可以执行所有的 gdb 调试指令。 483 | 484 | 485 | .. hint:: 486 | 集成到vscode的调试方法默认不支持 start.s 等汇编代码断点调试,如需调试 .s 文件,需在 vscode 中打开允许在任何文件中设置断点选项。 487 | 488 | .. .. image:: exp1_debug_on_dots.png 489 | 490 | .. image:: vscode-debug.png 491 | 492 | QEMU执行结果 493 | 494 | .. image:: qemu-result.png 495 | 496 | 自动化脚本 497 | -------------------------- 498 | 499 | 每次构建和运行系统都需要键入长短不一的命令。方便起见,我们可以使用 shell 脚本来简化这项工作。在项目目录 lab1 下新建 makeMiniEuler.sh 脚本来编译项目,新建 runMiniEuler.sh 脚本来运行项目。你也可以自行创建符合自己需求的脚本。 500 | 501 | makeMiniEuler.sh 502 | 503 | .. code-block:: console 504 | :linenos: 505 | 506 | # sh makeMiniEuler.sh 不打印编译命令 507 | # sh makeMiniEuler.sh -v 打印编译命令等详细信息 508 | rm -rf build/* 509 | mkdir build 510 | cd build 511 | cmake ../src 512 | cmake --build . $1 513 | 514 | runMiniEuler.sh 515 | 516 | .. code-block:: console 517 | :linenos: 518 | 519 | # sh runMiniEuler.sh 直接运行 520 | # sh runMiniEuler.sh -S 启动后在入口处暂停等待调试 521 | 522 | echo qemu-system-aarch64 -machine virt,gic-version=2 -m 1024M -cpu cortex-a53 -nographic -kernel build/miniEuler -s $1 523 | 524 | qemu-system-aarch64 -machine virt,gic-version=2 -m 1024M -cpu cortex-a53 -nographic -kernel build/miniEuler -s $1 525 | 526 | 之后编译及运行程序只需要执行: 527 | 528 | .. code-block:: console 529 | 530 | $ sh makeMiniEuler.sh 531 | $ sh runMiniEuler.sh 532 | 533 | .. note:: 534 | 运行 sh makeMiniEuler.sh -v 将会显示实际执行的编译指令,方便了解编译的过程并查找编译错误原因。 535 | 536 | 运行 sh runMiniEuler.sh -S 将在程序启动后在入口处暂停等待调试,此时可通过 aarch64-none-elf-gdb 或 vscode 连入调试服务器。 537 | 538 | lab1 作业 539 | -------------------------- 540 | 541 | 完成下列实验,并撰写实验报告。 542 | 543 | 作业1 544 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 545 | 请操作 NZCV 寄存器获取 start.S 中执行 `CMP w6, w2` 前后 NZCV 寄存器的变化。 546 | 547 | 作业2 548 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 549 | 550 | 商业操作系统都有复杂的构建系统,试简要分析 UniProton 的构建系统。 551 | 552 | .. hint:: 553 | UniProton 通过在根目录下执行 python build.py m4 (m4是指目标平台,还有如hi3093等)进行构建,所以构建系统的分析可从 build.py 入手进行。 554 | 555 | 作业3 556 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 557 | 558 | 学习如何调试项目。 559 | 560 | -------------------------------------------------------------------------------- /source/lab1/qemu-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab1/qemu-result.png -------------------------------------------------------------------------------- /source/lab1/tree_init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab1/tree_init.png -------------------------------------------------------------------------------- /source/lab1/vscode-debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab1/vscode-debug.png -------------------------------------------------------------------------------- /source/lab10/index.rst: -------------------------------------------------------------------------------- 1 | 实验九 操作系统安全 2 | ===================== 3 | 4 | 5 | 由于操作系统拥有系统权限,一旦出现安全漏洞并被攻击者利用,后果比普通应用程序出现漏洞的影响更大。攻击者若获得了操作系统的控制权,则可以绕过包括访问控制在内的所有基于操作系统的安全机制,从而窃取或篡改系 统中属于其他应用程序的关键数据,或直接对硬件设备进行非法操作从而造成系统毁坏。 6 | 7 | (待完善...) -------------------------------------------------------------------------------- /source/lab2/down-pl011-ref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab2/down-pl011-ref.png -------------------------------------------------------------------------------- /source/lab2/index.rst: -------------------------------------------------------------------------------- 1 | 实验二 Hello, miniEuler 2 | ========================= 3 | 4 | print函数是学习几乎任何一种软件开发语言时最先学习使用的函数,同时该函数也是最基本和原始的程序调试手段,但该函数的实现却并不简单。本实验的目的在于理解操作系统与硬件的接口方法,并实现一个可打印字符的函数(非系统调用),用于后续的调试和开发。 5 | 6 | 了解virt机器 7 | -------------------------- 8 | 9 | 操作系统介于硬件和应用程序之间,向下管理硬件资源,向上提供应用编程接口。设计并实现操作系统需要熟悉底层硬件的组成及其操作方法。 10 | 11 | 本系列实验都会在QEMU模拟器上完成,首先来了解一下模拟的机器信息。可以通过下列两种方法: 12 | 13 | 1. 查看QEMU关于 `virt的描述 `_ , 或者查看QEMU的源码,如github上的 `virt.h `_ 和 `virt.c `_。virt.c中可见如下有关内存映射的内容。 14 | 15 | .. code-block:: c 16 | 17 | static const MemMapEntry base_memmap[] = { 18 | /* Space up to 0x8000000 is reserved for a boot ROM */ 19 | [VIRT_FLASH] = { 0, 0x08000000 }, 20 | [VIRT_CPUPERIPHS] = { 0x08000000, 0x00020000 }, 21 | /* GIC distributor and CPU interfaces sit inside the CPU peripheral space */ 22 | [VIRT_GIC_DIST] = { 0x08000000, 0x00010000 }, 23 | [VIRT_GIC_CPU] = { 0x08010000, 0x00010000 }, 24 | [VIRT_GIC_V2M] = { 0x08020000, 0x00001000 }, 25 | [VIRT_GIC_HYP] = { 0x08030000, 0x00010000 }, 26 | [VIRT_GIC_VCPU] = { 0x08040000, 0x00010000 }, 27 | /* The space in between here is reserved for GICv3 CPU/vCPU/HYP */ 28 | [VIRT_GIC_ITS] = { 0x08080000, 0x00020000 }, 29 | /* This redistributor space allows up to 2*64kB*123 CPUs */ 30 | [VIRT_GIC_REDIST] = { 0x080A0000, 0x00F60000 }, 31 | [VIRT_UART] = { 0x09000000, 0x00001000 }, 32 | [VIRT_RTC] = { 0x09010000, 0x00001000 }, 33 | [VIRT_FW_CFG] = { 0x09020000, 0x00000018 }, 34 | [VIRT_GPIO] = { 0x09030000, 0x00001000 }, 35 | [VIRT_SECURE_UART] = { 0x09040000, 0x00001000 }, 36 | [VIRT_SMMU] = { 0x09050000, 0x00020000 }, 37 | [VIRT_PCDIMM_ACPI] = { 0x09070000, MEMORY_HOTPLUG_IO_LEN }, 38 | [VIRT_ACPI_GED] = { 0x09080000, ACPI_GED_EVT_SEL_LEN }, 39 | [VIRT_NVDIMM_ACPI] = { 0x09090000, NVDIMM_ACPI_IO_LEN}, 40 | [VIRT_PVTIME] = { 0x090a0000, 0x00010000 }, 41 | [VIRT_SECURE_GPIO] = { 0x090b0000, 0x00001000 }, 42 | [VIRT_MMIO] = { 0x0a000000, 0x00000200 }, 43 | /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */ 44 | [VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 }, 45 | [VIRT_SECURE_MEM] = { 0x0e000000, 0x01000000 }, 46 | [VIRT_PCIE_MMIO] = { 0x10000000, 0x2eff0000 }, 47 | [VIRT_PCIE_PIO] = { 0x3eff0000, 0x00010000 }, 48 | [VIRT_PCIE_ECAM] = { 0x3f000000, 0x01000000 }, 49 | /* Actual RAM size depends on initial RAM and device memory settings */ 50 | [VIRT_MEM] = { GiB, LEGACY_RAMLIMIT_BYTES }, 51 | }; 52 | 53 | 2. 通过QEMU导出设备树 54 | 55 | 1. 安装设备树格式转换工具 56 | 57 | Mac下安装 58 | 59 | .. code-block:: console 60 | 61 | 62 | $ brew install dtc 63 | 64 | Linux下安装 65 | 66 | .. code-block:: console 67 | 68 | $ apt-get install device-tree-compiler 69 | 70 | 71 | 2. 通过QEMU导出设备树并转成可读格式 72 | 73 | .. code-block:: console 74 | 75 | $ qemu-system-aarch64 -machine virt,dumpdtb=virt.dtb -cpu cortex-a53 -nographic 76 | $ dtc -I dtb -O dts -o virt.dts virt.dtb 77 | 78 | .. note:: 79 | -machine virt 指明机器类型为virt,这是QEMU仿真的虚拟机器。 80 | 81 | virt.dtb转换后生成的virt.dts中可找到如下内容 82 | 83 | .. code-block:: 84 | 85 | pl011@9000000 { 86 | clock-names = "uartclk\0apb_pclk"; 87 | clocks = <0x8000 0x8000>; 88 | interrupts = <0x00 0x01 0x04>; 89 | reg = <0x00 0x9000000 0x00 0x1000>; 90 | compatible = "arm,pl011\0arm,primecell"; 91 | }; 92 | 93 | chosen { 94 | stdout-path = "/pl011@9000000"; 95 | kaslr-seed = <0xcbd0568d 0xb463306c>; 96 | }; 97 | 98 | 由上可以看出,virt机器包含有pl011的设备,该设备的寄存器在0x9000000开始处。pl011实际上是一个UART设备,即串口。可以看到virt选择使用pl011作为标准输出,这是因为与PC不同,大部分嵌入式系统默认情况下并不包含VGA设备。 99 | 100 | 101 | 实现 PRT_Printf 函数 102 | -------------------------- 103 | 104 | .. 我们参照 `Writing an OS in Rust `_ ( `中文版 `_ )来实现println!宏,但与之不同,我们使用串口来输出,而不是通过操作VGA的Frame Buffer。 105 | 106 | 本系列实验每个实验均依赖前序相关实验,因此可拷贝 lab1 目录并重命名为 lab2 ,在 lab2 目录中再操作(后续实验照此操作)。 107 | 108 | 新建 src/bsp/print.c 文件,完成如下部分。 109 | 110 | 宏定义 111 | ^^^^^^^^^^^^^^^^^^^^^ 112 | 113 | 在 print.c 中包含所需头文件,并定义后续将会用到的宏 114 | 115 | .. code-block:: c 116 | :linenos: 117 | 118 | #include 119 | #include "prt_typedef.h" 120 | 121 | #define UART_0_REG_BASE 0x09000000 // pl011 设备寄存器地址 122 | // 寄存器及其位定义参见:https://developer.arm.com/documentation/ddi0183/g/programmers-model/summary-of-registers 123 | #define DW_UART_THR 0x00 // UARTDR(Data Register) 寄存器 124 | #define DW_UART_FR 0x18 // UARTFR(Flag Register) 寄存器 125 | #define DW_UART_LCR_HR 0x2c // UARTLCR_H(Line Control Register) 寄存器 126 | #define DW_XFIFO_NOT_FULL 0x020 // 发送缓冲区满置位 127 | #define DW_FIFO_ENABLE 0x10 // 启用发送和接收FIFO 128 | 129 | #define UART_BUSY_TIMEOUT 1000000 130 | #define OS_MAX_SHOW_LEN 0x200 131 | 132 | 133 | #define UART_REG_READ(addr) (*(volatile U32 *)(((uintptr_t)addr))) // 读设备寄存器 134 | #define UART_REG_WRITE(value, addr) (*(volatile U32 *)((uintptr_t)addr) = (U32)value) // 写设备寄存器 135 | 136 | 137 | 串口的初始化 138 | ^^^^^^^^^^^^^^^^^^^^^ 139 | 140 | .. note:: 141 | 如何操作硬件通常需要阅读硬件制造商提供的技术手册。如pl011串口设备(PrimeCell UART)是arm设计的,其技术参考手册可以通过其 `官网 `_ 查看。也可以通过顶部的下载链接下载pdf版本,如下图所示。 142 | 143 | .. image:: down-pl011-ref.png 144 | 145 | 依据之前virt.dts中的描述,pl011的寄存器在virt机器中被映射到了0x9000000的内存位置。通过访问pl011的技术参考手册中的“Chapter 3. Programmers Model”中的”Summary of registers“一节可知,第0号寄存器是pl011串口的数据寄存器,用于数据的收发。其详细描述参见 `这里 `_。 146 | 147 | 注意到我们只是向UART0写入,而没从UART0读出(如果读出会读出其他设备通过串口发送过来的数据,而不是刚才写入的数据,注意体会这与读写内存时是不一样的,详情参见pl011的技术手册),编译器在优化时可能对这部分代码进行错误的优化,如把这些操作都忽略掉。在 UART_REG_READ 宏和 UART_REG_WRITE 宏中使用 volatile 关键字的目的是告诉编译器,这些读取或写入有特定目的,不应将其优化(也就是告诉编译器不要瞎优化,这些写入和读出都有特定用途。如连续两次读,编译器可能认为第二次读就是前次的值,所以优化掉第二次读,但对外设寄存器的连续读可能返回不同的值。再比如写,编译器可能认为写后没有读所以写没有作用,或者连续的写会覆盖前面的写,但对外设而言对这些寄存器的写入都有特定作用)。 148 | 149 | .. code-block:: c 150 | :linenos: 151 | 152 | U32 PRT_UartInit(void) 153 | { 154 | U32 result = 0; 155 | U32 reg_base = UART_0_REG_BASE; 156 | // LCR寄存器: https://developer.arm.com/documentation/ddi0183/g/programmers-model/register-descriptions/line-control-register--uartlcr-h?lang=en 157 | result = UART_REG_READ((unsigned long)(reg_base + DW_UART_LCR_HR)); 158 | UART_REG_WRITE(result | DW_FIFO_ENABLE, (unsigned long)(reg_base + DW_UART_LCR_HR)); // 启用 FIFO 159 | 160 | return OS_OK; 161 | } 162 | 163 | 164 | 165 | 往串口发送字符 166 | ^^^^^^^^^^^^^^^^^^^^^ 167 | 168 | .. code-block:: C 169 | :linenos: 170 | 171 | // 读 reg_base + offset 寄存器的值。 uartno 参数未使用 172 | S32 uart_reg_read(S32 uartno, U32 offset, U32 *val) 173 | { 174 | S32 ret; 175 | U32 reg_base = UART_0_REG_BASE; 176 | 177 | 178 | *val = UART_REG_READ((unsigned long)(reg_base + offset)); 179 | return OS_OK; 180 | } 181 | 182 | // 通过检查 FR 寄存器的标志位确定发送缓冲是否满,满时返回1. 183 | S32 uart_is_txfifo_full(S32 uartno) 184 | { 185 | S32 ret; 186 | U32 usr = 0; 187 | 188 | ret = uart_reg_read(uartno, DW_UART_FR, &usr); 189 | if (ret) { 190 | return OS_OK; 191 | } 192 | 193 | return (usr & DW_XFIFO_NOT_FULL); 194 | } 195 | 196 | // 往 reg_base + offset 寄存器中写入值 val。 197 | void uart_reg_write(S32 uartno, U32 offset, U32 val) 198 | { 199 | S32 ret; 200 | U32 reg_base = UART_0_REG_BASE; 201 | 202 | UART_REG_WRITE(val, (unsigned long)(reg_base + offset)); 203 | return; 204 | } 205 | 206 | // 通过轮询的方式发送字符到串口 207 | void uart_poll_send(unsigned char ch) 208 | { 209 | 210 | S32 timeout = 0; 211 | S32 max_timeout = UART_BUSY_TIMEOUT; 212 | 213 | // 轮询发送缓冲区是否满 214 | int result = uart_is_txfifo_full(0); 215 | while (result) { 216 | timeout++; 217 | if (timeout >= max_timeout) { 218 | return; 219 | } 220 | result = uart_is_txfifo_full(0); 221 | } 222 | 223 | // 如果缓冲区没满,通过往数据寄存器写入数据发送字符到串口 224 | uart_reg_write(0, DW_UART_THR, (U32)(U8)ch); 225 | return; 226 | } 227 | 228 | // 轮询的方式发送字符到串口,且转义换行符 229 | void TryPutc(unsigned char ch) 230 | { 231 | uart_poll_send(ch); 232 | if (ch == '\n') { 233 | uart_poll_send('\r'); 234 | } 235 | } 236 | 237 | 上面的代码很简单,就是通过轮询的方式向 PL011 的数据寄存器 DR 写入数据即可实现往串口发送字符,实现字符输出。 238 | 239 | 240 | 支持格式化输出 241 | ^^^^^^^^^^^^^^^^^^^^^ 242 | 243 | .. code-block:: C 244 | :linenos: 245 | 246 | extern int vsnprintf_s(char *buff, int buff_size, int count, char const *fmt, va_list arg); 247 | int TryPrintf(const char *format, va_list vaList) 248 | { 249 | int len; 250 | char buff[OS_MAX_SHOW_LEN]; 251 | for(int i = 0; i < OS_MAX_SHOW_LEN; i++) { 252 | buff[i] = 0; 253 | } 254 | char *str = buff; 255 | 256 | len = vsnprintf_s(buff, OS_MAX_SHOW_LEN, OS_MAX_SHOW_LEN, format, vaList); 257 | if (len == -1) { 258 | return len; 259 | } 260 | 261 | while (*str != '\0') { 262 | TryPutc(*str); 263 | str++; 264 | } 265 | 266 | return OS_OK; 267 | } 268 | 269 | U32 PRT_Printf(const char *format, ...) 270 | { 271 | va_list vaList; 272 | S32 count; 273 | 274 | va_start(vaList, format); 275 | count = TryPrintf(format, vaList); 276 | va_end(vaList); 277 | 278 | return count; 279 | } 280 | 281 | 为了实现与 C 语言中 printf 函数类似的格式化功能,我们要用到可变参数列表 va_list 。而真正实现格式化控制转换的函数是 vsnprintf_s 函数。 282 | 283 | 实现 vsnprintf_s 函数 284 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 285 | 286 | 新建 src/bsp/vsnprintf_s.c 实现 vsnprintf_s 函数 287 | 288 | vsnprintf_s 函数的主要作用是依据格式控制符将可变参数列表转换成字符列表写入缓冲区。UniProton 提供了 libboundscheck 库,其中实现了 vsnprintf_s 函数,作为可选作业你可以试着使用 libboundscheck 库中的 vsnprintf_s 函数。简单起见,我们从另一个国产实时操作系统 RT-Thread 的 `kservice.c `_ 中引入了一个实现并进行了简单修改。 可以在 `这里 <../\_static/vsnprintf_s.c>`_ 下载 vsnprintf_s.c。 289 | 290 | .. hint:: 291 | 292 | 你可以读一读 vsnprintf_s 函数的实现代码,但由于与操作系统关键技术的相关性较弱,我们不过多说明。 293 | 294 | 从这里你也可以看出计算机里没有魔法,魔法只是别人帮你做了而已。所以学习一定要去神秘化! 295 | 296 | 调用 PRT_Printf 函数 297 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 298 | 299 | main.c 修改为调用 PRT_Printf 函数输出信息。 300 | 301 | .. code-block:: C 302 | :linenos: 303 | 304 | #include "prt_typedef.h" 305 | 306 | extern U32 PRT_Printf(const char *format, ...); 307 | extern void PRT_UartInit(void); 308 | 309 | S32 main(void) 310 | { 311 | PRT_UartInit(); 312 | 313 | PRT_Printf("Test PRT_Printf int format %d \n\n", 10); 314 | } 315 | 316 | 将新增文件纳入构建系统 317 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 318 | 319 | 修改 src/bsp/CMakeLists.txt 文件加入新增文件 print.c 和 vsnprintf_s.c 320 | 321 | .. code-block:: cmake 322 | :linenos: 323 | 324 | set(SRCS start.S prt_reset_vector.S print.c vsnprintf_s.c) 325 | add_library(bsp OBJECT ${SRCS}) # OBJECT类型只编译生成.o目标文件,但不实际链接成库 326 | 327 | 启用 FPU 328 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 329 | 330 | 构建项目并执行发现程序没有任何输出。 需启用 FPU (src/bsp/start.S)。 331 | 332 | .. code-block:: asm 333 | :linenos: 334 | 335 | Start: 336 | LDR x1, =__os_sys_sp_end // ld文件中定义,堆栈设置 337 | BIC sp, x1, #0xf 338 | 339 | //参考: https://developer.arm.com/documentation/den0024/a/Memory-Ordering/Barriers/ISB-in-more-detail 340 | Enable_FPU: 341 | MRS X1, CPACR_EL1 342 | ORR X1, X1, #(0x3 << 20) 343 | MSR CPACR_EL1, X1 344 | ISB 345 | 346 | B OsEnterMain 347 | 348 | 349 | Hello, miniEuler 350 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 351 | 352 | 再次构建项目并执行,发现已可正常输出。至此,我们获得了一个基本的输出和调试手段,如我们可以在系统崩溃时调用 PRT_Printf 函数进行输出。 353 | 354 | 我们可以利用 PRT_Printf 函数来打印一个文本 banner 让我们写的 OS 显得专业一点😁。 `manytools.org `_ 可以创建 ascii banner,选择你喜欢的样式和文字(下面选择的是 Standard 样式),然后在 main.c 的 main 函数中调用 PRT_Printf 输出。 355 | 356 | .. code-block:: c 357 | :linenos: 358 | 359 | 360 | S32 main(void) 361 | { 362 | PRT_UartInit(); 363 | 364 | PRT_Printf(" _ _ _____ _ _ _ _ _ _ _ _ \n"); 365 | PRT_Printf(" _ __ ___ (_)_ __ (_) ____| _| | ___ _ __ | |__ _ _ | | | | \\ | | | | | ___ _ __ \n"); 366 | PRT_Printf(" | '_ ` _ \\| | '_ \\| | _|| | | | |/ _ \\ '__| | '_ \\| | | | | |_| | \\| | | | |/ _ \\ '__|\n"); 367 | PRT_Printf(" | | | | | | | | | | | |__| |_| | | __/ | | |_) | |_| | | _ | |\\ | |_| | __/ | \n"); 368 | PRT_Printf(" |_| |_| |_|_|_| |_|_|_____\\__,_|_|\\___|_| |_.__/ \\__, | |_| |_|_| \\_|\\___/ \\___|_| \n"); 369 | PRT_Printf(" |___/ \n"); 370 | 371 | 372 | PRT_Printf("Test PRT_Printf int format %d \n\n", 10); 373 | } 374 | 375 | 376 | 377 | 构建项目并执行 378 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 379 | 380 | .. image:: lab2-qemu-result.png 381 | 382 | lab2 作业 383 | -------------------------- 384 | 385 | 完成下列实验,并撰写实验报告。 386 | 387 | 作业1 388 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 389 | 390 | 不启用 fifo,通过检测 UARTFR 寄存器的 TXFE 位来发送数据。 391 | 392 | 作业2(可选) 393 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 394 | 395 | 采用 UniProton 提供的 libboundscheck 库实现 vsnprintf_s 函数。 -------------------------------------------------------------------------------- /source/lab2/lab2-qemu-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab2/lab2-qemu-result.png -------------------------------------------------------------------------------- /source/lab3/dev-tree-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab3/dev-tree-example.png -------------------------------------------------------------------------------- /source/lab3/index.rst: -------------------------------------------------------------------------------- 1 | 实验三 设备树(可选) 2 | ===================== 3 | 4 | 设备树是用于解决ARM等嵌入式系统由于设备种类纷繁复杂导致的与平台相关的大量内核代码被大量重复的问题。通过设备树来描述系统硬件及其属性,然后通过bootLoader将其传递给kernel,以便kernel可以有较大的灵活性。如下图所示设备树的例子。 5 | 6 | .. image:: dev-tree-example.png 7 | 8 | https://www.devicetree.org/ 旨在促进devicetree标准的发展,其最新的版本为 `设备树规范 v0.4-rc1 `_。 `kernel.org `_ 上有关于设备树的例子,如 `cpu `_ 的描述。 9 | 10 | 设备树是描述硬件的数据结构。 无需将设备的每个细节都硬编码到操作系统中,可以在引导时通过DTB这种形式的数据结构对硬件的各方面进行描述,然后传递给操作系统从而增强操作系统的灵活性。 设备树由 OpenFirmware、OpenPOWER 抽象层 (OPAL)、Power Architecture Platform Requirements (PAPR) 以独立的 Flattened Device Tree (FDT) 形式使用。 11 | 12 | 基本上,那些可以动态探测到的设备是不需要描述的,例如USB device。不过对于SOC上的usb host controller,它是无法动态识别的,需要在device tree中描述。同样的道理,在computer system中,PCI device可以被动态探测到,不需要在device tree中描述,但是PCI bridge如果不能被探测,那么就需要描述 [1]_。 13 | 14 | 很多系统工具是OS开发过程中自然产生的需求,如将.dtb文件进行解析并转换成可读的.dts格式。 15 | 16 | .. hint:: 由于本系列实验暂时没有使用bootloader,而是通过QEMU将内核直接加载到内存,所以不会借由设备树往kernel传递参数。 17 | 18 | .. [1] http://www.wowotech.net/device_model/dt_basic_concept.html 19 | 20 | 21 | 22 | 实验内容 23 | ----------------------- 24 | 25 | 依据 `设备树规范 `_ 选择你熟悉的语言编写程序解析virt.dtb文件。 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /source/lab4/aarch64_exception_levels_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | 20 | Page-1 21 | 22 | 23 | 24 | Block.3 25 | Secure firmware 26 | 27 | 28 | 29 | 30 | 31 | Secure firmware 32 | 33 | Sheet.6 34 | 35 | 36 | 37 | Block.7 38 | Application 39 | 40 | 41 | 42 | 43 | 44 | Application 45 | 46 | Sheet.10 47 | Normal world 48 | 49 | 50 | 51 | Normal world 52 | 53 | Sheet.11 54 | Secure world 55 | 56 | 57 | 58 | Secure world 59 | 60 | Block.1 61 | Application 62 | 63 | 64 | 65 | 66 | 67 | Application 68 | 69 | Block.16 70 | Application 71 | 72 | 73 | 74 | 75 | 76 | Application 77 | 78 | Block.17 79 | Application 80 | 81 | 82 | 83 | 84 | 85 | Application 86 | 87 | Sheet.18 88 | No Hypervisor in Secure world 89 | 90 | 91 | 92 | No Hypervisor in Secure world 93 | 94 | Dynamic connector.24 95 | 96 | 97 | 98 | Sheet.26 99 | EL0 100 | 101 | 102 | 103 | EL0 104 | 105 | Sheet.27 106 | EL1 107 | 108 | 109 | 110 | EL1 111 | 112 | Sheet.28 113 | EL3 114 | 115 | 116 | 117 | EL3 118 | 119 | Sheet.29 120 | EL2 121 | 122 | 123 | 124 | EL2 125 | 126 | Sheet.25 127 | 128 | 129 | 130 | Sheet.30 131 | Guest OS 132 | 133 | 134 | 135 | Guest OS 136 | 137 | Sheet.2 138 | Guest OS 139 | 140 | 141 | 142 | Guest OS 143 | 144 | Sheet.8 145 | Trusted OS 146 | 147 | 148 | 149 | Trusted OS 150 | 151 | Sheet.4 152 | Hypervisor 153 | 154 | 155 | 156 | Hypervisor 157 | 158 | Sheet.9 159 | Secure monitor 160 | 161 | 162 | 163 | Secure monitor 164 | 165 | Dynamic connector.19 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /source/lab4/enter_into_os.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab4/enter_into_os.png -------------------------------------------------------------------------------- /source/lab4/index.rst: -------------------------------------------------------------------------------- 1 | 实验四 异常处理 2 | ===================== 3 | 4 | 中断、异常和陷阱指令是操作系统的基石,现代操作系统就是由中断驱动的。本实验和实验五的目的在于深刻理解中断的原理和机制,掌握CPU访问中断控制器的方法,掌握Arm体系结构的中断机制和规范,实现时钟中断服务和部分异常处理等。 5 | 6 | 陷入操作系统 7 | -------------------------- 8 | 9 | 如下图所示,操作系统是一个多入口的程序,执行陷阱(Trap)指令,出现异常、发生中断时都会陷入到操作系统。 10 | 11 | .. image:: enter_into_os.png 12 | 13 | 14 | ARMv8的中断与异常处理 15 | ------------------------------ 16 | 17 | .. attention:: 访问Arm官网下载并阅读 `ARM Cortex-A Series Programmer's Guide for ARMv8-A `_ 和 `AArch64 Exception and Interrupt Handling `_ 等技术参考手册。 18 | 19 | ARMv8 架构定义了两种执行状态(Execution States),AArch64 和 AArch32。分别对应使用64位宽通用寄存器或32位宽通用寄存器的执行 [1]_ 。 20 | 21 | .. image:: aarch64_exception_levels_2.svg 22 | 23 | 上图所示为AArch64中的异常级别(Exception levels)的组织。可见AArch64中共有4个异常级别,分别为EL0,EL1,EL2和EL3。在AArch64中,Interrupt是Exception的子类型,称为异常。 AArch64 中有四种类型的异常 [2]_ : 24 | 25 | - Sync(Synchronous exceptions,同步异常),在执行时触发的异常,例如在尝试访问不存在的内存地址时。 26 | - IRQ (Interrupt requests,中断请求),由外部设备产生的中断 27 | - FIQ (Fast Interrupt Requests,快速中断请求),类似于IRQ,但具有更高的优先级,因此 FIQ 中断服务程序不能被其他 IRQ 或 FIQ 中断。 28 | - SError (System Error,系统错误),用于外部数据中止的异步中断。 29 | 30 | 当异常发生时,处理器将执行与该异常对应的异常处理代码。在ARM架构中,这些异常处理代码将会被保存在内存的异常向量表中。每一个异常级别(EL0,EL1,EL2和EL3)都有其对应的异常向量表。需要注意的是,与x86等架构不同,该表包含的是要执行的指令,而不是函数地址 [3]_ 。 31 | 32 | 异常向量表的基地址由VBAR_ELn给出,然后每个表项都有一个从该基地址定义的偏移量。 每个表有16个表项,每个表项的大小为128(0x80)字节(32 条指令)。 该表实际上由4组,每组4个表项组成。 分别是: 33 | 34 | - 发生于当前异常级别的异常且SPSel寄存器选择SP0 [4]_ , Sync、IRQ、FIQ、SError对应的4个异常处理。 35 | - 发生于当前异常级别的异常且SPSel寄存器选择SPx [4]_ , Sync、IRQ、FIQ、SError对应的4个异常处理。 36 | - 发生于较低异常级别的异常且执行状态为AArch64, Sync、IRQ、FIQ、SError对应的4个异常处理。 37 | - 发生于较低异常级别的异常且执行状态为AArch32, Sync、IRQ、FIQ、SError对应的4个异常处理。 38 | 39 | .. - 异常类型(SError、FIQ、IRQ 或 Sync) 40 | .. - 如果是在当前异常级别发生异常,使用的堆栈指针(SP0 或 SPx)的情况。 41 | .. - 如果是在较低的异常级别发生的异常,执行状态(AArch64 或 AArch32)的情况。 42 | 43 | 44 | 异常向量表 45 | ^^^^^^^^^^^^^^^^^^^^^^^^ 46 | 47 | 新建 src/bsp/prt_vector.S 文件,参照这里 [3]_ 定义异常向量表如下: 48 | 49 | .. code-block:: asm 50 | :linenos: 51 | 52 | .section .os.vector.text, "ax" 53 | 54 | .global OsVectorTable 55 | .type OsVectorTable,function 56 | 57 | .align 13 58 | 59 | OsVectorTable: 60 | .set VBAR, OsVectorTable 61 | .org VBAR // Synchronous, Current EL with SP_EL0 62 | EXC_HANDLE 0 OsExcDispatch 63 | 64 | .org (VBAR + 0x80) // IRQ/vIRQ, Current EL with SP_EL0 65 | EXC_HANDLE 1 OsExcDispatch 66 | 67 | .org (VBAR + 0x100) // FIQ/vFIQ, Current EL with SP_EL0 68 | EXC_HANDLE 2 OsExcDispatch 69 | 70 | .org (VBAR + 0x180) // SERROR, Current EL with SP_EL0 71 | EXC_HANDLE 3 OsExcDispatch 72 | 73 | .org (VBAR + 0x200) // Synchronous, Current EL with SP_ELx 74 | EXC_HANDLE 4 OsExcDispatch 75 | 76 | .org (VBAR + 0x280) // IRQ/vIRQ, Current EL with SP_ELx 77 | EXC_HANDLE 5 OsExcDispatch 78 | 79 | .org (VBAR + 0x300) // FIQ/vFIQ, Current EL with SP_ELx 80 | EXC_HANDLE 6 OsExcDispatch 81 | 82 | .org (VBAR + 0x380) // SERROR, Current EL with SP_ELx 83 | EXC_HANDLE 7 OsExcDispatch 84 | 85 | .org (VBAR + 0x400) // Synchronous, EL changes and the target EL is using AArch64 86 | EXC_HANDLE 8 OsExcDispatchFromLowEl 87 | 88 | .org (VBAR + 0x480) // IRQ/vIRQ, EL changes and the target EL is using AArch64 89 | EXC_HANDLE 9 OsExcDispatch 90 | 91 | .org (VBAR + 0x500) // FIQ/vFIQ, EL changes and the target EL is using AArch64 92 | EXC_HANDLE 10 OsExcDispatch 93 | 94 | .org (VBAR + 0x580) // SERROR, EL changes and the target EL is using AArch64 95 | EXC_HANDLE 11 OsExcDispatch 96 | 97 | .org (VBAR + 0x600) // Synchronous, L changes and the target EL is using AArch32 98 | EXC_HANDLE 12 OsExcDispatch 99 | 100 | .org (VBAR + 0x680) // IRQ/vIRQ, EL changes and the target EL is using AArch32 101 | EXC_HANDLE 13 OsExcDispatch 102 | 103 | .org (VBAR + 0x700) // FIQ/vFIQ, EL changes and the target EL is using AArch32 104 | EXC_HANDLE 14 OsExcDispatch 105 | 106 | .org (VBAR + 0x780) // SERROR, EL changes and the target EL is using AArch32 107 | EXC_HANDLE 15 OsExcDispatch 108 | 109 | .text 110 | 111 | 可以看到:针对4组,每组4类异常共16类异常均定义有其对应的入口,且其入口均定义为 EXC_HANDLE vecId handler 的形式。 112 | 113 | .. hint:: CPSR 寄存器中有当前栈的选择 bits[0] 0:SP_EL0,1:SP_ELX 114 | 115 | 在 prt_reset_vector.S 中的 OsEnterMain: 标号后加入代码 116 | 117 | .. code-blocK:: asm 118 | :linenos: 119 | 120 | OsVectTblInit: // 设置 EL1 级别的异常向量表 121 | LDR x0, =OsVectorTable 122 | MSR VBAR_EL1, X0 123 | 124 | 125 | 上下文保存与恢复 126 | ^^^^^^^^^^^^^^^^^^^^^^^^ 127 | 128 | EXC_HANDLE 实际上是一个宏,其定义如下。 129 | 130 | .. code-block:: asm 131 | :linenos: 132 | 133 | .global OsExcHandleEntry 134 | .type OsExcHandleEntry, function 135 | 136 | .macro SAVE_EXC_REGS // 保存通用寄存器的值到栈中 137 | stp x1, x0, [sp,#-16]! 138 | stp x3, x2, [sp,#-16]! 139 | stp x5, x4, [sp,#-16]! 140 | stp x7, x6, [sp,#-16]! 141 | stp x9, x8, [sp,#-16]! 142 | stp x11, x10, [sp,#-16]! 143 | stp x13, x12, [sp,#-16]! 144 | stp x15, x14, [sp,#-16]! 145 | stp x17, x16, [sp,#-16]! 146 | stp x19, x18, [sp,#-16]! 147 | stp x21, x20, [sp,#-16]! 148 | stp x23, x22, [sp,#-16]! 149 | stp x25, x24, [sp,#-16]! 150 | stp x27, x26, [sp,#-16]! 151 | stp x29, x28, [sp,#-16]! 152 | stp xzr, x30, [sp,#-16]! 153 | .endm 154 | 155 | .macro RESTORE_EXC_REGS // 从栈中恢复通用寄存器的值 156 | ldp xzr, x30, [sp],#16 157 | ldp x29, x28, [sp],#16 158 | ldp x27, x26, [sp],#16 159 | ldp x25, x24, [sp],#16 160 | ldp x23, x22, [sp],#16 161 | ldp x21, x20, [sp],#16 162 | ldp x19, x18, [sp],#16 163 | ldp x17, x16, [sp],#16 164 | ldp x15, x14, [sp],#16 165 | ldp x13, x12, [sp],#16 166 | ldp x11, x10, [sp],#16 167 | ldp x9, x8, [sp],#16 168 | ldp x7, x6, [sp],#16 169 | ldp x5, x4, [sp],#16 170 | ldp x3, x2, [sp],#16 171 | ldp x1, x0, [sp],#16 172 | .endm 173 | 174 | .macro EXC_HANDLE vecId handler 175 | SAVE_EXC_REGS // 保存寄存器宏 176 | 177 | mov x1, #\vecId // x1 记录异常类型 178 | b \handler // 跳转到异常处理 179 | .endm 180 | 181 | .. hint:: 注意把这部分代码放到 src/bsp/prt_vector.S 文件的开头 182 | 183 | EXC_HANDLE 宏的主要作用是一发生异常就立即保存CPU寄存器的值,然后跳转到异常处理函数进行异常处理。 184 | 185 | 186 | 187 | 随后,我们继续在 src/bsp/prt_vector.S 文件中实现异常处理函数,包括 OsExcDispatch 和 OsExcDispatchFromLowEl。 188 | 189 | .. code-block:: asm 190 | :linenos: 191 | 192 | .global OsExcHandleEntry 193 | .type OsExcHandleEntry, function 194 | 195 | .global OsExcHandleEntryFromLowEl 196 | .type OsExcHandleEntryFromLowEl, function 197 | 198 | 199 | .section .os.init.text, "ax" 200 | .globl OsExcDispatch 201 | .type OsExcDispatch, @function 202 | .align 4 203 | OsExcDispatch: 204 | mrs x5, esr_el1 205 | mrs x4, far_el1 206 | mrs x3, spsr_el1 207 | mrs x2, elr_el1 208 | stp x4, x5, [sp,#-16]! 209 | stp x2, x3, [sp,#-16]! 210 | 211 | mov x0, x1 // x0: 异常类型 212 | mov x1, sp // x1: 栈指针 213 | bl OsExcHandleEntry // 跳转到实际的 C 处理函数, x0, x1分别为该函数的第1,2个参数。 214 | 215 | ldp x2, x3, [sp],#16 216 | add sp, sp, #16 // 跳过far, esr, HCR_EL2.TRVM==1的时候,EL1不能写far, esr 217 | msr spsr_el1, x3 218 | msr elr_el1, x2 219 | dsb sy 220 | isb 221 | 222 | RESTORE_EXC_REGS // 恢复上下文 223 | 224 | eret //从异常返回 225 | 226 | 227 | .globl OsExcDispatchFromLowEl 228 | .type OsExcDispatchFromLowEl, @function 229 | .align 4 230 | OsExcDispatchFromLowEl: 231 | mrs x5, esr_el1 232 | mrs x4, far_el1 233 | mrs x3, spsr_el1 234 | mrs x2, elr_el1 235 | stp x4, x5, [sp,#-16]! 236 | stp x2, x3, [sp,#-16]! 237 | 238 | mov x0, x1 239 | mov x1, sp 240 | bl OsExcHandleFromLowElEntry 241 | 242 | ldp x2, x3, [sp],#16 243 | add sp, sp, #16 // 跳过far, esr, HCR_EL2.TRVM==1的时候,EL1不能写far, esr 244 | msr spsr_el1, x3 245 | msr elr_el1, x2 246 | dsb sy 247 | isb 248 | 249 | RESTORE_EXC_REGS // 恢复上下文 250 | 251 | eret //从异常返回 252 | 253 | OsExcDispatch 首先保存了4个系统寄存器到栈中,然后调用实际的异常处理 OsExcHandleEntry 函数。当执行完 OsExcHandleEntry 函数后,我们需要依序恢复寄存器的值。这就是操作系统课程中重点讲述的上下文的保存和恢复过程。 254 | 255 | OsExcDispatchFromLowEl 与 OsExcDispatch 的操作除调用的实际异常处理函数不同外其它完全一致。 256 | 257 | 异常处理函数 258 | ^^^^^^^^^^^^^^^^^^^^^^^^ 259 | 260 | 新建 src/bsp/prt_exc.c 文件,实现实际的 OsExcHandleEntry 和 OsExcHandleFromLowElEntry 异常处理函数。 261 | 262 | .. code-block:: c 263 | :linenos: 264 | 265 | #include "prt_typedef.h" 266 | #include "os_exc_armv8.h" 267 | 268 | extern U32 PRT_Printf(const char *format, ...); 269 | 270 | // ExcRegInfo 格式与 OsExcDispatch 中寄存器存储顺序对应 271 | void OsExcHandleEntry(U32 excType, struct ExcRegInfo *excRegs) 272 | { 273 | PRT_Printf("Catch a exception.\n"); 274 | } 275 | 276 | // ExcRegInfo 格式与 OsExcDispatchFromLowEl 中寄存器存储顺序对应 277 | void OsExcHandleFromLowElEntry(U32 excType, struct ExcRegInfo *excRegs) 278 | { 279 | PRT_Printf("Catch a exception from low exception level.\n"); 280 | } 281 | 282 | 注意到上面两个异常处理函数的第2个参数是 struct ExcRegInfo * 类型,而在 src/bsp/prt_vector.S 中我们为该参数传递是栈指针 sp。所以该结构需与异常处理寄存器保存的顺序保持一致。 283 | 284 | 新建 src/bsp/os_exc_armv8.h 文件,定义 ExcRegInfo 结构。 285 | 286 | .. code-block:: C 287 | :linenos: 288 | 289 | #ifndef ARMV8_EXC_H 290 | #define ARMV8_EXC_H 291 | 292 | #include "prt_typedef.h" 293 | 294 | #define XREGS_NUM 31 295 | 296 | struct ExcRegInfo { 297 | // 以下字段的内存布局与TskContext保持一致 298 | uintptr_t elr; // 返回地址 299 | uintptr_t spsr; 300 | uintptr_t far; 301 | uintptr_t esr; 302 | uintptr_t xzr; 303 | uintptr_t xregs[XREGS_NUM]; // 0~30 : x30~x0 304 | }; 305 | 306 | #endif /* ARMV8_EXC_H */ 307 | 308 | .. hint:: 注意把上面的新增文件加入构建系统。 309 | 310 | 触发异常 311 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 312 | 313 | 注释掉 FPU 启用代码,构建系统并执行发现没有任何信息输出,通过调试将会观察到异常。 314 | 315 | 系统调用 316 | -------------------------- 317 | 318 | .. hint:: 下面请启用 FPU。 319 | 320 | 系统调用是通用操作系统为应用程序提供服务的方式,理解系统调用对理解通用操作系统的实现非常重要。下面我们来实现1条简单的系统调用。 321 | 322 | EL 0 是用户程序所在的级别,而在lab1中我们已经知道CPU启动后进入的是EL1或以上级别。 323 | 324 | 在 main 函数中我们首先返回到 EL0 级别,然后通过 SVC 调用一条系统调用. 325 | 326 | .. code-block:: C 327 | :linenos: 328 | 329 | S32 main(void) 330 | { 331 | 332 | const char Test_SVC_str[] = "Hello, my first system call!"; 333 | 334 | PRT_UartInit(); 335 | 336 | PRT_Printf(" _ _ _____ _ _ _ _ _ _ _ _ \n"); 337 | PRT_Printf(" _ __ ___ (_)_ __ (_) ____| _| | ___ _ __ | |__ _ _ | | | | \\ | | | | | ___ _ __ \n"); 338 | PRT_Printf(" | '_ ` _ \\| | '_ \\| | _|| | | | |/ _ \\ '__| | '_ \\| | | | | |_| | \\| | | | |/ _ \\ '__|\n"); 339 | PRT_Printf(" | | | | | | | | | | | |__| |_| | | __/ | | |_) | |_| | | _ | |\\ | |_| | __/ | \n"); 340 | PRT_Printf(" |_| |_| |_|_|_| |_|_|_____\\__,_|_|\\___|_| |_.__/ \\__, | |_| |_|_| \\_|\\___/ \\___|_| \n"); 341 | PRT_Printf(" |___/ \n"); 342 | 343 | PRT_Printf("ctr-a h: print help of qemu emulator. ctr-a x: quit emulator.\n\n"); 344 | 345 | 346 | 347 | // 回到异常 EL 0级别,模拟系统调用,查看异常的处理,了解系统调用实现机制。 348 | // 《Bare-metal Boot Code for ARMv8-A Processors》 349 | OS_EMBED_ASM( 350 | "MOV X1, #0b00000\n" // Determine the EL0 Execution state. 351 | "MSR SPSR_EL1, X1\n" 352 | "ADR x1, EL0Entry\n" // Points to the first instruction of EL0 code 353 | " MSR ELR_EL1, X1\n" 354 | "eret\n" // 返回到 EL 0 级别 355 | "EL0Entry: \n" 356 | "MOV x0, %0 \n" //参数1 357 | "MOV x8, #1\n" //在linux中,用x8传递 syscall number,保持一致。 358 | "SVC 0\n" // 系统调用 359 | "B .\n" // 死循环,以上代码只用于演示,EL0级别的栈未正确设置 360 | ::"r"(&Test_SVC_str[0]) 361 | ); 362 | 363 | 364 | // 在 EL1 级别上模拟系统调用 365 | // OS_EMBED_ASM("SVC 0"); 366 | return 0; 367 | 368 | } 369 | 370 | .. note:: 371 | OS_EMBED_ASM 在 prt_typedef.h 中定义为 `__asm__ __volatile__`,用于 C 与 ASM 混合编程。 372 | 373 | SVC 是 arm 中的系统调用指令,相当于 x86 中的 int 指令。 374 | 375 | 376 | .. note:: 377 | 378 | 汇编语法可以参考 GNU ARM Assembler Quick Reference [5]_ 和 Arm Architecture Reference Manual Armv8 (Chapter C3 A64 Instruction Set Overview) [6]_ 379 | 380 | 内联汇编中Clobbers的用途到底是什么? [7]_ 381 | 382 | 383 | 系统调用实现 384 | ^^^^^^^^^^^^^^^^^^^^^ 385 | 386 | 在 src/bsp/prt_exc.c 修改 OsExcHandleFromLowElEntry 函数实现 1 条系统调用。 387 | 388 | .. code-block:: c 389 | :linenos: 390 | 391 | extern void TryPutc(unsigned char ch); 392 | void MyFirstSyscall(char *str) 393 | { 394 | while (*str != '\0') { 395 | TryPutc(*str); 396 | str++; 397 | } 398 | } 399 | // ExcRegInfo 格式与 OsExcDispatch 中寄存器存储顺序对应 400 | void OsExcHandleFromLowElEntry(U32 excType, struct ExcRegInfo *excRegs) 401 | { 402 | int ExcClass = (excRegs->esr&0xfc000000)>>26; 403 | if (ExcClass == 0x15){ //SVC instruction execution in AArch64 state. 404 | PRT_Printf("Catch a SVC call.\n"); 405 | // syscall number存在x8寄存器中, x0为参数1 406 | int syscall_num = excRegs->xregs[(XREGS_NUM - 1)- 8]; //uniproton存储的顺序x0在高,x30在低 407 | uintptr_t param0 = excRegs->xregs[(XREGS_NUM - 1)- 0]; 408 | PRT_Printf("syscall number: %d, param 0: 0x%x\n", syscall_num, param0); 409 | 410 | switch(syscall_num){ 411 | case 1: 412 | MyFirstSyscall((void *)param0); 413 | break; 414 | default: 415 | PRT_Printf("Unimplemented syscall.\n"); 416 | } 417 | }else{ 418 | PRT_Printf("Catch a exception.\n"); 419 | 420 | } 421 | } 422 | 423 | 424 | 425 | lab4 作业 426 | -------------------------- 427 | 428 | 作业1 429 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 430 | 431 | 查找 启用FPU 前异常出现的位置和原因。禁用FPU后PRT_Printf工作不正常,需通过调试跟踪查看异常发生的位置和原因 elr_el1 esr_el1 寄存器 432 | 433 | 434 | 435 | .. [1] https://developer.arm.com/documentation/den0024/a/Fundamentals-of-ARMv8/Execution-states 436 | .. [2] https://developer.arm.com/documentation/den0024/a/AArch64-Exception-Handling/Synchronous-and-asynchronous-exceptions 437 | .. [3] https://developer.arm.com/documentation/den0024/a/AArch64-Exception-Handling/AArch64-exception-table 438 | .. [4] https://developer.arm.com/documentation/den0024/a/ARMv8-Registers/AArch64-special-registers/Stack-pointer 439 | .. [5] https://www.ic.unicamp.br/~celio/mc404-2014/docs/gnu-arm-directives.pdf 440 | .. [6] https://developer.arm.com/documentation/ddi0487/gb 441 | .. [7] https://cloud.tencent.com/developer/article/1520799 442 | 443 | 444 | -------------------------------------------------------------------------------- /source/lab5/ARMGIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab5/ARMGIC.png -------------------------------------------------------------------------------- /source/lab5/gicv2-logic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiatgithub/os2024/bc7536260e1559337f3b643cbcf020786927aecf/source/lab5/gicv2-logic.png -------------------------------------------------------------------------------- /source/lab5/index.rst: -------------------------------------------------------------------------------- 1 | 实验五 时钟Tick 2 | ===================== 3 | 4 | 5 | Arm的中断系统 6 | -------------------------- 7 | 8 | .. 中断 9 | .. ^^^^^^^^^^^^^^^^^^^^^ 10 | 11 | 中断是一种硬件机制。借助于中断,CPU可以不必再采用轮询这种低效的方式访问外部设备。将所有的外部设备与CPU直接相连是不现实的,外部设备的中断请求一般经由中断控制器,由中断控制器仲裁后再转发给CPU。如下图所示Arm的中断系统。 12 | 13 | .. image:: ARMGIC.png 14 | 15 | 其中nIRQ是普通中断,nFIQ是快速中断。 Arm采用的中断控制器叫做GIC,即general interrupt controller。gic包括多个版本,如GICv1(已弃用),GICv2,GICv3,GICv4。简单起见,我们实验将选用GICv2版本。 16 | 17 | 为了配置好gicv2中断控制器,与pl011串口一样,我们需要阅读其技术参考手册。访问Arm官网在 `这里 `_ 下载ARM Generic Interrupt Controller Architecture Specification - version 2.0 的pdf版本。 18 | 19 | .. image:: gicv2-logic.png 20 | 21 | 从上图(来源于ARM Generic Interrupt Controller Architecture Specification - version 2.0中的Chapter 2 GIC Partitioning)可以看出: 22 | 23 | - GICv2 最多支持8个核的中断管理。 24 | - GIC包括两大主要部分(由图中蓝色虚竖线分隔,Distributor和CPU Interface由蓝色虚矩形框标示),分别是: 25 | 26 | - Distributor,其通过GICD_开头的寄存器进行控制(蓝色实矩形框标示) 27 | - CPU Interface,其通过GICC_开头的寄存器进行控制(蓝色实矩形框标示) 28 | 29 | 30 | - 中断类型分为以下几类(由图中红色虚线椭圆标示): 31 | 32 | - SPI:(shared peripheral interrupt),共享外设中断。该中断来源于外设,通过Distributor分发给特定的core,其中断编号为32-1019。从图中可以看到所有核共享SPI。 33 | - PPI:(private peripheral interrupt),私有外设中断。该中断来源于外设,但只对指定的core有效,中断信号只会发送给指定的core,其中断编号为16-31。从图中可以看到每个core都有自己的PPI。 34 | - SGI:(software-generated interrupt),软中断。软件产生的中断,用于给其他的core发送中断信号,其中断编号为0-15。 35 | - virtual interrupt,虚拟中断,用于支持虚拟机。图中也可以看到,因为我们暂时不关心,所以没有标注。 36 | - 此外可以看到(FIQ, IRQ)可通过b进行旁路,我们也不关心。如感兴趣可以查看技术手册了解细节。 37 | 38 | 此外,由ARM Generic Interrupt Controller Architecture Specification - version 2.0 (section 1.4.2)可知,外设中断可由两种方式触发: 39 | 40 | - edge-triggered: 边沿触发,当检测到中断信号上升沿时中断有效。 41 | - level-sensitive:电平触发,当中断源为指定电平时中断有效。 42 | 43 | 44 | 因为soc中,中断有很多,为了方便对中断的管理,对每个中断,附加了中断优先级。在中断仲裁时,高优先级的中断,会优于低优先级的中断,发送给cpu处理。当cpu在响应低优先级中断时,如果此时来了高优先级中断,那么高优先级中断会抢占低优先级中断,而被处理器响应。 45 | 46 | 由ARM Generic Interrupt Controller Architecture Specification - version 2.0 (section 3.3)可知,GICv2最多支持256个中断优先级。GICv2中规定,所支持的中断优先级别数与GIC的具体实现有关,如果支持的中断优先级数比256少(最少为16),则8位优先级的低位为0,且遵循RAZ/WI(Read-As-Zero, Writes Ignored)原则。 47 | 48 | 49 | GICv2初始化 50 | -------------------------- 51 | 52 | 由下图中virt.dts中intc和timer的部分 53 | 54 | .. code-block:: dts 55 | 56 | intc@8000000 { 57 | phandle = <0x8001>; 58 | reg = <0x00 0x8000000 0x00 0x10000 0x00 0x8010000 0x00 0x10000>; 59 | compatible = "arm,cortex-a15-gic"; 60 | ranges; 61 | #size-cells = <0x02>; 62 | #address-cells = <0x02>; 63 | interrupt-controller; 64 | #interrupt-cells = <0x03>; 65 | 66 | v2m@8020000 { 67 | phandle = <0x8002>; 68 | reg = <0x00 0x8020000 0x00 0x1000>; 69 | msi-controller; 70 | compatible = "arm,gic-v2m-frame"; 71 | }; 72 | }; 73 | 74 | timer { 75 | interrupts = <0x01 0x0d 0x104 0x01 0x0e 0x104 0x01 0x0b 0x104 0x01 0x0a 0x104>; 76 | always-on; 77 | compatible = "arm,armv8-timer\0arm,armv7-timer"; 78 | }; 79 | 80 | 并结合kernel.org中关于 `ARM Generic Interrupt Controller `_ 和 `ARM architected timer `_ 的devicetree的说明可知: 81 | 82 | - intc中的 ``reg`` 指明GICD寄存器映射到内存的位置为0x8000000,长度为0x10000, GICC寄存器映射到内存的位置为0x8010000,长度为0x10000 83 | - intc中的 ``#interrupt-cells`` 指明 interrupts 包括3个cells。`第一个文档 `_ 指明:第一个cell为中断类型,0表示SPI,1表示PPI;第二个cell为中断号,SPI范围为[0-987],PPI为[0-15];第三个cell为flags,其中[3:0]位表示触发类型,4表示高电平触发,[15:8]为PPI的cpu中断掩码,每1位对应一个cpu,为1表示该中断会连接到对应的cpu。 84 | - 以timer设备为例,其中包括4个中断。以第二个中断的参数 ``0x01 0x0e 0x104`` 为例,其指明该中断为PPI类型的中断,中断号14, 路由到第一个cpu,且高电平触发。但注意到PPI的起始中断号为16,所以实际上该中断在GICv2中的中断号应为16 + 14 = 30。 85 | 86 | 阅读ARM Generic Interrupt Controller Architecture Specification - version 2.0,在其Chapter 4 Programmers’ Model部分有关于GICD和GICC寄存器的描述,以及如何使能Distributor和CPU Interfaces的方法。 87 | 88 | 新建 src/bsp/hwi_init.c 文件,初始化 GIC 89 | 90 | .. code-block:: c 91 | :linenos: 92 | 93 | #include "prt_typedef.h" 94 | #include "os_attr_armv8_external.h" 95 | 96 | #define OS_GIC_VER 2 97 | 98 | #define GIC_DIST_BASE 0x08000000 99 | #define GIC_CPU_BASE 0x08010000 100 | 101 | #define GICD_CTLR (GIC_DIST_BASE + 0x0000U) 102 | #define GICD_TYPER (GIC_DIST_BASE + 0x0004U) 103 | #define GICD_IIDR (GIC_DIST_BASE + 0x0008U) 104 | #define GICD_IGROUPRn (GIC_DIST_BASE + 0x0080U) 105 | #define GICD_ISENABLERn (GIC_DIST_BASE + 0x0100U) 106 | #define GICD_ICENABLERn (GIC_DIST_BASE + 0x0180U) 107 | #define GICD_ISPENDRn (GIC_DIST_BASE + 0x0200U) 108 | #define GICD_ICPENDRn (GIC_DIST_BASE + 0x0280U) 109 | #define GICD_ISACTIVERn (GIC_DIST_BASE + 0x0300U) 110 | #define GICD_ICACTIVERn (GIC_DIST_BASE + 0x0380U) 111 | #define GICD_IPRIORITYn (GIC_DIST_BASE + 0x0400U) 112 | #define GICD_ICFGR (GIC_DIST_BASE + 0x0C00U) 113 | 114 | 115 | #define GICD_CTLR_ENABLE 1 /* Enable GICD */ 116 | #define GICD_CTLR_DISABLE 0 /* Disable GICD */ 117 | #define GICD_ISENABLER_SIZE 32 118 | #define GICD_ICENABLER_SIZE 32 119 | #define GICD_ICPENDR_SIZE 32 120 | #define GICD_IPRIORITY_SIZE 4 121 | #define GICD_IPRIORITY_BITS 8 122 | #define GICD_ICFGR_SIZE 16 123 | #define GICD_ICFGR_BITS 2 124 | 125 | #define GICC_CTLR (GIC_CPU_BASE + 0x0000U) 126 | #define GICC_PMR (GIC_CPU_BASE + 0x0004U) 127 | #define GICC_BPR (GIC_CPU_BASE + 0x0008U) 128 | #define IAR_MASK 0x3FFU 129 | #define GICC_IAR (GIC_CPU_BASE + 0xc) 130 | #define GICC_EOIR (GIC_CPU_BASE + 0x10) 131 | #define GICD_SGIR (GIC_DIST_BASE + 0xf00) 132 | 133 | #define BIT(n) (1 << (n)) 134 | 135 | #define GICC_CTLR_ENABLEGRP0 BIT(0) 136 | #define GICC_CTLR_ENABLEGRP1 BIT(1) 137 | #define GICC_CTLR_FIQBYPDISGRP0 BIT(5) 138 | #define GICC_CTLR_IRQBYPDISGRP0 BIT(6) 139 | #define GICC_CTLR_FIQBYPDISGRP1 BIT(7) 140 | #define GICC_CTLR_IRQBYPDISGRP1 BIT(8) 141 | 142 | #define GICC_CTLR_ENABLE_MASK (GICC_CTLR_ENABLEGRP0 | \ 143 | GICC_CTLR_ENABLEGRP1) 144 | 145 | #define GICC_CTLR_BYPASS_MASK (GICC_CTLR_FIQBYPDISGRP0 | \ 146 | GICC_CTLR_IRQBYPDISGRP0 | \ 147 | GICC_CTLR_FIQBYPDISGRP1 | \ 148 | GICC_CTLR_IRQBYPDISGRP1) 149 | 150 | #define GICC_CTLR_ENABLE 1 151 | #define GICC_CTLR_DISABLE 0 152 | // Priority Mask Register. interrupt priority filter, Higher priority corresponds to a lower Priority field value. 153 | #define GICC_PMR_PRIO_LOW 0xff 154 | // The register defines the point at which the priority value fields split into two parts, 155 | // the group priority field and the subpriority field. The group priority field is used to 156 | // determine interrupt preemption. NO GROUP. 157 | #define GICC_BPR_NO_GROUP 0x00 158 | 159 | #define GIC_REG_READ(addr) (*(volatile U32 *)((uintptr_t)(addr))) 160 | #define GIC_REG_WRITE(addr, data) (*(volatile U32 *)((uintptr_t)(addr)) = (U32)(data)) 161 | 162 | 163 | // src/arch/drv/gic/prt_gic_init.c 164 | /* 165 | * 描述: 去使能(禁用)指定中断 166 | */ 167 | OS_SEC_L4_TEXT void OsGicDisableInt(U32 intId) 168 | { 169 | // Interrupt Clear-Enable Registers 170 | } 171 | 172 | /* 173 | * 描述: 使能指定中断 174 | */ 175 | OS_SEC_L4_TEXT void OsGicEnableInt(U32 intId) 176 | { 177 | // Interrupt Set-Enable Registers 178 | } 179 | 180 | // 直接清除中断的pending状态 181 | OS_SEC_L4_TEXT void OsGicClearIntPending(uint32_t interrupt) 182 | { 183 | // Interrupt Clear-Pending state of an interrupt Registers 184 | GIC_REG_WRITE(GICD_ICPENDRn + (interrupt / GICD_ICPENDR_SIZE)*sizeof(U32), 1 << (interrupt % GICD_ICPENDR_SIZE)); 185 | } 186 | 187 | // 设置中断号为interrupt的中断的优先级为priority 188 | OS_SEC_L4_TEXT void OsGicIntSetPriority(uint32_t interrupt, uint32_t priority) { 189 | uint32_t shift = (interrupt % GICD_IPRIORITY_SIZE) * GICD_IPRIORITY_BITS; 190 | volatile uint32_t* addr = ((volatile U32 *)(uintptr_t)(GICD_IPRIORITYn + (interrupt / GICD_IPRIORITY_SIZE) * sizeof(U32))) ; 191 | uint32_t value = GIC_REG_READ(addr); 192 | value &= ~(0xff << shift); // 每个中断占8位,所以掩码为 0xFF 193 | value |= priority << shift; 194 | GIC_REG_WRITE(addr, value); 195 | } 196 | 197 | // 设置中断号为interrupt的中断的属性为config 198 | OS_SEC_L4_TEXT void OsGicIntSetConfig(uint32_t interrupt, uint32_t config) { 199 | uint32_t shift = (interrupt % GICD_ICFGR_SIZE) * GICD_ICFGR_BITS; 200 | volatile uint32_t* addr = ((volatile U32 *)(uintptr_t)(GICD_ICFGR + (interrupt / GICD_ICFGR_SIZE)*sizeof(U32))); 201 | uint32_t value = GIC_REG_READ(addr); 202 | value &= ~(0x03 << shift); 203 | value |= config << shift; 204 | GIC_REG_WRITE(addr, value); 205 | } 206 | 207 | /* 208 | * 描述: 中断确认 209 | */ 210 | OS_SEC_L4_TEXT U32 OsGicIntAcknowledge(void) 211 | { 212 | // reads this register to obtain the interrupt ID of the signaled interrupt. 213 | // This read acts as an acknowledge for the interrupt. 214 | U32 value = GIC_REG_READ(GICC_IAR); 215 | return value; 216 | } 217 | 218 | /* 219 | * 描述: 标记中断完成,清除相应中断位 220 | */ 221 | OS_SEC_L4_TEXT void OsGicIntClear(U32 value) 222 | { 223 | // A processor writes to this register to inform the CPU interface either: 224 | // • that it has completed the processing of the specified interrupt 225 | // • in a GICv2 implementation, when the appropriate GICC_CTLR.EOImode bit is set to 1, to indicate that the interface should perform priority drop for the specified interrupt. 226 | GIC_REG_WRITE(GICC_EOIR, value); 227 | } 228 | 229 | U32 OsHwiInit(void) 230 | { 231 | 232 | // 初始化Gicv2的distributor和cpu interface 233 | // 禁用distributor和cpu interface后进行相应配置 234 | GIC_REG_WRITE(GICD_CTLR, GICD_CTLR_DISABLE); 235 | GIC_REG_WRITE(GICC_CTLR, GICC_CTLR_DISABLE); 236 | GIC_REG_WRITE(GICC_PMR, GICC_PMR_PRIO_LOW); 237 | GIC_REG_WRITE(GICC_BPR, GICC_BPR_NO_GROUP); 238 | 239 | 240 | // 启用distributor和cpu interface 241 | GIC_REG_WRITE(GICD_CTLR, GICD_CTLR_ENABLE); 242 | GIC_REG_WRITE(GICC_CTLR, GICC_CTLR_ENABLE); 243 | 244 | return OS_OK; 245 | } 246 | 247 | 在 hwi_init.c 中 OsHwiInit 函数实现 GIC 的初始化,此外还提供了其他函数实现开关指定中断、设置中断属性、确认中断和标记中断完成等功能。 248 | 249 | .. attention:: 你需要参照 OsGicIntSetPriority 等函数实现 OsGicEnableInt 和 OsGicDisableInt 函数。 250 | 251 | 252 | 253 | 使能时钟中断 254 | --------------------------- 255 | 新建 src/include/prt_config.h 256 | 257 | .. code-block:: C 258 | :linenos: 259 | 260 | /* Tick中断时间间隔,tick处理时间不能超过1/OS_TICK_PER_SECOND(s) */ 261 | #define OS_TICK_PER_SECOND 1000 262 | 263 | 新建 src/include/os_cpu_armv8.h。 264 | 265 | .. code-block:: C 266 | :linenos: 267 | 268 | #ifndef OS_CPU_ARMV8_H 269 | #define OS_CPU_ARMV8_H 270 | 271 | #include "prt_typedef.h" 272 | 273 | // CurrentEl等级 274 | #define CURRENT_EL_2 0x8 275 | #define CURRENT_EL_1 0x4 276 | #define CURRENT_EL_0 0x0 277 | 278 | #define DAIF_DBG_BIT (1U << 3) 279 | #define DAIF_ABT_BIT (1U << 2) 280 | #define DAIF_IRQ_BIT (1U << 1) 281 | #define DAIF_FIQ_BIT (1U << 0) 282 | 283 | #define INT_MASK (1U << 7) 284 | 285 | #define PRT_DSB() OS_EMBED_ASM("DSB sy" : : : "memory") 286 | #define PRT_DMB() OS_EMBED_ASM("DMB sy" : : : "memory") 287 | #define PRT_ISB() OS_EMBED_ASM("ISB" : : : "memory") 288 | 289 | #endif /* OS_CPU_ARMV8_H */ 290 | 291 | 新建 src/bsp/timer.c 文件,对定时器和对应的中断进行配置 292 | 293 | .. code-block:: c 294 | :linenos: 295 | 296 | #include "prt_typedef.h" 297 | #include "prt_config.h" 298 | #include "os_cpu_armv8.h" 299 | 300 | U64 g_timerFrequency; 301 | extern void OsGicIntSetConfig(uint32_t interrupt, uint32_t config); 302 | extern void OsGicIntSetPriority(uint32_t interrupt, uint32_t priority); 303 | extern void OsGicEnableInt(U32 intId); 304 | extern void OsGicClearIntPending(uint32_t interrupt); 305 | 306 | void CoreTimerInit(void) 307 | { 308 | // 配置中断控制器 309 | OsGicIntSetConfig(30, 0); // 配置为电平触发 310 | OsGicIntSetPriority(30, 0); // 优先级为0 311 | OsGicClearIntPending(30); // 清除中断pending 312 | OsGicEnableInt(30); // 启用中断 313 | 314 | // 配置定时器 315 | OS_EMBED_ASM("MRS %0, CNTFRQ_EL0" : "=r"(g_timerFrequency) : : "memory", "cc"); //读取时钟频率 316 | 317 | U32 cfgMask = 0x0; 318 | U64 cycle = g_timerFrequency / OS_TICK_PER_SECOND; 319 | 320 | OS_EMBED_ASM("MSR CNTP_CTL_EL0, %0" : : "r"(cfgMask) : "memory"); 321 | PRT_ISB(); 322 | OS_EMBED_ASM("MSR CNTP_TVAL_EL0, %0" : : "r"(cycle) : "memory", "cc"); //设置中断周期 323 | 324 | cfgMask = 0x1; 325 | OS_EMBED_ASM("MSR CNTP_CTL_EL0, %0" : : "r"(cfgMask) : "memory"); //启用定时器 enable=1, imask=0, istatus= 0, 326 | OS_EMBED_ASM("MSR DAIFCLR, #2"); 327 | } 328 | 329 | 时钟中断处理 330 | --------------------------- 331 | 332 | - 将 prt_vector.S 中的 EXC_HANDLE 5 OsExcDispatch 改为 EXC_HANDLE 5 OsHwiDispatcher,表明我们将对 IRQ 类型的异常(即中断)使用 OsHwiDispatcher 处理。 333 | 334 | .. hint:: 需修改为 EXC_HANDLE 5 OsHwiDispatcher ,否则还是 OsExcDispatch 函数处理,仅会输出 "Catch a exception." 信息 335 | 336 | - 在 prt_vector.S 中加入 OsHwiDispatcher 处理代码,其类似于之前的 OsExcDispatch ,因此不再说明。 337 | 338 | .. code-block:: asm 339 | :linenos: 340 | 341 | .globl OsHwiDispatcher 342 | .type OsHwiDispatcher, @function 343 | .align 4 344 | OsHwiDispatcher: 345 | mrs x5, esr_el1 346 | mrs x4, far_el1 347 | mrs x3, spsr_el1 348 | mrs x2, elr_el1 349 | stp x4, x5, [sp,#-16]! 350 | stp x2, x3, [sp,#-16]! 351 | 352 | mov x0, x1 // 异常类型0~15,参见异常向量表 353 | mov x1, sp // 异常时寄存器信息,通过栈及其sp指针提供 354 | bl OsHwiDispatch 355 | 356 | ldp x2, x3, [sp],#16 357 | add sp, sp, #16 // 跳过far, esr, HCR_EL2.TRVM==1的时候,EL1不能写far, esr 358 | msr spsr_el1, x3 359 | msr elr_el1, x2 360 | dsb sy 361 | isb 362 | 363 | RESTORE_EXC_REGS // 恢复上下文 364 | 365 | eret //从异常返回 366 | 367 | - 在 prt_exc.c 中引用头文件 os_attr_armv8_external.h , os_cpu_armv8.h , OsHwiDispatch 处理 IRQ 类型的中断。 368 | 369 | 370 | 371 | .. code-block:: C 372 | :linenos: 373 | 374 | 375 | extern void OsTickDispatcher(void); 376 | OS_SEC_ALW_INLINE INLINE void OsHwiHandleActive(U32 irqNum) 377 | { 378 | switch(irqNum){ 379 | case 30: 380 | OsTickDispatcher(); 381 | // PRT_Printf("."); 382 | break; 383 | default: 384 | break; 385 | } 386 | } 387 | 388 | extern U32 OsGicIntAcknowledge(void); 389 | extern void OsGicIntClear(U32 value); 390 | // src/arch/cpu/armv8/common/hwi/prt_hwi.c OsHwiDispatch(),OsHwiDispatchHandle() 391 | /* 392 | * 描述: 中断处理入口, 调用处外部已关中断 393 | */ 394 | OS_SEC_L0_TEXT void OsHwiDispatch( U32 excType, struct ExcRegInfo *excRegs) //src/arch/cpu/armv8/common/hwi/prt_hwi.c 395 | { 396 | // 中断确认,相当于OsHwiNumGet() 397 | U32 value = OsGicIntAcknowledge(); 398 | U32 irq_num = value & 0x1ff; 399 | U32 core_num = value & 0xe00; 400 | 401 | OsHwiHandleActive(irq_num); 402 | 403 | // 清除中断,相当于 OsHwiClear(hwiNum); 404 | OsGicIntClear(irq_num|core_num); 405 | } 406 | 407 | src/bsp/os_attr_armv8_external.h 头文件可以在 `此处 <../\_static/os_attr_armv8_external.h>`_ 下载。 408 | 409 | - 新建 src/kernel/tick/prt_tick.c 文件,提供 OsTickDispatcher 时钟中断处理函数。 410 | 411 | .. code-block:: c 412 | :linenos: 413 | 414 | #include "os_attr_armv8_external.h" 415 | #include "prt_typedef.h" 416 | #include "prt_config.h" 417 | #include "os_cpu_armv8_external.h" 418 | 419 | extern U64 g_timerFrequency; 420 | 421 | /* Tick计数 */ 422 | OS_SEC_BSS U64 g_uniTicks; //src/core/kernel/sys/prt_sys.c 423 | 424 | /* 425 | * 描述:Tick中断的处理函数。扫描任务超时链表、扫描超时软件定时器、扫描TSKMON等。 426 | */ 427 | OS_SEC_TEXT void OsTickDispatcher(void) 428 | { 429 | uintptr_t intSave; 430 | 431 | intSave = OsIntLock(); 432 | g_uniTicks++; 433 | OsIntRestore(intSave); 434 | 435 | U64 cycle = g_timerFrequency / OS_TICK_PER_SECOND; 436 | OS_EMBED_ASM("MSR CNTP_TVAL_EL0, %0" : : "r"(cycle) : "memory", "cc"); //设置中断周期 437 | 438 | } 439 | 440 | /* 441 | * 描述:获取当前的tick计数 442 | */ 443 | OS_SEC_L2_TEXT U64 PRT_TickGetCount(void) //src/core/kernel/sys/prt_sys_time.c 444 | { 445 | return g_uniTicks; 446 | } 447 | 448 | 注意需将 hwi_init.c timer.c prt_tick.c 等文件加入构建系统。 449 | 450 | .. hint:: src/kernel, src/kernel/tick 目录下均需加入 CMakeLists.txt, src/ 和 src/bsp/ 下的 CMakeLists.txt 需修改。其中, 451 | 452 | src/kernel/tick/CMakeLists.txt 类似 src/bsp/CMakeLists.txt 453 | 454 | src/kernel/CMakeLists.txt 内容为: add_subdirectory(tick) 455 | 456 | src/CMakeLists.txt 需修改增加include目录、包含子目录和编译目标: 457 | 458 | .. code-block:: c 459 | 460 | ... ... 461 | include_directories( 462 | ${CMAKE_CURRENT_SOURCE_DIR}/include # 增加 src/include 目录 463 | ${CMAKE_CURRENT_SOURCE_DIR}/bsp 464 | ) 465 | 466 | add_subdirectory(bsp) 467 | add_subdirectory(kernel) # 增加 kernel 子目录 468 | 469 | list(APPEND OBJS $ $) # 增加 $ 目标 470 | add_executable(${APP} main.c ${OBJS}) 471 | 472 | 后续实验中若新增文件加入构建系统不再赘述,请参照此处。 473 | 474 | - 在 OsTickDispatcher 中调用了 OsIntLock 和 OsIntRestore 函数,这两个函数用于关中断和开中断。简单起见,将其放入 prt_exc.c 中。 475 | 476 | .. code-block:: c 477 | :linenos: 478 | 479 | /* 480 | * 描述: 开启全局可屏蔽中断。 481 | */ 482 | OS_SEC_L0_TEXT uintptr_t PRT_HwiUnLock(void) //src/arch/cpu/armv8/common/hwi/prt_hwi.c 483 | { 484 | uintptr_t state = 0; 485 | 486 | OS_EMBED_ASM( 487 | "mrs %0, DAIF \n" 488 | "msr DAIFClr, %1 \n" 489 | : "=r"(state) 490 | : "i"(DAIF_IRQ_BIT) 491 | : "memory", "cc"); 492 | 493 | return state & INT_MASK; 494 | } 495 | 496 | /* 497 | * 描述: 关闭全局可屏蔽中断。 498 | */ 499 | OS_SEC_L0_TEXT uintptr_t PRT_HwiLock(void) //src/arch/cpu/armv8/common/hwi/prt_hwi.c 500 | { 501 | uintptr_t state = 0; 502 | OS_EMBED_ASM( 503 | "mrs %0, DAIF \n" 504 | "msr DAIFSet, %1 \n" 505 | : "=r"(state) 506 | : "i"(DAIF_IRQ_BIT) 507 | : "memory", "cc"); 508 | return state & INT_MASK; 509 | } 510 | 511 | /* 512 | * 描述: 恢复原中断状态寄存器。 513 | */ 514 | OS_SEC_L0_TEXT void PRT_HwiRestore(uintptr_t intSave) //src/arch/cpu/armv8/common/hwi/prt_hwi.c 515 | { 516 | if ((intSave & INT_MASK) == 0) { 517 | OS_EMBED_ASM( 518 | "msr DAIFClr, %0\n" 519 | : 520 | : "i"(DAIF_IRQ_BIT) 521 | : "memory", "cc"); 522 | } else { 523 | OS_EMBED_ASM( 524 | "msr DAIFSet, %0\n" 525 | : 526 | : "i"(DAIF_IRQ_BIT) 527 | : "memory", "cc"); 528 | } 529 | return; 530 | } 531 | 532 | 头文件 src/bsp/os_cpu_armv8_external.h 533 | 534 | .. code-block:: C 535 | :linenos: 536 | 537 | #ifndef OS_CPU_ARMV8_EXTERNAL_H 538 | #define OS_CPU_ARMV8_EXTERNAL_H 539 | 540 | extern uintptr_t PRT_HwiUnLock(void); 541 | extern uintptr_t PRT_HwiLock(void); 542 | extern void PRT_HwiRestore(uintptr_t intSave); 543 | 544 | #define OsIntUnLock() PRT_HwiUnLock() 545 | #define OsIntLock() PRT_HwiLock() 546 | #define OsIntRestore(intSave) PRT_HwiRestore(intSave) 547 | 548 | #endif 549 | 550 | 551 | 读取系统Tick值 552 | -------------------- 553 | 554 | 新建 prt_tick.h,声明 Tick 相关的接口函数. 555 | 556 | .. code-block:: c 557 | :linenos: 558 | 559 | #ifndef PRT_TICK_H 560 | #define PRT_TICK_H 561 | 562 | #include "prt_typedef.h" 563 | 564 | extern U64 PRT_TickGetCount(void); 565 | 566 | #endif /* PRT_TICK_H */ 567 | 568 | main.c 修改为: 569 | 570 | .. code-block:: c 571 | :linenos: 572 | 573 | #include "prt_typedef.h" 574 | #include "prt_tick.h" 575 | 576 | extern U32 PRT_Printf(const char *format, ...); 577 | extern void PRT_UartInit(void); 578 | extern void CoreTimerInit(void); 579 | extern U32 OsHwiInit(void); 580 | 581 | U64 delay_time = 10000; 582 | 583 | S32 main(void) 584 | { 585 | // 初始化GIC 586 | OsHwiInit(); 587 | // 启用Timer 588 | CoreTimerInit(); 589 | 590 | PRT_UartInit(); 591 | 592 | PRT_Printf(" _ _ _____ _ _ _ _ _ _ _ _ \n"); 593 | PRT_Printf(" _ __ ___ (_)_ __ (_) ____| _| | ___ _ __ | |__ _ _ | | | | \\ | | | | | ___ _ __ \n"); 594 | PRT_Printf(" | '_ ` _ \\| | '_ \\| | _|| | | | |/ _ \\ '__| | '_ \\| | | | | |_| | \\| | | | |/ _ \\ '__|\n"); 595 | PRT_Printf(" | | | | | | | | | | | |__| |_| | | __/ | | |_) | |_| | | _ | |\\ | |_| | __/ | \n"); 596 | PRT_Printf(" |_| |_| |_|_|_| |_|_|_____\\__,_|_|\\___|_| |_.__/ \\__, | |_| |_|_| \\_|\\___/ \\___|_| \n"); 597 | PRT_Printf(" |___/ \n"); 598 | 599 | PRT_Printf("ctr-a h: print help of qemu emulator. ctr-a x: quit emulator.\n\n"); 600 | 601 | for(int i = 0; i < 10; i++) 602 | { 603 | 604 | U32 tick = PRT_TickGetCount(); 605 | PRT_Printf("[%d] current tick: %u\n", i, tick); 606 | 607 | //delay 608 | int delay_time = 10000000; // 根据自己机器计算能力不同调整该值 609 | while(delay_time>0){ 610 | PRT_TickGetCount(); //消耗时间,防止延时代码被编译器优化 611 | delay_time--; 612 | } 613 | 614 | } 615 | 616 | while(1); 617 | return 0; 618 | 619 | } 620 | 621 | 622 | lab5 作业 623 | -------------------------- 624 | 625 | 作业1 626 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 627 | 628 | 实现 hwi_init.c 中缺失的 OsGicEnableInt 和 OsGicDisableInt 函数。 629 | 630 | .. 作业2 631 | .. ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 632 | .. 将 PRT_TickGetCount 实现为系统调用。 633 | 634 | 635 | 636 | 637 | .. [1] https://developer.arm.com/documentation/den0024/a/Fundamentals-of-ARMv8/Execution-states 638 | .. [2] https://developer.arm.com/documentation/den0024/a/AArch64-Exception-Handling/Synchronous-and-asynchronous-exceptions 639 | .. [3] https://developer.arm.com/documentation/den0024/a/AArch64-Exception-Handling/AArch64-exception-table 640 | .. [4] https://developer.arm.com/documentation/den0024/a/ARMv8-Registers/AArch64-special-registers/Stack-pointer 641 | .. [5] https://www.ic.unicamp.br/~celio/mc404-2014/docs/gnu-arm-directives.pdf 642 | .. [6] https://developer.arm.com/documentation/ddi0487/gb 643 | .. [7] https://doc.rust-lang.org/reference/inline-assembly.html#register-operands 644 | .. [8] https://cloud.tencent.com/developer/article/1520799 645 | 646 | 647 | -------------------------------------------------------------------------------- /source/lab7/index.rst: -------------------------------------------------------------------------------- 1 | 实验七 信号量与同步 2 | ===================== 3 | 4 | 信号量结构初始化 5 | -------------------------- 6 | 7 | 新建 lab7/src/include/prt_sem_external.h 头文件 8 | 9 | .. code-block:: C 10 | :linenos: 11 | 12 | #ifndef PRT_SEM_EXTERNAL_H 13 | #define PRT_SEM_EXTERNAL_H 14 | 15 | #include "prt_sem.h" 16 | #include "prt_task_external.h" 17 | #if defined(OS_OPTION_POSIX) 18 | #include "bits/semaphore_types.h" 19 | #endif 20 | 21 | #define OS_SEM_UNUSED 0 22 | #define OS_SEM_USED 1 23 | 24 | #define SEM_PROTOCOL_PRIO_INHERIT 1 25 | #define SEM_TYPE_BIT_WIDTH 0x4U 26 | #define SEM_PROTOCOL_BIT_WIDTH 0x8U 27 | 28 | #define OS_SEM_WITH_LOCK_FLAG 1 29 | #define OS_SEM_WITHOUT_LOCK_FLAG 0 30 | 31 | #define MAX_POSIX_SEMAPHORE_NAME_LEN 31 32 | 33 | #define GET_SEM_LIST(ptr) LIST_COMPONENT(ptr, struct TagSemCb, semList) 34 | #define GET_SEM(semid) (((struct TagSemCb *)g_allSem) + (semid)) 35 | #define GET_SEM_TSK(semid) (((SEM_TSK_S *)g_semTsk) + (semid)) 36 | #define GET_TSK_SEM(tskid) (((TSK_SEM_S *)g_tskSem) + (tskid)) 37 | #define GET_SEM_TYPE(semType) (U32)((semType) & ((1U << SEM_TYPE_BIT_WIDTH) - 1)) 38 | #define GET_MUTEX_TYPE(semType) (U32)(((semType) >> SEM_TYPE_BIT_WIDTH) & ((1U << SEM_TYPE_BIT_WIDTH) - 1)) 39 | #define GET_SEM_PROTOCOL(semType) (U32)((semType) >> SEM_PROTOCOL_BIT_WIDTH) 40 | 41 | struct TagSemCb { 42 | /* 是否使用 OS_SEM_UNUSED/OS_SEM_USED */ 43 | U16 semStat; 44 | /* 核内信号量索引号 */ 45 | U16 semId; 46 | #if defined(OS_OPTION_SEM_RECUR_PV) 47 | /* 二进制互斥信号量递归P计数,计数型信号量和二进制同步模式信号量无效 */ 48 | U32 recurCount; 49 | #endif 50 | /* 当该信号量已用时,其信号量计数 */ 51 | U32 semCount; 52 | /* 挂接阻塞于该信号量的任务 */ 53 | struct TagListObject semList; 54 | /* 挂接任务持有的互斥信号量,计数型信号量信号量无效 */ 55 | struct TagListObject semBList; 56 | 57 | /* Pend到该信号量的线程ID */ 58 | U32 semOwner; 59 | /* 信号量唤醒阻塞任务的方式 */ 60 | enum SemMode semMode; 61 | /* 信号量,计数型或二进制 */ 62 | U32 semType; 63 | #if defined(OS_OPTION_POSIX) 64 | /* 信号量名称 */ 65 | char name[MAX_POSIX_SEMAPHORE_NAME_LEN + 1]; // + \0 66 | /* sem_open 句柄 */ 67 | sem_t handle; 68 | #endif 69 | }; 70 | 71 | /* 模块间全局变量声明 */ 72 | extern U16 g_maxSem; 73 | 74 | /* 指向核内信号量控制块 */ 75 | extern struct TagSemCb *g_allSem; 76 | 77 | extern U32 OsSemCreate(U32 count, U32 semType, enum SemMode semMode, SemHandle *semHandle, U32 cookie); 78 | extern bool OsSemBusy(SemHandle semHandle); 79 | 80 | #endif /* PRT_SEM_EXTERNAL_H */ 81 | 82 | 83 | 新建 src/kernel/sem/prt_sem_init.c 文件。 84 | 85 | .. code-block:: c 86 | :linenos: 87 | 88 | #include "prt_sem_external.h" 89 | #include "os_attr_armv8_external.h" 90 | #include "os_cpu_armv8_external.h" 91 | 92 | OS_SEC_BSS struct TagListObject g_unusedSemList; 93 | OS_SEC_BSS struct TagSemCb *g_allSem; 94 | 95 | extern void *OsMemAllocAlign(U32 mid, U8 ptNo, U32 size, U8 alignPow); 96 | /* 97 | * 描述:信号量初始化 98 | */ 99 | OS_SEC_L4_TEXT U32 OsSemInit(void) 100 | { 101 | struct TagSemCb *semNode = NULL; 102 | U32 idx; 103 | U32 ret = OS_OK; 104 | 105 | g_allSem = (struct TagSemCb *)OsMemAllocAlign((U32)OS_MID_SEM, 106 | 0, 107 | 4096, 108 | OS_SEM_ADDR_ALLOC_ALIGN); 109 | 110 | if (g_allSem == NULL) { 111 | return OS_ERRNO_SEM_NO_MEMORY; 112 | } 113 | 114 | g_maxSem = 4096 / sizeof(struct TagSemCb); 115 | 116 | char *cg_allSem = (char *)g_allSem; 117 | for(int i = 0; i < 4096; i++) 118 | cg_allSem[i] = 0; 119 | 120 | INIT_LIST_OBJECT(&g_unusedSemList); 121 | for (idx = 0; idx < g_maxSem; idx++) { 122 | semNode = ((struct TagSemCb *)g_allSem) + idx; //指针操作 123 | semNode->semId = (U16)idx; 124 | ListTailAdd(&semNode->semList, &g_unusedSemList); 125 | } 126 | 127 | return ret; 128 | } 129 | 130 | /* 131 | * 描述:创建一个信号量 132 | */ 133 | OS_SEC_L4_TEXT U32 OsSemCreate(U32 count, U32 semType, enum SemMode semMode, 134 | SemHandle *semHandle, U32 cookie) 135 | { 136 | uintptr_t intSave; 137 | struct TagSemCb *semCreated = NULL; 138 | struct TagListObject *unusedSem = NULL; 139 | (void)cookie; 140 | 141 | if (semHandle == NULL) { 142 | return OS_ERRNO_SEM_PTR_NULL; 143 | } 144 | 145 | intSave = OsIntLock(); 146 | 147 | if (ListEmpty(&g_unusedSemList)) { 148 | OsIntRestore(intSave); 149 | return OS_ERRNO_SEM_ALL_BUSY; 150 | } 151 | 152 | /* 在空闲链表中取走一个控制节点 */ 153 | unusedSem = OS_LIST_FIRST(&(g_unusedSemList)); 154 | ListDelete(unusedSem); 155 | 156 | /* 获取到空闲节点对应的信号量控制块,并开始填充控制块 */ 157 | semCreated = (GET_SEM_LIST(unusedSem)); 158 | semCreated->semCount = count; 159 | semCreated->semStat = OS_SEM_USED; 160 | semCreated->semMode = semMode; 161 | semCreated->semType = semType; 162 | semCreated->semOwner = OS_INVALID_OWNER_ID; 163 | if (GET_SEM_TYPE(semType) == SEM_TYPE_BIN) { 164 | INIT_LIST_OBJECT(&semCreated->semBList); 165 | #if defined(OS_OPTION_SEM_RECUR_PV) 166 | if (GET_MUTEX_TYPE(semType) == PTHREAD_MUTEX_RECURSIVE) { 167 | semCreated->recurCount = 0; 168 | } 169 | #endif 170 | } 171 | 172 | INIT_LIST_OBJECT(&semCreated->semList); 173 | *semHandle = (SemHandle)semCreated->semId; 174 | 175 | OsIntRestore(intSave); 176 | return OS_OK; 177 | } 178 | 179 | /* 180 | * 描述:创建一个信号量 181 | */ 182 | OS_SEC_L4_TEXT U32 PRT_SemCreate(U32 count, SemHandle *semHandle) 183 | { 184 | U32 ret; 185 | 186 | if (count > OS_SEM_COUNT_MAX) { 187 | return OS_ERRNO_SEM_OVERFLOW; 188 | } 189 | 190 | ret = OsSemCreate(count, SEM_TYPE_COUNT, SEM_MODE_FIFO, semHandle, (U32)(uintptr_t)semHandle); 191 | return ret; 192 | } 193 | 194 | 在 src/bsp/os_cpu_armv8_external.h 加入 定义 195 | 196 | .. code-block:: c 197 | :linenos: 198 | 199 | #define OS_SEM_ADDR_ALLOC_ALIGN 2U //按2的幂对齐,即2^2=4字节 200 | 201 | 202 | 新建 src/kernel/sem/prt_sem.c 文件。 203 | 204 | .. code-block:: c 205 | :linenos: 206 | 207 | #include "prt_sem_external.h" 208 | #include "prt_asm_cpu_external.h" 209 | #include "os_attr_armv8_external.h" 210 | #include "os_cpu_armv8_external.h" 211 | 212 | /* 核内信号量最大个数 */ 213 | OS_SEC_BSS U16 g_maxSem; 214 | 215 | 216 | OS_SEC_ALW_INLINE INLINE U32 OsSemPostErrorCheck(struct TagSemCb *semPosted, SemHandle semHandle) 217 | { 218 | (void)semHandle; 219 | /* 检查信号量控制块是否UNUSED,排除大部分错误场景 */ 220 | if (semPosted->semStat == OS_SEM_UNUSED) { 221 | return OS_ERRNO_SEM_INVALID; 222 | } 223 | 224 | /* post计数型信号量的错误场景, 释放计数型信号量且信号量计数大于最大计数 */ 225 | if ((semPosted)->semCount >= OS_SEM_COUNT_MAX) { 226 | return OS_ERRNO_SEM_OVERFLOW; 227 | } 228 | 229 | return OS_OK; 230 | } 231 | 232 | 233 | /* 234 | * 描述:把当前运行任务挂接到信号量链表上 235 | */ 236 | OS_SEC_L0_TEXT void OsSemPendListPut(struct TagSemCb *semPended, U32 timeOut) 237 | { 238 | struct TagTskCb *curTskCb = NULL; 239 | struct TagTskCb *runTsk = RUNNING_TASK; 240 | struct TagListObject *pendObj = &runTsk->pendList; 241 | 242 | OsTskReadyDel((struct TagTskCb *)runTsk); 243 | 244 | runTsk->taskSem = (void *)semPended; 245 | 246 | TSK_STATUS_SET(runTsk, OS_TSK_PEND); 247 | /* 根据唤醒方式挂接此链表,同优先级再按FIFO子顺序插入 */ 248 | if (semPended->semMode == SEM_MODE_PRIOR) { 249 | LIST_FOR_EACH(curTskCb, &semPended->semList, struct TagTskCb, pendList) { 250 | if (curTskCb->priority > runTsk->priority) { 251 | ListTailAdd(pendObj, &curTskCb->pendList); 252 | // goto TIMER_ADD; 253 | return; 254 | } 255 | } 256 | } 257 | /* 如果到这里,说明是FIFO方式;或者是优先级方式且挂接首个节点或者挂接尾节点 */ 258 | ListTailAdd(pendObj, &semPended->semList); 259 | 260 | } 261 | 262 | /* 263 | * 描述:从非空信号量链表上摘首个任务放入到ready队列 264 | */ 265 | OS_SEC_L0_TEXT struct TagTskCb *OsSemPendListGet(struct TagSemCb *semPended) 266 | { 267 | struct TagTskCb *taskCb = GET_TCB_PEND(LIST_FIRST(&(semPended->semList))); 268 | 269 | ListDelete(LIST_FIRST(&(semPended->semList))); 270 | /* 如果阻塞的任务属于定时等待的任务时候,去掉其定时等待标志位,并将其从去除 */ 271 | if (TSK_STATUS_TST(taskCb, OS_TSK_TIMEOUT)) { 272 | OS_TSK_DELAY_LOCKED_DETACH(taskCb); 273 | } 274 | 275 | /* 必须先去除 OS_TSK_TIMEOUT 态,再入队[睡眠时是先出ready队,再置OS_TSK_TIMEOUT态] */ 276 | TSK_STATUS_CLEAR(taskCb, OS_TSK_TIMEOUT | OS_TSK_PEND); 277 | taskCb->taskSem = NULL; 278 | /* 如果去除信号量阻塞位后,该任务不处于阻塞态则将该任务挂入就绪队列并触发任务调度 */ 279 | if (!TSK_STATUS_TST(taskCb, OS_TSK_SUSPEND)) { 280 | OsTskReadyAddBgd(taskCb); 281 | } 282 | 283 | return taskCb; 284 | } 285 | 286 | OS_SEC_L0_TEXT U32 OsSemPendParaCheck(U32 timeout) 287 | { 288 | if (timeout == 0) { 289 | return OS_ERRNO_SEM_UNAVAILABLE; 290 | } 291 | 292 | if (OS_TASK_LOCK_DATA != 0) { 293 | return OS_ERRNO_SEM_PEND_IN_LOCK; 294 | } 295 | return OS_OK; 296 | } 297 | 298 | OS_SEC_L0_TEXT bool OsSemPendNotNeedSche(struct TagSemCb *semPended, struct TagTskCb *runTsk) 299 | { 300 | if (semPended->semCount > 0) { 301 | semPended->semCount--; 302 | semPended->semOwner = runTsk->taskPid; 303 | 304 | return TRUE; 305 | } 306 | return FALSE; 307 | } 308 | 309 | /* 310 | * 描述:指定信号量的P操作 311 | */ 312 | OS_SEC_L0_TEXT U32 PRT_SemPend(SemHandle semHandle, U32 timeout) 313 | { 314 | uintptr_t intSave; 315 | U32 ret; 316 | struct TagTskCb *runTsk = NULL; 317 | struct TagSemCb *semPended = NULL; 318 | 319 | if (semHandle >= (SemHandle)g_maxSem) { 320 | return OS_ERRNO_SEM_INVALID; 321 | } 322 | 323 | semPended = GET_SEM(semHandle); 324 | 325 | intSave = OsIntLock(); 326 | if (semPended->semStat == OS_SEM_UNUSED) { 327 | OsIntRestore(intSave); 328 | return OS_ERRNO_SEM_INVALID; 329 | } 330 | 331 | if (OS_INT_ACTIVE) { 332 | OsIntRestore(intSave); 333 | return OS_ERRNO_SEM_PEND_INTERR; 334 | } 335 | 336 | runTsk = (struct TagTskCb *)RUNNING_TASK; 337 | 338 | if (OsSemPendNotNeedSche(semPended, runTsk) == TRUE) { 339 | OsIntRestore(intSave); 340 | return OS_OK; 341 | } 342 | 343 | ret = OsSemPendParaCheck(timeout); 344 | if (ret != OS_OK) { 345 | OsIntRestore(intSave); 346 | return ret; 347 | } 348 | /* 把当前任务挂接在信号量链表上 */ 349 | OsSemPendListPut(semPended, timeout); 350 | if (timeout != OS_WAIT_FOREVER) { 351 | OsIntRestore(intSave); 352 | return OS_ERRNO_SEM_FUNC_NOT_SUPPORT; 353 | } else { 354 | /* 恢复ps的快速切换 */ 355 | OsTskScheduleFastPs(intSave); 356 | 357 | } 358 | 359 | OsIntRestore(intSave); 360 | return OS_OK; 361 | } 362 | 363 | OS_SEC_ALW_INLINE INLINE void OsSemPostSchePre(struct TagSemCb *semPosted) 364 | { 365 | struct TagTskCb *resumedTask = NULL; 366 | 367 | resumedTask = OsSemPendListGet(semPosted); 368 | semPosted->semOwner = resumedTask->taskPid; 369 | } 370 | 371 | /* 372 | * 描述:判断信号量post是否有效 373 | * 备注:以下情况表示post无效,返回TRUE: post互斥二进制信号量,若该信号量被嵌套pend或者已处于空闲状态 374 | */ 375 | OS_SEC_ALW_INLINE INLINE bool OsSemPostIsInvalid(struct TagSemCb *semPosted) 376 | { 377 | if (GET_SEM_TYPE(semPosted->semType) == SEM_TYPE_BIN) { 378 | /* 释放互斥二进制信号量且信号量已处于空闲状态 */ 379 | if ((semPosted)->semCount == OS_SEM_FULL) { 380 | return TRUE; 381 | } 382 | } 383 | return FALSE; 384 | } 385 | 386 | /* 387 | * 描述:指定信号量的V操作 388 | */ 389 | OS_SEC_L0_TEXT U32 PRT_SemPost(SemHandle semHandle) 390 | { 391 | U32 ret; 392 | uintptr_t intSave; 393 | struct TagSemCb *semPosted = NULL; 394 | 395 | if (semHandle >= (SemHandle)g_maxSem) { 396 | return OS_ERRNO_SEM_INVALID; 397 | } 398 | 399 | semPosted = GET_SEM(semHandle); 400 | intSave = OsIntLock(); 401 | 402 | ret = OsSemPostErrorCheck(semPosted, semHandle); 403 | if (ret != OS_OK) { 404 | OsIntRestore(intSave); 405 | return ret; 406 | } 407 | 408 | /* 信号量post无效,不需要调度 */ 409 | if (OsSemPostIsInvalid(semPosted) == TRUE) { 410 | OsIntRestore(intSave); 411 | return OS_OK; 412 | } 413 | 414 | /* 如果有任务阻塞在信号量上,就激活信号量阻塞队列上的首个任务 */ 415 | if (!ListEmpty(&semPosted->semList)) { 416 | OsSemPostSchePre(semPosted); 417 | /* 相当于快速切换+中断恢复 */ 418 | OsTskScheduleFastPs(intSave); 419 | } else { 420 | semPosted->semCount++; 421 | semPosted->semOwner = OS_INVALID_OWNER_ID; 422 | 423 | } 424 | 425 | OsIntRestore(intSave); 426 | return OS_OK; 427 | } 428 | 429 | 430 | src/include/prt_task_external.h 加入 OsTskReadyAddBgd() 431 | 432 | .. code-block:: c 433 | :linenos: 434 | 435 | OS_SEC_ALW_INLINE INLINE void OsTskReadyAddBgd(struct TagTskCb *task) 436 | { 437 | OsTskReadyAdd(task); 438 | } 439 | 440 | src/kernel/task/prt_task.c 加入 OsTskScheduleFastPs() 441 | 442 | .. code-block:: c 443 | :linenos: 444 | 445 | // src/core/kernel/task/prt_amp_task.c 446 | /* 447 | * 描述:如果快速切换后只有中断恢复,使用此接口 448 | */ 449 | OS_SEC_TEXT void OsTskScheduleFastPs(uintptr_t intSave) 450 | { 451 | /* Find the highest task */ 452 | OsTskHighestSet(); 453 | 454 | /* In case that running is not highest then reschedule */ 455 | if ((g_highestTask != RUNNING_TASK) && (g_uniTaskLock == 0)) { 456 | UNI_FLAG |= OS_FLG_TSK_REQ; 457 | 458 | /* only if there is not HWI or TICK the trap */ 459 | if (OS_INT_INACTIVE) { 460 | OsTaskTrapFastPs(intSave); 461 | } 462 | } 463 | } 464 | 465 | src/bsp/os_cpu_armv8_external.h 加入 OsTaskTrapFastPs() 466 | 467 | .. code-block:: C 468 | :linenos: 469 | 470 | OS_SEC_ALW_INLINE INLINE void OsTaskTrapFastPs(uintptr_t intSave) 471 | { 472 | (void)intSave; 473 | OsTaskTrap(); 474 | } 475 | 476 | 加入 src/include/prt_sem.h [`下载 <../\_static/prt_sem.h>`_],该头文件主要是信号量相关的函数声明和宏定义。 477 | 478 | .. hint:: 将新增文件加入构建系统 479 | 480 | 验证 481 | -------------------------- 482 | 483 | .. code-block:: c 484 | :linenos: 485 | 486 | #include "prt_typedef.h" 487 | #include "prt_tick.h" 488 | #include "prt_task.h" 489 | #include "prt_sem.h" 490 | 491 | extern U32 PRT_Printf(const char *format, ...); 492 | extern void PRT_UartInit(void); 493 | extern U32 OsActivate(void); 494 | extern U32 OsTskInit(void); 495 | extern U32 OsSemInit(void); 496 | 497 | 498 | static SemHandle sem_sync; 499 | 500 | 501 | void Test1TaskEntry() 502 | { 503 | PRT_Printf("task 1 run ...\n"); 504 | PRT_SemPost(sem_sync); 505 | U32 cnt = 5; 506 | while (cnt > 0) { 507 | // PRT_TaskDelay(200); 508 | PRT_Printf("task 1 run ...\n"); 509 | cnt--; 510 | } 511 | } 512 | 513 | void Test2TaskEntry() 514 | { 515 | PRT_Printf("task 2 run ...\n"); 516 | 517 | PRT_SemPend(sem_sync, OS_WAIT_FOREVER); 518 | U32 cnt = 5; 519 | while (cnt > 0) { 520 | // PRT_TaskDelay(100); 521 | PRT_Printf("task 2 run ...\n"); 522 | cnt--; 523 | } 524 | } 525 | 526 | S32 main(void) 527 | { 528 | // 任务模块初始化 529 | OsTskInit(); 530 | OsSemInit(); // 参见demos/ascend310b/config/prt_config.c 系统初始化注册表 531 | 532 | PRT_UartInit(); 533 | 534 | PRT_Printf(" _ _ _____ _ _ _ _ _ _ _ _ \n"); 535 | PRT_Printf(" _ __ ___ (_)_ __ (_) ____| _| | ___ _ __ | |__ _ _ | | | | \\ | | | | | ___ _ __ \n"); 536 | PRT_Printf(" | '_ ` _ \\| | '_ \\| | _|| | | | |/ _ \\ '__| | '_ \\| | | | | |_| | \\| | | | |/ _ \\ '__|\n"); 537 | PRT_Printf(" | | | | | | | | | | | |__| |_| | | __/ | | |_) | |_| | | _ | |\\ | |_| | __/ | \n"); 538 | PRT_Printf(" |_| |_| |_|_|_| |_|_|_____\\__,_|_|\\___|_| |_.__/ \\__, | |_| |_|_| \\_|\\___/ \\___|_| \n"); 539 | PRT_Printf(" |___/ \n"); 540 | 541 | PRT_Printf("ctr-a h: print help of qemu emulator. ctr-a x: quit emulator.\n\n"); 542 | 543 | U32 ret; 544 | ret = PRT_SemCreate(0, &sem_sync); 545 | if (ret != OS_OK) { 546 | PRT_Printf("failed to create synchronization sem\n"); 547 | return 1; 548 | } 549 | 550 | struct TskInitParam param = {0}; 551 | 552 | // task 1 553 | // param.stackAddr = 0; 554 | param.taskEntry = (TskEntryFunc)Test1TaskEntry; 555 | param.taskPrio = 35; 556 | // param.name = "Test1Task"; 557 | param.stackSize = 0x1000; //固定4096,参见prt_task_init.c的OsMemAllocAlign 558 | 559 | TskHandle tskHandle1; 560 | ret = PRT_TaskCreate(&tskHandle1, ¶m); 561 | if (ret) { 562 | return ret; 563 | } 564 | 565 | ret = PRT_TaskResume(tskHandle1); 566 | if (ret) { 567 | return ret; 568 | } 569 | 570 | // task 2 571 | // param.stackAddr = 0; 572 | param.taskEntry = (TskEntryFunc)Test2TaskEntry; 573 | param.taskPrio = 30; 574 | // param.name = "Test2Task"; 575 | param.stackSize = 0x1000; //固定4096,参见prt_task_init.c的OsMemAllocAlign 576 | 577 | TskHandle tskHandle2; 578 | ret = PRT_TaskCreate(&tskHandle2, ¶m); 579 | if (ret) { 580 | return ret; 581 | } 582 | 583 | ret = PRT_TaskResume(tskHandle2); 584 | if (ret) { 585 | return ret; 586 | } 587 | 588 | // 启动调度 589 | OsActivate(); 590 | 591 | // while(1); 592 | return 0; 593 | 594 | } 595 | 596 | 597 | 598 | lab7 作业 599 | -------------------------- 600 | 601 | 作业1 602 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 603 | 604 | 各种并发问题模拟,至少3种。 -------------------------------------------------------------------------------- /source/lab8/index.rst: -------------------------------------------------------------------------------- 1 | 2 | 实验八 分页内存管理 3 | ===================== 4 | 5 | 6 | Armv8的地址转换 7 | ------------------------------ 8 | 9 | `ARM Cortex-A Series Programmer's Guide for ARMv8-A `_ 中提到:For EL0 and EL1, there are two translation tables. TTBR0_EL1 provides translations for the bottom of Virtual Address space, which is typically application space and TTBR1_EL1 covers the top of Virtual Address space, typically kernel space. This split means that the OS mappings do not have to be replicated in the translation tables of each task. 即TTBR0指向整个虚拟空间下半部分通常用于应用程序的空间,TTBR1指向虚拟空间的上半部分通常用于内核的空间。其中TTBR0除了在EL1中存在外,也在EL2 and EL3中存在,但TTBR1只在EL1中存在。 10 | 11 | TTBR0_ELn 和 TTBR1_ELn 是页表基地址寄存器,地址转换的过程如下所示。 12 | 13 | .. image:: v2p-translate.svg 14 | 15 | In a simple address translation involving only one level of look-up. It assumes we are using a 64KB granule with a 42-bit Virtual Address. The MMU translates a Virtual Address as follows: 16 | 17 | 1. If VA[63:42] = 1 then TTBR1 is used for the base address for the first page table. When VA[63:42] = 0, TTBR0 is used for the base address for the first page table. 18 | 2. The page table contains 8192 64-bit page table entries, and is indexed using VA[41:29]. The MMU reads the pertinent level 2 page table entry from the table. 19 | 3. The MMU checks the page table entry for validity and whether or not the requested memory access is allowed. Assuming it is valid, the memory access is allowed. 20 | 4. In the above Figure, the page table entry refers to a 512MB page (it is a block descriptor). 21 | 5. Bits [47:29] are taken from this page table entry and form bits [47:29] of the Physical Address. 22 | 6. Because we have a 512MB page, bits [28:0] of the VA are taken to form PA[28:0]. See Effect of granule sizes on translation tables 23 | 7. The full PA[47:0] is returned, along with additional information from the page table entry. 24 | 25 | In practice, such a simple translation process severely limits how finely you can divide up your address space. Instead of using only this first-level translation table, a first-level table entry can also point to a second-level page table. 26 | 27 | 28 | mmu管理 29 | ------------------------------ 30 | 31 | 新建 src/bsp/mmu.c 文件 32 | 33 | .. code-block:: c 34 | :linenos: 35 | 36 | #include "prt_typedef.h" 37 | #include "prt_module.h" 38 | #include "prt_errno.h" 39 | #include "mmu.h" 40 | #include "prt_task.h" 41 | 42 | extern U64 g_mmu_page_begin; 43 | extern U64 g_mmu_page_end; 44 | 45 | extern void os_asm_invalidate_dcache_all(void); 46 | extern void os_asm_invalidate_icache_all(void); 47 | extern void os_asm_invalidate_tlb_all(void); 48 | 49 | static mmu_mmap_region_s g_mem_map_info[] = { 50 | { 51 | .virt = 0x0, 52 | .phys = 0x0, 53 | .size = 0x40000000, // 1G size 54 | .max_level = 0x2, // 不应大于3 55 | .attrs = MMU_ATTR_DEVICE_NGNRNE | MMU_ACCESS_RWX, // 设备 56 | }, { 57 | .virt = 0x40000000, 58 | .phys = 0x40000000, 59 | .size = 0x40000000, // 1G size 60 | .max_level = 0x2, // // 不应大于3 61 | .attrs = MMU_ATTR_CACHE_SHARE | MMU_ACCESS_RWX, // 内存 62 | } 63 | }; 64 | 65 | static mmu_ctrl_s g_mmu_ctrl = { 0 }; 66 | 67 | // 依据实际情况生成tcr的值,pva_bits返回虚拟地址位数。Translation Control Register (tcr) 68 | static U64 mmu_get_tcr(U32 *pips, U32 *pva_bits) 69 | { 70 | U64 max_addr = 0; 71 | U64 ips, va_bits; 72 | U64 tcr; 73 | U32 i; 74 | U32 mmu_table_num = sizeof(g_mem_map_info) / sizeof(mmu_mmap_region_s); 75 | 76 | // 根据g_mem_map_info表计算所使用的虚拟地址的最大值 77 | for (i = 0; i < mmu_table_num; ++i) { 78 | max_addr = MAX(max_addr, g_mem_map_info[i].virt + g_mem_map_info[i].size); 79 | } 80 | 81 | // 依据虚拟地址最大值计算虚拟地址所需的位数, 82 | // 实际上应该分别计算物理地址的ips和虚拟地址的va_bits,而不是如下同时进行。 83 | if (max_addr > (1ULL << MMU_BITS_44)) { 84 | ips = MMU_PHY_ADDR_LEVEL_5; 85 | va_bits = MMU_BITS_48; 86 | } else if (max_addr > (1ULL << MMU_BITS_42)) { 87 | ips = MMU_PHY_ADDR_LEVEL_4; 88 | va_bits = MMU_BITS_44; 89 | } else if (max_addr > (1ULL << MMU_BITS_40)) { 90 | ips = MMU_PHY_ADDR_LEVEL_3; 91 | va_bits = MMU_BITS_42; 92 | } else if (max_addr > (1ULL << MMU_BITS_36)) { 93 | ips = MMU_PHY_ADDR_LEVEL_2; 94 | va_bits = MMU_BITS_40; 95 | } else if (max_addr > (1ULL << MMU_BITS_32)) { 96 | ips = MMU_PHY_ADDR_LEVEL_1; 97 | va_bits = MMU_BITS_36; 98 | } else { 99 | ips = MMU_PHY_ADDR_LEVEL_0; 100 | va_bits = MMU_BITS_32; 101 | } 102 | 103 | // 构建Translation Control Register寄存器的值,tcr可控制TTBR0_EL1和TTBR1_EL1的影响 104 | tcr = TCR_EL1_RSVD | TCR_IPS(ips); 105 | 106 | if (g_mmu_ctrl.granule == MMU_GRANULE_4K) { 107 | tcr |= TCR_TG0_4K | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA; 108 | } else { 109 | tcr |= TCR_TG0_64K | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA; 110 | } 111 | 112 | tcr |= TCR_T0SZ(va_bits); // Memory region 2^(64-T0SZ) 113 | 114 | if (pips != NULL) { 115 | *pips = ips; 116 | } 117 | 118 | if (pva_bits != NULL) { 119 | *pva_bits = va_bits; 120 | } 121 | 122 | return tcr; 123 | } 124 | 125 | static U32 mmu_get_pte_type(U64 const *pte) 126 | { 127 | return (U32)(*pte & PTE_TYPE_MASK); 128 | } 129 | 130 | // 根据页表项级别计算当个页表项表示的范围(位数) 131 | static U32 mmu_level2shift(U32 level) 132 | { 133 | if (g_mmu_ctrl.granule == MMU_GRANULE_4K) { 134 | return (U32)(MMU_BITS_12 + MMU_BITS_9 * (MMU_LEVEL_3 - level)); 135 | } else { 136 | return (U32)(MMU_BITS_16 + MMU_BITS_13 * (MMU_LEVEL_3 - level)); 137 | } 138 | } 139 | 140 | // 根据虚拟地址找到对应级别的页表项 141 | static U64 *mmu_find_pte(U64 addr, U32 level) 142 | { 143 | U64 *pte = NULL; 144 | U64 idx; 145 | U32 i; 146 | 147 | if (level < g_mmu_ctrl.start_level) { 148 | return NULL; 149 | } 150 | 151 | pte = (U64 *)g_mmu_ctrl.tlb_addr; 152 | 153 | // 从顶级页表开始,直到找到所需level级别的页表项或返回NULL 154 | for (i = g_mmu_ctrl.start_level; i < MMU_LEVEL_MAX; ++i) { 155 | // 依据级别i计算页表项在页表中的索引idx 156 | if (g_mmu_ctrl.granule == MMU_GRANULE_4K) { 157 | idx = (addr >> mmu_level2shift(i)) & 0x1FF; 158 | } else { 159 | idx = (addr >> mmu_level2shift(i)) & 0x1FFF; 160 | } 161 | 162 | // 找到对应的页表项 163 | pte += idx; 164 | 165 | // 如果是需要level级别的页表项则返回 166 | if (i == level) { 167 | return pte; 168 | } 169 | 170 | // 从顶级页表开始找, 171 | // 找到当前级别页表项不是有效的(无效或是block entry)直接返回NULL 172 | if (mmu_get_pte_type(pte) != PTE_TYPE_TABLE) { 173 | return NULL; 174 | } 175 | 176 | // 不是所需级别但pte指向有效,依据页表粒度准备访问下级页表 177 | if (g_mmu_ctrl.granule == MMU_GRANULE_4K) { 178 | pte = (U64 *)(*pte & PTE_TABLE_ADDR_MARK_4K); 179 | } else { 180 | pte = (U64 *)(*pte & PTE_TABLE_ADDR_MARK_64K); 181 | } 182 | } 183 | 184 | return NULL; 185 | } 186 | 187 | // 根据页表粒度在页表区域新建一个页表,返回页表起始位置 188 | static U64 *mmu_create_table(void) 189 | { 190 | U32 pt_len; 191 | U64 *new_table = (U64 *)g_mmu_ctrl.tlb_fillptr; 192 | 193 | if (g_mmu_ctrl.granule == MMU_GRANULE_4K) { 194 | pt_len = MAX_PTE_ENTRIES_4K * sizeof(U64); 195 | } else { 196 | pt_len = MAX_PTE_ENTRIES_64K * sizeof(U64); 197 | } 198 | 199 | // 根据页表粒度在页表区域新建一个页表(4K或64K) 200 | g_mmu_ctrl.tlb_fillptr += pt_len; 201 | 202 | if (g_mmu_ctrl.tlb_fillptr - g_mmu_ctrl.tlb_addr > g_mmu_ctrl.tlb_size) { 203 | return NULL; 204 | } 205 | 206 | // 初始化页表全为0,因此该页表所有的页表项初始都是PTE_TYPE_FAULT 207 | // (void)memset_s((void *)new_table, MAX_PTE_ENTRIES_64K * sizeof(U64), 0, pt_len); 208 | U64 *tmp = new_table; 209 | for(int i = 0; i < pt_len; i+=sizeof(U64)){ 210 | *tmp = 0; 211 | tmp++; 212 | } 213 | 214 | return new_table; 215 | } 216 | 217 | static void mmu_set_pte_table(U64 *pte, U64 *table) 218 | { 219 | // https://developer.arm.com/documentation/den0024/a/The-Memory-Management-Unit/Translation-tables-in-ARMv8-A/AArch64-descriptor-format 220 | *pte = PTE_TYPE_TABLE | (U64)table; 221 | } 222 | 223 | // 依据mmu_mmap_region_s填充pte 224 | static S32 mmu_add_map_pte_process(mmu_mmap_region_s const *map, U64 *pte, U64 phys, U32 level) 225 | { 226 | U64 *new_table = NULL; 227 | 228 | // 属于上级页表项 229 | if (level < map->max_level) { 230 | // 如果页表项指向无效,新建一个页表且pte指向该页表 231 | if (mmu_get_pte_type(pte) == PTE_TYPE_FAULT) { 232 | // 新建一个页表 233 | new_table = mmu_create_table(); 234 | if (new_table == NULL) { 235 | return -1; 236 | } 237 | // pte指向下级页表 238 | mmu_set_pte_table(pte, new_table); 239 | } //else: 如果页表项指向有效,不做任何处理。 240 | } else if (level == MMU_LEVEL_3) { // 最多4级页表(0,1,2,3),这是最后一级页表项,最后L3级页表项定义略有不同 241 | *pte = phys | map->attrs | PTE_TYPE_PAGE; 242 | } else { 243 | // 这里的情况:等于map->max_level且不到最后L3级页表,依据mmu_mmap_region_s的配置作为block entry类型直接指向物理区域 244 | *pte = phys | map->attrs | PTE_TYPE_BLOCK; 245 | } 246 | 247 | return 0; 248 | } 249 | 250 | // 依据 mmu_mmap_region_s 的定义,生成 mmu 映射 251 | static S32 mmu_add_map(mmu_mmap_region_s const *map) 252 | { 253 | U64 virt = map->virt; 254 | U64 phys = map->phys; 255 | U64 max_level = map->max_level; 256 | U64 start_level = g_mmu_ctrl.start_level; 257 | U64 block_size = 0; 258 | U64 map_size = 0; 259 | U32 level; 260 | U64 *pte = NULL; 261 | S32 ret; 262 | 263 | if (map->max_level <= start_level) { 264 | return -2; 265 | } 266 | 267 | while (map_size < map->size) { 268 | // 从起始级别start_level开始遍历页表。注意起始级别页表肯定存在 269 | for (level = start_level; level <= max_level; ++level) { 270 | // 找到对应level的页表项 271 | pte = mmu_find_pte(virt, level); 272 | if (pte == NULL) { 273 | return -3; 274 | } 275 | 276 | // 如果为上级页表项且pte指向无效,新建下级页表且pte指向该新建的页表 277 | // 如果为最低页表项或到达设定级别页表项,直接设置页表项的值 278 | ret = mmu_add_map_pte_process(map, pte, phys, level); 279 | if (ret) { 280 | return ret; 281 | } 282 | 283 | if (level != start_level) { 284 | block_size = 1ULL << mmu_level2shift(level); 285 | } 286 | } 287 | 288 | virt += block_size; 289 | phys += block_size; 290 | map_size += block_size; 291 | } 292 | 293 | return 0; 294 | } 295 | 296 | static inline void mmu_set_ttbr_tcr_mair(U64 table, U64 tcr, U64 attr) 297 | { 298 | OS_EMBED_ASM("dsb sy"); 299 | 300 | OS_EMBED_ASM("msr ttbr0_el1, %0" : : "r" (table) : "memory"); 301 | // OS_EMBED_ASM("msr ttbr1_el1, %0" : : "r" (table) : "memory"); 302 | OS_EMBED_ASM("msr tcr_el1, %0" : : "r" (tcr) : "memory"); 303 | OS_EMBED_ASM("msr mair_el1, %0" : : "r" (attr) : "memory"); 304 | 305 | OS_EMBED_ASM("isb"); 306 | } 307 | 308 | static U32 mmu_setup_pgtables(mmu_mmap_region_s *mem_map, U32 mem_region_num, U64 tlb_addr, U64 tlb_len, U32 granule) 309 | { 310 | U32 i; 311 | U32 ret; 312 | U64 tcr; 313 | U64 *new_table = NULL; 314 | 315 | g_mmu_ctrl.tlb_addr = tlb_addr; 316 | g_mmu_ctrl.tlb_size = tlb_len; 317 | g_mmu_ctrl.tlb_fillptr = tlb_addr; 318 | g_mmu_ctrl.granule = granule; 319 | g_mmu_ctrl.start_level = 0; 320 | 321 | tcr = mmu_get_tcr(NULL, &g_mmu_ctrl.va_bits); 322 | 323 | // 依据页表粒度和虚拟地址位数计算地址转换起始级别 324 | if (g_mmu_ctrl.granule == MMU_GRANULE_4K) { 325 | if (g_mmu_ctrl.va_bits < MMU_BITS_39) { 326 | g_mmu_ctrl.start_level = MMU_LEVEL_1; 327 | } else { 328 | g_mmu_ctrl.start_level = MMU_LEVEL_0; 329 | } 330 | } else { 331 | if (g_mmu_ctrl.va_bits <= MMU_BITS_36) { 332 | g_mmu_ctrl.start_level = MMU_LEVEL_2; 333 | } else { 334 | g_mmu_ctrl.start_level = MMU_LEVEL_1; 335 | return 3; 336 | } 337 | } 338 | 339 | // 创建一个顶级页表,不一定是L0 340 | new_table = mmu_create_table(); 341 | if (new_table == NULL) { 342 | return 1; 343 | } 344 | 345 | for (i = 0; i < mem_region_num; ++i) { 346 | ret = mmu_add_map(&mem_map[i]); 347 | if (ret) { 348 | return ret; 349 | } 350 | } 351 | 352 | mmu_set_ttbr_tcr_mair(g_mmu_ctrl.tlb_addr, tcr, MEMORY_ATTRIBUTES); 353 | 354 | return 0; 355 | } 356 | 357 | static S32 mmu_setup(void) 358 | { 359 | S32 ret; 360 | U64 page_addr; 361 | U64 page_len; 362 | 363 | page_addr = (U64)&g_mmu_page_begin; 364 | page_len = (U64)&g_mmu_page_end - (U64)&g_mmu_page_begin; 365 | 366 | ret = mmu_setup_pgtables(g_mem_map_info, (sizeof(g_mem_map_info) / sizeof(mmu_mmap_region_s)), 367 | page_addr, page_len, MMU_GRANULE_4K); 368 | if (ret) { 369 | return ret; 370 | } 371 | 372 | return 0; 373 | } 374 | 375 | 376 | 377 | S32 mmu_init(void) 378 | { 379 | S32 ret; 380 | 381 | ret = mmu_setup(); 382 | if (ret) { 383 | return ret; 384 | } 385 | 386 | os_asm_invalidate_dcache_all(); 387 | os_asm_invalidate_icache_all(); 388 | os_asm_invalidate_tlb_all(); 389 | 390 | set_sctlr(get_sctlr() | CR_C | CR_M | CR_I); 391 | 392 | return 0; 393 | } 394 | 395 | 396 | 新建 src/bsp/mmu.h, 该文件可从 `这里 <../\_static/mmu.h>`_ 下载 397 | 398 | 新建 src/bsp/cache_asm.S, 该文件可从 `这里 <../\_static/cache_asm.S>`_ 下载 399 | 400 | 401 | 402 | 启用 mmu 403 | -------------------------- 404 | 405 | start.S 中在 B OsEnterMain 之前启用 MMU 406 | 407 | .. code-block:: asm 408 | :linenos: 409 | 410 | // 启用 MMU 411 | BL mmu_init 412 | // 进入 main 函数 413 | B OsEnterMain 414 | 415 | 416 | .. hint:: 将新增文件加入构建系统 417 | 418 | .. hint:: 通过调试确保你真的启动了 MMU 419 | 420 | lab8 作业 421 | -------------------------- 422 | 423 | 作业1 424 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 425 | 426 | 启用 TTBR1 ,将地址映射到虚拟地址的高半部分,使用高地址访问串口 427 | 修改后:(1)src/bsp/print.c中 428 | 429 | .. code-block:: c 430 | 431 | #define UART_0_REG_BASE (0xffffffff00000000 + 0x09000000) 432 | 433 | (2)src/bsp/hwi_init.c 中 434 | 435 | .. code-block:: c 436 | 437 | #define GIC_DIST_BASE (0xffffffff00000000 + 0x08000000) 438 | #define GIC_CPU_BASE (0xffffffff00000000 + 0x08010000) 439 | 440 | 程序可以正常运行。(GIC_DIST_BASE 和 GIC_CPU_BASE 的高位多少个f与你对MMU的配置有关) 441 | 442 | 443 | 444 | -------------------------------------------------------------------------------- /source/lab8/v2p-translate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Page-1 57 | 58 | 59 | text3132.8 60 | 63 61 | 62 | 63 | 64 | 63 65 | 66 | text3132.32 67 | 0 68 | 69 | 70 | 71 | 0 72 | 73 | Sheet.3 74 | Level 2 index 75 | 76 | 77 | 78 | Level 2 index 79 | 80 | Sheet.4 81 | Physical address [28:0] 82 | 83 | 84 | 85 | Physical address [28:0] 86 | 87 | text3132.34 88 | 63 89 | 90 | 91 | 92 | 63 93 | 94 | text3132.35 95 | 0 96 | 97 | 98 | 99 | 0 100 | 101 | Sheet.7 102 | Index in table 103 | 104 | 105 | 106 | Index in table 107 | 108 | Sheet.8 109 | Virtual address from core 110 | 111 | 112 | 113 | Virtual address from core 114 | 115 | Bus 1.49 116 | 117 | 118 | 119 | 120 | 121 | Sheet.10 122 | Level 2 page table with 8192 entries 123 | 124 | 125 | 126 | Level 2 page table with 8192 entries 127 | 128 | rect5245-4.6 129 | TTBRx 130 | 131 | 132 | 133 | TTBRx 134 | 135 | Sheet.12 136 | Low bits of virtual address form low bits of physical address 137 | 138 | 139 | 140 | Low bits of virtual address form low bits of physical address 141 | 142 | rect5245-4.33 143 | 144 | 145 | 146 | text3132.36 147 | 28 148 | 149 | 150 | 151 | 28 152 | 153 | text3132.37 154 | 29 155 | 156 | 157 | 158 | 29 159 | 160 | text3132.40 161 | 41 162 | 163 | 164 | 165 | 41 166 | 167 | Sheet.17 168 | 169 | 170 | 171 | Sheet.18 172 | ... 173 | 174 | 175 | 176 | ... 177 | 178 | Sheet.19 179 | Physical address [28:0] 180 | 181 | 182 | 183 | Physical address [28:0] 184 | 185 | Bezier line.38 186 | 198 | 199 | 200 | Sheet.21 201 | Page table base address 202 | 203 | 204 | 205 | Page table base address 206 | 207 | rect5245-4.51 208 | TTBR select 209 | 210 | 211 | 212 | TTBR select 213 | 214 | rect5245-4.44 215 | PA[47:29] 216 | 217 | 218 | 219 | PA[47:29] 220 | 221 | rect5245-4.52 222 | 223 | 224 | 225 | Sheet.25 226 | ... 227 | 228 | 229 | 230 | ... 231 | 232 | Sheet.26 233 | 234 | 235 | 236 | Sheet.27 237 | 238 | 239 | 240 | Sheet.28 241 | 242 | 243 | 244 | Sheet.29 245 | 246 | 247 | 248 | Bezier line.11 249 | 258 | 259 | 260 | Bezier line.13 261 | 274 | 275 | 276 | Sheet.32 277 | Page table entry contains PA [47:29] 278 | 279 | 280 | 281 | Page table entry contains PA [47:29] 282 | 283 | Sheet.33 284 | Page table entry 285 | 286 | 287 | 288 | Page table entry 289 | 290 | Sheet.34 291 | PA 292 | 293 | 294 | 295 | PA 296 | 297 | Sheet.35 298 | VA 299 | 300 | 301 | 302 | VA 303 | 304 | 305 | -------------------------------------------------------------------------------- /source/lab9/index.rst: -------------------------------------------------------------------------------- 1 | 实验十 Shell 2 | ===================== 3 | 4 | 5 | 新建 src/include/prt_shell.h 头文件 6 | 7 | .. code-block:: c 8 | :linenos: 9 | 10 | #ifndef _HWLITEOS_SHELL_H 11 | #define _HWLITEOS_SHELL_H 12 | 13 | #include "prt_typedef.h" 14 | 15 | #define SHELL_SHOW_MAX_LEN 272 16 | #define PATH_MAX 1024 17 | 18 | typedef struct { 19 | U32 consoleID; 20 | U32 shellTaskHandle; 21 | U32 shellEntryHandle; 22 | void *cmdKeyLink; 23 | void *cmdHistoryKeyLink; 24 | void *cmdMaskKeyLink; 25 | U32 shellBufOffset; 26 | U32 shellBufReadOffset; 27 | U32 shellKeyType; 28 | char shellBuf[SHELL_SHOW_MAX_LEN]; 29 | char shellWorkingDirectory[PATH_MAX]; 30 | } ShellCB; 31 | 32 | #endif /* _HWLITEOS_SHELL_H */ 33 | 34 | 接收输入 35 | -------------------------- 36 | QEMU的virt机器默认没有键盘作为输入设备,但当我们执行QEMU使用 -nographic 参数(disable graphical output and redirect serial I/Os to console)时QEMU会将串口重定向到控制台,因此我们可以使用UART作为输入设备。 37 | 38 | 在 src/bsp/print.c 中的 PRT_UartInit 添加初始化代码,使其支持接收数据中断。 同时定义了用于串口接收的信号量 sem_uart_rx。 39 | 40 | .. code-block:: C 41 | :linenos: 42 | 43 | #include "prt_sem.h" 44 | #include "prt_shell.h" 45 | 46 | 47 | #define UARTCR_UARTEN (1 << 0) 48 | #define UARTCR_TXE (1 << 8) 49 | #define UARTCR_RXE (1 << 9) 50 | 51 | #define UARTICR_ALL (1 << 0) 52 | 53 | #define UARTIMSC_RXIM (1 << 4) 54 | 55 | #define UARTIBRD_IBRD_MASK 0xFFFF 56 | #define UARTFBRD_FBRD_MASK 0x3F 57 | 58 | #define UARTLCR_H_WLEN_MASK (3 << 5) 59 | #define UARTLCR_H_PEN (1 << 1) 60 | #define UARTLCR_H_STP1 (0 << 3) 61 | 62 | SemHandle sem_uart_rx; 63 | 64 | extern void OsGicIntSetConfig(uint32_t interrupt, uint32_t config); 65 | extern void OsGicIntSetPriority(uint32_t interrupt, uint32_t priority); 66 | extern void OsGicEnableInt(U32 intId); 67 | extern void OsGicClearInt(uint32_t interrupt); 68 | extern U32 PRT_Printf(const char *format, ...); 69 | U32 PRT_UartInit(void) 70 | { 71 | U32 result = 0; 72 | U32 reg_base = UART_0_REG_BASE; 73 | 74 | UART_REG_WRITE(0, (unsigned long)(reg_base + 0x30));// 禁用pl011 75 | UART_REG_WRITE(0x7ff, (unsigned long)(reg_base + 0x44));// 清空中断状态 76 | UART_REG_WRITE(UARTIMSC_RXIM, (unsigned long)(reg_base + 0x38));// 设定中断mask,需要使能的中断 77 | UART_REG_WRITE(13, (unsigned long)(reg_base + 0x24)); 78 | UART_REG_WRITE(1, (unsigned long)(reg_base + 0x28)); 79 | 80 | // https://developer.arm.com/documentation/ddi0183/g/programmers-model/register-descriptions/line-control-register--uartlcr-h?lang=en 81 | result = UART_REG_READ((unsigned long)(reg_base + DW_UART_LCR_HR)); 82 | result = result | UARTLCR_H_WLEN_MASK | UARTLCR_H_PEN | UARTLCR_H_STP1 | DW_FIFO_ENABLE; 83 | UART_REG_WRITE(result, (unsigned long)(reg_base + DW_UART_LCR_HR)); // 8N1 FIFO enable 84 | 85 | UART_REG_WRITE(UARTCR_UARTEN | UARTCR_RXE | UARTCR_TXE, (unsigned long)(reg_base + 0x30));// 启用pl011 86 | 87 | 88 | // 启用UART 接收中断 89 | OsGicIntSetConfig(33, 0); //可省略 90 | OsGicIntSetPriority(33, 0); 91 | OsGicClearInt(33); //可省略 92 | OsGicEnableInt(33); 93 | 94 | // 创建uart数据接收信号量 95 | U32 ret; 96 | ret = PRT_SemCreate(0, &sem_uart_rx); 97 | if (ret != OS_OK) { 98 | PRT_Printf("failed to create uart_rx sem\n"); 99 | return 1; 100 | } 101 | 102 | return OS_OK; 103 | } 104 | 105 | 简单起见,在 src/bsp/print.c 中实现 OsUartRxHandle() 处理接收中断。 106 | 107 | .. code-block:: c 108 | :linenos: 109 | 110 | extern ShellCB g_shellCB; 111 | void OsUartRxHandle(void) 112 | { 113 | U32 flag = 0; 114 | U32 result = 0; 115 | U32 reg_base = UART_0_REG_BASE; 116 | 117 | flag = UART_REG_READ((unsigned long)(reg_base + 0x18)); 118 | while((flag & (1<<4)) == 0) 119 | { 120 | result = UART_REG_READ((unsigned long)(reg_base + 0x0)); 121 | // PRT_Printf("%c", result); 122 | 123 | // 将收到的字符存到g_shellCB的缓冲区 124 | g_shellCB.shellBuf[g_shellCB.shellBufOffset] = (char) result; 125 | g_shellCB.shellBufOffset++; 126 | if (g_shellCB.shellBufOffset == SHELL_SHOW_MAX_LEN) 127 | g_shellCB.shellBufOffset = 0; 128 | 129 | PRT_SemPost(sem_uart_rx); 130 | flag = UART_REG_READ((unsigned long)(reg_base + 0x18)); 131 | } 132 | return; 133 | } 134 | 135 | 在 src/bsp/prt_exc.c 中OsHwiHandleActive() 链接中断和处理函数OsUartRxHandle() 136 | 137 | .. code-block:: c 138 | :linenos: 139 | 140 | extern void OsTickDispatcher(void); 141 | extern void OsUartRxHandle(void); 142 | OS_SEC_ALW_INLINE INLINE void OsHwiHandleActive(U32 irqNum) 143 | { 144 | switch(irqNum){ 145 | case 30: 146 | OsTickDispatcher(); 147 | // PRT_Printf("."); 148 | break; 149 | case 33: 150 | OsUartRxHandle(); 151 | default: 152 | break; 153 | } 154 | } 155 | 156 | 157 | 在 src/kernel/task/prt_task.c 中加入函数 158 | 159 | .. code-block:: c 160 | :linenos: 161 | 162 | extern U32 PRT_Printf(const char *format, ...); 163 | OS_SEC_TEXT void OsDisplayTasksInfo(void) 164 | { 165 | struct TagTskCb *taskCb = NULL; 166 | U32 cnt = 0; 167 | 168 | PRT_Printf("\nPID\t\tPriority\tStack Size\n"); 169 | // 遍历g_runQueue队列,查找优先级最高的任务 170 | LIST_FOR_EACH(taskCb, &g_runQueue, struct TagTskCb, pendList) { 171 | cnt++; 172 | PRT_Printf("%d\t\t%d\t\t%d\n", taskCb->taskPid, taskCb->priority, taskCb->stackSize); 173 | } 174 | PRT_Printf("Total %d tasks", cnt); 175 | 176 | } 177 | 178 | 在 src/kernel/tick/prt_tick.c 中加入函数 179 | 180 | .. code-block:: c 181 | :linenos: 182 | 183 | extern U32 PRT_Printf(const char *format, ...); 184 | OS_SEC_TEXT void OsDisplayCurTick(void) 185 | { 186 | PRT_Printf("\nCurrent Tick: %d", PRT_TickGetCount()); 187 | } 188 | 189 | 190 | shell 处理 191 | -------------------------- 192 | 193 | 新建 src/shell/shmsg.c 文件。 194 | 195 | .. code-block:: c 196 | :linenos: 197 | 198 | #include "prt_typedef.h" 199 | #include "prt_shell.h" 200 | #include "os_attr_armv8_external.h" 201 | #include "prt_task.h" 202 | #include "prt_sem.h" 203 | 204 | extern SemHandle sem_uart_rx; 205 | extern U32 PRT_Printf(const char *format, ...); 206 | extern void OsDisplayTasksInfo(void); 207 | extern void OsDisplayCurTick(void); 208 | 209 | 210 | OS_SEC_TEXT void ShellTask(uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4) 211 | { 212 | U32 ret; 213 | char ch; 214 | char cmd[SHELL_SHOW_MAX_LEN]; 215 | U32 idx; 216 | ShellCB *shellCB = (ShellCB *)param1; 217 | 218 | while (1) { 219 | PRT_Printf("\nminiEuler # "); 220 | idx = 0; 221 | for(int i = 0; i < SHELL_SHOW_MAX_LEN; i++) 222 | { 223 | cmd[i] = 0; 224 | } 225 | 226 | while (1){ 227 | PRT_SemPend(sem_uart_rx, OS_WAIT_FOREVER); 228 | 229 | // 读取shellCB缓冲区的字符 230 | ch = shellCB->shellBuf[shellCB->shellBufReadOffset]; 231 | cmd[idx] = ch; 232 | idx++; 233 | shellCB->shellBufReadOffset++; 234 | if(shellCB->shellBufReadOffset == SHELL_SHOW_MAX_LEN) 235 | shellCB->shellBufReadOffset = 0; 236 | 237 | PRT_Printf("%c", ch); //回显 238 | if (ch == '\r'){ 239 | // PRT_Printf("\n"); 240 | if(cmd[0]=='t' && cmd[1]=='o' && cmd[2]=='p'){ 241 | OsDisplayTasksInfo(); 242 | } else if(cmd[0]=='t' && cmd[1]=='i' && cmd[2]=='c' && cmd[3]=='k'){ 243 | OsDisplayCurTick(); 244 | } 245 | break; 246 | } 247 | 248 | } 249 | } 250 | } 251 | 252 | OS_SEC_TEXT U32 ShellTaskInit(ShellCB *shellCB) 253 | { 254 | U32 ret = 0; 255 | struct TskInitParam param = {0}; 256 | 257 | // task 1 258 | // param.stackAddr = 0; 259 | param.taskEntry = (TskEntryFunc)ShellTask; 260 | param.taskPrio = 9; 261 | // param.name = "Test1Task"; 262 | param.stackSize = 0x1000; //固定4096,参见prt_task_init.c的OsMemAllocAlign 263 | param.args[0] = (uintptr_t)shellCB; 264 | 265 | TskHandle tskHandle1; 266 | ret = PRT_TaskCreate(&tskHandle1, ¶m); 267 | if (ret) { 268 | return ret; 269 | } 270 | 271 | ret = PRT_TaskResume(tskHandle1); 272 | if (ret) { 273 | return ret; 274 | } 275 | } 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | .. hint:: 将新增文件加入构建系统 284 | 285 | lab9 作业 286 | -------------------------- 287 | 288 | 作业1 289 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 290 | 291 | 实现一条有用的 shell 指令。 -------------------------------------------------------------------------------- /source/references/index.rst: -------------------------------------------------------------------------------- 1 | 参考资料 2 | ===================== 3 | 4 | - 交叉工具链可以从Arm官网的 `GNU Toolchain Downloads `_ 页面下载。 5 | - Arm官网的AArch64基础文档: `AArch64基础 `_。 6 | - 完整的寄存器列表可参考 Arm 官网的 `AArch64 System Registers `_ 页面。 7 | - 完整的指令集可参考 Arm 官网的 `Arm A-profile A64 Instruction Set Architecture `_ 页面。 8 | - 详情可参考 `The GNU linker `_。此外,这里还有一个简单的 `链接脚本基本介绍 `_ 可参考。 9 | - CMake 的命令和参数等可参考 `官网文档 `_。此外,这里还有一个很好的入门 `博客文章 `_。 10 | 11 | 12 | 执行gdb时报错 13 | 14 | .. code-block:: console 15 | 16 | root@e1370b14f32a:~/netdisk/os2024_exp/os2024_exp_redo/lab4# ../../aarch64-none-elf/bin/aarch64-none-elf-gdb build/miniEuler 17 | ../../aarch64-none-elf/bin/aarch64-none-elf-gdb: error while loading shared libraries: libncursesw.so.5: cannot open shared object file: No such file or directory 18 | root@e1370b14f32a:~/netdisk/os2024_exp/os2024_exp_redo/lab4# apt-get install libncursesw5 19 | 20 | root@e1370b14f32a:~/netdisk/os2024_exp/os2024_exp_redo/lab4# ../../aarch64-none-elf/bin/aarch64-none-elf-gdb build/miniEuler 21 | ../../aarch64-none-elf/bin/aarch64-none-elf-gdb: error while loading shared libraries: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory 22 | root@e1370b14f32a:~/netdisk/os2024_exp/os2024_exp_redo/lab4# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/anaconda3/lib/ 23 | root@e1370b14f32a:~/netdisk/os2024_exp/os2024_exp_redo/lab4# ../../aarch64-none-elf/bin/aarch64-none-elf-gdb build/miniEuler 24 | -------------------------------------------------------------------------------- /source/requirements.txt: -------------------------------------------------------------------------------- 1 | # Defining the exact version will make sure things don't break 2 | sphinx==5.3.0 3 | sphinx_rtd_theme==1.1.1 4 | readthedocs-sphinx-search==0.1.1 5 | --------------------------------------------------------------------------------