├── README.md ├── docs ├── arm64 │ ├── Google TEE环境trusty分析.md │ ├── arm-trusted-firmware分析.md │ ├── arm-trusted-firmware历史漏洞分析.md │ ├── arm64架构分析.md │ ├── device-tree分析.md │ └── dragonboard410c │ │ ├── Device-Tree与linux驱动模型.md │ │ ├── dragonboard410c系统安装与内核编译.md │ │ └── little-kernel分析.md ├── linksys-wrt1900acs-openwrt-for-daily-use.md ├── opentitan │ ├── Peripherals.md │ ├── ibex.md │ ├── opentitan-rtl-synthesis-with-yosys.md │ ├── prim.md │ └── tlul.md ├── riscv │ ├── coreboot_opensbi_test_debug.md │ ├── coreboot分析.md │ ├── hifiveunleashed_coreboot_notes-en.md │ ├── hifiveunleashed_coreboot_notes.md │ ├── opensbi代码分析.md │ ├── riscv-debug_and_openocd.md │ ├── riscv_security.md │ └── riscv架构.md ├── secure-your-crenditial-with-nitrokey-storage-hsm-keepass.md ├── usb-armory-for-daily-use.md └── x86 │ └── coreboot │ ├── 1.coreboot-x86.md │ ├── 2.coreboot-x86.md │ ├── 3.coreboot-x86.md │ └── 4.coreboot-x86移植相关.md ├── hardened_config ├── usbarmory-4.9.config ├── usbarmory-grsec-4.8.config └── usbarmory-grsec-4.9.config └── resources └── images └── docs └── opentitan ├── alert_handler_esc_timer.png ├── alert_handler_ping_timer.png ├── hmac_core_fsm.png ├── i2c_fsm.png ├── ibex.md-controller_fsm.png ├── sha2_fifo_fsm.png ├── sha2_hash_ctrl.png ├── sha2_pad.png ├── spi_fwm_rxf_ctrl_fsm.png ├── spi_fwm_txf_ctrl_fsm.png ├── usb_fs_nb_in_pe_fsm.png ├── usb_fs_nb_out_pe_fsm.png └── usb_fs_tx_fsm.png /README.md: -------------------------------------------------------------------------------- 1 | ### embedded-iot_profile 2 | 3 | ######[USB Armory for daily use](https://github.com/hardenedlinux/embedded-iot_profile/blob/master/docs/usb-armory-for-daily-use.md) 4 | 5 | 1. Getting started(bootable image, network, sdcard resizing) 6 | 7 | ######[Linksys WRT1900acs OpenWrt for daily use](https://github.com/hardenedlinux/embedded-iot_profile/blob/master/docs/linksys-wrt1900acs-openwrt-for-daily-use.md)   8 | 9 | 1. Getting started(Flashing firmware, network configuration..etc) 10 | 2. Advance use(Integrated shadowsocks)   11 | -------------------------------------------------------------------------------- /docs/arm64/arm-trusted-firmware历史漏洞分析.md: -------------------------------------------------------------------------------- 1 | # ARM Trusted Firmware历史漏洞分析 2 | 3 | ## CVE-2016-10319 4 | 5 | ### 概要 6 | 7 | 在ATF(ARM Trusted Firmware)的BL1中,有一个FWU(Firmware Update feature)功能。此功能用于更新固件,BL1中实现了Secure部分。如果要实现更新固件功能需要实现BL1U,Non-Secure部分。不过BL1U在ATF中并没有实现。 8 | 9 | BL1的FWU存在一个整数溢出的漏洞,此漏洞可以导致在Non-Secure中通过SMC调用实现缓冲区溢出攻击。 10 | 11 | 漏洞代码获取 12 | ```shell 13 | git clone git@github.com:ARM-software/arm-trusted-firmware.git CVE-2016-10319 14 | cd CVE-2016-10319 15 | git checkout 48bfb88 16 | ``` 17 | 18 | ### 漏洞代码分析 19 | 20 | FWU功能主要在`bl1/bl1_fwu.c`文件中实现,`bl1_fwu_image_copy`用于把镜像从Non-Secure空间拷贝到Secure空间,此代码存在漏洞,代码如下 21 | 22 | ```c 23 | /******************************************************************************* 24 | * This function is responsible for copying secure images in AP Secure RAM. 25 | ******************************************************************************/ 26 | static int bl1_fwu_image_copy(unsigned int image_id, 27 | uintptr_t image_src, 28 | unsigned int block_size, 29 | unsigned int image_size, 30 | unsigned int flags) 31 | { 32 | uintptr_t base_addr; 33 | meminfo_t *mem_layout; 34 | 35 | /* Get the image descriptor. */ 36 | image_desc_t *image_desc = bl1_plat_get_image_desc(image_id); 37 | 38 | /* Check if we are in correct state. */ 39 | /* FWU功能由多次SMC调用组合完才,服务程序有一个状态机来记录执行到那一步,此处验证状态是否正常 */ 40 | if ((!image_desc) || 41 | ((image_desc->state != IMAGE_STATE_RESET) && 42 | (image_desc->state != IMAGE_STATE_COPYING))) { 43 | WARN("BL1-FWU: Copy not allowed due to invalid state\n"); 44 | return -EPERM; 45 | } 46 | 47 | /* Only Normal world is allowed to copy a Secure image. */ 48 | /* 此SMC服务,只能由Nor-Secure调用,用于更新Secure的固件 */ 49 | if ((GET_SEC_STATE(flags) == SECURE) || 50 | (GET_SEC_STATE(image_desc->ep_info.h.attr) == NON_SECURE)) { 51 | WARN("BL1-FWU: Copy not allowed for Non-Secure " 52 | "image from Secure-world\n"); 53 | return -EPERM; 54 | } 55 | 56 | if ((!image_src) || (!block_size)) { 57 | WARN("BL1-FWU: Copy not allowed due to invalid image source" 58 | " or block size\n"); 59 | return -ENOMEM; 60 | } 61 | 62 | /* Get the image base address. */ 63 | base_addr = image_desc->image_info.image_base; 64 | 65 | if (image_desc->state == IMAGE_STATE_COPYING) { 66 | /* 拷贝可以通过多次SMC调用组合完成 67 | IMAGE_STATE_COPYING状态用于标示 执行了第一次拷贝但拷贝还没有完才 */ 68 | /* 69 | * If last block is more than expected then 70 | * clip the block to the required image size. 71 | */ 72 | /* BUG 此处加法存在整数溢出风险,block_size由SMC调用传入 73 | * 此处可以传入一个很大的block_size使加法溢出,从而通过条件判定 74 | * 导致下面拷贝(memcpy)溢出 75 | */ 76 | if (image_desc->image_info.copied_size + block_size > 77 | image_desc->image_info.image_size) { 78 | block_size = image_desc->image_info.image_size - 79 | image_desc->image_info.copied_size; 80 | WARN("BL1-FWU: Copy argument block_size > remaining image size." 81 | " Clipping block_size\n"); 82 | } 83 | 84 | /* Make sure the image src/size is mapped. */ 85 | if (bl1_plat_mem_check(image_src, block_size, flags)) { 86 | WARN("BL1-FWU: Copy arguments source/size not mapped\n"); 87 | return -ENOMEM; 88 | } 89 | 90 | INFO("BL1-FWU: Continuing image copy in blocks\n"); 91 | 92 | /* Copy image for given block size. */ 93 | base_addr += image_desc->image_info.copied_size; 94 | image_desc->image_info.copied_size += block_size; 95 | memcpy((void *)base_addr, (const void *)image_src, block_size); 96 | flush_dcache_range(base_addr, block_size); 97 | 98 | /* Update the state if last block. */ 99 | if (image_desc->image_info.copied_size == 100 | image_desc->image_info.image_size) { 101 | image_desc->state = IMAGE_STATE_COPIED; 102 | INFO("BL1-FWU: Image copy in blocks completed\n"); 103 | } 104 | } else { 105 | /* This means image is in RESET state and ready to be copied. */ 106 | INFO("BL1-FWU: Fresh call to copy an image\n"); 107 | 108 | if (!image_size) { 109 | WARN("BL1-FWU: Copy not allowed due to invalid image size\n"); 110 | return -ENOMEM; 111 | } 112 | 113 | /* 114 | * If block size is more than total size then 115 | * assume block size as the total image size. 116 | */ 117 | if (block_size > image_size) { 118 | block_size = image_size; 119 | WARN("BL1-FWU: Copy argument block_size > image size." 120 | " Clipping block_size\n"); 121 | } 122 | 123 | /* Make sure the image src/size is mapped. */ 124 | if (bl1_plat_mem_check(image_src, block_size, flags)) { 125 | WARN("BL1-FWU: Copy arguments source/size not mapped\n"); 126 | return -ENOMEM; 127 | } 128 | 129 | /* Find out how much free trusted ram remains after BL1 load */ 130 | /* BUG此处加法存在整数溢出风险,image_size由SMC调用传入 131 | * 可以传入一个很大的image_size使加法溢出,并绕过此条件判断 132 | * 从而导致后面的内存拷贝(memcpy)溢出 133 | */ 134 | mem_layout = bl1_plat_sec_mem_layout(); 135 | if ((image_desc->image_info.image_base < mem_layout->free_base) || 136 | (image_desc->image_info.image_base + image_size > 137 | mem_layout->free_base + mem_layout->free_size)) { 138 | WARN("BL1-FWU: Memory not available to copy\n"); 139 | return -ENOMEM; 140 | } 141 | 142 | /* Update the image size. */ 143 | image_desc->image_info.image_size = image_size; 144 | 145 | /* Copy image for given size. */ 146 | memcpy((void *)base_addr, (const void *)image_src, block_size); 147 | flush_dcache_range(base_addr, block_size); 148 | 149 | /* Update the state. */ 150 | if (block_size == image_size) { 151 | image_desc->state = IMAGE_STATE_COPIED; 152 | INFO("BL1-FWU: Image is copied successfully\n"); 153 | } else { 154 | image_desc->state = IMAGE_STATE_COPYING; 155 | INFO("BL1-FWU: Started image copy in blocks\n"); 156 | } 157 | image_desc->image_info.copied_size = block_size; 158 | } 159 | 160 | return 0; 161 | } 162 | ``` 163 | 164 | 以上代码两出加法可能会造成整数溢出,导致后面的内存拷贝出现缓冲区溢出问题 165 | 166 | ### 可利用性 167 | 168 | 此SMC调用在BL1是建立,被BL31的SMC调用替代。BL1U和BL一起被加载,一般位于芯片内修改比较困难,BL2需要BL1认证后加载一般不会给攻击者替换。 169 | 170 | ## CVE-2017-7564 171 | 172 | ### 概要 173 | 174 | 此漏洞是ARM的一个调试寄存器设置错误。导致Non-Secure通过访问调试寄存器获取到Secure的信息。 175 | 176 | 代码获取 177 | 178 | ``` 179 | git clone git@github.com:ARM-software/arm-trusted-firmware.git CVE-2016-10319 180 | cd CVE-2016-10319 181 | git checkout 48bfb88 182 | ``` 183 | 184 | ### 漏洞分析 185 | 186 | ARM具有Self-Hosted调试模块(根一般桌面系统一样)。 187 | 188 | MDCR_EL3.SDD是Secure Word Self-Hosted调试的使能位,类似的MDCR_EL3.SPD32为32比特模式下Self-Hosted调试模块的控制寄存器 189 | 190 | MDCR_EL3.SDD 191 | 192 | ​ 0 使能Secure Word Self-Hosted调试 193 | 194 | ​ 1 关闭Secure Word Self-Hosted调试 195 | 196 | MDCR_EL3.SPD32 197 | 198 | ​ 00 传统模式 199 | 200 | ​ 01 关闭Secure优先级调试,关闭Secure-EL1的调试异常 201 | 202 | ​ 11 使能Secure优先级调试,使能Secure-EL1的调试异常 203 | 204 | **el3_entrypoint_common**宏用于el3环境的初始化(大小端、主次处理器、内存、异常),其中调用**el3_arch_init_common**执行架构相关初始化。 205 | 206 | 漏洞代码 207 | 208 | ```assembly 209 | /* --------------------------------------------------------------------- 210 | * Reset registers that may have architecturally unknown reset values 211 | * --------------------------------------------------------------------- 212 | */ 213 | msr mdcr_el3, xzr 214 | ``` 215 | 216 | 修复后 217 | 218 | ```assembly 219 | /* MDCR definitions */ 220 | #define MDCR_SPD32(x) ((x) << 14) 221 | #define MDCR_SPD32_LEGACY 0x0 222 | #define MDCR_SPD32_DISABLE 0x2 223 | #define MDCR_SPD32_ENABLE 0x3 224 | #define MDCR_SDD_BIT (1 << 16) 225 | 226 | #define MDCR_DEF_VAL (MDCR_SDD_BIT | MDCR_SPD32(MDCR_SPD32_DISABLE)) 227 | 228 | /* --------------------------------------------------------------------- 229 | * Disable secure self-hosted invasive debug. 230 | * --------------------------------------------------------------------- 231 | */ 232 | mov_imm x0, MDCR_DEF_VAL 233 | msr mdcr_el3, x0 234 | ``` 235 | 236 | ### 可利用性 237 | 238 | Secure EL0、Secure EL1调试异常有两种路由方式Secure EL1、Non-Secure EL2,要利用此漏洞获取Secure信息,需要提前获取Non-Secure EL2的权限。 -------------------------------------------------------------------------------- /docs/arm64/arm64架构分析.md: -------------------------------------------------------------------------------- 1 | # arm64架构分析 2 | 3 | 此文分析基于arm官方文档 4 | 5 | [ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile](https://static.docs.arm.com/ddi0487/a/DDI0487A_k_armv8_arm_iss10775.pdf) 6 | 7 | [ARM Architecture Reference Manual Supplement ARMv8.1, for ARMv8-A architecture profile Errata markup](https://static.docs.arm.com/ddi0557/a/DDI0557A_b_armv8_1_supplement_errata.pdf) 8 | 9 | ## 指令集 10 | 11 | 1\. A64 64位ARM指令集,这里64位指数据处理能力即一条指令处理的数据宽度,指令编码使用定长32比特编码 12 | 13 | 2\. A32 32位ARM指令集,这里32位指数据处理能力即一条指令处理的数据宽度,指令编码使用定长32比特编码 14 | 15 | 3\. T32 thumb指令集,32位指令(即一条指令可以处理32比特到数据),指令编码为16比特32比特混合编码(具有更高的指令密度,16比特指令可以访问的寄存器数受限,性能会低很多) 16 | 17 | ## 寄存器 18 | 19 | arm64有31通用寄存器X0-X30,可以作为32或64比特的寄存器,32比特时记为W0-W30,64比特时记为X0-X30。 20 | 21 | 指令使用5比特编码寄存器。当寄存器编号为31时,使用零寄存器(zero register)。零寄存器非物理寄存器,32比特时记作WZR,64比特时记作XZR。 22 | 23 | LR为X30。LR(链接寄存器)在函数调用指令(BL、BLR)中使用,用于保存返回地址 24 | 25 | SP(stack point)、PC(program counter)、CPSR(current program state register)为特殊功能寄存器,通过特殊指令访问,在指令中没有寄存器编号。 26 | 27 | SP(stack point) 28 | 29 | - 在ARMv8有4个SP指针,SP0、SP_EL1、SP_EL2、SP_EL3 30 | - EL0只能使用SP0 31 | - ELx(x > 0)可以使用SP0或者使用SP_ELx(即在EL1下可以使用SP0、SP_EL1,在EL2下可以使用SP0、SP_EL2) 32 | 33 | CPSR(current program state register)非物理寄存器,是一组寄存器(NZCV/DAIF/CurrentEL/SPSel)的映射,不能通过指令直接访问。通过访问NZCV/DAIF/CurrentEL/SPSel寄存器间接访问CPSR 34 | 35 | - N , bit[31] 标记计算结果为负 36 | - Z , bit[30] 标记计算结果为零 37 | - C , bit[29] 标记无符号数计算进位 38 | - V , bit[28] 标记有符号数计算益处 39 | - Q , bit[27] ??? 40 | - J , bit[24] 41 | - T , bit[5] 42 | - T和J配合用于确定32 位执行模式下的状态 43 | - {0,0} ARM状态 44 | - Thumb状态,执行thumb2指令 45 | - Thumb状态,执行thumbEE指令 46 | - ARM 32下指令集切换 47 | - ARM和thumb状态切换,执行BL、BLX 48 | - thumb和thumbEE状态切换,执行ENTERX、LEAVEX 49 | - PAN,bit[22] 权限相关 50 | - GE ,bit[19:16] 并行加减法大于等于标志位 51 | - IT ,bit[15:10] bit[25:26] 52 | - thumb32 IT(if-then)指令相关 53 | - E ,bit[9] 大小端状态位 54 | - A ,bit[8] 异步数据终止异常标记位 55 | - I ,bit[7] IRQ异常标记位 56 | - F ,bit[6] FIQ异常标记位 57 | - M ,bit[4:0] 处理器模式位,64和32位模式下意义不同 58 | - M[4] 59 | - 1 执行状态是ARRCH32 60 | - 0 执行状态是ARRCH64 61 | - M[3:0] AARCH32标记异常模式 62 | - 0b0000 User 63 | - 0b0001 FIQ 64 | - 0b0010 IRQ 65 | - 0b0011 Supervisor 66 | - 0b0110 Monitor 67 | - 0b0111 Abort 68 | - 0b1010 Hyp 69 | - 0b1011 Undefined 70 | - 0b1111 System 71 | - M[3:0] AARCH64下分成多段 72 | - M[3:2] 异常等级 73 | - M[1] 保留未用 74 | - M[0] SP选择 75 | - 0 使用SP_EL0 76 | - 使用SP_ELx 77 | 78 | ## 异常等级(Exception Level) 79 | 80 | 异常等级是ARMv8引入到权限等级,ARMv8有4级异常等级EL0、EL1、EL2、EL3数值越大权限越大,具体芯片可以选择实现EL3、EL2 81 | 82 | 通常 83 | 84 | EL0用于User App 85 | 86 | EL1用于OS 87 | 88 | EL2用于Hypervisor 89 | 90 | EL3用于Secure monitor 91 | 92 | ## 执行状态(Execution State) 93 | 94 | ARMv8有两种执行状态: 95 | 96 | - AArch64用于执行64位指令集 97 | 98 | - AArch32用于执行32位指令集 99 | 100 | 101 | 执行状态的控制 102 | 103 | - 如果高异常等级执行在AARCH32状态下,低的异常等级必须执行在AARCH32状态下 104 | 105 | - EL3执行状态 106 | - AARCH64->AARCH32 107 | - RMR_EL3.AA64写0 108 | - AARCH32->AARCH64 109 | - RMR.AA64写1 110 | - 在AARCH32状态下RMR是CP15协处理器的寄存器,通过以下指令访问 111 | - `MRC P15, 0, , C12, C0, 2`; Read RMR into Rt 112 | - `MCR P15, 0, , C12, C0, 2`; Write Rt to RMR 113 | - RMR是RMR_EL3在32模式下的映射,写此寄存器将触发热复位 114 | 115 | - EL2执行状态 116 | - SCR_EL3.RW,如果为0低异常等级(EL0/EL1/EL2)只能执行AARCH32,如果为1低异常等级执行状态由CPSR.M[4]决定0->64bit、1->32bit 117 | 118 | - EL1执行状态 119 | - HCR_EL2.RW,如果为0低异常等级(EL0/EL1)只能执行AARCH32,如果为1低异常等级执行状态由CPSR.M[4]决定0->64bit、1->32bit 120 | - CPSR.M[4]不能直接修改,只能通过异常进入高异常等级修改SPSR.M[4]来间接修改 121 | 122 | ## 安全状态(Security state) 123 | 124 | - Non-secure(EL0、EL1、EL2) 只能访问Non-secure memory 125 | 126 | - secure(EL0、EL1、EL3) 可以访问secure & Non-secure memory 127 | - EL3只能在secure state 128 | - EL2只能在Non-secure 129 | - EL1&0安全状态由SCR_EL3.NS位决定,1->Non-secure,0->secure 130 | 131 | ## 异常路由(发生异常时跳转到ELx) 132 | 133 | - SCR_EL3.EA、SCR_EL3.IRQ、SCR_EL3.FIQ,如果为1,底异常等级发生的异常都路由到EL3 134 | - HCR_EL2.AMO、HCR_EL2.IMO、HCR_EL2.FMO,如果为1,底异常等级Non-secure下的异常都路由到EL2 135 | - EL0不处理异常,以上比特位都为0,EL0的异常都路由到EL1 136 | 137 | ## 异常返回 138 | 139 | - 异常返回有特有的返回指令ERET 140 | - 异常有独立的链接寄存器ELR_ELx(保存返回地址) 141 | - 通过修改SPSR_ELx.M[3:2]可以确定异常返回后到异常等级 142 | - 通过修改SPSR_ELx.M[4]可以修改异常返回后的执行状态(ARRCH64/ARRCH32) 143 | 144 | ## 异常向量 145 | 146 | - EL0不能处理异常,所以EL0没有异常向量 147 | 148 | - 每一个异常等级有一个寄存器用于设置异常的起始地址,Vector Base Address Register(VBAR_ELx) 149 | 150 | - 根据异常得来源异常向量被分为4段(当前异常等级并使用SP_EL0,当前异常等级并使用SP_ELx,低异常等级64位模式,低异常等级32位模式) 151 | 152 | - 每一段有4中异常Synchronous exception/SError/IRQ/FIQ 153 | 154 | 异常地址如下: 155 | 156 | | 异常来源 | Synchronous | IRQ | FIQ | SError | 157 | | --------------- | ----------- | ----- | ----- | ------ | 158 | | 当前异常等级并使用SP_EL0 | 0x000 | 0x080 | 0x100 | 0x180 | 159 | | 当前异常等级并使用SP_ELx | 0x200 | 0x280 | 0x300 | 0x380 | 160 | | 低异常等级AARCH64 | 0x400 | 0x480 | 0x500 | 0x580 | 161 | | 低异常等级AARCH32 | 0x600 | 0x680 | 0x700 | 0x780 | 162 | 163 | ## 内存管理 164 | 165 | ### 基本概念 166 | 167 | - 物理地址(PA),芯片地址线上使用的实际地址 168 | - 虚拟地址(VA),程序可见的地址空间 169 | - 输入地址(IA),地址转换之前的输入地址 170 | - 输出地址(OA),地址转换之后的输出地址 171 | - 中间物理地址(IPA),ARM64支持两级地址转换,当使用两级地址转换时,第一级地址转换输出的地址会用于第二级地址转换的输入,两级转换的中间地址就叫做IPA 172 | - 页,MMU可以管理的最小内存块 173 | - 页表描述符,用于描述虚拟地址和物理地址转换关系,以及内存属性 174 | - 页表,页表描述符的表 175 | 176 | ### ARMv8内存管理 177 | 178 | - ARMv8支持4k/16k/64k三种类型的页 179 | - ARMv8最大支持48位的物理地址空间(即支持256T的物理内存) 180 | - ARMv8支持48位的虚拟地址空间(即支持256T的虚拟地址空间) 181 | - ARMv8有两级地址转换单元(stage1/stage2) 182 | 183 | - EL3 Secure 使用stage1转换 184 | - EL2 Non-Secure 使用stage1转换 185 | - EL0&1 Secure 使用stage1转换 186 | - EL0&1 Non-Secure 使用stage1&2转换(只有这中情况下有两级地址转换,才会存在IPA) 187 | 188 | #### 地址转换规则 189 | 190 | - ARMv8支持多级页表,即通过页表描述符找到的地址是下一级页表的地址,需要多次转换才能找到最终的地址 191 | 192 | - ARMv8把输入地址分为多个段(每个段分别作为各级页表的偏移量,或者目标页的偏移量) 193 | 194 | - 4k页时虚拟地址分段规则 195 | ``` 196 | +--------+--------+--------+--------+--------+--------+--------+--------+ 197 | |63 56|55 48|47 40|39 32|31 24|23 16|15 8|7 0| 198 | +--------+--------+--------+--------+--------+--------+--------+--------+ 199 | | | | | | | 200 | | | | | | v 201 | | | | | | [11:0] in-page offset 202 | | | | | +-> [20:12] L3 index 203 | | | | +-----------> [29:21] L2 index 204 | | | +---------------------> [38:30] L1 index 205 | | +-------------------------------> [47:39] L0 index 206 | +-------------------------------------------------> [63] TTBR0/1 207 | ``` 208 | 209 | - 16k页时虚拟地址分段规则 210 | ``` 211 | +--------+--------+--------+--------+--------+--------+--------+--------+ 212 | |63 56|55 48|47 40|39 32|31 24|23 16|15 8|7 0| 213 | +--------+--------+--------+--------+--------+--------+--------+--------+ 214 | | || | | | 215 | | || | | v 216 | | || | | [13:0] in-page offset 217 | | || | +------->[24:14] L3 index 218 | | || +------------------->[35:25] L2 index 219 | | |+------------------------------->[46:36] L1 index 220 | | +-------------------------------->[47] L0 index 221 | +-------------------------------------------------->[63] TTBR0/1 222 | ``` 223 | 224 | - 64k页时虚拟地址分段规则 225 | ``` 226 | +--------+--------+--------+--------+--------+--------+--------+--------+ 227 | |63 56|55 48|47 40|39 32|31 24|23 16|15 8|7 0| 228 | +--------+--------+--------+--------+--------+--------+--------+--------+ 229 | | | | | | 230 | | | | | v 231 | | | | | [15:0] in-page offset 232 | | | | +----------> [28:16] L3 index 233 | | | +--------------------------> [41:29] L2 index 234 | | +-------------------------------> [47:42] L1 index 235 | +-------------------------------------------------> [63] TTBR0/1 236 | ``` 237 | 238 | #### 页表描述符 239 | 240 | 页表描述符64位,地址8字节对齐,所以在查找下一级页表时低3比特全部为0。页表描述符在表示目标页地址时低12(4k)/14(16k)/16(64k)位都为0。所以在页表描述符中,低3比特肯定是无效位。 241 | 242 | ARMv8利用这个特性使用了其中低两个比特位,用于标识页表描述符的类型 243 | 244 | - L0/L1/L2时 245 | 246 | - bit[0]标识页表描述符是否有效(1有效,无效将触发却页中断) 247 | - bit[1]标识页表描述符有没有下一级页表(1有下一级页表,没有下一级页表时把没有使用的地址都作为地址偏移量) 248 | 249 | 250 | - L3时 251 | - bit[0]标识页表描述符是否有效(1有效,无效将触发却页中断) 252 | - bit[1]标识页表描述符是不是页地址(1是页地址) 253 | 254 | 页表描述符[47:3]比特记录地址信息,根据每一级页表偏移的位数或页偏移的位数把低位清0,这样只需要或上偏移量就可以算出目标地址 255 | 256 | - 所以,页地址需要4k/16k/64k地址对齐 257 | - 所以,页表描述符表也需要根据具体情况对齐,列如4k的页表描述符需要2k(512*64bit)对齐(页表偏移量都为9位)页表描述符,高16位无效(64-48),ARMv8把这一部分用于记录权限信息(只在stage1转换期间有效,在stage2转换期间保留为0) 258 | 259 | 页表描述符,高16位无效(64-48),ARMv8把这一部分用于记录权限信息(只在stage1转换期间有效,在stage2转换期间保留为0) 260 | 261 | - NSTable,[63] 用于标识描述符描述的内存是否为Non-Secure Memory(0 Non-Secure memory,1 Secure memory) 262 | - APTable,[62:61] 263 | - 01 标识目标内存EL0没有权限访问 264 | - 10 标识目标内存不能写(EL0/EL1/EL2/EL3) 265 | - 11 标识目标内存EL0不能访问,并且目标内存不能写(EL0/EL1/EL2/EL3) 266 | - XNTable,[60] 标识目标内存能否执行(1不能执行) 267 | - PXNTable,[59] 标识目标内存能否执行(1不能执行),标识特权级可执行PXNTable,[59] 标识目标内存能否执行(1不能执行),标识特权级可执行 268 | 269 | ## 缓存 270 | 271 | 缓存(cache)一般由tag data controller组成。tag用于记录下一级内存中那一部分被缓存了,data用于保存缓存的内容,controller用于控制缓存的工作。 272 | 273 | ### 基本概念 274 | 275 | - 缓存页(cache page)缓存与下一级存储器交换的最小单位 276 | - 缓存行(cache line)缓存页内部又被分成多个缓存行 277 | - 脏的缓存(dirty)与下一级缓存不一致的缓存 278 | - 干净的缓存(clean)与下一级缓存一致的缓存 279 | 280 | 281 | 282 | ### 缓存分类 283 | 284 | 缓存可以分为三类 285 | 286 | #### 全关联缓存(fully-associative) 287 | 288 | 这种缓存不使用page,直接通过line与下一级存储器交换数据,它的page容量等于line的容量 289 | 290 | 具有速度快、缓存粒度小、容量小、电路复杂的特征 291 | 292 | #### 直接映射(direct map) 293 | 294 | 这种缓存只有一个页。 295 | 296 | 具有速度慢、缓存粒度大、容量大、电路简单的特征 297 | 298 | #### 组关联(set-associative) 299 | 300 | 这种缓存具有多个缓存页。 301 | 302 | 这种缓存是性能和成本介于上面两种缓存中一种缓存 303 | 304 | ### 缓存策略(Cache policies) 305 | 306 | 缓存策略指何时向下一级缓存亲求数据以及何时把数据写入到下一级缓存 307 | 308 | #### Write Allocation(WA) 309 | 310 | 写操作不命中时进行缓存操作 311 | 312 | #### Read Allocation(RA) 313 | 314 | 读操作不命中时进行缓存操作 315 | 316 | #### Write-back (WB) 317 | 318 | 在缓存失效时,把脏的缓存写入下一级存储器 319 | 320 | #### Write-through (WT) 321 | 322 | 写的数据不缓存,直接写入到下一级存储器 323 | 324 | ### 内存一致性 325 | 326 | 在SOC内部有多个主控设备(CPU、MMU、DSP等),按些主控观察到的相同的内存点分类,ARM把内存一致性分为两类POC、POU 327 | 328 | #### POC(Point of Coherency) 329 | 330 | POC指所有主控看到内存一致的存储器。一般为主存储器。 331 | 332 | #### POU 333 | 334 | 指一个主控看到的内存一致的存储器。ARM一般把一级缓存分为数据缓存和指令缓存,所以一般的ARM芯片的POU指二级缓存,在没有二级缓存的芯片上就为主存储器。 335 | -------------------------------------------------------------------------------- /docs/arm64/device-tree分析.md: -------------------------------------------------------------------------------- 1 | # Device Tree分析 2 | 此文档主要用于分析Device Tree的源码格式与其二进制文件表现,此文主要参照[Devicetree Specification](http://www.devicetree.org/specifications-pdf) 3 | ## 1 DT介绍 4 | DT(Device Tree)用于描述硬件信息,是一种树形结构。DT由节点组成,每个节点有自己的属性(名字和值的键值对)和子节点(非必须,叶子节点就没有)。 5 | DT有两种形式 6 | 7 | - DTS(Device Tree Source),文本格式遍与阅读和修改 8 | 9 | - FDT(Flat Device Tree),DTS通过DTC编译产生的二进制文件 10 | 11 | - DTB(Device Tree BLOB),由多个FDT打包出的二进制文件 12 | 13 | > 此处名字比较乱,官方文档把经过DTC编译生成的二进制文件叫做DTB,而高通把多个打包的二进制文件也叫做DTB。这里暂且这样命名。 14 | 15 | 16 | DTC(Device Tree Compile)用于把DTS转换成FDT的工具 17 | 18 | ## 2 DTS(device tree source)格式 19 | ### 2.1 节点 20 | ```` 21 | [lable:]node-name[@unit-address]{ 22 | [properties definitions] 23 | [child nodes] 24 | } 25 | ```` 26 | 27 | `lable`,节点的标签,可以被其他节点引用 28 | `node-name`,节点名,1-31个字符,正则表达式`[a-zA-Z][a-zA-Z0-9,._+-]*` 29 | `unit-address`,设备的地址必须与属性reg匹配 30 | `properties definitions`,为属性定义 31 | `child nodes`,为子节点,子节点必须在塑性定义之后 32 | ### 2.2 属性 33 | 属性定义有两种格式 34 | ``` 35 | [label:] property-name = value;//此格式属性有值 36 | [label:] property-name;//此格式属性没有值 37 | ``` 38 | `label`,塑性标签,可以被其他节点引用 39 | `property-name`,属性名,1-31个字符,正则表达式[a-zA-Z0-9,._+?#]+ 40 | `value`,属性值,具有多种格式 41 | 42 | - 字符串,使用引号(")包围起来的字符序列 43 | - 字符串列表,用逗号(,)分隔的字符串 44 | - 数组(cells),用尖括号(<>)包围起来的数值(C语言32比特的整数)序列,用空格分隔。要表示64比特整数时可以使用连续的两个整数,第一个为高32比特第二个为低32比特。当数组只有一个32比特值时可以简写成一个数字不加尖括号。 45 | - 字节序列,用放括号([])包围起来的数字,数字必须是16进制,可以在每个字节之间加空格,也可以不加。如[00 01 02]和[000102]是一样的 46 | - 混合值,使用逗号分隔的多值(字符串、数组、字节序列中的一种或几种) 47 | 48 | ### 2.3 注释 49 | 可以使用C语言风格的注释,如下 50 | 51 | 格式1: 52 | > //单行注释 53 | 54 | 格式2: 55 | > /\* 56 | > 多行注释 57 | > \*/ 58 | 59 | ### 2.4 文件格式 60 | ``` 61 | /dts-v1/; 62 | [memory reservations] 63 | / { 64 | [property definitions] 65 | [child nodes] 66 | }; 67 | ``` 68 | `/dts-v1/`,标识dts的版本,如果没有dtc将认为是过时的版本(版本号0) 69 | `memory reservations`,用于表示保留的内存(不可用),格式`/memreserve/
`其中`
` ``为C语言风格的64比特的整数 70 | `/{}`为根节点 71 | ### 2.5 节点全路径 72 | 从根节点开始到目标节点路径上的所有节点,用'/'符号分隔的字符串,类似文件路径 73 | 74 | 在属性引用节点时就有以下一些方式 75 | ``` 76 | <&lable> 77 | <&{fullpath}> 78 | &lable 79 | &{fullpath} 80 | ``` 81 | ### 2.6 标准属性 82 | #### 2.6.1 compatible 83 | 值,字符串列表 84 | 标识兼容的驱动,操作系统通过此数字确定适用的驱动程序 85 | #### 2.6.2 model 86 | 值,字符串列表 87 | 推荐用于记录设备的名称,格式"公司注册码:设备名" 88 | #### 2.6.3 phandle 89 | 值, 90 | 用于标记节点,必须是唯一的值,可以在其他节点中被引用 91 | #### 2.6.4 status 92 | 值,字符串 93 | 94 | - "okay" 标识设备可用 95 | -"disabled" 标识设备不可用(预留或者硬件未连接) 96 | - "fail" 设备检查到错误,并且设备不能被操作在修复之前 97 | - "fail-sss" 设备检查到错误,并且设备不能被操作在修复之前。sss和具体设备相关,标识错位的原因。 98 | 99 | #### 2.6.5 \#addess-cells \#size-cells 100 | 此属性对子节点的的reg有影响,#address-cells表示address使用几个32比特,#size-cells表示size使用几个32比特 101 | 默认#address-cells=2、#size-cells=1 102 | 103 | #### 2.6.6 reg 104 | 此属性用于表示当前设备的地址 105 | 格式 106 | ``` 107 | reg
108 | ``` 109 | `address`、`size`大小和#addess-cells、#size-cells相关 110 | 111 | #### 2.6.7 ranges 112 | 格式 113 | ``` 114 | ranges 115 | ``` 116 | 此塑性性标记子节点和父节点的地址映射关系,列: 117 | `ranges <0x0 0x100 0x200>` 118 | 表示子设备地址0x0-0x200映射到父总线0x100-0x300 119 | #### 2.6.8 dma-ranges 120 | 类似与ranges,表示dma设备访问内存,表示dma内存和主内存的映射关系 121 | 2.6.10 name 122 | 值,字符串 123 | 表示节点的名字,此属性以弃用。 124 | #### 2.6.9 device_type 125 | 值,字符串 126 | 标识设备类型,此属性以弃用。只在CPU和memory节点中使用。 127 | ### 2.7 标准节点 128 | #### 2.7.1 根节点 129 | 根节点无名字/{} 130 | 根节点必须具备以下塑性 131 | \#address-cells 132 | \#size-cells 133 | model 134 | compatible 135 | #### 2.7.1 /aliases 136 | 此节点用于定义节点别名 137 | 别名长度1-31个字符,正则[0-9a-z-]* 138 | ``` 139 | aliases { 140 | serial0 = "/simple-bus@fe000000/serial@llc500"; 141 | ethernet0 = "/simple-bus@fe000000/ethernet@31c000"; 142 | } 143 | ``` 144 | #### 2.7.2 /memory 145 | 此节点用于指定内存信息 146 | 其中device-type必须指定为"memory" 147 | 其中reg指定内存范围 148 | #### 2.7.3 /cpus /cpu 149 | 用与配置处理器信息 150 | #### 2.7.4 /chosen 151 | 此节点不是必须的 152 | 此节点有三个属性 153 | bootargs 值为字符串,命令行参数 154 | stdout-path 值为字符串,指定启动时的输出设备 155 | stdin-path 值为字符串,指定启动时的输入设备 156 | 157 | ## 3 FDT(Flat Device Tree) 158 | FDT是DTS经过DTC处理得到的文件,是DTS的二进制格式,FDT库在kernel源码中的位置(scripts/dtc/libfdt),FDT库在little kernel源码中的位置(lib/libfdt)。理解FDT可以借助fdtdump工具,ubuntu下安装:`sudo apt install device-tree-compiler`,FDT使用大端格式,FDT文件格式如下: 159 | ``` 160 | --------------------------- 161 | fdt_header 162 | magic 163 | totalsize 164 | off_dt_struct 165 | off_dt_strings 166 | off_mem_rsvmap 167 | version 168 | last_comp_version 169 | boot_cpuid_phys 170 | size_dt_strings 171 | size_dt_struct 172 | --------------------------- 173 | (free spaces) 174 | --------------------------- 175 | memory reservation block 176 | --------------------------- 177 | (free spaces) 178 | --------------------------- 179 | structure block 180 | --------------------------- 181 | (free spaces) 182 | --------------------------- 183 | strings block 184 | --------------------------- 185 | (free spaces) 186 | --------------------------- 187 | ``` 188 | fdt_header中主要保存了memory reservation block、structure block、strings block的位置信息,以及structure block、strings block的大小信息 189 | 190 | 其中free spaces不一定存在,只是用来处理内存对齐的 191 | 192 | memory reservation block对应dts根节点之前定义的memory reservations,其中以两个64比特的数据保存(对应address和size),以一个size为0的结束 193 | 194 | ```c 195 | struct fdt_reserve_entry { 196 | uint64_t address; /* 保留内存的起始地址 */ 197 | uint64_t size; /* 保留内存的大小 */ 198 | };/* 此数据块以一个size为0的结构结束 */ 199 | ``` 200 | strings block字符串表,用于存储字符串 201 | structure block是文件的主体,记节点信息 202 | 203 | 从内存中读取一个32比特数,确定当前记录类型tag(节点、属性),有以下类型的记录 204 | ```c 205 | #define FDT_BEGIN_NODE 0x1 /* 节点头记录,后面紧接着节点的名字 */ 206 | #define FDT_END_NODE 0x2 /* 节点尾记录 */ 207 | #define FDT_PROP 0x3 /* 属性记录,后面接着属性值的长度、属性名在字符串表中的偏移、属性 */ 208 | #define FDT_NOP 0x4 /* 空的记录 */ 209 | #define FDT_END 0x9 /* 记录结束标记 */ 210 | ``` 211 | 其中FDT_BEGIN_NODE、FDT_END_NODE、FDT_PROP为常用记录,FDT_NOP、FDT_END在我分析的DTB中没有出现 212 | 213 | FDT_BEGIN_NODE:节点头记录,后面紧接着节点的名字,如果名字长度不足4的倍数在后面补0x00,对应C语言结构体。 214 | ```c 215 | struct fdt_node_header { 216 | uint32_t tag; /* 节点头固定为FDT_BEGIN_NODE */ 217 | char name[0]; /* 节点名字,长度不足4的倍数会在后面补0 */ 218 | }; 219 | ``` 220 | 节点名没有保存到字符串表中,因为节点名是唯一的,保存到字符串表中不能减少文件的大小 221 | FDT_END_NODE: 节点尾记录,和FDT_BEGIN_NODE成对出现,无任何数据 222 | FDT_PROP属性记录:用于记录属性信息,对应结构体如下 223 | ```c 224 | struct fdt_property { 225 | uint32_t tag; /* 固定为FDT_PROP */ 226 | uint32_t len; /* 属性值的长度,属性值的类型和属性名有关 */ 227 | uint32_t nameoff; /* 属性名在字符串表中的偏移量 */ 228 | char data[0]; /* 属性值,长度不为4的倍数会在后面补0 */ 229 | }; 230 | ``` 231 | 其中属性名保存在字符串表中,因为同一个属性会在多个节点中出现,保存到字符串表中可以大量减少文件大小 232 | 233 | nameoff为字符串在字符串表中的偏移量,off_dt_strings + nameoff为对应字符串在文件中的偏移量 234 | 235 | 数据类型和属性名相关,在这里不作类型处理,全部保存在字符数组中,需要解析程序自行处理 236 | 237 | ## 4 DTB(Device Tree BLOB) 238 | 239 | DTB由多个FDT打包得到,DTB文件格式如下 240 | ``` 241 | -------------------------------------------------------------- 242 | dt_table 243 | magic 244 | version 245 | num_entries 246 | -------------------------------------------------------------- 247 | dt_entry(entry 0) 248 | platform_id 249 | variant_id 250 | board_hw_subtype 251 | soc_rev 252 | pmic_rev[4] 253 | offset 254 | size 255 | -------------------------------------------------------------- 256 | dt_entry(entry 1) 257 | -------------------------------------------------------------- 258 | dt_entry(entry 2) 259 | -------------------------------------------------------------- 260 | ... 261 | -------------------------------------------------------------- 262 | dt_entry(entry n, n == dt_table.num_entries) 263 | -------------------------------------------------------------- 264 | FDT 0 265 | -------------------------------------------------------------- 266 | FDT 1 267 | -------------------------------------------------------------- 268 | FDT 2 269 | -------------------------------------------------------------- 270 | ... 271 | -------------------------------------------------------------- 272 | FDT n(n == dt_table.num_entries) 273 | -------------------------------------------------------------- 274 | ``` 275 | dt_table主要记录有多个dt_entry 276 | dt_entry记录fdt在文件中的位置(offset)和大小(size),以及fdt对应的硬件信息(platform_id、variant_id、board_hw_subtype、soc_rev) 277 | 278 | -------------------------------------------------------------------------------- /docs/arm64/dragonboard410c/Device-Tree与linux驱动模型.md: -------------------------------------------------------------------------------- 1 | # 0 概述 2 | 3 | 本文旨在分析Device Tree在linux驱动模型中的作用,以及系统内核启动后Device Tree的处理过程。此文的分析基于dragonboard410c的官方内核源代码[下载](https://git.linaro.org/landing-teams/working/qualcomm/kernel.git/snapshot/kernel-debian-qcom-dragonboard410c-16.09.tar.gz)。 4 | 5 | # 1 内核实现的类似面向对象的方法 6 | 7 | linux中驱动模型有很多层对象结构,内核使用一种特别的方式实现。 8 | 9 | 继承:通过在子类中嵌入父类的结构体实现(可以实现C++多重继承,有多个父类) 10 | 11 | 虚函数:子类可以在初始化是重写虚函数(给父类的函数指针赋值),这样实现多态(框架) 12 | 13 | 通过父类指针访问子类(虚函数传进的是父类指针,要做具体类的操作需要类型转换),通过宏container_of实现。container_of具体实现如下 14 | 15 | ```c 16 | #define container_of(ptr,type,member) \ 17 | (type*)((char*)ptr - (size_t)(&(((type*)0)->member))) 18 | //ptr为父类的指针 19 | //type为子类的类型 20 | //member为父类在子类中的成员名 21 | ``` 22 | 23 | # 2 内核扩展方式与内核模块 24 | 25 | 内核通过一些结构体,以及一些框架代码来实现内核功能扩展。要扩展内核的功能,只需要在内核初始化过程中调用一些函数(bus_register、device_register、driver_register...)。在这些函数中把新定义的结构体加到系统结构中(数组、链表等)。系统有两种方式扩展功能,编译进内核,编译成模块。 26 | 27 | ## 2.1 编译进内核 28 | 29 | 系统定义了一些列的宏,这些宏把函数的指针放到指定的段,便于程序访问 30 | 31 | ```c 32 | #define early_initcall(fn) __define_initcall(fn, early) 33 | #define pure_initcall(fn) __define_initcall(fn, 0) 34 | #define core_initcall(fn) __define_initcall(fn, 1) 35 | #define core_initcall_sync(fn) __define_initcall(fn, 1s) 36 | #define postcore_initcall(fn) __define_initcall(fn, 2) 37 | #define postcore_initcall_sync(fn) __define_initcall(fn, 2s) 38 | #define arch_initcall(fn) __define_initcall(fn, 3) 39 | #define arch_initcall_sync(fn) __define_initcall(fn, 3s) 40 | #define subsys_initcall(fn) __define_initcall(fn, 4) 41 | #define subsys_initcall_sync(fn) __define_initcall(fn, 4s) 42 | #define fs_initcall(fn) __define_initcall(fn, 5) 43 | #define fs_initcall_sync(fn) __define_initcall(fn, 5s) 44 | #define rootfs_initcall(fn) __define_initcall(fn, rootfs) 45 | #define device_initcall(fn) __define_initcall(fn, 6) 46 | #define device_initcall_sync(fn) __define_initcall(fn, 6s) 47 | #define late_initcall(fn) __define_initcall(fn, 7) 48 | #define late_initcall_sync(fn) __define_initcall(fn, 7s) 49 | ``` 50 | 51 | 这些宏都通过__define_initcall宏实现 52 | 53 | ```c 54 | #define __define_initcall(fn, id) \ 55 | static initcall_t __initcall_##fn##id __used \ 56 | __attribute__((__section__(".initcall" #id ".init"))) = fn; \ 57 | LTO_REFERENCE_INITCALL(__initcall_##fn##id) 58 | ``` 59 | 60 | 此宏定义了一个函数指针放在固定名字的段中,配合链接脚本,可以访问到对应的函数指针。 61 | 62 | 其中initcall_t是函数指针类型,`typedef int (*initcall_t)(void)` 63 | 64 | __used是防止链接器警告 65 | 66 | ```c 67 | #if GCC_VERSION < 30300 68 | #define __used __attribute__((__unused__)) 69 | #else 70 | #define __used __attribute__((__used__)) 71 | #endif 72 | ``` 73 | 74 | LTO_REFERENCE_INITCALL,用于防止链接器优化,生成一个静态函数并使用指针 75 | 76 | ```c 77 | #ifdef CONFIG_LTO 78 | #define LTO_REFERENCE_INITCALL(x) \ 79 | ; /* yes this is needed */ \ 80 | static __used __exit void *reference_##x(void) \ 81 | { \ 82 | return &x; \ 83 | } 84 | #else 85 | #define LTO_REFERENCE_INITCALL(x) 86 | #endif 87 | ``` 88 | 89 | 链接脚本处理与C程序访问,为了便于链接脚本生成,在include/asm-generic/vmlinux.lds.h头文件中定义了大量的宏(默认对齐、段保留丢弃宏以及各种段)。其中定义了INIT_CALLS,用于存放上面声明的函数指针 90 | 91 | ```c 92 | #define INIT_CALLS \ 93 | VMLINUX_SYMBOL(__initcall_start) = .; \ 94 | *(.initcallearly.init) \ 95 | INIT_CALLS_LEVEL(0) \ 96 | INIT_CALLS_LEVEL(1) \ 97 | INIT_CALLS_LEVEL(2) \ 98 | INIT_CALLS_LEVEL(3) \ 99 | INIT_CALLS_LEVEL(4) \ 100 | INIT_CALLS_LEVEL(5) \ 101 | INIT_CALLS_LEVEL(rootfs) \ 102 | INIT_CALLS_LEVEL(6) \ 103 | INIT_CALLS_LEVEL(7) \ 104 | VMLINUX_SYMBOL(__initcall_end) = .; 105 | #define INIT_CALLS_LEVEL(level) \ 106 | VMLINUX_SYMBOL(__initcall##level##_start) = .; \ 107 | *(.initcall##level##.init) \ 108 | *(.initcall##level##s.init) \ 109 | ``` 110 | 111 | 上面的两个宏生成了一个段,以符号\_\_initcall\_start开头,以符号\_\_initcall\_end结尾。中间嵌插符号\_\_initcall0\_start \_\_initcall1\_start \_\_initcall2\_start \.\.\. \_\_initcall7\_start。并且\_\_initcall\_start为\.initcallearly\.init段的开始符号,\_\_initcall0\_start为\.initcall0\.init \.initcall0s\.init段的开始符号,\_\_initcall1\_start为\.initcall1\.init \.initcall1s\.init段的开始符号,\_\_initcall2\_start为\.initcall2\.init \.initcall2s\.init段的开始符号\.\.\.\_\_initcall7\_start为\.initcall7\.init \.initcall7s\.init段的开始符号。 112 | 113 | 在init/main.c中声明 114 | 115 | ```c 116 | extern initcall_t __initcall_start[]; 117 | extern initcall_t __initcall0_start[]; 118 | extern initcall_t __initcall1_start[]; 119 | extern initcall_t __initcall2_start[]; 120 | extern initcall_t __initcall3_start[]; 121 | extern initcall_t __initcall4_start[]; 122 | extern initcall_t __initcall5_start[]; 123 | extern initcall_t __initcall6_start[]; 124 | extern initcall_t __initcall7_start[]; 125 | extern initcall_t __initcall_end[]; 126 | static initcall_t *initcall_levels[] __initdata = { 127 | __initcall0_start, 128 | __initcall1_start, 129 | __initcall2_start, 130 | __initcall3_start, 131 | __initcall4_start, 132 | __initcall5_start, 133 | __initcall6_start, 134 | __initcall7_start, 135 | __initcall_end, 136 | }; 137 | ``` 138 | 通过do\_pre\_smp\_initcalls、do\_initcalls函数完才初始化工作。 139 | 140 | ```c 141 | static void __init do_pre_smp_initcalls(void); 142 | static void __init do_initcalls(void); 143 | ``` 144 | 145 | ## 2.2 编译成模块 146 | 147 | 一个模块需要两个宏 148 | 149 | module\_init(fn) 把函数指针放到段\.initcall7\.init中 150 | 151 | module\_exit(fn) 把函数指针放到段\.exitcall\.exit中 152 | 153 | 模块在加载时调用段.initcall7.init中的函数 154 | 155 | 模块在移除时调用段.exitcall.exit中的函数 156 | 157 | 其他的驱动也定义了一些宏,处理这些问题比如platform 158 | 159 | ```c 160 | #define module_platform_driver(__platform_driver) \ 161 | module_driver(__platform_driver, platform_driver_register, \ 162 | platform_driver_unregister) 163 | #define module_driver(__driver, __register, __unregister, ...) \ 164 | static int __init __driver##_init(void) \ 165 | { \ 166 | return __register(&(__driver) , ##__VA_ARGS__); \ 167 | } \ 168 | module_init(__driver##_init); \ 169 | static void __exit __driver##_exit(void) \ 170 | { \ 171 | __unregister(&(__driver) , ##__VA_ARGS__); \ 172 | } \ 173 | module_exit(__driver##_exit); 174 | ``` 175 | 176 | __platform_driver是要注册的驱动对象(结构体) 177 | 178 | platform_driver_register platform驱动注册函数 179 | 180 | platform_driver_unregister platform反驱动注册函数 181 | 182 | 这样就可以在驱动加载时,把结构体添加到系统的链表中,便于框架处理 183 | 184 | # 3 sysfs 185 | 186 | sysfs是linux内核模块在用户空间的映射,sysfs通过ramfs实现,与kobject密切相关。kobject与kset助成树形结构,对应目录结构。kobj_type对应目录中的文件。 187 | 188 | ## 3.1 kobject 189 | 190 | kobject是驱动模型的关键,提供了以下功能 191 | 192 | - 实现一个树形结构 193 | - 对象上锁 194 | - 引用计数(用于生存周期计算,用于释放内存) 195 | - 在用户空间的表示 196 | 197 | 以下是kobject结构体 198 | 199 | ```c 200 | struct kobject { 201 | const char *name; //名字对应在/sys目录中的名字 202 | struct list_head entry; //一个双向链表,用于链接对象 203 | struct kobject *parent; //指向父指针,对应所在的目录 204 | struct kset *kset; //集合,当前对象所在的集合 205 | //包含一些属性,以及属性操作相关的函数,用于释放kobject对象的函数 206 | struct kobj_type *ktype; 207 | struct kernfs_node *sd; //内核文件系统相关 208 | struct kref kref; //引用计数 209 | #ifdef CONFIG_DEBUG_KOBJECT_RELEASE 210 | struct delayed_work release; 211 | #endif 212 | unsigned int state_initialized:1; //初始化标记 213 | unsigned int state_in_sysfs:1; //指示为sysfs对象 214 | unsigned int state_add_uevent_sent:1; 215 | unsigned int state_remove_uevent_sent:1; 216 | unsigned int uevent_suppress:1; 217 | }; 218 | ``` 219 | 220 | 系统为kobject对象提供了,设置名字、初始化、添加、移除、获取路径相关的方法 221 | 222 | ## 3.2 kset 223 | 224 | kset结构体如下 225 | 226 | ```c 227 | struct kset{ 228 | struct list_head list;//与集合元素构成链表用于访问集合中的元素 229 | spinlock_t list_lock;//锁 230 | struct kobject kobj;//kobject中parent指向此处 231 | const struct kset_uevent_ops *uevent_ops;//消息处理回调函数 232 | }; 233 | ``` 234 | 235 | 通过关联分析kobject、kset,可以知道kobject和kset一起组成了一个树形结构 236 | 237 | ## 3.3 kobj_type 238 | 239 | kobj_type结构如下 240 | 241 | ```c 242 | struct kobj_type{ 243 | //用于释放kobject内存 244 | void (*release)(struct kobject *kobj); 245 | //sysfs_ops主要包含两个函数分别用于属性显示和修改 246 | const struct sysfs_ops *sysfs_ops; 247 | //属性对应sysfs中的文件,这个结构体中有文件名和权限 248 | struct attribute **default_attrs; 249 | const struct kobj_ns_type_operations * 250 | (*child_ns_type)(struct kobject *kobj); 251 | const void *(*namespace)(struct kobject *kobj); 252 | }; 253 | struct attribute { 254 | const char *name; //属性名,对应sysfs中的文件名 255 | umode_t mode; //访问权限 256 | }; 257 | struct sysfs_ops { 258 | ssize_t (*show)(struct kobject *, 259 | struct attribute *, 260 | char *); 261 | ssize_t (*store)(struct kobject *, 262 | struct attribute *, 263 | const char *, size_t); 264 | }; 265 | ``` 266 | 267 | show/store用于显示属性和写属性 268 | 269 | ## 3.4 kref 270 | 271 | 内核中的引用计算器,用于监视对象的生命周期,在适当的时候释放内存销毁对象。结构如下: 272 | ```c 273 | struct kref { 274 | atomic_t refcount; 275 | }; 276 | ``` 277 | 278 | atomic_t是内核维护的原子量,即一个整数,对多线程是安全的 279 | 280 | 它主要操作有init(refcount=1) 281 | 282 | 它主要操作有put(refcount--) 283 | 284 | 它主要操作有gut(refcount++) 285 | 286 | 还可以在put时,查看refcount==0时调用释放内存的函数 287 | 288 | # 4 驱动架构 289 | 290 | 驱动框架实现主要位于drivers/base中 291 | 292 | 框架有关的结构体 293 | 294 | - bus_type 295 | 296 | - device 297 | 298 | - device_driver 299 | 300 | 301 | ## 4.1 bus_type 302 | 303 | 定义如下 304 | 305 | ```c 306 | struct bus_type { 307 | const char *name;//总线的名字 308 | const char *dev_name;//对应设备的名字 309 | struct device *dev_root;//总线设备 310 | struct device_attribute *dev_attrs; //总线的设备的属性 311 | const struct attribute_group **bus_groups;//链接到总线的总线属性 312 | const struct attribute_group **dev_groups;//链接到总线的设备属性 313 | const struct attribute_group **drv_groups;//链接到总线的驱动属性 314 | //用于发现设备或者注册新驱动时,匹配设备和驱动 315 | int (*match)(struct device *dev, struct device_driver *drv); 316 | //在发送热插拔事件消息到用户空间之前添加环境变量 317 | int (*uevent)(struct device *dev, struct kobj_uevent_env *env); 318 | //用于确认设备可以链接到总线并进行初始化 319 | int (*probe)(struct device *dev); 320 | //移除设备相关操作 321 | int (*remove)(struct device *dev); 322 | //断点需要的操作 323 | void (*shutdown)(struct device *dev); 324 | 325 | //可热插拔相关 326 | int (*online)(struct device *dev); 327 | int (*offline)(struct device *dev); 328 | 329 | //系统挂起恢复时调用 330 | int (*suspend)(struct device *dev, pm_message_t state); 331 | int (*resume)(struct device *dev); 332 | 333 | const struct dev_pm_ops *pm;//电源相关操作 334 | const struct iommu_ops *iommu_ops;//mmu相关操作 335 | 336 | //总线私有数据,里面包含devices_kset/drivers_kset 337 | struct subsys_private *p; 338 | struct lock_class_key lock_key; 339 | }; 340 | ``` 341 | 342 | 在有新的设备注册时,系统会调用match函数遍历所有连接到当前总线的驱动。 343 | 344 | 在有新的驱动注册时,系统会调用match函数遍历所有连接到当前总线的设备。 345 | 346 | match只执行和总线相关的匹配,probe执行设备相关的匹配,并进行初始化操作。match时会设置dev的driver指针,所以probe不需要传入drv指针。 347 | 348 | 一般不对bus_type进行扩展,在系统中bus_type一般以静态变量形式存在 349 | 350 | 在定义好bus_type结构体后需要调用bus_register,把驱动注册到驱动核心。bus_register负责维护对应的sys,以及把bus_type添加到bus_kset。对应的bus_unregister负责从系统反注册bus_type。 351 | 352 | ## 4.2 device 353 | 354 | 定义如下 355 | 356 | ```c 357 | struct device { 358 | struct device *parent;//父设备(总线设备) 359 | struct device_private *p;//私有数据 360 | struct kobject kobj;//用于处理sysfs以及引用计数 361 | const char *init_name;//设备名 362 | const struct device_type *type;//设备类型 363 | struct mutex mutex; //互斥锁 364 | struct bus_type *bus;//设备所在的总线 365 | struct device_driver *driver; //驱动 366 | void *platform_data; //platform总线相关的数据 367 | void *driver_data; //驱动相关的数据 368 | struct dev_pm_info power; 369 | struct dev_pm_domain *pm_domain; 370 | 371 | #ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN 372 | struct irq_domain *msi_domain; 373 | #endif 374 | #ifdef CONFIG_PINCTRL 375 | struct dev_pin_info *pins; 376 | #endif 377 | #ifdef CONFIG_GENERIC_MSI_IRQ 378 | struct list_head msi_list; 379 | #endif 380 | 381 | #ifdef CONFIG_NUMA 382 | int numa_node; /* NUMA node this device is close to */ 383 | #endif 384 | u64 *dma_mask; /* dma mask (if dma'able device) */ 385 | u64 coherent_dma_mask;/* Like dma_mask, but for 386 | alloc_coherent mappings as 387 | not all hardware supports 388 | 64 bit addresses for consistent 389 | allocations such descriptors. */ 390 | unsigned long dma_pfn_offset; 391 | 392 | struct device_dma_parameters *dma_parms; 393 | 394 | struct list_head dma_pools; /* dma pools (if dma'ble) */ 395 | 396 | struct dma_coherent_mem *dma_mem; /* internal for coherent mem 397 | override */ 398 | #ifdef CONFIG_DMA_CMA 399 | struct cma *cma_area; /* contiguous memory area for dma 400 | allocations */ 401 | #endif 402 | /* arch specific additions */ 403 | struct dev_archdata archdata; 404 | 405 | struct device_node *of_node; /* associated device tree node */ 406 | struct fwnode_handle *fwnode; /* firmware device node */ 407 | 408 | dev_t devt; /* dev_t, creates the sysfs "dev" */ 409 | u32 id; /* device instance */ 410 | 411 | spinlock_t devres_lock; 412 | struct list_head devres_head; 413 | 414 | struct klist_node knode_class; 415 | struct class *class; 416 | const struct attribute_group **groups; /* optional groups */ 417 | 418 | void (*release)(struct device *dev); 419 | struct iommu_group *iommu_group; 420 | 421 | bool offline_disabled:1; 422 | bool offline:1; 423 | }; 424 | ``` 425 | 426 | device中保存了各自驱动操作相关的资源,一般需要对device进行扩展 427 | 428 | device_register注册设备,把对应的结构添加到全局变量devices_kset中,并且把device加到对应的总线并进行驱动匹配 429 | 430 | 一般需要对device进行继承,并重写device_register。在其在加入设备特定操作,并调用父类的device_register进行通用操作。 431 | 432 | ## 4.3 device_driver 433 | 434 | 定义如下 435 | 436 | ```c 437 | struct device_driver { 438 | const char *name;//驱动名 439 | struct bus_type *bus;//要挂载的总线 440 | struct module *owner;//模块 441 | const char *mod_name;/* used for built-in modules */ 442 | bool suppress_bind_attrs;/* disables bind/unbind via sysfs */ 443 | enum probe_type probe_type; 444 | 445 | const struct of_device_id *of_match_table;//用于匹配设备的表 446 | const struct acpi_device_id *acpi_match_table;//用于acpi匹配的表 447 | 448 | int (*probe) (struct device *dev);//确认设备是否匹配并初始化 449 | int (*remove) (struct device *dev);//移除设备时调用 450 | void (*shutdown) (struct device *dev);//设备断电时调用 451 | int (*suspend) (struct device *dev, pm_message_t state);//设备挂起时调用 452 | int (*resume) (struct device *dev);//设备从挂起恢复时调用 453 | const struct attribute_group **groups;//驱动属性 454 | 455 | const struct dev_pm_ops *pm;//电源相关操作 456 | 457 | struct driver_private *p;//私有数据,其在有驱动的所有的设备列表 458 | }; 459 | ``` 460 | 461 | 驱动程序中保存了 462 | 463 | - 驱动设备相关的总线、模块、驱动名字 464 | 465 | - 用于匹配设备的信息(of_match_table与device-tree相关) 466 | 467 | - 以及驱动相关的基本操作(匹配初始化、移除、挂起、恢复) 468 | 469 | 一般具体的驱动需要继承device_driver,并实现自己的注册函数,在注册函数中调用driver_register函数 470 | 471 | driver_register会维护sysfs以及负责把设备挂载到驱动等基础操作 472 | 473 | # 5 驱动与Device-Tree 474 | 475 | linux通过三个基本的类(bus_type/device/device_driver)来描述驱动相关信息 476 | 477 | 并在注册阶段完成sysfs的维护,以及建立bus_type/device/device_driver关联 478 | 479 | 建立关联时涉及三个匹配 480 | ```c++ 481 | bus_type.match(struct device *dev, struct device_driver *drv); 482 | bus_type.probe(struct device *dev); 483 | device_driver.probe(struct device *dev); 484 | ``` 485 | bus_type.match在设备或驱动注册到系统时被调用,完成设备和驱动的匹配,并修改dev的device_driver指针。 486 | 487 | bus_type.match没有实现,默认认为设备和驱动是匹配的,并调用bus_type.probe进行设备匹配和初始化。 488 | 489 | 如果bus_type.probe没有实现,就调用device_driver.probe进行设备匹配和初始化。 490 | 491 | 匹配操作要使用device_driver中of_match_table。of_match_table保存了Device-Tree的compatible字段。具体定义如下: 492 | 493 | ```c 494 | struct of_device_id { 495 | char name[32];//要匹配的设备的名字 496 | char type[32];//要匹配的设备的类型 497 | char compatible[128];//要匹配的设备的device-tree的compatible字段 498 | const void *data;// 499 | }; 500 | ``` 501 | 502 | 具体匹配过程和具体设备相关,使用不使用Device-Tree和具体驱动相关。 503 | 504 | 在device_driver中还有一个值得关注的对象acpi_match_table,此对象和of_device_id类似,用于acpi总线的匹配。 505 | 506 | # 6 ARMv8总线自举过程 507 | 508 | 根据ARMv8启动协议(内核代码位置Documentation/arm64/booting.txt),可知内核通过x0传递Device-Tree的首地在给内核,x1-x3保留并且必须为0。 509 | 510 | arch/arm64/kernel/head.S为内核入口代码,其中主要关注Device-Tree保存到__fdt_pointer相关的代码 511 | 512 | ```asm 513 | /* 514 | * Kernel startup entry point. 515 | * --------------------------- 516 | * 517 | * The requirements are: 518 | * MMU = off, D-cache = off, I-cache = on or off, 519 | * x0 = physical address to the FDT blob. 520 | * 521 | * This code is mostly position independent so you call this at 522 | * __pa(PAGE_OFFSET + TEXT_OFFSET). 523 | * 524 | * Note that the callee-saved registers are used for storing variables 525 | * that are useful before the MMU is enabled. The allocations are described 526 | * in the entry routines. 527 | */ 528 | __HEAD //在linux/init.h中定义 529 | //#define __HEAD .section ".head.text","ax" 530 | //结合链接脚本,这个段就是输出段的头部 531 | 532 | /* 533 | * DO NOT MODIFY. Image header expected by Linux boot-loaders. 534 | */ 535 | #ifdef CONFIG_EFI 536 | efi_head: 537 | /* 538 | * This add instruction has no meaningful effect except that 539 | * its opcode forms the magic "MZ" signature required by UEFI. 540 | */ 541 | add x13, x18, #0x16// 此条指令无效,用于伪装成PE的DOS头的signature “MZ” 542 | b stext// 跳转到主程序 543 | /* 544 | ……此处主要用于模拟PE文件 545 | */ 546 | ENTRY(stext) 547 | bl preserve_boot_args// 保存bootloader传递过来的参数(X0 ... X3)到boot_args 548 | //根据Documentation/arm64/booting.txt描述X0保存dtb地址,其他几个寄存器保留未用 549 | //x0被暂时保存到x21 550 | bl el2_setup// Drop to EL1, w20=cpu_boot_mode 551 | //异常等级降到EL1 552 | //如果在原来在EL2需要开放一些寄存器给EL1访问,记录原本的异常等级到W20 553 | adrp x24, __PHYS_OFFSET 554 | bl set_cpu_boot_mode_flag 555 | bl __create_page_tables // x25=TTBR0, x26=TTBR1 556 | /* 557 | * The following calls CPU setup code, see arch/arm64/mm/proc.S for 558 | * details. 559 | * On return, the CPU will be ready for the MMU to be turned on and 560 | * the TCR will have been set. 561 | */ 562 | ldr x27, =__mmap_switched// address to jump to after 563 | // MMU has been enabled 564 | adr_l lr, __enable_mmu// return (PIC) address 565 | b __cpu_setup// initialise processor 566 | //函数__cpu_setup返回后进入__enable_mmu 567 | //__enable_mmu出口调用__mmap_switched 568 | ENDPROC(stext) 569 | /* 570 | * 省略部分不相关代码 571 | */ 572 | .set initial_sp, init_thread_union + THREAD_START_SP 573 | __mmap_switched: 574 | adr_l x6, __bss_start 575 | adr_l x7, __bss_stop 576 | 577 | 1: cmp x6, x7 578 | b.hs 2f 579 | str xzr, [x6], #8 // Clear BSS 580 | b 1b 581 | 2: 582 | adr_l sp, initial_sp, x4 583 | str_l x21, __fdt_pointer, x5// Save FDT pointer 584 | //这里保存x21到__fdt_pointer 585 | //x21在preserve_boot_args保存了x0的值 586 | //x0的值为Device-Tree的地址 587 | str_l x24, memstart_addr, x6 // Save PHYS_OFFSET 588 | mov x29, #0 589 | #ifdef CONFIG_KASAN 590 | bl kasan_early_init 591 | #endif 592 | b start_kernel 593 | ENDPROC(__mmap_switched) 594 | ``` 595 | 596 | init/main.c中的start_kernel为C语言部分的入口,其中调用了setup_arch。在setup_arch中对Device-Tree进行校验并展开为树的形式。 597 | 598 | ```c 599 | void __init setup_arch(char **cmdline_p) 600 | { 601 | //…… 602 | 603 | setup_machine_fdt(__fdt_pointer); 604 | //__fdt_pointer在head.S中被设置,此处设initial_boot_parmas(指向fdt),校验fdt并初始化 605 | 606 | //…… 607 | if (acpi_disabled) { 608 | unflatten_device_tree();//把fdt(flat device tree)转换为树(device_node)结构 609 | psci_dt_init(); 610 | } else { 611 | psci_acpi_init(); 612 | } 613 | //…… 614 | //此处用于校验bootloader有没有按启动协议传参数 615 | if (boot_args[1] || boot_args[2] || boot_args[3]) { 616 | pr_err("WARNING: x1-x3 nonzero in violation of boot protocol:\n" 617 | "\tx1: %016llx\n\tx2: %016llx\n\tx3: %016llx\n" 618 | "This indicates a broken bootloader or old kernel\n", 619 | boot_args[1], boot_args[2], boot_args[3]); 620 | } 621 | } 622 | ``` 623 | 624 | 设备注册通过arm64_device\_init完成。这里和前面第2章相关,最终arm64\_device\_init会在init/main.c中被调用。 625 | 626 | ```c 627 | static int __init arm64_device_init(void) 628 | { 629 | if (of_have_populated_dt()) {//判断DT有无展开(__fdt_pointer -> of_root) 630 | of_iommu_init(); 631 | //处理DT,创建Device并注册到对应的总线 632 | of_platform_populate(NULL, of_default_bus_match_table, 633 | NULL, NULL); 634 | } else if (acpi_disabled) { 635 | pr_crit("Device tree not populated\n"); 636 | } 637 | return 0; 638 | } 639 | arch_initcall_sync(arm64_device_init); 640 | ``` 641 | 642 | 设备注册主要通过of_platform_populate完才。of_platform_populate会遍历Device-Tree注册设备,注册设备的设备会挂载到platform总线。如果注册的设备是总线设备,会触发bus_register。根据的类型会自动匹配(可自举总线,如USB等)。不可自举总线会在遍历过程中进行设备的注册挂载。 -------------------------------------------------------------------------------- /docs/arm64/dragonboard410c/dragonboard410c系统安装与内核编译.md: -------------------------------------------------------------------------------- 1 | # 0 概要 2 | 3 | 此文就dragonboard410c系统安装,内核编译等问题进行描述。此文主要参考官方文档[Dragonboard 410c Installation Guide for Linux and Android](https://github.com/96boards/documentation/wiki/Dragonboard-410c-Installation-Guide-for-Linux-and-Android)编写。 4 | 5 | 6 | 7 | # 1 系统安装 8 | 9 | ## 1.1 通过SD卡安装系统 10 | 11 | ### 1.1.1 准备SD卡 12 | 13 | 1\. 下载SD卡镜像压缩文件[下载](http://builds.96boards.org/releases/dragonboard410c/linaro/debian/latest/dragonboard410c_sdcard_install_debian*.zip) 14 | 15 | 2\. 解压文件 16 | 17 | 3\. 写入SD卡,linux系统下通过`sudo dd if=db410c_sd_install_YYY.img of=/dev/XXX bs=4M oflag=sync status=noxfer`命令写入SD卡(XXX为设备名称),win下需要通过[Win32DiskImager tool](http://sourceforge.net/projects/win32diskimager/)把镜像文件烧入SD卡。 18 | 19 | ### 1.1.2 选择从SD卡启动 20 | 21 | 在dragonboard410c背后有4个一组的拨码开关,开关开假定为状态1反之为0,默认状态为0000。根据丝印SD BOOT,拨动此开关,即可选择从SD卡启动,这时状态为0100。 22 | 23 | ### 1.1.3 准备外设 24 | 25 | 要通过SD卡安装系统,需要键盘鼠标以及HDMI接口的显示器。链接好这些设备。 26 | 27 | ### 1.1.4 系统安装 28 | 开发板连接电源,等待显示器出现安装界面,根据提示操作即可。 29 | 30 | 在系统安装完后,拨码开关恢复状态0000,重新上电即可。 31 | 32 | ## 1.2 通过fastboot安装系统 33 | 34 | ### 1.2.1 安装fastboot 35 | 36 | linux通过下命令`sudo apt install android-tools-fastboot`安装 37 | 38 | win请自行百度adb工具,一般会附带fastboot 39 | 40 | ### 1.2.2 准备镜像文件 41 | 42 | 1\. 下载启动镜像压缩包[下载](http://builds.96boards.org/releases/dragonboard410c/linaro/debian/latest/boot-linaro-jessie-qcom-snapdragon-arm64*.img.gz) 43 | 44 | 2\. 下载根文件系统镜像压缩包[下载](http://builds.96boards.org/releases/dragonboard410c/linaro/debian/latest/linaro-jessie-developer-qcom-snapdragon-arm64*.img.gz) 45 | 46 | 47 | 48 | 3\. 下载后解压获取镜像文件 49 | 50 | ### 1.2.3 写入内部eMMC存储器 51 | 52 | 1\. 拨码开关拨到状态0000 53 | 54 | 2\. 按住音量-(S4)开关,上电 55 | 56 | 3\. 保持音量-(S4)按下,点击一下电源开关(S2) 57 | 58 | 4\. 松开音量-(S4) 59 | 60 | 5\. 插入micro-usb数据线,链接主机 61 | 62 | 6\. 输入`sudo fastboot devices`测试开发板是否进入fastboot(没有输出即进入fastboot失败) 63 | 64 | 7\. 输入`sudo fastboot flash boot boot-linaro-jessie-qcom-snapdragon-arm64-BUILD#.img`命令烧入启动镜像文件到boot分区 65 | 66 | 8\. 输入`sudo fastboot flash rootfs linaro-jessie-developer-qcom-snapdragon-arm64-BUILD#.img`命令烧写根文件系统镜像到rootfs分区 67 | 68 | 9\. 输入`sudo fastboot reboot`命令重启开发板 69 | 70 | 10\. 记住,要拔掉micro-usb数据线,不然启动会失败 71 | 72 | 11\. 用户名和密码都为linaro 73 | 74 | # 2 内核编译 75 | 76 | ## 2.1 获取内核 77 | 下载内核源码压缩包[链接](https://git.linaro.org/landing-teams/working/qualcomm/kernel.git),解压 78 | 79 | ## 2.2 获取交叉编译工具 80 | 下载交叉编译工具[链接](https://releases.linaro.org/components/toolchain/binaries/5.2-2015.11-2/aarch64-linux-gnu/gcc-linaro-5.2-2015.11-2-x86_64_aarch64-linux-gnu.tar.xz) 81 | 82 | ## 2.3 安装交叉编译工具 83 | 解压交叉编译工具到任意目录,设置PATH环境变量即可 84 | 85 | ## 2.4 为内核编译设置环境变量 86 | ```shell 87 | export ARCH=arm64 # 设置内核的目标平台 88 | export CROSS_COMPILE=aarch64-linux-gnu- #设置交叉编译器前缀 89 | ``` 90 | ## 2.5 内核配置 91 | 92 | 进入内核源码根目录 93 | ```shell 94 | make defconfig distro.config 95 | ``` 96 | ## 2.6 编译 97 | 98 | 通过`make -j16 Image dtbs modules KERNELRELEASE=4.4.23-linaro-lt-qcom`命令编译内核镜像、device-tree、内核模块 99 | 100 | ## 2.7 制作启动镜像文件 101 | 102 | 1\. 下载工具`git clone git://codeaurora.org/quic/kernel/skales,ubuntu下依赖libfdt-dev,需要使用sudo apt install libfdt-dev`命令安装。 103 | 104 | 2\. 下载初始化内存盘`wget http://builds.96boards.org/releases/dragonboard410c/linaro/debian/16.09/initrd.img-4.4.23-linaro-lt-qcom` 105 | 106 | 3\. 制作device-tree-blob命令` ./skales/dtbTool -o dt.img -s 2048 arch/arm64/boot/dts/qcom/` 107 | 108 | 4\. 制作启动镜像,命令 109 | 110 | ```shell 111 | 112 | ``` 113 | 114 | ## 2.8 测试内核 115 | 116 | `sudo fastboot boot boot-db410c.img` 117 | ## 2.9 替换内核 118 | 119 | `sudo fastboot falsh boot boot-db410c.img` 120 | -------------------------------------------------------------------------------- /docs/linksys-wrt1900acs-openwrt-for-daily-use.md: -------------------------------------------------------------------------------- 1 | ###Getting Started 2 | 3 | #####Introduction 4 | 5 | We got this great rouer to run OpenWrt. It has 128MB NAND Flash, 512MB RAM. We could do a lot things in this machine. 6 | 7 | #####OpenWrt Firmware 8 | 9 | Official OpenWrt support[1] for the WRT AC Series began under [Chaos Calmer](https://wiki.openwrt.org/toh/linksys/wrt1x00ac_series#stable1) [CC], with [Designated Driver](https://wiki.openwrt.org/toh/linksys/wrt1x00ac_series#development) [DD] being the current Development Branch. 10 | For daily use, we recommend the [Official OpenWrt Stable branch](https://wiki.openwrt.org/toh/linksys/wrt1x00ac_series#stable1). 11 | You can simply download `system image` and using wrt1900acs stock rom's "firmware upgrade" function to flash it. 12 | 13 | If you experiencing WiFi stability issues, please following this [instructions](https://wiki.openwrt.org/toh/linksys/wrt1x00ac_series#w8864_mwlwifi) to upgrade the wifi firmware. 14 | 15 | #####Luci interface 16 | 17 | Luci is a web ui for management. By default, the url is `http://192.168.1.1` 18 | After login, we should change the default password. And disable the password authentication and root login with password for SSH access, and add ssh-key. So we can use SSH public-key authentication only 19 | 20 | #####Routine Configuration 21 | 22 | As a wireless router, if we want to use the wifi function, we should enable it manually, because it disable by default. 23 | 24 | ###Advance use 25 | 26 | #####Integrated shadowsocks 27 | 28 | In our location, there is something \*\*Special\*\* about network connectivity. So we must take measures to overcome this kind of \*\*Special\*\*. As a temporary measure, here comes the [shadowsocks](https://github.com/shadowsocks/shadowsocks). In OpenWrt we using the [shadowsocks-libev](https://github.com/shadowsocks/shadowsocks-libev) as our choose. We can find the Shadowsocks-libev for OpenWrt in this [link](https://github.com/shadowsocks/openwrt-shadowsocks) 29 | 30 | You can build it on your own by following this [instruction](https://github.com/shadowsocks/openwrt-shadowsocks)   31 | 32 | or   33 | 34 | You can using the [prebuild binary](https://bintray.com/aa65535/opkg/shadowsocks-libev/) by Shadowsocks-libev for OpenWrt's maintainer [aa65535](https://github.com/aa65535)   35 |
36 | opkg update
37 | opkg install shadowsocks-libev_2.6.1-1_mvebu.ipk
38 | 
39 | #####Using EAP-TLS in wifi authentication 40 | 41 | Before we using EAP-TLS we must enable WPA Enterprise. After we compare different package that provide wifi support[2]   42 | 43 | We know, by default the OpenWrt Image for wrt1900acs is using wpad-mini, which don't provide the `WPA Enterprise`, so we should replace something that do provide this function. 44 | 45 |
46 | opkg update
47 | opkg remove wpad-mini   
48 | opkg install wpad
49 | 
50 | 51 | ###Reference 52 | 53 | [1] https://wiki.openwrt.org/toh/linksys/wrt_ac_series   54 | 55 | [2] https://wiki.openwrt.org/doc/uci/wireless/encryption#atheros_and_generic_mac80211_wifi   56 | -------------------------------------------------------------------------------- /docs/opentitan/tlul.md: -------------------------------------------------------------------------------- 1 | # 概要 2 | 3 | tlul是TileLink的轻量级未缓存的变体 4 | 5 | # 接口 6 | 7 | 它的接口信号定义在`hw/ip/tlul/rtl/tlul_pkg.sv`中 8 | ``` 9 | parameter ArbiterImpl = "PPC"; 10 | 11 | // 总线上的host可以向设备发送的命令 12 | typedef enum logic [2:0] { 13 | PutFullData = 3'h 0, // 写整个字 14 | PutPartialData = 3'h 1, // 写一部分,对应sram接口中的be信号 15 | Get = 3'h 4 // 读取 16 | } tl_a_op_e; 17 | 18 | // 总线上的device可以响应host的命令 19 | typedef enum logic [2:0] { 20 | AccessAck = 3'h 0, // 应答,这种应答不带数据响应,表示操作完成 21 | AccessAckData = 3'h 1 // 应答,这种应答带数据响应 22 | } tl_d_op_e; 23 | 24 | // 总线上host到device的信号 25 | typedef struct packed { 26 | logic a_valid; // 信号有效标志 27 | tl_a_op_e a_opcode; // host请求的操作码 28 | logic [2:0] a_param; // 未用 29 | logic [top_pkg::TL_SZW-1:0] a_size; // 要操作的大小 30 | logic [top_pkg::TL_AIW-1:0] a_source; 31 | logic [top_pkg::TL_AW-1:0] a_address; // 地址 32 | logic [top_pkg::TL_DBW-1:0] a_mask; // 掩码,用来标识那些字节要写,在tl_a_op_e==PutPartialData时有效 33 | logic [top_pkg::TL_DW-1:0] a_data; // 写操作时,要写的数据 34 | tl_a_user_t a_user; 35 | 36 | logic d_ready; // host就绪 37 | } tl_h2d_t; 38 | 39 | // 总线上device到host的信号 40 | typedef struct packed { 41 | logic d_valid; // 信号有效 42 | tl_d_op_e d_opcode; // device响应的操作码 43 | logic [2:0] d_param; // 未用 44 | logic [top_pkg::TL_SZW-1:0] d_size; // Bouncing back a_size 45 | logic [top_pkg::TL_AIW-1:0] d_source; 46 | logic [top_pkg::TL_DIW-1:0] d_sink; 47 | logic [top_pkg::TL_DW-1:0] d_data; // 读操作时,返回读取到的数据 48 | logic [top_pkg::TL_DUW-1:0] d_user; 49 | logic d_error; // 设备发生设备 50 | 51 | logic a_ready; // device就绪 52 | } tl_d2h_t; 53 | ``` 54 | 55 | 对于一个host设备应该有如下接口 56 | ``` 57 | output tl_h2d_t tl_o, 58 | input tl_d2h_t tl_i 59 | ``` 60 | 61 | 对于一个device设备应该有如下接口 62 | ``` 63 | input tl_h2d_t td_i 64 | output tl_d2h_t td_o, 65 | ``` 66 | 67 | 如果host和device直接链接,链接方式如下 68 | ``` 69 | tl_o ------> td_i 70 | tl_i <------ td_o 71 | ``` 72 | 73 | # fifo 74 | 75 | tlul总线有使用到队列(fifo),队列的实现在`hw/ip/prim/prim_fifo_sync.sv` `hw/ip/prim/prim_fifo_async.sv`。其中:prim\_fifo\_sync是同步队列,读写端口使用相同的时钟和复位信号;prim\_fifo\_async是异步队列,读写端口使用不同的时钟和复位信号。 76 | 77 | prim\_fifo\_sync同步队列的接口如下: 78 | ```systemverilog 79 | module prim_fifo_sync #( 80 | parameter int unsigned Width = 16, 81 | parameter bit Pass = 1'b1, // if == 1 allow requests to pass through empty FIFO 82 | parameter int unsigned Depth = 4, 83 | // derived parameter 84 | localparam int unsigned DepthWNorm = $clog2(Depth+1), 85 | localparam int unsigned DepthW = (DepthWNorm == 0) ? 1 : DepthWNorm 86 | ) ( 87 | input clk_i, // 时钟信号 88 | input rst_ni, // 复位信号 89 | // synchronous clear / flush port 90 | input clr_i, // 此信号用于清除队列内缓存的内容 91 | // write port 92 | input wvalid, // 写端口,信号有效标识 93 | output wready, // 写端口,队列就绪可以接受写操着 94 | input [Width-1:0] wdata, // 写端口,要写的数据 95 | // read port 96 | output rvalid, // 读端口,信号有效标识 97 | input rready, // 读端口,读的设备就绪队列可以输出数据 98 | output [Width-1:0] rdata, // 读端口,读取到的数据 99 | // occupancy 100 | output [DepthW-1:0] depth 101 | ); 102 | ``` 103 | 104 | prim\_fifo\_async异步队列的接口如下: 105 | ```systemverilog 106 | module prim_fifo_async #( 107 | parameter int unsigned Width = 16, 108 | parameter int unsigned Depth = 3, 109 | localparam int unsigned DepthW = $clog2(Depth+1) // derived parameter representing [0..Depth] 110 | ) ( 111 | // write port 112 | input clk_wr_i, // 写端口,时钟信号 113 | input rst_wr_ni, // 写端口,复位信号 114 | input wvalid, // 写端口,信号有效标识 115 | output wready, // 写端口,队列就绪可以接受写操作 116 | input [Width-1:0] wdata, // 写端口,要写的数据 117 | output [DepthW-1:0] wdepth, 118 | 119 | // read port 120 | input clk_rd_i, // 读端口,时钟信号 121 | input rst_rd_ni, // 读端口,复位信号 122 | output rvalid, // 读端口,信号有效标识 123 | input rready, // 读端口,读的设备准备就绪队列可以输出数据 124 | output [Width-1:0] rdata, // 读端口,读取到的数据 125 | output [DepthW-1:0] rdepth 126 | ); 127 | ``` 128 | 129 | 其中,当前tlul主要使用的是同步队列prim\_fifo\_sync。队列用来中转缓存信号,接口定义如下 130 | ```systemverilog 131 | module tlul_fifo_sync #( 132 | parameter int unsigned ReqPass = 1'b1, 133 | parameter int unsigned RspPass = 1'b1, 134 | parameter int unsigned ReqDepth = 2, 135 | parameter int unsigned RspDepth = 2, 136 | parameter int unsigned SpareReqW = 1, 137 | parameter int unsigned SpareRspW = 1 138 | ) ( 139 | input clk_i, // 时钟信号 140 | input rst_ni, // 复位信号 141 | input tlul_pkg::tl_h2d_t tl_h_i, // host接口,host到device的信号 142 | output tlul_pkg::tl_d2h_t tl_h_o, // host接口,device到host的信号 143 | output tlul_pkg::tl_h2d_t tl_d_o, // device接口,host到device的信号 144 | input tlul_pkg::tl_d2h_t tl_d_i, // device接口,device到host的信号 145 | input [SpareReqW-1:0] spare_req_i, // 缓存host请求的设备的设备号 146 | output [SpareReqW-1:0] spare_req_o, // 缓存host请求的设备的设备号 147 | input [SpareRspW-1:0] spare_rsp_i, // 缓存device响应的host请求的host编码 148 | output [SpareRspW-1:0] spare_rsp_o // 缓存device响应的host请求的host编码 149 | ); 150 | ``` 151 | 152 | 在模块内部维护以下队列 153 | ``` 154 | host device 155 | 156 | tl_h_i ----------------> tl_d_o 此队列维护host发生给设备的信号 157 | spare_req_i ----------------> spare_req_o 此信号,用于设备选择 158 | 159 | tl_h_o <---------------- tl_d_i 此队列用于维护device发生给host的响应 160 | spare_rsp_o <---------------- spare_rsp_i 此信号,用于标识响应的host的编号 161 | ``` 162 | 163 | # 总线逻辑tlul_socket_1n 164 | 165 | 此模块创造了一个tlul总线,一个host多个device。接口如下 166 | ```systemverilog 167 | module tlul_socket_1n #( 168 | parameter int unsigned N = 4, // 设备个数 169 | parameter bit HReqPass = 1'b1, 170 | parameter bit HRspPass = 1'b1, 171 | parameter bit [N-1:0] DReqPass = {N{1'b1}}, 172 | parameter bit [N-1:0] DRspPass = {N{1'b1}}, 173 | parameter bit [3:0] HReqDepth = 4'h2, 174 | parameter bit [3:0] HRspDepth = 4'h2, 175 | parameter bit [N*4-1:0] DReqDepth = {N{4'h2}}, 176 | parameter bit [N*4-1:0] DRspDepth = {N{4'h2}}, 177 | localparam int unsigned NWD = $clog2(N+1) // derived parameter 178 | ) ( 179 | input clk_i, // 时钟信号 180 | input rst_ni, // 复位信号 181 | input tlul_pkg::tl_h2d_t tl_h_i, // host接口,host->device的信号 182 | output tlul_pkg::tl_d2h_t tl_h_o, // host接口,device->host的信号 183 | output tlul_pkg::tl_h2d_t tl_d_o [N], // device接口,host->device的信号 184 | input tlul_pkg::tl_d2h_t tl_d_i [N], // device接口,device->host的信号 185 | input [NWD-1:0] dev_select // 设备选择信号 186 | ); 187 | ``` 188 | 189 | 此模块内部给每个接口创建了一个队列,用来缓存信号,如下图 190 | ``` 191 | host : 192 | dev_select --------> dev_select_t 193 | tl_h_i --------> tl_t_o 194 | tl_h_o <-------- tl_t_i 195 | 196 | devices: 197 | tl_u_o[i] -----------> tl_d_i[i] 198 | tl_u_i[i] <----------- tl_d_o[i] 199 | ``` 200 | 201 | 总线要处理的就是把tl\_t\_o连接到tl\_u\_o中的一个,把tl\_u\_i中的一个链接到tl\_t\_i。 202 | 203 | 系统通过如下代码连接tl\_t\_o连接到tl\_u\_o。其中,通过`(dev_select_t == NWD'(i)`确定只有一路`tl_u_o`是有效的,`hold_all_requests`用于错误检测。 204 | ``` 205 | for (genvar i = 0 ; i < N ; i++) begin : gen_u_o 206 | assign tl_u_o[i].a_valid = tl_t_o.a_valid & 207 | (dev_select_t == NWD'(i)) & 208 | ~hold_all_requests; 209 | assign tl_u_o[i].a_opcode = tl_t_o.a_opcode; 210 | assign tl_u_o[i].a_param = tl_t_o.a_param; 211 | assign tl_u_o[i].a_size = tl_t_o.a_size; 212 | assign tl_u_o[i].a_source = tl_t_o.a_source; 213 | assign tl_u_o[i].a_address = tl_t_o.a_address; 214 | assign tl_u_o[i].a_mask = tl_t_o.a_mask; 215 | assign tl_u_o[i].a_data = tl_t_o.a_data; 216 | assign tl_u_o[i].a_user = tl_t_o.a_user; 217 | end 218 | ``` 219 | 220 | 通过如下逻辑连接tl\_u\_i到tl\_t\_i 221 | ``` 222 | tlul_pkg::tl_d2h_t tl_t_p ; 223 | 224 | // for the returning reqready, only look at the slave we're addressing 225 | logic hfifo_reqready; 226 | always_comb begin 227 | hfifo_reqready = tl_u_i[N].a_ready; // default to error 228 | for (int idx = 0 ; idx < N ; idx++) begin 229 | //if (dev_select_outstanding == NWD'(idx)) hfifo_reqready = tl_u_i[idx].a_ready; 230 | if (dev_select_t == NWD'(idx)) hfifo_reqready = tl_u_i[idx].a_ready; 231 | end 232 | if (hold_all_requests) hfifo_reqready = 1'b0; 233 | end 234 | // Adding a_valid as a qualifier. This prevents the a_ready from having unknown value 235 | // when the address is unknown and the Host TL-UL FIFO is bypass mode. 236 | assign tl_t_i.a_ready = tl_t_o.a_valid & hfifo_reqready; 237 | 238 | always_comb begin 239 | tl_t_p = tl_u_i[N]; 240 | for (int idx = 0 ; idx < N ; idx++) begin 241 | if (dev_select_outstanding == NWD'(idx)) tl_t_p = tl_u_i[idx]; 242 | end 243 | end 244 | assign tl_t_i.d_valid = tl_t_p.d_valid ; 245 | assign tl_t_i.d_opcode = tl_t_p.d_opcode; 246 | assign tl_t_i.d_param = tl_t_p.d_param ; 247 | assign tl_t_i.d_size = tl_t_p.d_size ; 248 | assign tl_t_i.d_source = tl_t_p.d_source; 249 | assign tl_t_i.d_sink = tl_t_p.d_sink ; 250 | assign tl_t_i.d_data = tl_t_p.d_data ; 251 | assign tl_t_i.d_user = tl_t_p.d_user ; 252 | assign tl_t_i.d_error = tl_t_p.d_error ; 253 | ``` 254 | 255 | # socket_m1 256 | 257 | 此模块实现了一个多host单device的总线结构。其中,为了竞争总线权限使用到了仲裁器。仲裁器接口如下: 258 | ``` 259 | module prim_arbiter_ppc #( 260 | parameter int unsigned N = 4, 261 | parameter int unsigned DW = 32, 262 | 263 | // Configurations 264 | // EnDataPort: {0, 1}, if 0, input data will be ignored 265 | parameter int EnDataPort = 1 266 | ) ( 267 | input clk_i, // 时钟信号 268 | input rst_ni, // 复位信号 269 | 270 | input [ N-1:0] req_i, // 请求信号 271 | input [DW-1:0] data_i [N], // 输入信号 272 | output logic [ N-1:0] gnt_o, // 273 | output logic [$clog2(N)-1:0] idx_o, // 输出当前选择信号的编号 274 | 275 | output logic valid_o, // 输出有效标识 276 | output logic [DW-1:0] data_o, // 输出选择的信号的值 277 | input ready_i // 输入信号有效 278 | ); 279 | ``` 280 | 281 | 此模块有N组信号输入`data_i[N]`,`req_i`标识请求那些信号,仲裁器根据自己的逻辑选择出一组信号输出。 282 | 283 | socket\_m1接口如下 284 | ``` 285 | module tlul_socket_m1 #( 286 | parameter int unsigned M = 4, 287 | parameter bit [M-1:0] HReqPass = {M{1'b1}}, 288 | parameter bit [M-1:0] HRspPass = {M{1'b1}}, 289 | parameter bit [M*4-1:0] HReqDepth = {M{4'h2}}, 290 | parameter bit [M*4-1:0] HRspDepth = {M{4'h2}}, 291 | parameter bit DReqPass = 1'b1, 292 | parameter bit DRspPass = 1'b1, 293 | parameter bit [3:0] DReqDepth = 4'h2, 294 | parameter bit [3:0] DRspDepth = 4'h2 295 | ) ( 296 | input clk_i, // 时钟信号 297 | input rst_ni, // 复位信号 298 | 299 | input tlul_pkg::tl_h2d_t tl_h_i [M], // host接口,host->device的信号 300 | output tlul_pkg::tl_d2h_t tl_h_o [M], // host接口,device->host的信号 301 | 302 | output tlul_pkg::tl_h2d_t tl_d_o, // device接口,host->device的信号 303 | input tlul_pkg::tl_d2h_t tl_d_i // device接口,device->host的信号 304 | ); 305 | ``` 306 | 307 | 模块内部创建了一些如下队列来缓冲信号 308 | ``` 309 | host: 310 | tl_h_i[M] ------------> hreq_fifo_o[M] 311 | tl_h_o[M] <------------ hrsp_fifo_i[M] 312 | 313 | device: 314 | dreq_fifo_i -----------> tl_d_o 315 | drsp_fifo_o <----------- tl_d_i 316 | ``` 317 | 318 | 为了使设备知道信号来自哪个主机,把主机编号编码进了a\_source。代码如下: 319 | ``` 320 | // ID Shifting 321 | logic [STIDW-1:0] reqid_sub; 322 | logic [IDW-1:0] shifted_id; 323 | assign reqid_sub = i; // i为设备编号 324 | assign shifted_id = { 325 | tl_h_i[i].a_source[0+:(IDW-STIDW)], // 高位保留原本的内容 326 | reqid_sub // 低位存放设备编号 327 | }; 328 | 329 | assign hreq_fifo_i = '{ 330 | a_valid: tl_h_i[i].a_valid, 331 | a_opcode: tl_h_i[i].a_opcode, 332 | a_param: tl_h_i[i].a_param, 333 | a_size: tl_h_i[i].a_size, 334 | a_source: shifted_id, 335 | a_address: tl_h_i[i].a_address, 336 | a_mask: tl_h_i[i].a_mask, 337 | a_data: tl_h_i[i].a_data, 338 | a_user: tl_h_i[i].a_user, 339 | d_ready: tl_h_i[i].d_ready 340 | }; 341 | ``` 342 | 343 | 然后通过仲裁器,选出一组主机信号hreq\_fifo\_o连接到dreq\_fifo\_i。 344 | 345 | 总裁器,选择信号的相关代码如下 346 | ``` 347 | // 构建一个标识那些信号有效 348 | for (genvar i = 0 ; i < M ; i++) begin : gen_arbreqgnt 349 | assign hrequest[i] = hreq_fifo_o[i].a_valid; 350 | end 351 | 352 | assign arb_ready = drsp_fifo_o.a_ready; 353 | 354 | prim_arbiter_ppc #( 355 | .N (M), 356 | .DW ($bits(tlul_pkg::tl_h2d_t)) 357 | ) u_reqarb ( 358 | .clk_i, 359 | .rst_ni, 360 | .req_i ( hrequest ), 361 | .data_i ( hreq_fifo_o ), 362 | .gnt_o ( hgrant ), 363 | .idx_o ( ), 364 | .valid_o ( arb_valid ), 365 | .data_o ( arb_data ), 366 | .ready_i ( arb_ready ) 367 | ); 368 | ``` 369 | 370 | 选择出的信号通过arb\_data输出,然后连接到dreq\_fifo\_i。代码如下 371 | ``` 372 | assign dreq_fifo_i = '{ 373 | a_valid: arb_valid, 374 | a_opcode: arb_data.a_opcode, 375 | a_param: arb_data.a_param, 376 | a_size: arb_data.a_size, 377 | a_source: arb_data.a_source, 378 | a_address: arb_data.a_address, 379 | a_mask: arb_data.a_mask, 380 | a_data: arb_data.a_data, 381 | a_user: arb_data.a_user, 382 | 383 | d_ready: dfifo_rspready_merged 384 | }; 385 | ``` 386 | 387 | 连接drsp\_fifo\_o到hrsp\_fifo\_i时会根据d_source,来设定信号是否有效,来把一组信号拆分为多组信号。代码如下: 388 | ``` 389 | for (genvar i = 0 ; i < M ; i++) begin : gen_idrouting 390 | assign hfifo_rspvalid[i] = drsp_fifo_o.d_valid & 391 | (drsp_fifo_o.d_source[0+:STIDW] == i); // 通过判断d_source是否等于i,来设置d_valid 392 | assign dfifo_rspready[i] = hreq_fifo_o[i].d_ready & 393 | (drsp_fifo_o.d_source[0+:STIDW] == i) & 394 | drsp_fifo_o.d_valid; 395 | 396 | assign hrsp_fifo_i[i] = '{ 397 | d_valid: hfifo_rspvalid[i], 398 | d_opcode: drsp_fifo_o.d_opcode, 399 | d_param: drsp_fifo_o.d_param, 400 | d_size: drsp_fifo_o.d_size, 401 | d_source: hfifo_rspid, 402 | d_sink: drsp_fifo_o.d_sink, 403 | d_data: drsp_fifo_o.d_data, 404 | d_user: drsp_fifo_o.d_user, 405 | d_error: drsp_fifo_o.d_error, 406 | a_ready: hgrant[i] 407 | }; 408 | end 409 | ``` 410 | 411 | # 适配器 412 | 413 | 适配器用于把其他类型的接口转换为tlul,系统实现了三个适配器: 414 | - tlul\_adapter\_host sram接口主设备转tlul host接口 415 | - tlul\_adapter\_sram sram接口从设备转tlul device设备 416 | - tlul\_adapter\_reg reg接口转tlul device设备 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | -------------------------------------------------------------------------------- /docs/riscv/coreboot_opensbi_test_debug.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | This article is a breif intro about how to run [coreboot](https://www.coreboot.org/) + [opensbi](https://github.com/riscv/opensbi) + linux kernel on [HiFive Unleashed](https://www.sifive.com/boards/hifive-unleashed). 4 | 5 | Separate hardware-initiated code for a simpler upgrade. To do this, a loader called riscv-gptl was added to load opensbi and linux from the sdcard and run as a coreboot payload. 6 | 7 | # Get Sources Code 8 | 9 | ``` 10 | git clone git@github.com:hardenedlinux/coreboot-HiFiveUnleashed.git -b gptl coreboot 11 | git clone git@github.com:wxjstz/opensbi.git -b gptl 12 | git clone git@github.com:wxjstz/riscv-gptl.git 13 | git clone git@github.com:sifive/freedom-u-sdk.git 14 | ``` 15 | 16 | # intro riscv-gptl 17 | 18 | riscv-gptl is a loader that runs in M mode and loads the riscv-gptl image from the GPT partition of the **4750544c-0000-0000-0000-524953432d56** partition type. 19 | 20 | riscv-gptl image is a binary, which contains opensbi, kernel, optional fdt and optional initrd. This image has a header `struct gptl_header` to describe the layout of this files (opensbi, kernel, fdt and initrd). 21 | ```c 22 | struct gptl_header { 23 | uint32_t magic; /* "GPTL" -> 0x4750544c */ 24 | 25 | uint32_t version; 26 | uint32_t header_size; 27 | uint32_t total_size; 28 | 29 | uint32_t opensbi_offset; 30 | uint32_t opensbi_size; 31 | 32 | uint32_t kernel_offset; 33 | uint32_t kernel_size; 34 | 35 | uint32_t initrd_offset; 36 | uint32_t initrd_size; 37 | 38 | uint32_t fdt_offset; 39 | uint32_t fdt_size; 40 | 41 | char commandline[0]; 42 | }; 43 | ``` 44 | 45 | If you want to adapt to a specific motherboard, you need to implement `struct platform_interface` 46 | ```c 47 | struct platform_interface { 48 | int hart_num; 49 | char *platform_name; 50 | void (*init) (void); 51 | void (*putchar) (char c); 52 | size_t logic_block_size; 53 | void *(*read) (size_t offset, size_t size, void *buff); 54 | }; 55 | ``` 56 | 57 | Currently using opensbi's FW\_JUMP firmware to provide sbi services. FW\_JUMP needs to load openbsi/kernel to a fixed address and reserve memory with fixed address for fdt. 58 | 59 | Build dependencies with the following environment variables 60 | 61 | - **CROSS\_COMPILE**: Must be defined. The name prefix of the RISC-V compiler toolchain executables, e.g. **riscv64-elf-** if the gcc executable used is **riscv64-elf-gcc**. 62 | - **PLATFORM**: Must be defined. A subdirectory under the platform that specifies the target platform 63 | - **DEBUG**: The build option of riscv-gptl to generate DEBUG macro, when $(DEBUG) = y. 64 | - **CONFIG\_FW\_START**: The starting address of riscv-gptl, the default is 0x80000000 65 | - **CONFIG\_HEAP\_SIZE**: The heap size of riscv-gptl, the default is 65536. 66 | - **CONFIG\_STACK\_SIZE**: The stack size of riscv-gptl, the default is 65536. 67 | - **CONFIG\_RISCV\_ARCH**: The build option of riscv-gptl, -march=$(CONFIG\_RISCV\_ARCH) 68 | - **CONFIG\_RISCV\_ABI**: The build option of riscv-gptl, -mabi=$(CONFIG\_RISCV\_ABI) 69 | - **CONFIG\_RISCV\_CODEMODEL**: The build option of riscv-gptl, -mcmodel=$(CONFIG\_RISCV\_CODEMODEL) 70 | - **CONFIG\_OPENSBI\_ADDR**: Must be defined. The running address of opensbi, related to the opensbi build parameter 71 | - **CONFIG\_KERNEL\_ADDR**: Must be defined. The running address of kernel, related to the opensbi build parameter 72 | - **CONFIG\_FDT\_ADDR**: Must be defined. The memory address reserved for opensbi, used by opensbi to modify fdt, related to the opensbi build parameter 73 | - **CONFIG\_RESERVED\_FOR\_FDT**: Must be defined. The fdt memory size reserved for opensbi, used to calculate the memory start address of the initrd 74 | 75 | # Build 76 | 77 | ## riscv-gptl 78 | 79 | ``` 80 | cd riscv-gptl 81 | . ./platform/hifive-unleashed/build.env 82 | make CROSS_COMPILE=riscv64-elf- CONFIG_FW_START=0x82000000 DEBUG=y 83 | 84 | # build riscv-tool which used to create riscv-gptl image 85 | make -C util/gptl-tool 86 | ``` 87 | 88 | ## coreboot 89 | 90 | ``` 91 | cd coreboot 92 | make menuconfig 93 | ``` 94 | 95 | - Mainboard->Mainboard vendor, select **SiFive** 96 | - Mainboard->Mainboard model, select **HiFive Unleashed** 97 | - Payload->Add a payload, select **An ELF executable payload** 98 | - Payload->Payload path and filename, Use default value **payload.elf** 99 | 100 | ``` 101 | cp ../riscv-gptl/build/program.elf ./payload.elf 102 | make 103 | ``` 104 | 105 | ## opensbi 106 | 107 | ``` 108 | cd opensbi 109 | make CROSS_COMPILE=riscv64-elf- PLATFORM=sifive/fu540 110 | ``` 111 | 112 | ## linux 113 | 114 | ``` 115 | cd freedom-u-sdk 116 | make 117 | riscv64-unknown-elf-copy -O binary work/linux/vmlinux-stripped vmlinux.bin 118 | ``` 119 | 120 | ## create riscv-gptl image 121 | 122 | ``` 123 | cd riscv-gptl 124 | build/util/gptl-tools/gptl-tools -O gptl.img -o ../opensbi/build/platform/sifive/fu540/firmware/fw_jump.bin -k ../freedom-u-sdk/vmlinux.bin 125 | ``` 126 | 127 | 128 | # Burn 129 | 130 | Insert the sd card into your computer's card reader 131 | 132 | ``` 133 | sudo dd if=coreboot/build/coreboot.rom of=/dev/sdX; sync 134 | sudo dd if=riscv-gptl/gptl.img of=/dev/sdX2; sync 135 | ``` 136 | 137 | # Testing 138 | 139 | Turn the MSEL DIP switch to 11, and connect ttyUSB1 via minicom (baud rate 115200 8N1), then press the reset button to restart. Then you will see the log in the terminal. 140 | 141 | Linux username: root, password: sifive. 142 | 143 | -------------------------------------------------------------------------------- /docs/riscv/coreboot分析.md: -------------------------------------------------------------------------------- 1 | # 概要 2 | 3 | coreboot是一个开源项目,旨在替换大多数计算机中的专有固件(BIOS)。coreboot之前被称为LinuxBIOS。coreboot在进行一点硬件相关的初始化后引导一个payload,这个payload可以是标准固件的实现、操作系统的引导程序或者操作系统本身。 4 | 5 | # 编译和调试 6 | 7 | 当前riscv没有具体硬件,需要使用**spike**进行仿真。这里我选择linux作为coreboot的payload。 8 | 9 | ## 编译 10 | 11 | ### 构建工具以及模拟器 12 | 13 | 1. 获取源码,**git clone https://github.com/riscv/riscv-tools.git** 14 | 2. 更新源码,**cd riscv-tools ; git submodule update --init --recursive** 15 | 3. 修正代码(https://github.com/riscv/riscv-isa-sim/pull/53),添加8250串口支持 16 | 4. 修正代码添加configstring支持 17 | 5. 设置环境变量,**export RISCV=path**,path为软件希望安装的路径 18 | 6. 编译,**./build.sh** 19 | 20 | ### 构建内核 21 | 22 | 1. 获取linux内核源码,**git clone https://github.com/torvalds/linux.git** 23 | 2. 获取riscv linux内核源码,**git clone https://github.com/riscv/riscv-linux** 24 | 3. 创建符号链接,**cd linux-4.6.x/arch ; ln -s ../../riscv-linux/arch/riscv .** 25 | 4. 重置为默认配置,**make ARCH=riscv defconfig** 26 | 5. 设置交叉编译器,**make ARCH=riscv menuconfig**,修改setup->Cross-compiler tool prefix 27 | 6. 编译,**make ARCH=riscv** 28 | 29 | ### 构建coreboot 30 | 31 | 1. 获取源码,**git clone https://review.coreboot.org/coreboot.git** 32 | 2. 构建交叉编译器,**make crossgcc-ricsv** 33 | 3. 配置,**make menuconfig** 34 | 4. 编译,**make** 35 | 36 | 37 | make menuconfig需要注意: 38 | >修改Mainboard->Mainboard vendor为Emulation 39 | >修改Mainboard->Mainboard model为SPIKE ucb riscv 40 | >修改Payload->Add a payload为An ELF executable payload 41 | >修改Payload->Payload path and filename为编译产生的Linux镜像文件的路径 42 | >修改Chipset->Location of pointer to RISCV config string,地址需要与模拟器中configstring地址匹配 43 | 44 | ## 调试 45 | 46 | ### 串口 47 | 48 | coreboot使用8250串口作为终端,这并没有合并到spike代码中,这需要手动合并。合并过程可以参考clint实现。 49 | 50 | 带8250串口的源码位于https://github.com/riscv/riscv-isa-sim/pull/53 51 | 52 | ### 寄存器错误 53 | 54 | 这和特权指令集的变更相关,mcounter寄存器地址由0x320变更为0x306。 55 | 56 | 这个patch我已经提交,https://review.coreboot.org/#/c/20043/ 57 | 58 | ### 架构判定宏 59 | 60 | 这和工具链的更改相关,老版本gcc通过**\_\_riscv__**来标示架构,新版本gcc使用**__riscv**。这导致加载文件到内存时出现内存访问不对齐的异常。 61 | 62 | 这个patch我已经提交,https://review.coreboot.org/#/c/20125/ 63 | 64 | ### configstring问题 65 | 66 | configstring是一个字符串,用于描述一些设备信息。当前通过spike硬编码进ROM实现,但官方的spike并没有这部分代码,这需要自己添加。 67 | 68 | 而且configstring是一个临时解决办法,将来coreboot会使用linux的设备描述信息fdt。可以参见https://mail.coreboot.org/pipermail/coreboot/2017-June/084534.html 69 | 70 | ### linux内核加载问题 71 | 72 | 内核编译出来后,起始地址为0xffffffff80000000。而spike内存范围0x80000000-0xffffffff,这会导致加载失败。可以通过链接器再链接一次修改内核的起始地址。这里使用0x90000000作为起始地址,因为0x80000000处有coreboot的镜像文件。 73 | 74 | 创建链接脚本 75 | 76 | ``` 77 | ENTRY(_start) 78 | SECTIONS 79 | { 80 | . = 0x90000000; 81 | .data : { 82 | *.* 83 | } 84 | } 85 | ``` 86 | 87 | 执行命令**riscv64-unknown-elf-ld -T payload.ld vmlinux -o vmlinux.payload** 88 | 89 | 输出的文件即可作为payload 90 | 91 | # 代码分析 92 | 93 | ## 基本结构 94 | 95 | coreboot引导过程被分为多个过程。 96 | 97 | - bootblock : 初始化部分硬件(Flash),引导加载romstage 98 | - romstage : 初始化存储器和部分芯片组,并引导执行ramstage 99 | - ramstage : 设备枚举资源分配,创建ACPI表和SMM句柄,并加载执行payload 100 | - payload : 操作系统、操作系统引导程序或标准固件等 101 | 102 | ## bootblock 103 | 104 | 此部分是机器上电最先执行的代码,负责初始化部分硬件然后引导romstage。 105 | 106 | 此不分主要有两个文件组成,一个汇编文件用于初始化C运行环境然后跳转到C代码。一个通用的框架文件,初始化部分硬件后启动下一阶段(romstage)。 107 | 108 | 两个文件分别位于: 109 | 110 | - **src/arch/riscv/bootblock.S** 111 | - **src/lib/bootblock.c** 112 | 113 | ### 汇编入口 114 | 115 | 此部分位于**src/arch/riscv/bootblock.S** 116 | 117 | 主要负责初始化堆栈、遗常、线程数据 118 | 119 | ```assembly 120 | .section ".text._start", "ax", %progbits 121 | 122 | .globl _stack 123 | .global _estack //栈内存的起始和结束符号(_stack < _estack) 124 | 125 | .globl _start 126 | _start: 127 | 128 | # N.B. This only works on low 4G of the address space 129 | # and the stack must be page-aligned. 130 | la sp, _estack //初始化堆栈 131 | 132 | # poison the stack 133 | la t1, _stack 134 | li t0, 0xdeadbeef 135 | sd t0, 0(t1) //在栈顶放一个标记 136 | 137 | # make room for HLS and initialize it 138 | addi sp, sp, -HLS_SIZE //开辟空间存放线程数据,线程数据存放在堆栈开始位置 139 | 140 | // Once again, the docs and toolchain disagree. 141 | // Rather than get fancy I'll just lock this down 142 | // until it all stabilizes. 143 | //csrr a0, mhartid 144 | csrr a0, 0xf14 //获取hart ID 145 | call hls_init //初始化线程数据 146 | 147 | la t0, trap_entry 148 | csrw mtvec, t0 //初始化异常处理 149 | 150 | # clear any pending interrupts 151 | csrwi mip, 0 //清除中断标记 152 | 153 | # set up the mstatus register for VM 154 | call mstatus_init //中断设置、初始化定时器 155 | tail main //跳转到bootblock的主程序 156 | ``` 157 | 158 | ### 框架 159 | 160 | 此部分位于**src/lib/bootblock.c** 161 | 162 | 此文件提供了一个初始化框架。 163 | 164 | 其中需要提供时钟相关初始化**init_timer** 165 | 166 | 具体设备根据自身情况实现4个函数(riscv当前只使用软件模拟,所有没有实现) 167 | 168 | >bootblock_soc_early_init 169 | >bootblock_mainboard_early_init 170 | >bootblock_soc_init 171 | >bootblock_mainboard_init 172 | 173 | 174 | ```c 175 | //声明一些弱符号的空函数,当具体架构有实现时调用具体架构的函数,如果没有使用这些函数 176 | __attribute__((weak)) void bootblock_mainboard_early_init(void) { /* no-op */ } 177 | __attribute__((weak)) void bootblock_soc_early_init(void) { /* do nothing */ } 178 | __attribute__((weak)) void bootblock_soc_init(void) { /* do nothing */ } 179 | __attribute__((weak)) void bootblock_mainboard_init(void) { /* do nothing */ } 180 | 181 | asmlinkage void bootblock_main_with_timestamp(uint64_t base_timestamp) 182 | { 183 | /* Initialize timestamps if we have TIMESTAMP region in memlayout.ld. */ 184 | if (IS_ENABLED(CONFIG_COLLECT_TIMESTAMPS) && _timestamp_size > 0) 185 | timestamp_init(base_timestamp); 186 | 187 | cmos_post_init(); //cmos初始化 188 | 189 | bootblock_soc_early_init(); //soc早期初始化 190 | bootblock_mainboard_early_init(); //主板早期初始化 191 | 192 | if (IS_ENABLED(CONFIG_BOOTBLOCK_CONSOLE)) { 193 | console_init(); //串口初始化 194 | exception_init(); //异常初始化 195 | } 196 | 197 | bootblock_soc_init(); //与bootblock相关的SOC初始化 198 | bootblock_mainboard_init(); //与bootblock相关的主板初始化 199 | 200 | run_romstage(); //运行下一个阶段程序(romstage) 201 | } 202 | 203 | void main(void) 204 | { 205 | uint64_t base_timestamp = 0; 206 | 207 | init_timer(); //时钟初始化 208 | 209 | if (IS_ENABLED(CONFIG_COLLECT_TIMESTAMPS)) 210 | base_timestamp = timestamp_get(); //获取时间戳 211 | 212 | bootblock_main_with_timestamp(base_timestamp); 213 | } 214 | ``` 215 | 216 | 在riscv中,只使用了串口uart8250。对于uart8250只需要实现一个函数(**uart_platform_base**),用来获取设备地址。此函数在**src/mainboard/emulation/spike-riscv/uart.c**中实现 217 | 218 | ### 异常处理分析 219 | 220 | 从bootblock.S中看出,异常入口为**trap_entry**。此部分实现也分了两个部分,**trap_util.S**用于处理现场保护和恢复,trap_hendler为中断逻辑部分。 221 | 222 | #### 现场保护结构体 223 | 224 | ```c 225 | typedef struct 226 | { 227 | uintptr_t gpr[32]; 228 | uintptr_t status; 229 | uintptr_t epc; 230 | uintptr_t badvaddr; 231 | uintptr_t cause; 232 | uintptr_t insn; 233 | } trapframe; 234 | ``` 235 | 236 | #### 现场保护代码分析 237 | 238 | ```assembly 239 | #define STORE sd # 用于处理32位、64位、128位系统寄存器宽度不一样的问题 240 | #define LOAD ld # 用于处理32位、64位、128位系统寄存器宽度不一样的问题 241 | 242 | #define LOG_REGBYTES 3 243 | #define REGBYTES (1 << LOG_REGBYTES) # 用于描述一个寄存器有多少个字节 244 | 245 | .macro save_tf 246 | # save gprs 247 | # 保存通用寄存器,此时X2(sp)已经保存在mscratch中 248 | STORE x1,1*REGBYTES(x2) 249 | STORE x3,3*REGBYTES(x2) 250 | STORE x4,4*REGBYTES(x2) 251 | STORE x5,5*REGBYTES(x2) 252 | STORE x6,6*REGBYTES(x2) 253 | STORE x7,7*REGBYTES(x2) 254 | STORE x8,8*REGBYTES(x2) 255 | STORE x9,9*REGBYTES(x2) 256 | STORE x10,10*REGBYTES(x2) 257 | STORE x11,11*REGBYTES(x2) 258 | STORE x12,12*REGBYTES(x2) 259 | STORE x13,13*REGBYTES(x2) 260 | STORE x14,14*REGBYTES(x2) 261 | STORE x15,15*REGBYTES(x2) 262 | STORE x16,16*REGBYTES(x2) 263 | STORE x17,17*REGBYTES(x2) 264 | STORE x18,18*REGBYTES(x2) 265 | STORE x19,19*REGBYTES(x2) 266 | STORE x20,20*REGBYTES(x2) 267 | STORE x21,21*REGBYTES(x2) 268 | STORE x22,22*REGBYTES(x2) 269 | STORE x23,23*REGBYTES(x2) 270 | STORE x24,24*REGBYTES(x2) 271 | STORE x25,25*REGBYTES(x2) 272 | STORE x26,26*REGBYTES(x2) 273 | STORE x27,27*REGBYTES(x2) 274 | STORE x28,28*REGBYTES(x2) 275 | STORE x29,29*REGBYTES(x2) 276 | STORE x30,30*REGBYTES(x2) 277 | STORE x31,31*REGBYTES(x2) 278 | 279 | # get sr, epc, badvaddr, cause 280 | csrrw t0,mscratch,x0 # 从mscratch中取出X2 281 | 282 | # 取出其他异常相关寄存器 283 | csrr s0,mstatus 284 | csrr t1,mepc 285 | csrr t2,mbadaddr 286 | csrr t3,mcause 287 | STORE t0,2*REGBYTES(x2) #保存X2 288 | 289 | #保存其他异常相关的寄存器 290 | STORE s0,32*REGBYTES(x2) 291 | STORE t1,33*REGBYTES(x2) 292 | STORE t2,34*REGBYTES(x2) 293 | STORE t3,35*REGBYTES(x2) 294 | ``` 295 | 296 | 以上定义了一个宏**save_tf**来进行现场保护,在调用此宏之前需要保存用户的X2到mscratch。并且初始化X2(指向现场保护地址) 297 | 298 | #### 现场恢复 299 | 300 | ```assembly 301 | .macro restore_regs 302 | # restore x registers 303 | LOAD x1,1*REGBYTES(a0) 304 | LOAD x2,2*REGBYTES(a0) 305 | LOAD x3,3*REGBYTES(a0) 306 | LOAD x4,4*REGBYTES(a0) 307 | LOAD x5,5*REGBYTES(a0) 308 | LOAD x6,6*REGBYTES(a0) 309 | LOAD x7,7*REGBYTES(a0) 310 | LOAD x8,8*REGBYTES(a0) 311 | LOAD x9,9*REGBYTES(a0) 312 | LOAD x11,11*REGBYTES(a0) 313 | LOAD x12,12*REGBYTES(a0) 314 | LOAD x13,13*REGBYTES(a0) 315 | LOAD x14,14*REGBYTES(a0) 316 | LOAD x15,15*REGBYTES(a0) 317 | LOAD x16,16*REGBYTES(a0) 318 | LOAD x17,17*REGBYTES(a0) 319 | LOAD x18,18*REGBYTES(a0) 320 | LOAD x19,19*REGBYTES(a0) 321 | LOAD x20,20*REGBYTES(a0) 322 | LOAD x21,21*REGBYTES(a0) 323 | LOAD x22,22*REGBYTES(a0) 324 | LOAD x23,23*REGBYTES(a0) 325 | LOAD x24,24*REGBYTES(a0) 326 | LOAD x25,25*REGBYTES(a0) 327 | LOAD x26,26*REGBYTES(a0) 328 | LOAD x27,27*REGBYTES(a0) 329 | LOAD x28,28*REGBYTES(a0) 330 | LOAD x29,29*REGBYTES(a0) 331 | LOAD x30,30*REGBYTES(a0) 332 | LOAD x31,31*REGBYTES(a0) 333 | # restore a0 last 334 | LOAD x10,10*REGBYTES(a0) 335 | ``` 336 | 337 | 现场恢复比较简单,此宏在使用前需要初始化a0(执行现场保护的内存) 338 | 339 | #### 异常处理 340 | 341 | 异常处理主要处理了,内存非对齐访问、S-Mode系统调用、以及中断。其他异常会打印异常信息,然后停止运行。 342 | 343 | ```c 344 | void trap_handler(trapframe *tf) 345 | { 346 | write_csr(mscratch, tf); /* 把异常保护地址保存到mscratch */ 347 | if (tf->cause & 0x8000000000000000ULL) { 348 | interrupt_handler(tf); /* 中断处理 */ 349 | return; 350 | } 351 | 352 | /* 异常处理 */ 353 | switch(tf->cause) { 354 | case CAUSE_MISALIGNED_FETCH: /* 取指不对齐 */ 355 | case CAUSE_FAULT_FETCH: /* 取指失败 */ 356 | case CAUSE_ILLEGAL_INSTRUCTION: /* 非法指令 */ 357 | case CAUSE_BREAKPOINT: /* 断点指令 */ 358 | case CAUSE_FAULT_LOAD: /* LOAD失败 */ 359 | case CAUSE_FAULT_STORE: /* STORE失败 */ 360 | case CAUSE_USER_ECALL: /* 来自U-Mode的系统调用 */ 361 | case CAUSE_HYPERVISOR_ECALL: /* 来自H-Mode的系统调用 */ 362 | case CAUSE_MACHINE_ECALL: /* 来自M-Mode的系统调用 */ 363 | print_trap_information(tf); /* 打印异常信息 */ 364 | break; 365 | case CAUSE_MISALIGNED_LOAD: 366 | print_trap_information(tf); /* 打印异常信息 */ 367 | handle_misaligned_load(tf); /* 通过字节访问实现非对齐访问内存 */ 368 | break; 369 | case CAUSE_MISALIGNED_STORE: 370 | print_trap_information(tf); /* 打印异常信息 */ 371 | handle_misaligned_store(tf);/* 通过字节访问实现非对齐访问内存 */ 372 | break; 373 | case CAUSE_SUPERVISOR_ECALL: 374 | /* Don't print so we make console putchar calls look 375 | the way they should */ 376 | handle_supervisor_call(tf); /* 处理S—Mode的系统调用 */ 377 | break; 378 | default: 379 | printk(BIOS_EMERG, "================================\n"); 380 | printk(BIOS_EMERG, "Coreboot: can not handle a trap:\n"); 381 | printk(BIOS_EMERG, "================================\n"); 382 | print_trap_information(tf); /* 打印异常信息 */ 383 | break; 384 | } 385 | die("Can't recover from trap. Halting.\n"); 386 | } 387 | ``` 388 | 389 | ##### 内存非对齐访问 390 | 391 | 此部分处理非对齐访问内存引起的异常,并通过字节操作实现非对齐内存访问。(只实现了64位) 392 | 393 | ```c 394 | //获取指令 395 | static uint32_t fetch_instruction(uintptr_t vaddr) { 396 | printk(BIOS_SPEW, "fetching instruction at 0x%016zx\n", (size_t)vaddr); 397 | return mprv_read_u32((uint32_t *) vaddr); 398 | } 399 | 400 | void handle_misaligned_load(trapframe *tf) { 401 | printk(BIOS_DEBUG, "Trapframe ptr: %p\n", tf); 402 | 403 | //获取异常发生的地址 404 | uintptr_t faultingInstructionAddr = tf->epc; 405 | 406 | //获取异常的指令 407 | insn_t faultingInstruction = fetch_instruction(faultingInstructionAddr); 408 | printk(BIOS_DEBUG, "Faulting instruction: 0x%x\n", faultingInstruction); 409 | 410 | //获取访问内存的宽度 411 | insn_t widthMask = 0x7000; 412 | insn_t memWidth = (faultingInstruction & widthMask) >> 12; 413 | 414 | //获取目标寄存器编号 415 | insn_t destMask = 0xF80; 416 | insn_t destRegister = (faultingInstruction & destMask) >> 7; 417 | 418 | printk(BIOS_DEBUG, "Width: 0x%x\n", memWidth); 419 | if (memWidth == 3) {//只处理64位内存访问 420 | // load double, handle the issue 421 | void* badAddress = (void*) tf->badvaddr; 422 | uint64_t value = 0; 423 | for (int i = 0; i < 8; i++) {//按字节读取 424 | value <<= 8; 425 | value += mprv_read_u8(badAddress+i); 426 | } 427 | tf->gpr[destRegister] = value;//回写到现场保护 428 | } else { 429 | // panic, this should not have happened 430 | die("Code should not reach this path, misaligned on a non-64 bit store/load\n"); 431 | } 432 | 433 | // return to where we came from 434 | write_csr(mepc, read_csr(mepc) + 4); 435 | asm volatile("j machine_call_return"); 436 | } 437 | 438 | void handle_misaligned_store(trapframe *tf) { 439 | printk(BIOS_DEBUG, "Trapframe ptr: %p\n", tf); 440 | 441 | //获取异常发生的地址 442 | uintptr_t faultingInstructionAddr = tf->epc; 443 | 444 | //获取异常的指令 445 | insn_t faultingInstruction = fetch_instruction(faultingInstructionAddr); 446 | printk(BIOS_DEBUG, "Faulting instruction: 0x%x\n", faultingInstruction); 447 | 448 | //获取访问内存的宽度 449 | insn_t widthMask = 0x7000; 450 | insn_t memWidth = (faultingInstruction & widthMask) >> 12; 451 | 452 | //获取源寄存器编号 453 | insn_t srcMask = 0x1F00000; 454 | insn_t srcRegister = (faultingInstruction & srcMask) >> 20; 455 | 456 | printk(BIOS_DEBUG, "Width: 0x%x\n", memWidth); 457 | if (memWidth == 3) {//只处理64位内存访问 458 | // store double, handle the issue 459 | void* badAddress = (void*) tf->badvaddr; 460 | uint64_t value = tf->gpr[srcRegister];//从现场保护获取寄存器的值 461 | for (int i = 0; i < 8; i++) {//按字节回写到内存中去 462 | mprv_write_u8(badAddress+i, value); 463 | value >>= 8; 464 | } 465 | } else { 466 | // panic, this should not have happened 467 | die("Code should not reach this path, misaligned on a non-64 bit store/load\n"); 468 | } 469 | 470 | // return to where we came from 471 | write_csr(mepc, read_csr(mepc) + 4); 472 | asm volatile("j machine_call_return"); 473 | } 474 | ``` 475 | 476 | ##### S-Mode系统调用 477 | 478 | ```c 479 | void handle_supervisor_call(trapframe *tf) { 480 | uintptr_t call = tf->gpr[17]; /* a7 */ 481 | uintptr_t arg0 = tf->gpr[10]; /* a0 */ 482 | uintptr_t arg1 = tf->gpr[11]; /* a1 */ 483 | uintptr_t returnValue; 484 | switch(call) { 485 | case MCALL_HART_ID: //获取核心编号 486 | printk(BIOS_DEBUG, "Getting hart id...\n"); 487 | returnValue = read_csr(0xf14);//mhartid); 488 | break; 489 | case MCALL_NUM_HARTS: //获取核心数 490 | /* TODO: parse the hardware-supplied config string and 491 | return the correct value */ 492 | returnValue = 1; 493 | break; 494 | case MCALL_CONSOLE_PUTCHAR: //向终端打印字符 495 | returnValue = mcall_console_putchar(arg0); 496 | break; 497 | 498 | //SBI相关,当前未实现 499 | case MCALL_SEND_IPI: 500 | printk(BIOS_DEBUG, "Sending IPI...\n"); 501 | returnValue = mcall_send_ipi(arg0); 502 | break; 503 | case MCALL_CLEAR_IPI: 504 | printk(BIOS_DEBUG, "Clearing IPI...\n"); 505 | returnValue = mcall_clear_ipi(); 506 | break; 507 | 508 | case MCALL_SHUTDOWN: //关机 509 | printk(BIOS_DEBUG, "Shutting down...\n"); 510 | returnValue = mcall_shutdown(); 511 | break; 512 | case MCALL_SET_TIMER: //设置定时器 513 | returnValue = mcall_set_timer(arg0); 514 | break; 515 | case MCALL_QUERY_MEMORY: //获取内存信息 516 | printk(BIOS_DEBUG, "Querying memory, CPU #%lld...\n", arg0); 517 | returnValue = mcall_query_memory(arg0, (memory_block_info*) arg1); 518 | break; 519 | default: 520 | printk(BIOS_DEBUG, "ERROR! Unrecognized SBI call\n"); 521 | returnValue = 0; 522 | break; // note: system call we do not know how to handle 523 | } 524 | tf->gpr[10] = returnValue; 525 | write_csr(mepc, read_csr(mepc) + 4); 526 | asm volatile("j supervisor_call_return"); 527 | } 528 | ``` 529 | 530 | ##### 中断 531 | 532 | 中断只处理了M-Mode的时钟中断。**gettime**函数用于从configstring获取定时器寄存器映射到内存的地址。 533 | 534 | ```c 535 | static void interrupt_handler(trapframe *tf) 536 | { 537 | uint64_t cause = tf->cause & ~0x8000000000000000ULL; 538 | uint32_t msip, ssie; 539 | 540 | switch (cause) { 541 | case IRQ_M_TIMER: 542 | // The only way to reset the timer interrupt is to 543 | // write mtimecmp. But we also have to ensure the 544 | // comparison fails, for a long time, to let 545 | // supervisor interrupt handler compute a new value 546 | // and set it. Finally, it fires if mtimecmp is <= 547 | // mtime, not =, so setting mtimecmp to 0 won't work 548 | // to clear the interrupt and disable a new one. We 549 | // have to set the mtimecmp far into the future. 550 | // Akward! 551 | // 552 | // Further, maybe the platform doesn't have the 553 | // hardware or the payload never uses it. We hold off 554 | // querying some things until we are sure we need 555 | // them. What to do if we can not find them? There are 556 | // no good options. 557 | 558 | // This hart may have disabled timer interrupts. If 559 | // so, just return. Kernels should only enable timer 560 | // interrupts on one hart, and that should be hart 0 561 | // at present, as we only search for 562 | // "core{0{0{timecmp" above. 563 | ssie = read_csr(sie); 564 | if (!(ssie & SIE_STIE)) 565 | break; 566 | 567 | if (!timecmp) 568 | gettimer(); 569 | //printk(BIOS_SPEW, "timer interrupt\n"); 570 | *timecmp = (uint64_t) -1; 571 | msip = read_csr(mip); 572 | msip |= SIP_STIP; 573 | write_csr(mip, msip); 574 | break; 575 | default: 576 | printk(BIOS_EMERG, "======================================\n"); 577 | printk(BIOS_EMERG, "Coreboot: Unknown machine interrupt: 0x%llx\n", 578 | cause); 579 | printk(BIOS_EMERG, "======================================\n"); 580 | print_trap_information(tf); 581 | break; 582 | } 583 | } 584 | ``` 585 | 586 | ## romstage 587 | 588 | romstage位于**src/mainboard/emulation/spike-riscv/romstage.c**中 589 | 590 | 此文件比较简单,只有对串口的初始化,然后直接引导ramstage 591 | 592 | ```c 593 | void main(void) 594 | { 595 | uintptr_t base; 596 | size_t size; 597 | 598 | //初始化串口终端 599 | console_init(); 600 | 601 | //从configstring中获取内存配置信息 602 | query_mem(configstring(), &base, &size); 603 | printk(BIOS_SPEW, "0x%zx bytes of memory at 0x%llx\n", size, base); 604 | 605 | //引导ramstage 606 | run_ramstage(); 607 | } 608 | ``` 609 | 610 | ## ramstage 611 | 612 | ramstage位于**src/lib/hardwaremain.c**中,与romstage类似,他提供了一个框架,初始化设备,然后引导下一级payload。但与romstage相比,ramstage具有更强的灵活性。 613 | 614 | 主程序位于hardwaremain.c文件中的main函数。 615 | 616 | ```c 617 | void main(void) 618 | { 619 | /* 620 | * We can generally jump between C and Ada code back and forth 621 | * without trouble. But since we don't have an Ada main() we 622 | * have to do some Ada package initializations that GNAT would 623 | * do there. This has to be done before calling any Ada code. 624 | * 625 | * The package initializations should not have any dependen- 626 | * cies on C code. So we can call them here early, and don't 627 | * have to worry at which point we can start to use Ada. 628 | */ 629 | //给Ada语言初始化环境,便于Ada和C混合编程 630 | //在代码中暂时没有发现Ada的代码 631 | ramstage_adainit(); 632 | 633 | /* TODO: Understand why this is here and move to arch/platform code. */ 634 | /* For MMIO UART this needs to be called before any other printk. */ 635 | if (IS_ENABLED(CONFIG_ARCH_X86)) 636 | init_timer();//时钟初始化 637 | 638 | /* console_init() MUST PRECEDE ALL printk()! Additionally, ensure 639 | * it is the very first thing done in ramstage.*/ 640 | console_init();//终端初始化 641 | 642 | post_code(POST_CONSOLE_READY); 643 | 644 | /* 645 | * CBMEM needs to be recovered in the EARLY_CBMEM_INIT case because 646 | * timestamps, APCI, etc rely on the cbmem infrastructure being 647 | * around. Explicitly recover it. 648 | */ 649 | if (IS_ENABLED(CONFIG_EARLY_CBMEM_INIT)) 650 | cbmem_initialize(); 651 | 652 | /* Record current time, try to locate timestamps in CBMEM. */ 653 | timestamp_init(timestamp_get()); 654 | 655 | timestamp_add_now(TS_START_RAMSTAGE); 656 | post_code(POST_ENTRY_RAMSTAGE); 657 | 658 | /* Handoff sleep type from romstage. */ 659 | #if CONFIG_HAVE_ACPI_RESUME 660 | acpi_is_wakeup(); 661 | #endif 662 | 663 | exception_init(); 664 | threads_initialize(); 665 | 666 | /* Schedule the static boot state entries. */ 667 | boot_state_schedule_static_entries(); 668 | 669 | bs_walk_state_machine(); 670 | 671 | die("Boot state machine failure.\n"); 672 | } 673 | ``` 674 | 675 | ### 启动步骤 676 | 677 | ramstage把启动分为如下步骤 678 | 679 | ```c 680 | typedef enum { 681 | BS_PRE_DEVICE, 682 | BS_DEV_INIT_CHIPS, 683 | BS_DEV_ENUMERATE, 684 | BS_DEV_RESOURCES, 685 | BS_DEV_ENABLE, 686 | BS_DEV_INIT, 687 | BS_POST_DEVICE, 688 | BS_OS_RESUME_CHECK, 689 | BS_OS_RESUME, 690 | BS_WRITE_TABLES, 691 | BS_PAYLOAD_LOAD, 692 | BS_PAYLOAD_BOOT, 693 | } boot_state_t; 694 | ``` 695 | 696 | 每一个步骤对应一个结构体**boot_state**。每个步骤被分为3个阶段,前后通过枚举**boot_state_sequence_t**描述。**boot_state_callback**标示一个回调对象列表,即一个步骤前后可以执行一系列的方法。 697 | 698 | ```c 699 | struct boot_state_callback { /* 回调对象 */ 700 | void *arg; /* 参数 */ 701 | void (*callback)(void *arg); /* 回调的方法 */ 702 | /* For use internal to the boot state machine. */ 703 | struct boot_state_callback *next; /* 用于多个回调对象构成链表 */ 704 | #if IS_ENABLED(CONFIG_DEBUG_BOOT_STATE) 705 | const char *location; 706 | #endif 707 | }; 708 | 709 | typedef enum { /* 用于描述一个步骤的执行状态 */ 710 | BS_ON_ENTRY, 711 | BS_ON_EXIT 712 | } boot_state_sequence_t; 713 | 714 | struct boot_phase { /* 用于处理每个步骤情后的事 */ 715 | /* 回调对象 */ 716 | struct boot_state_callback *callbacks; 717 | int blockers; 718 | }; 719 | 720 | struct boot_state { 721 | const char *name; /* 名字 */ 722 | boot_state_t id; /* id对应一个boot_state_t的枚举对象 */ 723 | u8 post_code; /* 调试用,可以输出到终端 */ 724 | struct boot_phase phases[2]; /* 一个步骤前后需要处理的回调,下标对应boot_state_sequence_t */ 725 | boot_state_t (*run_state)(void *arg);/* 一个步骤的主方法 */ 726 | void *arg; /* 步骤方法的参数 */ 727 | int complete : 1; /* 标记步骤处理完成 */ 728 | #if CONFIG_HAVE_MONOTONIC_TIMER 729 | struct boot_state_times times; 730 | #endif 731 | }; 732 | ``` 733 | 734 | 对每一个步骤定义了一个宏,用于快速生成一个**boot_state**,宏定义如下 735 | 736 | ```c 737 | #define BS_INIT(state_, run_func_) \ 738 | { \ 739 | .name = #state_, \ 740 | .id = state_, \ 741 | .post_code = POST_ ## state_, \ 742 | .phases = { { NULL, 0 }, { NULL, 0 } }, \ 743 | .run_state = run_func_, \ 744 | .arg = NULL, \ 745 | .complete = 0, \ 746 | } 747 | ``` 748 | 749 | 所有的步骤通过一个**boot_state**数组描述 750 | 751 | ```c 752 | #define BS_INIT_ENTRY(state_, run_func_) \ 753 | [state_] = BS_INIT(state_, run_func_) 754 | 755 | static struct boot_state boot_states[] = { 756 | BS_INIT_ENTRY(BS_PRE_DEVICE, bs_pre_device), 757 | BS_INIT_ENTRY(BS_DEV_INIT_CHIPS, bs_dev_init_chips), 758 | BS_INIT_ENTRY(BS_DEV_ENUMERATE, bs_dev_enumerate), 759 | BS_INIT_ENTRY(BS_DEV_RESOURCES, bs_dev_resources), 760 | BS_INIT_ENTRY(BS_DEV_ENABLE, bs_dev_enable), 761 | BS_INIT_ENTRY(BS_DEV_INIT, bs_dev_init), 762 | BS_INIT_ENTRY(BS_POST_DEVICE, bs_post_device), 763 | BS_INIT_ENTRY(BS_OS_RESUME_CHECK, bs_os_resume_check), 764 | BS_INIT_ENTRY(BS_OS_RESUME, bs_os_resume), 765 | BS_INIT_ENTRY(BS_WRITE_TABLES, bs_write_tables), 766 | BS_INIT_ENTRY(BS_PAYLOAD_LOAD, bs_payload_load), 767 | BS_INIT_ENTRY(BS_PAYLOAD_BOOT, bs_payload_boot), 768 | }; 769 | ``` 770 | 771 | ### 启动步骤定制 772 | 773 | 特定平台可以对启动步骤进行定制,主要是在步骤前后添加回调对象。**boot_states**中的对象,前后回调是空的。 774 | 775 | 通过**boot_state_init_entry**结构体描述,启动步骤的什么位置需要调用那个方法。 776 | 777 | ```c 778 | struct boot_state_init_entry { 779 | boot_state_t state; /* 指定步骤 */ 780 | boot_state_sequence_t when; /* 指定步骤的前后 */ 781 | struct boot_state_callback bscb; /* 回调对象 */ 782 | }; 783 | ``` 784 | 785 | 软件通过扫描所有的**boot_state_init_entry**对**boot_states**中的**phases**初始化 786 | 787 | 为了软件便于访问到所有的**boot_state_init_entry**对象,定义了如下宏 788 | 789 | ```c 790 | //定义了一个属性,声明成此属性的变量将被存放到.bs_init段中 791 | #define BOOT_STATE_INIT_ATTR __attribute__ ((used, section(".bs_init"))) 792 | 793 | //定义一个boot_state_init_entry结构体,并把它的指针放到.bs_init段中 794 | #define BOOT_STATE_INIT_ENTRY(state_, when_, func_, arg_) \ 795 | static struct boot_state_init_entry func_ ##_## state_ ##_## when_ = \ 796 | { \ 797 | .state = state_, \ 798 | .when = when_, \ 799 | .bscb = BOOT_STATE_CALLBACK_INIT(func_, arg_), \ 800 | }; \ 801 | static struct boot_state_init_entry * \ 802 | bsie_ ## func_ ##_## state_ ##_## when_ BOOT_STATE_INIT_ATTR = \ 803 | &func_ ##_## state_ ##_## when_; 804 | ``` 805 | 806 | 链接脚本中做了如下处理,开放.bs_init段的起始符号,并在指针的结尾放了一个0。这样访问到空指针就可以结束了。脚本位于**src/lib/program.ld** 807 | 808 | ``` 809 | 110 . = ALIGN(ARCH_POINTER_ALIGN_SIZE); 810 | 111 _bs_init_begin = .; 811 | 112 KEEP(*(.bs_init)); 812 | 113 LONG(0); 813 | ``` 814 | 815 | 然后通过**boot_state_schedule_static_entries**函数绑定**boot_state_init_entry**到**boot_states** 816 | 817 | ```c 818 | static void boot_state_schedule_static_entries(void) 819 | { 820 | extern struct boot_state_init_entry *_bs_init_begin[]; 821 | struct boot_state_init_entry **slot; 822 | 823 | /* 扫描所有的boot_state_init_entry结构体指针 */ 824 | for (slot = &_bs_init_begin[0]; *slot != NULL; slot++) { 825 | struct boot_state_init_entry *cur = *slot; 826 | 827 | if (cur->when == BS_ON_ENTRY) 828 | boot_state_sched_on_entry(&cur->bscb, cur->state); 829 | else 830 | boot_state_sched_on_exit(&cur->bscb, cur->state); 831 | } 832 | } 833 | ``` 834 | 835 | # coreboot riscv实现当前存在的问题 836 | 837 | ## 多核 838 | 839 | 当前由于不确定系统调用接口问题,导致不知道那些寄存器可以改变,现在保存了所有的现场导致没有更多的寄存器去处理当前那些寄存器可以使用。这部分讨论可以参见**https://mail.coreboot.org/pipermail/coreboot/2017-May/084378.html**,相关回复引用如下: 840 | 841 | > I never tested the code on an SMP system or properly thought through supporting SMP, but I remember the following problem: I wanted to give each hart its own stack (IOW its own initial stack pointer value) to avoid race conditions, but I didn't have enough free registers to calculate it. So I gave up and only allowed hart 0 to do SBI calls. What I didn't think about is that I could *probably* use all registers that are defined as caller-saved by the user-level spec. ("probably" because I don't know if that's a valid thing to do. The Privileged Spec should specify which registers are saved by the M-mode code across SBI calls, and which aren't.) 842 | 843 | ## FDT 844 | 845 | coreboot当前使用configstring来传入配置信息,未来涉想使用类似ARM的FDT来实现。这部分讨论可以参见https://mail.coreboot.org/pipermail/coreboot/2017-June/084534.html,相关回复引用如下: 846 | 847 | 848 | >On Mon, Jun 12, 2017 at 12:13 AM 王翔 wrote: 849 | > 850 | >> 851 | >> The configstring implement by spike or other SOC? 852 | >> 853 | > 854 | >yes. 855 | > 856 | >But it seems they will eventually use FDT, since most of the people in the 857 | >discussion think Linux compatibility is the single most important thing. 858 | >ron 859 | 860 | ## SBI 861 | 862 | SBI为操作系统调用接口,此部分当前基本没有实现,不清除具体需要实现那些功能。 863 | 864 | 865 | -------------------------------------------------------------------------------- /docs/riscv/hifiveunleashed_coreboot_notes-en.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | This article is a breif intro about how to run [coreboot](https://www.coreboot.org/) + BBL/opensbi (provide SBI support) + Linux kernel on [HiFive Unleashed](https://www.sifive.com/boards/hifive-unleashed). 4 | 5 | The current coreboot version( Jan 7 2019) is not able to run linux kernel on HiFive Unleashed yet. We've been using the workaround version([BBL provide SBI support](https://github.com/hardenedlinux/coreboot-HiFiveUnleashed/tree/HiFive-Unleashed-Test-Change) / [opensbi provide SBI support](https://github.com/hardenedlinux/coreboot-HiFiveUnleashed/tree/opensbi-test)) for test provided by HardenedLinux and BBL/linux provided by SiFive. Plz note that we will continue to upstreaming Unleashed code to the coreboot. W/ many thanks to Jonathan Neuschäfer, Philipp Hug and Ron Minnich. 6 | 7 | The difference of the boot process between sifive's firmware and coreboot 8 | 9 | ``` 10 | sifive's original firmware boot process: 11 | +-----------+ 12 | +------+ +------+ +------+ | BBL | 13 | | MSEL |--->| ZSBL |--->| FSBL |--->| +-------+ 14 | +------+ +------+ +------+ | | linux | 15 | +---+-------+ 16 | 17 | coreboot boot process: 18 | +---------------------------------------------------------------------+ 19 | | coreboot | 20 | +------+ +------+ | +-----------+ +----------+ +----------+ +-----------------------+ 21 | | MSEL |-->| ZSBL |-->| | bootblock |->| romstage |->| ramstage |->| payload (BBL/opensbi) | 22 | +------+ +------+ | +-----------+ +----------+ +----------+ | +-------+ | 23 | | | | linux | | 24 | +---------------------------------------------+-------------+-------+-+ 25 | ``` 26 | 27 | # BBL provide SBI support 28 | 29 | ## Get the source code 30 | 31 | ```bash 32 | git clone -b HiFive-Unleashed-Test-Change git@github.com:hardenedlinux/coreboot-HiFiveUnleashed.git 33 | git clone git@github.com:sifive/freedom-u-sdk.git 34 | ``` 35 | 36 | ## Build BBL 37 | 38 | Because coreboot will occupy a portion of the memory starting at 0x80000000, BBL cannot run from address 0x80000000. So we need to adjust the starting address of the BBL. Modify freedom-u-sdk/riscv-pk/bbl/bbl.lds as follows: 39 | 40 | ```diff 41 | diff --git a/bbl/bbl.lds b/bbl/bbl.lds 42 | index 2fd0d7c..181f3ff 100644 43 | --- a/bbl/bbl.lds 44 | +++ b/bbl/bbl.lds 45 | @@ -10,7 +10,7 @@ SECTIONS 46 | /*--------------------------------------------------------------------*/ 47 | 48 | /* Begining of code and text segment */ 49 | - . = 0x80000000; 50 | + . = 0x82000000; 51 | _ftext = .; 52 | PROVIDE( eprol = . ); 53 | ``` 54 | 55 | Type make to compile. BBL's elf image is located at freedom-u-sdk/work/riscv-pk/bbl 56 | 57 | ## Build coreboot 58 | 59 | ### Build toolchain 60 | 61 | ```bash 62 | make crossgcc-riscv 63 | ``` 64 | 65 | ### Configuration 66 | 67 | ```bash 68 | make menuconfig 69 | ``` 70 | 71 | - Mainboard->Mainboard vendor, select **SiFive** 72 | - Mainboard->Mainboard model, select **HiFive Unleashed** 73 | - Chipset->Privilege level for payload, select **payload running in m-mode** 74 | - Payload->Add a payload, select **An ELF executable payload** 75 | - Payload->Payload path and filename, Use default value **payload.elf** 76 | 77 | ### Compile 78 | 79 | Copy BBL image from freedom-u-sdk/work/riscv-pk/bbl to coreboot/payload.elf, then type make to compile. 80 | 81 | # opensbi provide SBI support 82 | 83 | ## Get the source code 84 | 85 | ```bash 86 | git clone -b opensbi-test git@github.com:hardenedlinux/coreboot-HiFiveUnleashed.git 87 | git clone git@github.com:sifive/freedom-u-sdk.git 88 | ``` 89 | 90 | ## Build linux kernel 91 | 92 | Type make to compile. linux's elf image is located at freedom-u-sdk/work/linux/vmlinux-stripped 93 | 94 | ## Build coreboot 95 | 96 | ### Build toolchain 97 | 98 | ```bash 99 | make crossgcc-riscv 100 | ``` 101 | 102 | ### Configuration 103 | 104 | ```bash 105 | make menuconfig 106 | ``` 107 | 108 | - Mainboard->Mainboard vendor, select **SiFive** 109 | - Mainboard->Mainboard model, select **HiFive Unleashed** 110 | - Payload->Add a payload, select **An linux binary payload** 111 | - Payload->Payload path and filename, Use default value **payload.bin** 112 | 113 | ### Compile 114 | 115 | Create linux image: `riscv64-elf-objcopy -O binary freedom-u-sdk/work/linux/vmlinux-stripped coreboot/payload.bin`, then type make to compile. 116 | 117 | # Burn 118 | 119 | There are two ways to burn coreboot, write to spi flash or sdcard. 120 | 121 | ## Burn to sdcard 122 | 123 | ```bash 124 | sudo dd build/coreboot.rom /dev/sdx 125 | ``` 126 | 127 | **/dev/sdx** is the device of your sd card reader 128 | 129 | ## Burn to spi flash 130 | 131 | ### Get the original firmware 132 | 133 | ```bash 134 | wget https://static.dev.sifive.com/dev-kits/hifive-unleashed/hifive-unleashed-firmware-1.0.zip 135 | ``` 136 | 137 | ### Start the original firmware from the sd card 138 | 139 | Unzip the original firmware and burn hifive-unleashed-a00-A.B-YYYY-MM-DD.gpt to the sd card. 140 | 141 | ```bash 142 | sudo dd if=hifive-unleashed-a00-A.B-YYYY-MM-DD.gpt of=/dev/sdx 143 | ``` 144 | 145 | **/dev/sdx** is the device of your sd card reader 146 | 147 | Turn the MSEL DIP switch to 11, connect the USB and network cable, and connect ttyUSB1 via minicom (baud rate 115200 8N1), then press the reset button to restart. 148 | 149 | Then log in the Linux by terminal, username: root, password: sifive. In my test the network can not be automatically configured, you need to enter the following command by yourself. 150 | 151 | ```bash 152 | /etc/init.d/S40network restart 153 | ``` 154 | 155 | ### Burn to spi flash 156 | 157 | ```bash 158 | scp coreboot/build/coreboot.rom root@$target_ip:/tmp/ 159 | ssh root@$target_ip "/usr/sbin/flashcp -v /tmp/coreboot.rom /dev/mtd0" 160 | ``` 161 | 162 | **target_ip** is the IP of HiFive Unleashed 163 | 164 | # Testing 165 | 166 | Turn the MSEL DIP switch to 15/11 (15 for boot from spi flash, 11 for boot from sdcard), connect the USB and network cable, and connect ttyUSB1 via minicom (baud rate 115200 8N1), then press the reset button to restart. Then you will see the log in the terminal. 167 | 168 | Linux username: root, password: sifive. 169 | -------------------------------------------------------------------------------- /docs/riscv/hifiveunleashed_coreboot_notes.md: -------------------------------------------------------------------------------- 1 | # 概要 2 | 3 | 这篇文章主要用于介绍如何在[HiFive Unleashed](https://www.sifive.com/boards/hifive-unleashed)上运行[coreboot](https://www.coreboot.org/) + BBL/opensbi(提供Supervisor Binary Interface支持)+ Linux。 4 | 5 | 当前coreboot的社区版本还不能支持linux的运行,这里使用hardenedlinux的测试版本。linux和BBL使用sifive提供的版本。非常感谢Jonathan Neuschäfer, Philipp Hug and Ron Minnich等社区黑客的支持。 6 | 7 | sifive原厂固件和coreboot启动过程差异 8 | ``` 9 | sifive原厂固件启动过程: 10 | +-----------+ 11 | +------+ +------+ +------+ | BBL | 12 | | MSEL |--->| ZSBL |--->| FSBL |--->| +-------+ 13 | +------+ +------+ +------+ | | linux | 14 | +---+-------+ 15 | 16 | coreboot启动过程: 17 | +---------------------------------------------------------------------+ 18 | | coreboot | 19 | +------+ +------+ | +-----------+ +----------+ +----------+ +-----------------------+ 20 | | MSEL |-->| ZSBL |-->| | bootblock |->| romstage |->| ramstage |->| payload (BBL/opensbi) | 21 | +------+ +------+ | +-----------+ +----------+ +----------+ | +-------+ | 22 | | | | linux | | 23 | +---------------------------------------------+-------------+-------+-+ 24 | ``` 25 | 26 | # 通过BBL支持SBI 27 | ## 源码获取 28 | 29 | ```bash 30 | git clone -b HiFive-Unleashed-Test-Change git@github.com:hardenedlinux/coreboot-HiFiveUnleashed.git 31 | git clone git@github.com:sifive/freedom-u-sdk.git 32 | ``` 33 | 34 | ## 编译BBL镜像 35 | 36 | 因为coreboot会占用一部分内存,所以bbl不能从0x80000000处开始运行,需要往后移动一点。这里需要对freedom-u-sdk/riscv-pk/bbl/bbl.lds进行修正,修正如下: 37 | 38 | ```diff 39 | diff --git a/bbl/bbl.lds b/bbl/bbl.lds 40 | index 2fd0d7c..181f3ff 100644 41 | --- a/bbl/bbl.lds 42 | +++ b/bbl/bbl.lds 43 | @@ -10,7 +10,7 @@ SECTIONS 44 | /*--------------------------------------------------------------------*/ 45 | 46 | /* Begining of code and text segment */ 47 | - . = 0x80000000; 48 | + . = 0x82000000; 49 | _ftext = .; 50 | PROVIDE( eprol = . ); 51 | ``` 52 | 53 | 然后执行make编译。bbl的elf镜像位于freedom-u-sdk/work/riscv-pk/bbl 54 | 55 | ## 编译coreboot 56 | 57 | ### 第一步需要编译出riscv工具链 58 | 59 | ```bash 60 | make crossgcc-riscv 61 | ``` 62 | 63 | ### 配置编译选项 64 | 65 | ```bash 66 | make menuconfig 67 | ``` 68 | 69 | - Mainboard->Mainboard vendor,选中**SiFive** 70 | - Mainboard->Mainboard model,选中**HiFive Unleashed** 71 | - Chipset->Privilege level for payload,选中**payload running in m-mode** 72 | - Payload->Add a payload,选中**An ELF executable payload** 73 | - Payload->Payload path and filename,使用默认值**payload.elf** 74 | 75 | ### 编译 76 | 77 | 拷贝bbl镜像freedom-u-sdk/work/riscv-pk/bbl到coreboot/payload.elf,然后在coreboot下执行make 78 | 79 | # 通过opensbi支持SBI 80 | 81 | ## 获取源码 82 | 83 | ```bash 84 | git clone -b opensbi-test git@github.com:hardenedlinux/coreboot-HiFiveUnleashed.git 85 | git clone git@github.com:sifive/freedom-u-sdk.git 86 | ``` 87 | ## 编译linux 88 | 89 | 在freedom-u-sdk目录下执行make。linux的elf镜像位于freedom-u-sdk/work/linux/vmlinux-stripped 90 | 91 | ## 编译coreboot 92 | ### 第一步需要编译出riscv工具链 93 | 94 | ```bash 95 | make crossgcc-riscv 96 | ``` 97 | 98 | ### 配置编译选项 99 | 100 | ```bash 101 | make menuconfig 102 | ``` 103 | 104 | - Mainboard->Mainboard vendor,选中**SiFive** 105 | - Mainboard->Mainboard model,选中**HiFive Unleashed** 106 | - Payload->Add a payload,选中**An linux binary payload** 107 | - Payload->Payload path and filename,使用默认值**payload.bin** 108 | 109 | ### 编译 110 | 111 | 生成linux镜像`riscv64-elf-objcopy -O binary freedom-u-sdk/work/linux/vmlinux-stripped coreboot/payload.bin`,然后在coreboot下执行make 112 | 113 | # 烧录 114 | 115 | 有两种方式烧录coreboot,烧录到spi flash或sdcard 116 | 117 | ## 烧录到sdcard 118 | 119 | ```bash 120 | sudo dd build/coreboot.rom /dev/sdx 121 | ``` 122 | 123 | **/dev/sdx**是你的sd卡设备号 124 | 125 | ## 烧录到spi flash 126 | 127 | ### 获取原厂固件 128 | 129 | ```bash 130 | wget https://static.dev.sifive.com/dev-kits/hifive-unleashed/hifive-unleashed-firmware-1.0.zip 131 | ``` 132 | 133 | ### 从sd卡启动原厂固件 134 | 135 | 解压下载到的原厂固件,把hifive-unleashed-a00-A.B-YYYY-MM-DD.gpt烧写到sd卡 136 | 137 | ```bash 138 | sudo dd if=hifive-unleashed-a00-A.B-YYYY-MM-DD.gpt of=/dev/sdx 139 | ``` 140 | 141 | **/dev/sdx**是你的sd卡设备号 142 | 143 | 把MSEL拨到11,连接USB和网线,电脑通过minicom连接ttyUSB1(波特率115200 8N1),按复位键重启HiFive Unleashed 144 | 145 | 然后在终端登陆linux(用户名:root,密码:sifive),在我测试中网络不能上电启动需要自己输入以下命令启动网络 146 | 147 | ```bash 148 | /etc/init.d/S40network restart 149 | ``` 150 | 151 | ### 烧写coreboot到spi flash 152 | 153 | ```bash 154 | scp coreboot/build/coreboot.rom root@$target_ip:/tmp/ 155 | ssh root@$target_ip "/usr/sbin/flashcp -v /tmp/coreboot.rom /dev/mtd0" 156 | ``` 157 | 158 | 其中,**target_ip**为HiFive Unleashed获取到的ip地址 159 | 160 | # 测试 161 | 162 | 把MSEL拨到15或11(15用于从spi flash启动,11用于从sd卡启动),连接USB和网线,电脑通过minicom连接ttyUSB1(波特率115200 8N1),按复位键重启HiFive Unleashed,这时在终端将看到启动的log 163 | 164 | linux用户名:root,密码:sifive 165 | -------------------------------------------------------------------------------- /docs/riscv/opensbi代码分析.md: -------------------------------------------------------------------------------- 1 | # opensbi代码分析 2 | 3 | riscv约定了一个操作系统二进制接口,被称为[SBI(RISC-V Supervisor Binary Interface Specification)](https://github.com/riscv/riscv-sbi-doc/blob/master/riscv-sbi.adoc)。opensbi是SBI的一个开源实现。本文用于分析opensbi的源码结构。 4 | 5 | ## 固件型态 6 | 7 | opensbi,接受前一阶段传递过来的参数: 8 | 9 | - a0:hartid,核心的id编号 10 | - a1:next_arg1,opensbi执行完成后,需要把这个参数传递给下一个阶段 11 | - a2:opensbi dynamic型态固件额外的信息 12 | 13 | opensbi,下一阶段可以获得的参数: 14 | 15 | - a0:hartid,核心的id编号 16 | - a1:arg1,opensbi接受到的参数a1 17 | 18 | opensbi可以以三种型态工作: 19 | 20 | - jump,opensbi在执行完成后跳转到一个固定的地址 21 | - payload,opensbi下一阶段要执行的程序被作为payload链接到opensbi 22 | - dynamic,下一阶段的信息由额外的寄存器传递给opensbi 23 | 24 | ## 程序入口 firmware/fw_base.S 25 | 26 | 此文件主要执行以下操着 27 | 28 | ``` 29 | +------------------+ 30 | | select boot hart | 31 | +------------------+ 32 | | 33 | +----------------+--------------------+ 34 | ↓ boot hart Non-boot hart | 35 | +------------------------------+ | 36 | | move opensbi to link address | ↓ 37 | +------------------------------+ +--------------------+ 38 | | | waitting move done | 39 | ↓ +--------------------+ 40 | +---------------+ | 41 | | init scratch | | 42 | +---------------+ | 43 | ↓ | 44 | +-----------+ | 45 | | init bss | | 46 | +-----------+ | 47 | ↓ | 48 | +----------------------------+ | 49 | | move fdt to target address | | 50 | +----------------------------+ +------------------------+ 51 | | | watting boot hart done | 52 | | +------------------------+ 53 | | | 54 | +----------------+--------------------+ 55 | ↓ 56 | +------------------+ 57 | | init sp/mscratch | 58 | +------------------+ 59 | ↓ 60 | +-----------+ 61 | | init trap | 62 | +-----------+ 63 | ↓ 64 | +---------------+ 65 | | call sbi_init | 66 | +---------------+ 67 | ``` 68 | 69 | `sbi_init`是opensbi的c语言程序入口 70 | 71 | opensbi的固件型态,是通过在入口程序中添加的一些hook来实现的,hook方法如下 72 | 73 | ```c 74 | /* 用于选择boot_hart 75 | * 放回-1,表示没有指定boot_hart,fw_base.S通过原子操着选择boot_hart 76 | */ 77 | uintptr_t fw_boot_hart(); 78 | 79 | /* 用于保存前一阶段传递过来的信息 */ 80 | void fw_save_info(uintptr_t a0,uintptr_t a1,uintptr_t a2); 81 | 82 | /* 前一阶段传递过来的参数a1 */ 83 | uintptr_t fw_prev_arg1(); 84 | 85 | /* 传递给下一阶段的参数a1 */ 86 | uintptr_t fw_next_arg1(); 87 | 88 | /* 下一阶段程序的地址 */ 89 | uintptr_t fw_next_addr(); 90 | 91 | /* 下一阶段程序的运行模式 */ 92 | uintptr_t fw_next_mode(); 93 | 94 | /* opensbi的选项 */ 95 | uintptr_t fw_options(); 96 | ``` 97 | 98 | 以上描述使用了伪代码,这些函数使用汇编实现,上下文保护请参考源码 99 | 100 | ## hart id 不连续问题 101 | 102 | 根据riscv spec规定,必须具备hart id为0的hart用于早期初始化,但hart id不需要连续。为了获得一个连续的索引,opensbi添加了一个表`platform->hart_index2id`,用于记录id和索引的关系 103 | 104 | ## 内存布局 105 | 106 | 内存布局,下图中的fireware为可执行程序的镜像,其中的hart n(n为hart index,不是hart id) 107 | 108 | ``` 109 | +------+----------------------------- 110 | | | ↑ 111 | | | | 112 | | | | 113 | | | firmware 114 | | | | 115 | | | | 116 | | | ↓ 117 | +------+----------------------------- 118 | | | ↑ 119 | | | | 120 | | | stack of hart n 121 | | | | 122 | +------+-------- | 123 | | | ↑ | 124 | | | scratch | 125 | | | ↓ ↓ 126 | +------+----------------------------- 127 | | | ↑ 128 | | | | 129 | | | stack of hart n - 1 130 | | | | 131 | +------+-------- | 132 | | | ↑ | 133 | | | scratch | 134 | | | ↓ ↓ 135 | +------+----------------------------- 136 | | | ↑ 137 | | | | 138 | | | stack of hart n - 2 139 | | | | 140 | +------+-------- | 141 | | | ↑ | 142 | | | scratch | 143 | | | ↓ ↓ 144 | +------+----------------------------- 145 | . 146 | . 147 | . 148 | +------+----------------------------- 149 | | | ↑ 150 | | | | 151 | | | stack of hart 0 152 | | | | 153 | +------+-------- | 154 | | | ↑ | 155 | | | scratch | 156 | | | ↓ ↓ 157 | +------+----------------------------- 158 | ``` 159 | 160 | scratch是一个内存块,开始位置保存了如下一个结构体 161 | 162 | ```c 163 | struct sbi_scratch { 164 | /** 固件的起始地址 */ 165 | unsigned long fw_start; 166 | /** 固件的大小 */ 167 | unsigned long fw_size; 168 | /** opensbi运行结束后下一个阶段的arg1,rag0为hart id */ 169 | unsigned long next_arg1; 170 | /** opensbi运行结束后下一个阶段的起始地址 */ 171 | unsigned long next_addr; 172 | /** opensbi运行结束后下一个阶段的运行模式(特权等级) */ 173 | unsigned long next_mode; 174 | /** 热启动地址 */ 175 | unsigned long warmboot_addr; 176 | /** 平台相关的数据结构的地址 */ 177 | unsigned long platform_addr; 178 | /** 函数的地址,用于把hart转换为scratch地址 */ 179 | unsigned long hartid_to_scratch; 180 | /** 临时存储 */ 181 | unsigned long tmp0; 182 | /** opensbi的选项 */ 183 | unsigned long options; 184 | } __packed; 185 | ``` 186 | 187 | ## 堆内存 188 | 189 | scratch当前保留了4k空间,当前只存放了一个结构体`struct sbi_scratch`,剩余的空间用来作堆使用。在src/sbi_scratch.c中实现了堆内存申请。 190 | 191 | ```c 192 | unsigned long sbi_scratch_alloc_offset(unsigned long size, const char *owner) 193 | { 194 | u32 i; 195 | void *ptr; 196 | unsigned long ret = 0; 197 | struct sbi_scratch *rscratch; 198 | 199 | /* 200 | * We have a simple brain-dead allocator which never expects 201 | * anything to be free-ed hence it keeps incrementing the 202 | * next allocation offset until it runs-out of space. 203 | * 204 | * In future, we will have more sophisticated allocator which 205 | * will allow us to re-claim free-ed space. 206 | */ 207 | 208 | if (!size) 209 | return 0; 210 | 211 | if (size & (__SIZEOF_POINTER__ - 1)) 212 | size = (size & ~(__SIZEOF_POINTER__ - 1)) + __SIZEOF_POINTER__; 213 | 214 | spin_lock(&extra_lock); 215 | 216 | if (SBI_SCRATCH_SIZE < (extra_offset + size)) 217 | goto done; 218 | 219 | ret = extra_offset; 220 | extra_offset += size; 221 | 222 | done: 223 | spin_unlock(&extra_lock); 224 | 225 | if (ret) { 226 | for (i = 0; i < SBI_HARTMASK_MAX_BITS; i++) { 227 | rscratch = sbi_hartid_to_scratch(i); 228 | if (!rscratch) 229 | continue; 230 | ptr = sbi_scratch_offset_ptr(rscratch, ret); 231 | sbi_memset(ptr, 0, size); 232 | } 233 | } 234 | 235 | return ret; 236 | } 237 | ``` 238 | 239 | 此方法在每一个hart的scratch空间申请一个内存,不支持释放,一般用于多核通讯。返回值是相对与scratch的偏移量 240 | 241 | ## 平台相关 242 | 243 | opensbi为了移植和适配,把平台相关信息保存到一个变量`platform`中,类型如下 244 | 245 | ```c 246 | /** Representation of a platform */ 247 | struct sbi_platform { 248 | /** 249 | * OpenSBI version this sbi_platform is based on. 250 | * It's a 32-bit value where upper 16-bits are major number 251 | * and lower 16-bits are minor number 252 | */ 253 | u32 opensbi_version; 254 | /** 255 | * OpenSBI platform version released by vendor. 256 | * It's a 32-bit value where upper 16-bits are major number 257 | * and lower 16-bits are minor number 258 | */ 259 | u32 platform_version; 260 | /** Name of the platform */ 261 | char name[64]; 262 | /** Supported features */ 263 | u64 features; 264 | /** Total number of HARTs */ 265 | u32 hart_count; 266 | /** Per-HART stack size for exception/interrupt handling */ 267 | u32 hart_stack_size; 268 | /** Pointer to sbi platform operations */ 269 | unsigned long platform_ops_addr; 270 | /** Pointer to system firmware specific context */ 271 | unsigned long firmware_context; 272 | /** 273 | * HART index to HART id table 274 | * 275 | * For used HART index : 276 | * hart_index2id[] = some HART id 277 | * For unused HART index : 278 | * hart_index2id[] = -1U 279 | * 280 | * If hart_index2id == NULL then we assume identity mapping 281 | * hart_index2id[] = 282 | * 283 | * We have only two restrictions: 284 | * 1. HART index < sbi_platform hart_count 285 | * 2. HART id < SBI_HARTMASK_MAX_BITS 286 | */ 287 | const u32 *hart_index2id; 288 | } __packed; 289 | ``` 290 | 291 | 平台相关操着保存在`struct sbi_platform_operations`结构体中,结构体地址保存在`platform->platform_ops_addr` 292 | 293 | ## 初始化过程 294 | 295 | ``` 296 | +--------------------------------------+ 297 | | select a core to perform a cold boot | 298 | +--------------------------------------+ 299 | | 300 | +------------------+-------------------+ 301 | ↓ cold boot warm boot | 302 | +------------------------------+ | 303 | | init hartid_to_scratch_table | | 304 | +------------------------------+ | 305 | ↓ | 306 | +----------+ | 307 | | hms init | | 308 | +----------+ | 309 | ↓ | 310 | +-------------------+ | 311 | | system early init | | 312 | +-------------------+ | 313 | ↓ | 314 | +-----------+ | 315 | | hart init | | 316 | +-----------+ | 317 | ↓ | 318 | +--------------+ | 319 | | console init | | 320 | +--------------+ | 321 | ↓ | 322 | +--------------+ | 323 | | irqchip init | | 324 | +--------------+ | 325 | ↓ | 326 | +----------+ | 327 | | ipi init | | 328 | +----------+ | 329 | ↓ | 330 | +----------+ | 331 | | tlb init | | 332 | +----------+ | 333 | ↓ | 334 | +------------+ | 335 | | timer init | | 336 | +------------+ | 337 | ↓ | 338 | +------------+ | 339 | | ecall init | | 340 | +------------+ | 341 | ↓ | 342 | +-------------------+ | 343 | | system final init | | 344 | +-------------------+ | 345 | ↓ | 346 | +---------------------+ | 347 | | wake_coldboot_harts | | 348 | +---------------------+ ↓ 349 | | +------------------------+ 350 | | | waitting coldboot done | 351 | ↓ +------------------------+ 352 | +-------------------+ ↓ 353 | | run to next stage | +----------+ 354 | +-------------------+ | hsm init | 355 | +----------+ 356 | ↓ 357 | +-------------------+ 358 | | system early init | 359 | +-------------------+ 360 | ↓ 361 | +-----------+ 362 | | hart init | 363 | +-----------+ 364 | ↓ 365 | +--------------+ 366 | | irqchip_init | 367 | +--------------+ 368 | ↓ 369 | +----------+ 370 | | ipi init | 371 | +----------+ 372 | ↓ 373 | +----------+ 374 | | tlb init | 375 | +----------+ 376 | ↓ 377 | +------------+ 378 | | timer init | 379 | +------------+ 380 | ↓ 381 | +-------------------+ 382 | | system final init | 383 | +-------------------+ 384 | ↓ 385 | +-------------------+ 386 | | run to next stage | 387 | +-------------------+ 388 | ``` 389 | 390 | 其中有两个系统预留的初始化接口,用于执行特定平台需要执行的初始化操着,句柄保存在`struct sbi_platform_operations`中 391 | 392 | ```c 393 | /** Platform early initialization */ 394 | int (*early_init)(bool cold_boot); /* 对应上图中system early init */ 395 | /** Platform final initialization */ 396 | int (*final_init)(bool cold_boot); /* 对应上图中system final init */ 397 | ``` 398 | 399 | 其中,`hart init`执行hart相关的初始化 400 | 其中,`irqchip init`用于初始化中断芯片 401 | 其中,`console init`用于初始化终端 402 | 其中,`ecall init`用于初始化SBI调用 403 | 其他的用于初始化SBI的具体的服务 404 | 405 | ## 反初始化 406 | 407 | opensbi提供了反初始化操着,用于反初始化单个hart相关资源,反初始化过程如下 408 | 409 | ``` 410 | +-------------------------+ 411 | | sbi_platform_early_exit | 412 | +-------------------------+ 413 | ↓ 414 | +----------------+ 415 | | sbi_timer_exit | 416 | +----------------+ 417 | ↓ 418 | +--------------+ 419 | | sbi_ipi_exit | 420 | +--------------+ 421 | ↓ 422 | +---------------------------+ 423 | | sbi_platform_irqchip_exit | 424 | +---------------------------+ 425 | ↓ 426 | +-------------------------+ 427 | | sbi_platform_final_exit | 428 | +-------------------------+ 429 | ↓ 430 | +--------------+ 431 | | sbi_hsm_exit | 432 | +--------------+ 433 | ``` 434 | 435 | 其中由两个系统预留的反初始化接口,用于特定平台需要执行的反初始化操着,句柄保存在`struct sbi_platform_operations`中 436 | 437 | ```c 438 | /** Platform early exit */ 439 | void (*early_exit)(void); /* 对应上图的sbi_platform_early_exit */ 440 | /** Platform final exit */ 441 | void (*final_exit)(void); /* 对应上图的sbi_platform_final_exit */ 442 | ``` 443 | 444 | 其中,`sbi_timer_exit`用于反初始化定时器 445 | 其中,`sbi_ipi_exit`用于反初始化ipi 446 | 其中,`sbi_platform_irqchip_exit`用于反初始化中断 447 | 其中,`sbi_hsm_exit`用于反初始化hsm,跳转到热启动入口,hart进入`SBI_HART_STOPPED`状态 448 | 449 | ## SBI服务相关 450 | 451 | SBI服务,通过a7指定调用的扩展号,通过a6指定调用的功能号,a0-a5用于参数传递 452 | 453 | 为了便于扩展SBI服务,系统定义了一个结构体用于描述SBI服务。结构如下 454 | 455 | ```c 456 | struct sbi_ecall_extension { 457 | struct sbi_dlist head; 458 | unsigned long extid_start; 459 | unsigned long extid_end; 460 | /* 用于判断具体的扩展号是否可用 */ 461 | int (* probe)(struct sbi_scratch *scratch, 462 | unsigned long extid, unsigned long *out_val); 463 | /* SBI服务句柄 */ 464 | int (* handle)(struct sbi_scratch *scratch, 465 | unsigned long extid, unsigned long funcid, 466 | unsigned long *args, unsigned long *out_val, 467 | struct sbi_trap_info *out_trap); 468 | }; 469 | ``` 470 | 471 | 在`lib/sbi/sbi_ecall.c`中定义了一个链表`ecall_exts_list`,用于管理SBI服务,方法如下 472 | 473 | ```c 474 | /* 注册复位,把ext加到ecall_exts_list */ 475 | int sbi_ecall_register_extension(struct sbi_ecall_extension *ext); 476 | 477 | /* 反注册,把ext从ecall_exts_list中删除 */ 478 | void sbi_ecall_unregister_extension(struct sbi_ecall_extension *ext); 479 | 480 | /* 在ecall_exts_list中查找有无扩展号为extid的扩展 */ 481 | struct sbi_ecall_extension *sbi_ecall_find_extension(unsigned long extid); 482 | 483 | /* sbi服务句柄,查找出服务并执行 */ 484 | int sbi_ecall_handler(u32 hartid, ulong mcause, struct sbi_trap_regs *regs, 485 | struct sbi_scratch *scratch); 486 | 487 | /* 把各种扩展添加到链表 */ 488 | int sbi_ecall_init(void); 489 | ``` 490 | 491 | 当前系统添加了如下扩展 492 | 493 | ```c 494 | extern struct sbi_ecall_extension ecall_base; 495 | extern struct sbi_ecall_extension ecall_legacy; 496 | extern struct sbi_ecall_extension ecall_time; 497 | extern struct sbi_ecall_extension ecall_rfence; 498 | extern struct sbi_ecall_extension ecall_ipi; 499 | extern struct sbi_ecall_extension ecall_vendor; 500 | extern struct sbi_ecall_extension ecall_hsm; 501 | ``` 502 | 503 | ### ipi 504 | 505 | riscv可以有一个映射的内存的软件中断标识位,通过向这个标识写1可以向需要的hart发送软件中断。 506 | 507 | 为了使ipi可以做更多事,系统为每个hart创建了一个`struct sbi_ipi_data`,通过置位标识哪种软件中断被触发。 508 | 509 | ```c 510 | struct sbi_ipi_data { 511 | unsigned long ipi_type; 512 | }; 513 | ``` 514 | 515 | 具体的软件中断如何处理,通过`struct sbi_ipi_event_ops`来描述 516 | 517 | ```c 518 | /** IPI event operations or callbacks */ 519 | struct sbi_ipi_event_ops { 520 | /** Name of the IPI event operations */ 521 | char name[32]; 522 | 523 | /** 524 | * Update callback to save/enqueue data for remote HART 525 | * Note: This is an optional callback and it is called just before 526 | * triggering IPI to remote HART. 527 | */ 528 | int (* update)(struct sbi_scratch *scratch, 529 | struct sbi_scratch *remote_scratch, 530 | u32 remote_hartid, void *data); 531 | 532 | /** 533 | * Sync callback to wait for remote HART 534 | * Note: This is an optional callback and it is called just after 535 | * triggering IPI to remote HART. 536 | */ 537 | void (* sync)(struct sbi_scratch *scratch); 538 | 539 | /** 540 | * Process callback to handle IPI event 541 | * Note: This is a mandatory callback and it is called on the 542 | * remote HART after IPI is triggered. 543 | */ 544 | void (* process)(struct sbi_scratch *scratch); 545 | }; 546 | ``` 547 | 548 | 这些结构体构成一个数组`ipi_ops_array`,当`sbi_ipi_data->ipi_type`的第i为为1时,通过`ipi_ops_array[i]`中的句柄来处理,其中主要的三个句柄描述如下: 549 | 550 | - update,发送ipi消息的hart在发送ipi消息之前调用此函数,用于向接受ipi消息的hart传递数据 551 | - sync,发送ipi消息的hart在发送ipi消息之后调用此函数,用于同步处理 552 | - process,接受到ipi消息的hart,执行此方法 553 | 554 | 主要接口: 555 | 556 | ```c 557 | /** 注册一个ipi操着,反回在ipi_ops_array中的位置,这个位置作为事件编号event */ 558 | int sbi_ipi_event_create(const struct sbi_ipi_event_ops *ops); 559 | 560 | /** 反注册一个ipi操着 */ 561 | void sbi_ipi_event_destroy(u32 event); 562 | 563 | /** 564 | * 发生一个ipi消息给选定的hart 565 | * hbase 为一个整数指定接受消息的起始hartid 566 | * hmask 为一个掩码,标识那些hart可以接受消息 567 | * data 是要传递给接受ipi消息的hart的数据 568 | */ 569 | int sbi_ipi_send_many(struct sbi_scratch *scratch, ulong hmask, ulong hbase, 570 | u32 event, void *data); 571 | ``` 572 | 573 | 当前创建了两种ipi: 574 | 575 | - `ipi_smode_ops`,用于向s-mode发送软件中断 576 | - `ipi_halt_ops`,用于执行hms stop 577 | 578 | ### hsm 579 | 580 | hsm为Hart State Management的简写,它提供了一种能力用来管理hart。hsm创建了一个堆空间,用于记录当前内核的状态,可以方便其他hart访问修改 581 | 582 | hart具有如下几种状态 583 | 584 | ```c 585 | /** Hart state values **/ 586 | #define SBI_HART_STOPPED 0 587 | #define SBI_HART_STOPPING 1 588 | #define SBI_HART_STARTING 2 589 | #define SBI_HART_STARTED 3 590 | #define SBI_HART_UNKNOWN 4 591 | ``` 592 | 593 | 主要接口如下 594 | 595 | ```c 596 | /** 597 | * 初始化hsm 598 | * cold boot的hart将初始化所有hart的状态,cold boot为SBI_HART_STARTING,其他hart为SBI_HART_STOPPED 599 | * warm boot将进入等待,等待ipi以及hsm status变为SBI_HART_STARTING 600 | */ 601 | int sbi_hsm_init(struct sbi_scratch *scratch, u32 hartid, bool cold_boot) 602 | 603 | 604 | /** 605 | * 唤醒一个SBI_HART_STOPPED的hart,使其进入SBI_HART_STARTING状态 606 | * saddr 为下一阶段的起始地址 607 | * priv 为下一阶段的参数 608 | */ 609 | int sbi_hsm_hart_start(struct sbi_scratch *scratch, u32 hartid, 610 | ulong saddr, ulong priv); 611 | 612 | /** 把状态从SBI_HART_STARTING切换到SBI_HART_STARTED 613 | * 此方法一般在opensbi运行完成之后执行,然后跳转到下一阶段 614 | */ 615 | void sbi_hsm_prepare_next_jump(struct sbi_scratch *scratch, u32 hartid); 616 | 617 | /** 618 | * 把状态从SBI_HART_STARTED切换到SBI_HART_STOPPING 619 | * 如果exitnow为true,将进一步把状态切换到SBI_HART_STOPPED,然后跳转到热启动入口 620 | */ 621 | int sbi_hsm_hart_stop(struct sbi_scratch *scratch, bool exitnow); 622 | ``` 623 | 624 | opensbi实现了hsm的几个sbi调用,用于查询一个hart的状态,唤醒一个hart,退出。并且在ipi调用中也实现了退出。 625 | 626 | 实现了如下功能: 627 | 628 | - hart可以通过hsm调用停止自己 629 | - hart可以通过ipi停止其他hart 630 | - hart可以通过hsm调用启动一个hart,并运行指定的程序 631 | 632 | 关机的ecall也是由hsm实现的 633 | 634 | ### timer 635 | 636 | riscv具有两个CSR寄存器mtime/mtimecmp,这两个寄存器可以用于定时器。opensbi主要实现了如下接口 637 | 638 | ```c 639 | /* 此接口用于设置mtime */ 640 | void sbi_timer_event_start(struct sbi_scratch *scratch, u64 next_event); 641 | 642 | /* 此接口用于处理定时器中断,清除m-mode中断中断标识,并触发s-mode中断(写MIP.STIP) */ 643 | void sbi_timer_process(struct sbi_scratch *scratch); 644 | 645 | /* 此接口用于初始定时器 */ 646 | int sbi_timer_init(struct sbi_scratch *scratch, bool cold_boot); 647 | 648 | /* 此接口用于关闭定时器 */ 649 | void sbi_timer_exit(struct sbi_scratch *scratch); 650 | ``` 651 | 652 | 这些接口主要在`ecall_legacy` / `ecall_time`中被使用 653 | 654 | ### fifo 655 | 656 | opensbi实现了一个先进先出的队列,结构体如下 657 | 658 | ``` 659 | struct sbi_fifo { 660 | void *queue; /* 队列使用的内存的起始地址 */ 661 | spinlock_t qlock; /* 锁,用于防止多线程并发破坏数据 */ 662 | u16 entry_size; /* 队列中存放的每个数据的大小 */ 663 | u16 num_entries; /* 队列中最多存放数据的个数,entry_size × num_entries为队列内存的大小 */ 664 | u16 avail; /* 队列中多少个数据有效 */ 665 | u16 tail; /* 最早进入队列的数据的索引 */ 666 | }; 667 | ``` 668 | 669 | 主要接口如下: 670 | 671 | ```c 672 | /** 673 | * 初始化队列 674 | * fifo用于维护记录队列的信息 675 | * queue_mem 分配给队列的内存 676 | * entries 队列中最多存放数据的个数 677 | * entry_size 队列中存放的每个数据的大小 678 | */ 679 | void sbi_fifo_init(struct sbi_fifo *fifo, void *queue_mem, u16 entries, 680 | u16 entry_size); 681 | 682 | /* 判断队列为空 */ 683 | bool sbi_fifo_is_empty(struct sbi_fifo *fifo); 684 | 685 | /* 判断队列以满 */ 686 | bool sbi_fifo_is_full(struct sbi_fifo *fifo); 687 | 688 | /* 返回队列中数据的个数 */ 689 | u16 sbi_fifo_avail(struct sbi_fifo *fifo); 690 | 691 | /* 向队列中添加一个数据 */ 692 | int sbi_fifo_enqueue(struct sbi_fifo *fifo, void *data); 693 | 694 | /* 从队列中取出一个数据 */ 695 | int sbi_fifo_enqueue(struct sbi_fifo *fifo, void *data); 696 | 697 | /** 698 | * 更新队列中的数据,此方法会遍历队列中的每一个数据 699 | * fptr为更新队列的方法 700 | * in 为更新函数的参数 701 | * data 为队列中的一个数据 702 | */ 703 | int sbi_fifo_inplace_update(struct sbi_fifo *fifo, void *in, 704 | int (*fptr)(void *in, void *data)); 705 | ``` 706 | 707 | ### tlb 708 | 709 | tlb用于在多核直接进行缓存同步,它使用了前面介绍的ipi和fifo。opensbi在每个hart创建了一个fifo用于接受tlb操作指令,并创建了一个整数用来同步。tlb操作指令定义如下 710 | 711 | ```c 712 | /* tlb操作类型 */ 713 | enum sbi_tlb_info_types { 714 | SBI_TLB_FLUSH_VMA, 715 | SBI_TLB_FLUSH_VMA_ASID, 716 | SBI_TLB_FLUSH_GVMA, 717 | SBI_TLB_FLUSH_GVMA_VMID, 718 | SBI_TLB_FLUSH_VVMA, 719 | SBI_TLB_FLUSH_VVMA_ASID, 720 | SBI_ITLB_FLUSH 721 | }; 722 | 723 | struct sbi_tlb_info { 724 | unsigned long start; /* 起始地址 */ 725 | unsigned long size; /* 大小 */ 726 | unsigned long asid; /* 虚拟内存标识 */ 727 | unsigned long type; /* tlb操作类型 */ 728 | struct sbi_hartmask smask; /* 发生指令的hart的掩码 */ 729 | }; 730 | ``` 731 | 732 | 作为一个ipi服务,主要实现了三个函数: 733 | 734 | ```c 735 | /* 用于向接受ipi的hart传递数据,添加sbi_tlb_info到目标hart的fifo */ 736 | static int sbi_tlb_update(struct sbi_scratch *scratch, 737 | struct sbi_scratch *remote_scratch, 738 | u32 remote_hartid, void *data); 739 | 740 | /* 接受ipi的hart处理ipi请求, 741 | * 请求完成后,根据sbi_tlb_info->smask向发送请求的hart同步(同步变量写1) 742 | */ 743 | static void sbi_tlb_process(struct sbi_scratch *scratch); 744 | 745 | /* 监视同步变量等待收到1 */ 746 | static void sbi_tlb_sync(struct sbi_scratch *scratch); 747 | ``` 748 | 749 | ## 异常服务 750 | 751 | 代码入口`lib/sbi/sbi_trap.c`,主要实现了以下服务 752 | 753 | 1. 时钟中断,处理定时器事件转发中断给s-mode 754 | 2. 软件中断,处理软件中断时间转发中断给s-mode 755 | 3. 非法指令异常,软件模拟一些指令 756 | 4. 非对齐内存访问,通过字节操作实现非对齐内存访问 757 | 5. ecall,sbi服务入口 758 | 6. 内存访问错误也错误,转给s-mode处理 759 | 760 | 761 | 762 | 763 | -------------------------------------------------------------------------------- /docs/riscv/riscv_security.md: -------------------------------------------------------------------------------- 1 | ## Software/Firmware/Hardware security on RISC-V 2 | 3 | [RISC-V](https://riscv.org/) is an open ISA can provide more options to build our free/libre firmware/hardware solution. RISC-V may get us rid of "too many blobs" issues, which [is nearly impossible to be resolved in x86](https://hardenedlinux.github.io/system-security/2017/03/17/debian_hardened_boot.html). RISC-V is in very early phase and there are tons of work haven't done yet. [Priviledged ISA spec](https://riscv.org/specifications/privileged-isa/) is still in "draft" stage( v1.10 for now) and security spec is not open for the public yet( maybe in 2018?). The software/firmware side of RISC-V community is growing so fast in past two years, e.g: glibc/binutils got merged already, upstreaming it into linux kernel might still need a couple of months, coreboot can support it partially. In some aspect, RISC-V may have better opportunity to build-security-in in the 1st place. Both x86 and ARM took years to bring those security mitigations into the hardware support. Plz note that those security mitigation also takes time to being utilized by GNU/Linux. Let's quick review the linux kernel defense as an exmaple: 4 | 5 | Security defense | PaX/Grsecurity | x86 | armv7 | arm64 | coreboot | 6 | |:--------------------:|:-------------------:|:------------------:|:--------------:|:-----------:|:------------:| 7 | | Non-executable stack | PAGEEXEC( 2000) | NX bit(2004) | XN bit( 2011) | UXN( 2012) | ? | 8 | | ret2usr protection | KERNEXEC( 2004) | SMEP( 2011) | PXN( 2014) | PXN( 2012) | SMRR( year?) | 9 | | ret2usr protection | UDEREF( 2007) | SMAP( 2014) | N/A | PAN( 2019?) | | 10 | | CFI | RAP( 2015) | CET( 2018?) | N/A | PA( 2019?) | | 11 | 12 | For the detail, plz read [linux kernel mitigation](https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/kernel_mitigation.md). In the best case, RISC-V could've avoid some mistakes from what x86 and ARM has made. We've been through the paradigm shift of "Attacking the core" from RING 0 to RING -3 in past deacdes. Although firmware( RING -2) and peripheral have high risk attack surfaces, but we should not forget that RING 0( kernel) is still the gate, which the attacker need to be passed through to reach the "underworld". ["The RISC-V Files: Supervisor -> Machine Privilege Escalation Exploit"](http://blog.securitymouse.com/2017/04/the-risc-v-files-supervisor-machine.html) is the 1st public example about "Attacking the Core" on RISC-V, which is an excellent research done by Don A. Bailey. From defensive side, [hardening the COREs](https://github.com/hardenedlinux/hardenedlinux_profiles/blob/master/slide/hardening_the_core.pdf) is the burden we have to carry and it'd be more easier if RISC-V hardware guys can take security into account from the early phase. 13 | 14 | Anyway, I will update this page about RISC-V security stuff from time to time. Free free to add anything about RISC-V security! 15 | 16 | ## RING -2 17 | [The RISC-V Files: Supervisor -> Machine Privilege Escalation Exploit](http://blog.securitymouse.com/2017/04/the-risc-v-files-supervisor-machine.html) 18 | -------------------------------------------------------------------------------- /docs/riscv/riscv架构.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | RISCV指令集架构,是一个模块化设计的指令集架构,具有可裁剪可扩展的特性。RISCV指令集被分为:基础指令集、标准扩展、非标准扩展 4 | 5 | 基础指令集:实现了现代编译器级操作系统相关的指令 6 | 7 | 标准扩展:主要用于加速乘除法、浮点运算、SIMD、向量运算、原子操作等 8 | 9 | 非标准扩展:这一部分是客户定制的,根据具体芯片需求添加的功能 10 | 11 | 在2.2版本的RISCV指令集架构手册中,当前指令集的版本情况如下。 12 | 13 | | 基础指令集 | 版本 | 已冻结 | 14 | | ------ | ---- | ---- | 15 | | RV32I | 2.0 | Y | 16 | | RV32E | 1.9 | N | 17 | | RV64I | 2.0 | Y | 18 | | RV128I | 1.7 | N | 19 | 20 | 21 | 22 | | 扩展指令集 | 版本 | 已冻结 | 23 | | ----- | ---- | ---- | 24 | | M | 2.0 | Y | 25 | | A | 2.0 | Y | 26 | | F | 2.0 | Y | 27 | | D | 2.0 | Y | 28 | | Q | 2.0 | Y | 29 | | L | 0.0 | N | 30 | | C | 2.0 | Y | 31 | | B | 0.0 | N | 32 | | J | 0.0 | N | 33 | | T | 0.0 | N | 34 | | P | 0.1 | N | 35 | | V | 0.2 | N | 36 | | N | 1.1 | N | 37 | 38 | 指令集及意义 39 | 40 | | 名称 | 意义 | 41 | | ------ | --------------- | 42 | | RV32I | 32位机的基础指令集 | 43 | | RV32E | 适用与嵌入式系统的32位指令集 | 44 | | RV64I | 64位机的基础指令集 | 45 | | RV128I | 128位机的基础指令集 | 46 | | M | 整数乘除法 | 47 | | A | 原子操作 | 48 | | F | 32比特浮点运算 | 49 | | D | 64比特浮点运算 | 50 | | Q | 128比特浮点运算 | 51 | | L | 十进制浮点运算 | 52 | | C | 压缩指令集 | 53 | | B | 比特位操作 | 54 | | J | 动态语言相关扩展 | 55 | | T | 事务操作 | 56 | | P | SIMD | 57 | | V | 向量操作 | 58 | | N | 用户级中断扩展 | 59 | 60 | # 寄存器 61 | 62 | 在RV32I/RV64I/RV128下可以访问32个寄存器,X0-X31。在RV32I下为64比特宽度,在RV64I下为64比特宽度,在RV128下为128比特宽度。 63 | 64 | 其在X0为**ZERO-Register**,硬链接到0,读取此寄存器的值为0,任何下入此寄存器的值被丢弃 65 | 66 | PC寄存器不可用直接访问,只能通过分支指令间接操作PC 67 | 68 | # 浮点寄存器 69 | 70 | 硬件浮点数操作使用独立的寄存器f0-f31,并具有独立的状态寄存器fcsr。 71 | 72 | # 特权等级 73 | 74 | RISC-V具有3个特权等级 75 | 76 | | 等级 | 编码 | 名字 | 简写 | 77 | | ---- | ---- | ---------------- | ---- | 78 | | 0 | 00 | User/Application | U | 79 | | 1 | 01 | Supervisor | S | 80 | | 2 | 10 | Reserved | | 81 | | 3 | 11 | Machine | M | 82 | 83 | 只有M特权是必须实现的,其他特权等级都是可选实现的,以下是特权等级实现与应用场景 84 | 85 | | 特权等级个数 | 支持的特权等级 | 应用场景 | 86 | | ------ | ------- | ---------------- | 87 | | 1 | M | 简单的嵌入式系统 | 88 | | 2 | M,U | 安全的嵌入式系统 | 89 | | 3 | M,S,U | 可以运行类似Unix OS的系统 | 90 | 91 | # CSR(Control and Status Registers) 92 | 93 | CSR(Control and Status Registers)编码进12比特的地址空间。 94 | 95 | 编码规则 96 | 97 | * CSR[11:10] 用于标记寄存器的读写权限(00/01/10可读写,11只读) 98 | * CSR[9:8] 用于标记可以访问此寄存器的最低特权等级 99 | 100 | ## 机器信息相关寄存器 101 | 102 | 以下寄存器必须实现 103 | 104 | **misa** 处理器字长信息和指令信息(包含的基础指令和扩展信息) 105 | 106 | **mvendorid** 厂商ID,非商业实现为0 107 | 108 | **mimpid** 机器架构实现版本(不包含外设信息,之和内核实现相关),非商业实现为0 109 | 110 | **mhartid** 处理器核心ID,必须有一个处理器的核心id为0 111 | 112 | ## 状态寄存器 113 | 114 | 状态寄存器用于控制和监控处理器状态 115 | 116 | **mstatus**为M特权等级下的状态寄存器 117 | 118 | **sstatus** 为S特权等级下的状态寄存器 119 | 120 | **ustatus** 为U特权等级下的状态寄存器 121 | 122 | ### 中断使能 123 | 124 | **MIE/SIE/UIE**分别用于使能各特权等级下的异常和中断 125 | 126 | ### 中断保护 127 | 128 | **MPIE/SPIE/UPIE**用于保护进入异常前的中断使能位状态 129 | 130 | **MPP/SPP**用于保护进入异常之前的特权等级。没有UPP,因为进入U特权等级的异常只能返回到U特权等级 131 | 132 | ### 字长控制 133 | 134 | **SXL/UXL**用于控制低特权等级的字长 135 | 136 | ### 虚拟内存相关 137 | 138 | **MPRV**用于使能地址转换和权限保护 139 | 140 | **MXR**使只有执行权限的内存可以加载 141 | 142 | **SUM**使S特权等级可以访问U特权等级的内存 143 | 144 | ### 虚拟化相关 145 | 146 | **TVM**(Trap Virtual Memory)等于1时,访问**satp**寄存器或者执行**SFENCE.VMA**将促发非法指令异常 147 | 148 | **TW**(Timeout Wait)等于1时,执行**WFI**指令等待特定一段时间(一般为0)后将促发非法指令异常 149 | 150 | **TSR**(Trap SRET)等于1时,在S特权等级执行**SRET**将促发非法指令异常 151 | 152 | ### 现场保护恢复加速 153 | 154 | **FS/XS/SD**用于记录加速现场保护恢复 155 | 156 | 157 | ## 向量基地址 158 | 159 | 向量基地址用于控制发生异常时的跳转地址,向量基地址必须4个字节对齐,这样低两位空闲未用。这时低两位用于设定向量地址模式 160 | 161 | **mtvec** 路由到M特权等级的异常基地址 162 | 163 | **stvec** 路由到S特权等级的异常基地址 164 | 165 | **utvec** 路由到U特权等级的异常基地址 166 | 167 | 向量模式,用于控制异常发生时的跳转地址 168 | 169 | | 值 | 名字 | 描述 | 170 | | ---- | ---- | ------------------------------ | 171 | | 0 | 直接 | 所有异常跳转到BASE(BASE为mtvec指定的地址) | 172 | | 1 | 向量 | 异步中断跳转到BASE+4*case(case为异常向量号) | 173 | | >1 | — | 保留 | 174 | 175 | ## 异常委托 176 | 177 | **medeleg**在M特权等级下的异常委托寄存器,被标记的异常将委托给S特权等级处理 178 | 179 | **sedeleg**在S特权等级下的异常委托寄存器,被标记的异常将委托给U特权等级处理 180 | 181 | **mideleg**在M特权等级下的中断委托寄存器,被标记的中断将委托给S特权等级处理 182 | 183 | **sideleg**在S特权等级下的中断委托寄存器,被标记的中断将委托给U特权等级处理 184 | 185 | ## 中断控制寄存器 186 | 187 | **mip/sip**待处理异常寄存器,用于标记那些异常需要处理 188 | 189 | **mie/sie**用于使能异常(低异常等级软件中断、时钟中断、外部中断) 190 | 191 | ## 定时器 192 | 193 | **mtime**时钟计数器 194 | 195 | **mtimecmp**时钟比较器,当**mtime > mtimecmp**时将促发中断(需要中断使能) 196 | 197 | ## 计数器 198 | 199 | **mcycle**对内核执行周期计数 200 | 201 | **minstret**用于对内核阻塞周期计数 202 | 203 | **mhpmcounter3-mhpmcounter31**额外的29个计算器 204 | 205 | **mhpmevent3-mhpmevent31**额外29个计数器的事件选择器,用于选择计数对象(0标示没有计数对象) 206 | 207 | **mcounteren/scounteren**用于使能计算器 208 | 209 | ## scratch 210 | 211 | **mscratch/sscratch/uscratch**普通的可读写的寄存器,一般用于保存上下文,可根据客户需求使用 212 | 213 | ## 异常返回地址 214 | 215 | **mepc/sepc/uepc**用于保存异常返回地址 216 | 217 | ## 异常源 218 | 219 | **mcause/scause/ucause**分别记录进入M/S/U特权等级时的异常源 220 | 221 | 异常源以整数编码,最高位标记是中断 222 | 223 | ## 异常地址 224 | 225 | **mtval/stval/utval**用于记录指令取址、储存器访问不不齐、页错误、断点时的地址信息 226 | 227 | # 特权指令 228 | 229 | ## ECALL 230 | 231 | 系统调用指令 232 | 233 | 在M/S/U特权模式下分别产生**environment-call-from-M-mode/environment-call-from-S-mode/environment-call-from-U-mode**异常 234 | 235 | ## EBREAK 236 | 237 | 断点指令,用于产生**Breakpoint**异常,用于进入调试异常 238 | 239 | ## 异常返回指令 240 | 241 | **MRET/SRET/URET**用于M/S/U特权模式退出异常 242 | 243 | ## 中断等待指令 244 | 245 | **WFI**用于等待中断发生 246 | 247 | ## SFENCE.VMA 248 | 249 | 更新同步内存管理数据结构 250 | 251 | # 复位 252 | 253 | 复位时处于M状态,MIE=0禁止中断,MPRV=0禁止内存地址转换内存保护,pc的值与具体实现相关。 254 | 255 | mcause的值与具体实现相关,在不区分复位原因的机器上为0,在区分复位原因的机器上用0标示上电复位。 256 | 257 | # 物理内存保护 258 | 259 | 物理内存保护简称**PMP(physical memory protection )**,以下描述的是标准实现,具体平台实现可能和这个有差异 260 | 261 | RV32支持34比特物理地址(16GB),RV64支持56比特物理地址(64PB) 262 | 263 | 为了支持安全处理,M特权等级支持PMP 264 | 265 | PMP可以配置16个内存地址范围,每一个配置被称为一个PMP entries 266 | 267 | 每一个PMP entries有一个8比特的配置寄存器(pmp0cfg-pmp15cfg)和一个地址寄存器(pmpaddr0–pmpaddr15) 268 | 269 | 地址寄存器在RV32下为32比特表示物理地在的[33:2],地在寄存器在RV64下为64比特但只有54比特有效表示物理地在的[55:2] 270 | 271 | ## 配置寄存器 272 | 273 | 配置寄存器用于指定物理内存的权限(读、写、执行),地址匹配模式以及锁定配置 274 | 275 | 配置寄存器pmp0cfg-pmp15cfg,在RV32下每4个一组映射到CSR(pmpcfg0、pmpcfg1、pmpcfg2、pmpcfg3),在RV64下每8个一组映射到(pmpcfg0、pmpcfg2) 276 | 277 | 配置寄存器位域及作用 278 | 279 | | 名字 | 位域 | 作用 | 280 | | :--: | :---: | ------- | 281 | | R | [0:0] | 读权限 | 282 | | W | [1:1] | 写权限 | 283 | | X | [2:2] | 执行权限 | 284 | | A | [4:3] | 地址匹配模式 | 285 | | L | [7:7] | 锁定,防止篡改 | 286 | 287 | ## 地址匹配模式 288 | 289 | 地址匹配模式跟配置寄存器中A相关 290 | 291 | | 值 | 模式名 | 描述 | 292 | | ---- | ---------------------------------------- | --------------- | 293 | | 0 | OFF | 关闭 | 294 | | 1 | TOR(Top of range) | 地址记录的为内存的上限地址 | 295 | | 2 | NA4(Naturally aligned four-byte region) | 标示4个字节的地址范围 | 296 | | 3 | NAPOT(Naturally aligned power-of-two region,>=8 bytes) | 标示大于等于4个字节的地址范围 | 297 | 298 | - OFF 299 | 300 | 即不使用当前的配置 301 | 302 | - TOR 303 | 304 | 如果第i个配置寄存器配置模式配置为TOR时有两种情况: 305 | 306 | i = 0,匹配地址 $0 < address < pmpaddr_i$ 307 | 308 | i > 0,匹配地址 $pmpaddr_{i-1} < address < pmpaddr_i$ 309 | 310 | - NA4 311 | 312 | 观察地址可以发现,地址低2比特为0。NA4即标示配置地在开始的4个字节 313 | 314 | - NAPO 315 | 316 | 此模式比较特别,具体含有如图示意 317 | 318 | | 配置 | 起始地址 | 长度 | 319 | | ---------- | ------------ | ----------- | 320 | | aaa...aaa0 | aaa...aaa000 | $2^{0 + 3}$ | 321 | | aaa...aa01 | aaa...aa0000 | $2^{1 + 3}$ | 322 | | aaa...a011 | aaa...a00000 | $2^{2 + 3}$ | 323 | 324 | 从表中看出,表示的其实就算一个高位有效的地址范围 325 | 326 | ## 物理内存保护锁 327 | 328 | PMP配置寄存器中,有一个加锁位。置1加锁,加锁后不能改写配置寄存器和地址寄存器,如果配置寄存器地址匹配模式为TOR前一个地址寄存器也不可以修改。即上锁位置1后,配置将被锁定。 329 | 330 | ## 保护和匹配逻辑 331 | 332 | 一次内存访问的地址范围必须在一个PMP地址范围内。比如一个NA4的配置(0xc-0xF),访问0x8-0xF导致失败。 333 | 334 | 如果访问内存的地址不能匹配到PMP entries,在M特权等级下可以正常访问,在S、U特权等级下将访问失败。 335 | 336 | PMP会影响所有U、S特权等级下的内存访问。如果MPRV置1,还将影响从U、S特权等级陷入M特权等级下的LOAD、STORE 337 | 338 | 339 | 340 | # 地址转换与内存保护 341 | 342 | 地在转换和内存保护,即虚拟内存、MMU 343 | 344 | RV32支持34比特器物理地址空间(16GB),RV64支持56比特物理地址空间(64PB) 345 | 346 | ## 地址转换保护寄存器 347 | 348 | **satp(Superior Address Translation and protection)** 地址保护寄存器,主要用于设置虚拟地址转换模式以及根页表位置 349 | 350 | - RV32下描述如下 351 | 352 | | 名字 | 位域 | 描述 | 353 | | :--- | :------ | ----------- | 354 | | MODE | [31:31] | 地址转换与内存保护模式 | 355 | | ASID | [30:22] | 地址空间标示符 | 356 | | PPN | [21:0] | 物理页编号 | 357 | 358 | MODE描述 359 | 360 | | 值 | 名字 | 描述 | 361 | | ---- | ---- | ---------------------- | 362 | | 0 | Base | 不使用地址转换与内存保护 | 363 | | 1 | Sv32 | 使用32比特的虚拟地址空间进行内存转换和保护 | 364 | 365 | 366 | - RV64下描述如下 367 | 368 | | 名字 | 位域 | 描述 | 369 | | :--- | ------- | ----------- | 370 | | MODE | [63:60] | 地址转换与内存保护模式 | 371 | | ASID | [59:44] | 地址空间标示符 | 372 | | PPN | [43:0] | 物理页编号 | 373 | 374 | MODE描述 375 | 376 | | 值 | 名字 | 描述 | 377 | | ----- | ---- | ------------------------ | 378 | | 0 | Base | 不使用地址转换与内存保护 | 379 | | 1 - 7 | — | 保留未用 | 380 | | 8 | Sv39 | 使用39比特的虚拟地址空间进行内存地址转换和保护 | 381 | | 9 | Sv48 | 使用48比特的虚拟地址空间进行内存地址转换和保护 | 382 | | 10 | Sv57 | 为57比特虚拟地址空间保留 | 383 | | 11 | Sv64 | 为64比特虚拟地址空间保留 | 384 | | 12-15 | — | 保留未用 | 385 | 386 | ## Sv32 32比特虚拟内存系统 387 | 388 | Sv32是RV32下的虚拟内存系统,具有2级页表,每级页表4K字节包含1024个页表描述符 389 | 390 | Sv32具有两种页4K/4M 391 | 392 | 虚拟地址、物理地址及页表描述符如下图 393 | ![Markdown](http://i2.muimg.com/1949/782f961fdb6d9453.png) 394 | 395 | 页表描述符 396 | - V 标示页表描述符有效 397 | - R 目标页可读 398 | - W 目标页可写 399 | - X 目标页可执行 400 | - U 目标页为U特权等级内存 401 | - G 全局映射 402 | - A(accessed) 标示目标页被访问过,用于标示cache状态 403 | - D(dirty) 标示目标页被修改过,用于标示cache状态 404 | - RSW 保留给S特权等级使用 405 | 406 | 目标内存页如果可写必须标记为可读 407 | 408 | 如果R、W、X全部为0标示非叶子节点,当前描述符指向下一级页表 409 | 410 | ## Sv39 39比特虚拟内存系统 411 | Sv39是RV64下的虚拟内存系统,具有2级页表,每级页表2K字节包含512个页表描述符 412 | 413 | Sv39具有4K/2M/1G的页 414 | 415 | 虚拟地址、物理地址及页表描述符如下图 416 | 417 | ![Markdown](http://i1.buimg.com/1949/21a1ba54b0d5d786.png) 418 | 419 | 页表描述符 420 | - V 标示页表描述符有效 421 | - R 目标页可读 422 | - W 目标页可写 423 | - X 目标页可执行 424 | - U 目标页为U特权等级内存 425 | - G 全局映射 426 | - A(accessed) 标示目标页被访问过,用于标示cache状态 427 | - D(dirty) 标示目标页被修改过,用于标示cache状态 428 | - RSW 保留给S特权等级使用 429 | 430 | 目标内存页如果可写必须标记为可读 431 | 432 | 如果R、W、X全部为0标示非叶子节点,当前描述符指向下一级页表 433 | 434 | 435 | 436 | ## Sv48 48比特虚拟内存系统 437 | Sv48是RV64下的虚拟内存系统,具有2级页表,每级页表2K字节包含512个页表描述符 438 | 439 | Sv48具有页4K/2M/1G/512G的页 440 | 441 | 虚拟地址、物理地址及页表描述符如下图 442 | 443 | ![Markdown](http://i1.buimg.com/1949/f5db47b6d77e7ce4.png) 444 | 445 | 页表描述符 446 | - V 标示页表描述符有效 447 | - R 目标页可读 448 | - W 目标页可写 449 | - X 目标页可执行 450 | - U 目标页为U特权等级内存 451 | - G 全局映射 452 | - A(accessed) 标示目标页被访问过,用于标示cache状态 453 | - D(dirty) 标示目标页被修改过,用于标示cache状态 454 | - RSW 保留给S特权等级使用 455 | 456 | 目标内存页如果可写必须标记为可读 457 | 458 | 如果R、W、X全部为0标示非叶子节点,当前描述符指向下一级页表 -------------------------------------------------------------------------------- /docs/secure-your-crenditial-with-nitrokey-storage-hsm-keepass.md: -------------------------------------------------------------------------------- 1 | ###Secure your crenditial with Nitrokey Storage and Nitrokey HSM and keepass 2 | 3 | keyword: Nitrokey HSM, Nitrokey Storage 4 | The best case is local computer is always secure(no way), the tokens are 5 | intact. So we don't do much to thinking about the fallback plan. But in 6 | reality... 7 |
  8 | Nitrokey HSM --------> Secure all certificate and key (for 802.1x/VPN/Web)
  9 |      |
 10 |      |
 11 |      |fallback
 12 |      +---------------> Using air gap local backup
 13 |      
 14 |                         Figure 1
 15 | 
16 | 17 |
 18 | Nitrokey Storage --->  OTP  ----> Secure Keepass(HOTP)
 19 |        |                 |                  |
 20 |        |                 |                  |fallback
 21 |        |                 |                  +--------> OTP secret
 22 |        |                 |
 23 |        |                 +-------> Secure Github(TOTP)
 24 |        |                 |                  |
 25 |        |                 |                  |fallback
 26 |        |                 |                  +--------> Secure code
 27 |        |                 |
 28 |        |                 +-------> Secure Gmail(TOTP)
 29 |        |                 |                  |
 30 |        |                 |                  |fallback
 31 |        |                 |                  +--------> Secure code, sms
 32 |        |                 |
 33 |        |                 +-------> Secure Dropbox
 34 |        |                 |                  |
 35 |        |                 |                  |fallback
 36 |        |                 |                  +--------> Secure code, sms
 37 |        |                 |                  |
 38 |        |                 |                  |backup
 39 |        |                 |                  +--------> keepass(kdbx)
 40 |        |                 |
 41 |        |                 +-------> Secure more
 42 |        |
 43 |        |
 44 |        +------------->  Storage  ---> Encrypted Storage ---> Store Keepass(kdbx)
 45 |        |
 46 |        |
 47 |        |
 48 |        |
 49 |        |
 50 |        |
 51 |        |
 52 |        |
 53 |        +------------->  OpenPGP Card ---> Store PGP key
 54 |                               |                 
 55 |                               +---------> gpg agent ---> SSH agent
 56 | 
 57 | 
 58 |                        Figure 2
 59 | 
60 |
 61 | Keepass ---> Web Service Password ---> Dropbox password
 62 |    |                   |
 63 |    |                   +-------------> Google password
 64 |    |                   |
 65 |    |                   +-------------> Github password
 66 |    |                   |
 67 |    |                   +-------------> Social account
 68 |    |                   |
 69 |    |                   +-------------> More
 70 |    |
 71 |    |
 72 |    |
 73 |    +-------> Remote Server Password
 74 | 
 75 | 
 76 |                        Figure 3
 77 | 
 78 | 
79 | 80 | 81 | ######Sence 1 82 | 83 | We lost our OTP token (Nitrokey Storage), So we need to use OTP secret, secure 84 | code or sms to recovery it. See Figure 2. In this case, we should secure our 85 | otp secret and secure code and sms carefully. 86 | 87 | For secure code, you could store in keepass(if you think you can keep it safe. 88 | My choice, using keepass with nitrokey stroage build-in OTP function) 89 | 90 | For sms, I don't trust my Cell Phone Provider and the Rogue base station 91 | attack. So I will use sms as another factor of two factor authentication and 92 | encrypted again. For example I will using GnuPG encrypt my important email. 93 | 94 | For keepass OTP secret, I will store it offline 95 | 96 | ######Sence 2 97 | 98 | In case our kdbx(keepass password database) is damage or missing. We can use Dropbox 99 | to synchronize the kdbx file and do not syncronize the key file(if you use key 100 | file rather than OTP). Or send kdbx file as attachment to your own email with 101 | PGP encrypted, after every change you make. 102 | 103 | ######Sence 3 104 | 105 | If you encrypt your email and file with GnuGP, you should secure your private 106 | key very carefully. For daily use, I will use a OpenPGP card to store my 107 | subkeys and store my private key in air-gap environment. 108 | 109 | 110 | #####Reference 111 | http://security.stackexchange.com/questions/31594/what-is-a-good-general-purpose-gnupg-key-setup 112 | -------------------------------------------------------------------------------- /docs/usb-armory-for-daily-use.md: -------------------------------------------------------------------------------- 1 | ###Getting started 2 | 3 | 4 | #####Download image 5 | 6 | https://github.com/inversepath/usbarmory/wiki/Available-images 7 | https://dev.inversepath.com/download/usbarmory/ 8 | 9 | 10 | 11 | #####Write bootable image into sdcard 12 | 13 | sudo bash -c 'xzcat usbarmory-debian_jessie-base_image-20161213.raw.xz | dd of=/dev/sd[x] bs=1M conv=fsync' 14 | 15 | Change /dev/sd[x] to your sdcard location 16 | 17 | #####Customize your own GNU/Linux distro for USBArmory 18 | 19 | Please follow the [instruction](https://github.com/inversepath/usbarmory/wiki/Preparing-a-bootable-microSD-image#kernel-linux-490) to build your own GNU/Linux distro. You can use our [hardened configs](https://github.com/hardenedlinux/embedded-iot_profile/tree/master/hardened_config) to build the kernel. Patch the [USBArmory dts](http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=17028ca2a5030a2e97f87b3290f814ba860480c0) if your kernel version < 4.9. Otherwise, make sure you use "make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-" to build the u-boot. 20 | 21 | #####Connect to USB armory 22 | 23 | Read the wiki first: `https://github.com/inversepath/usbarmory/wiki/Host-communication` 24 | Following content is copy from wiki: 25 | 26 | # bring the USB virtual Ethernet interface up 27 | ip link set enp0s20f0u1 up 28 | 29 | # set the host IP address 30 | ip addr add 10.0.0.2/24 dev enp0s20f0u1 31 | 32 | # enable masquerading for outgoing connections towards wireless interface 33 | /sbin/iptables -t nat -A POSTROUTING -s 10.0.0.1/24 -o wlp4s0 -j MASQUERADE 34 | 35 | # enable IP forwarding 36 | echo 1 > /proc/sys/net/ipv4/ip_forward 37 | 38 | For the case that you have a running firewall on your laptop: 39 | 40 | iptables -I OUTPUT -s 10.0.0.1 -j ACCEPT 41 | iptables -I FORWARD -s 10.0.0.1 -j ACCEPT 42 | iptables -I INPUT -s 10.0.0.1 -j ACCEPT 43 | iptables -I INPUT -d 10.0.0.1 -j ACCEPT 44 | iptables -I OUTPUT -d 10.0.0.1 -j ACCEPT 45 | iptables -I FORWARD -d 10.0.0.1 -j ACCEPT 46 | 47 | Using `ip` command rather than `NetworkManager` will avoid the an default route adding to your routing table. 48 | For the ip 10.0.0.2 in the host, you can visit the `https://dev.inversepath.com/download/usbarmory/` for more detials. 49 | 50 | Using OpenSSH to connect the usb armory 51 | 52 | ssh usbarmory@10.0.0.1 53 | #default password:usbarmory 54 | 55 | 56 | #####Resizing your partition 57 | Follow this article `http://base16.io/?p=61` 58 | 59 | -------------------------------------------------------------------------------- /docs/x86/coreboot/1.coreboot-x86.md: -------------------------------------------------------------------------------- 1 | # 概要 2 | 3 | 此系列文章主要分析x86架构下的coreboot,这是此系列文章的第一篇,bootblock分析。此分析基于coreboot 4.6 Intel kblrvp(rvp3)主板。rvp3使用skylake平台。 4 | 5 | # 与coreboot相关的x86知识 6 | 7 | ## CPU内建上电自检 8 | 9 | BIST(Processor Built-in Self-Test)。x86处理器在上电或复位时会进行自检,自检结构保存在EAX中。只有当EAX等于0时表示处理器通过全部自检,如果非0表示检测到处理器故障。如果平台不需要自检,EAX上电后值为0。 10 | 11 | BIST的执行开销因处理器而已。intel保留权限修改BIST的执行开销,并且不做通知。 12 | 13 | ## 启动时的寻址方式 14 | 15 | 8086/8088启动时寄存器默认值CS=0xF000,IP=0xFFF0,目标地址为0xFFF0 16 | 17 | 8086后的芯片都是32为机器,启动时芯片处于一种特别的状态:运行16位代码,使用32为内存分段机制进行内存寻址。这种状态一直持续到CS被重新赋值,而后进入真正的实模式。 18 | 19 | x86为了加速分段机制,在CPU内部集成了段描述符的缓存。这部分缓存可以看作段寄存器的不可见部分。x86启动时初始化状态如下: 20 | 21 | > CS=0xFFFF, CS.base=0xFFFF0000, CS.limit=0xFFFF, EIP=0x0000FFF0 22 | 23 | 因为使用分段机制,所以启动时第一条指令的地址:CS.base + CS.limit = 0xFFFFFFF0。 24 | 25 | ## CPU可以访问的空间 26 | 27 | ### 内存 28 | 29 | 这是CPU最常访问的空间,在大量指令中都有内存操作数 30 | 31 | #### 缓存策略 32 | 33 | - Uncacheable (UC) 不缓存 34 | - Write Combining (WC) 写合并,可以加速写操作,不能保证内存一致性 35 | - Write Through (WT) 写操作不缓存 36 | - Write Back (WB) 读写操作都缓存 37 | - Write Protected (WP) 写保护,写操作将促发异常 38 | 39 | #### 缓存控制 40 | 41 | ##### CR0.CD(Cache Disable) 42 | 43 | 为1禁止缓存 44 | 45 | 为0,启用缓存,但可能被其他缓存控制机制影响 46 | 47 | ##### CR0.NW (No Write-Back) 48 | 49 | 为1全局禁止Write-Back 50 | 51 | 为0,启用Write-Back,但可能被其他缓存控制机制影响 52 | 53 | ##### MTRRs(Memory Type Range Registers) 54 | 55 | 这是一组MSR,通过这组寄存器可以设定物理内存缓存策略。 56 | 57 | MTRRs有两种工作模式,fixed range MTRRs与variable range MTRRs。 58 | 59 | fixed range MTRRs把1M内的内存分成88个固定的范围,通过11个MSR描述他们的缓存策略 60 | 61 | variable range MTRRs通过一组寄存器描述一段内存的缓存策略PHYSBase、PHYSMask 62 | 63 | - PHYSBase用于记录内存起始地址,和内存缓存策略 64 | - PHYSMask用于记录内存长度 65 | 66 | 判断一个地址addr是否在variable range MTRRs表示范围内通过以下计算公式确定 67 | 68 | > addr & PHYSMask == PHYSBase & PHYSMask 69 | 70 | variable range MTRRs表示的内存必须4K地址对其,长度为4K*2^n 71 | 72 | ##### PAT(Page Attribute Table) 73 | 74 | 这是一组MSR,通过这组寄存器可以设定虚拟地址空间的缓存策略 75 | 76 | PAT有8个寄存器,定义了8种缓存策略 77 | 78 | Page Table Directory或Page Table Entry中的PAT、PCD、PWT会选择一个PAT寄存器 79 | 80 | PAT结合MTRRs确定最终内存缓存策略 81 | 82 | ### IO 83 | 84 | 这是x86专门为外设准备的空间,通过两条指令IN OUT访问,这段空间长度4K 85 | 86 | ### MSR 87 | 88 | MSR(Model Specific Register),为x86的配置控制寄存器。通过RDMSR、WRMSR读写MSR,一个MSR为64比特。最多可编码4G * 64bit的空间。 89 | 90 | >RDMSR EDX:EAX <- MSR[ECX] 91 | >WRMSR MSR[ECX] <- EDX:EAX 92 | 93 | ## PCI与PCIe 94 | 95 | PCI总线是一种可枚举的总线,具有灵活可热插拔的特点,是计算机内部最重要的总线结构。 96 | 97 | 特点: 98 | 99 | - 总线结构 100 | - 总线下可以挂载设备或桥,桥设备下是新的总线号 101 | - 并行通讯,由于串扰原因首频率限制速度较慢 102 | 103 | PCIe是由PCI总线发展而来,软件层面兼容PCI。 104 | 105 | 特点: 106 | 107 | - 点对点通讯,通过swicth和bridge中转信息 108 | - 差分串行全双工通讯,频率上限较高速度较快 109 | - 布线容易简化主板设计 110 | 111 | ### 编址 112 | 113 | PCI总线通过总线号(8bit)、设备号(5bit)、功能号(3bit)编址。这种方式叫做BDF编址。其中B表示Bus即总线编号,D表示Device即设备编号,F表示Function即功能编号。 114 | 115 | ### 配置空间 116 | 117 | 配置空间是PCI总线可以枚举遍历的主要原因,通过配置空间可以对设备进行控制访问。PCI具有256字节的配置空间,PCIe具有4K的配置空间。配置空间前64字节内容是固定的,后面的内容根据具体设备差异较大。前64字节有两种情况,桥和设备不同。 118 | 119 | **设备配置空间** 120 | ![设备配置空间](https://i.imgur.com/4DWl06A.png) 121 | 122 | **桥设备配置空间** 123 | ![桥设备配置空间](https://i.imgur.com/husbeg2.png) 124 | 125 | 具体字段意义如下 126 | 127 | | 字段 | 意义 | 128 | | -------------------- | ------------------------------ | 129 | | Vendor ID | 制造商ID | 130 | | Device ID | 由制造商分配的设备ID | 131 | | Revision ID | 同一个设备的不同修订版本号 | 132 | | Status | 设备状态信息 | 133 | | Command | 设备控制寄存器 | 134 | | Class code | 设备类型 | 135 | | Subclass | 设备具体类型 | 136 | | Prog IF | 一个只读寄存器,用于指定设备具有的寄存器级编程接口 | 137 | | BIST | 标记一个设备可自检 | 138 | | Base Address | 设备的内存空间映射地址(IO或MMIO),功能与具体设备相关 | 139 | | Primary Bus Number | 桥设备上端总线编号 | 140 | | Secondary Bus Number | 桥设备下端总线编号 | 141 | | Memory Base | 桥下设备映射内存的起始地址 | 142 | | Memory Lmit | 桥下设备映射内存空间的长度 | 143 | | Capabitiy Pointer | 属性指针 | 144 | 145 | **Capabitiy**为设备的功能描述信息。**Capabitiy Pointer**为第一个**Capabitiy**在配置空间的偏移量,每一个**Capabitiy**包含下一个**Capabitiy**在配置空间的偏移量,为一个设备添加一个功能只需要添加一个新的**Capabitiy**,舍弃一个功能只需要删除一个**Capabitiy**,配置比较灵活。 146 | 147 | #### 通过IO访问PCI配置空间 148 | 149 | IO方式访问配置空间,通过两个4字节IO端口实现 150 | 151 | - CONFIG_ADDRESS:地址0xCF8,长度4个字节 152 | - CONFIG_DATA:地址0xCFC,长度4个字节 153 | 154 | 通过在CONFIG\_ADDRESS中写入要访问的地址(总线、设备、功能、配置空间偏移量),再读写CONFIG\_DATA来访问配置空间。 155 | 156 | CONFIG_ADDRESS格式如下 157 | 158 | ![Imgur](https://i.imgur.com/WhqtrDk.png) 159 | 160 | Enable Bit:为1时,读写CONFIG_DATA对应访问PCI的配置空间。 161 | 162 | 其中,偏移量用7:0比特表示,所以只能访问传统的PCI的256个字节的配置空间,访问不了PCIe扩展的配置空间。并且1:0始终为0,所以访问配置空间需要4字节地址对其。 163 | 164 | 165 | #### 通过MMIO访问配置空间 166 | 167 | 由于PCIe使用8比特编码总线,5比特编码设备,3比特编码功能,每个功能具有4KByte配置空间。所以,PCI需要2^8 * 2^5 * 2^3 * 4KB = 256MB内存地址空间。 168 | 169 | 在skylake平台上,PCIe内存映射地址通过PCIe根设备(BDF编号00:0.0,x86下叫System Agent)上的一对寄存器PCIEXBAR(offset:0x60 length:4)、PCIEXBARH(offset:0x64 length:4)进行配置。 170 | 171 | PCIEXBAR 172 | 173 | 0:0 配置使能位,置1表示配置有效 174 | 175 | 2:1 配置PCI内存映射空间大小,00—>256M 01—>128M 10—>64M 11保留 176 | 177 | 26:31 映射内存地址的31:26位 178 | 179 | PCIEXBARH 180 | 181 | 0:31 映射内存地址的63:32位 182 | 183 | ## 总线结构 184 | 185 | 最初的CPU通过FSB总线(Front-Side Bus)与北桥(NorthBridge)连接。 186 | 187 | 北桥内部集成RC(PCIe Root Complex)与MCH(Memory Controller Hub)。MCH通过Memory Bus与内存链接。 188 | 189 | 北桥通过internal bus链接南桥,南桥提供各种外设接口,LPC是南桥上的一个桥设备,下面挂载着BIOS Flash、uart、键盘、鼠标等慢速设备。 190 | 191 | P2SB(Primary to SideBridge),PCH上大部分设备可以通过PCIe或IO方式访问,但部分PCH上部分设备需要访问PCH的私有空间,这部分空间通过P2SB的SBREG_BAR寄存器映射到内存空间。这段空间被称为PCR(PCH Private Configuration Space Register)。每个设备对应一个PortID,PortID表示设备在PCR空间的偏移量,在加上寄存器偏移可以获取寄存器的地址。 192 | 193 | 现在随着SOC流行,很多平台把北桥集成到SOC内部,并命名为System Agent。南桥被命名为PCH(Platform Controller Hub),PCH通过DMI接口直接与CPU链接。 194 | 195 | # 入口 196 | 197 | 在x86上,入口有两个: 198 | 199 | - src/arch/x86/bootblock_crt0.S 200 | - src/arch/x86/bootblock_romcc.S 201 | 202 | 由于在x86架构下,在内存初始化完成前要使用C语言编程存在问题(C语言使用堆栈内存实现函数栈帧,来处理局部变量以及函数调用返回)。为了可以运行C语言,以提高开发效率以及可移植性。coreboot使用两种方式在内存初始化完成前运行C代码: 203 | 204 | - cache as ram : 初始化cache把cache当作内存使用 205 | - romcc : 这是一个特别的编译器,编译的目标代码可以没有内存,所有数据保存在寄存器中 206 | 207 | bootblock\_crt0.S使用cache as ram,bootblock_romcc.S使用romcc。但它们在从实模式进入分段保护模式的方法都是一致的。都是通过引用三个文件实现: 208 | 209 | ``` 210 | #include 211 | #include 212 | #include 213 | ``` 214 | 215 | bootblock\_crt0.S在进入保护模式后依次执行以下操作: 216 | 217 | - 获取系统时间戳 218 | - 根据需要开启SSE 219 | - 跳转到cache初始化入口 220 | - cache初始化完成后,跳转到通用的bootblock入口bootblock_c_entry 221 | - 引导下一阶段代码 222 | 223 | src/arch/x86/bootblock_romcc.S在进入保护模式后依次执行以下操作: 224 | 225 | - 获取系统时间戳 226 | - 根据需要开启SSE 227 | - 引用romcc编译产生的汇编文件generated/bootblock.inc 228 | 229 | generated/bootblock.inc可以由两个文件编译生成 230 | 231 | - src/arch/x86/bootblock_simple.c 232 | - src/arch/x86/bootblock_normal.c 233 | 234 | ## reset16.inc 235 | 236 | 位置:src/cpu/x86/16bit/reset16.inc 237 | 238 | 此代码为x86机器启动时的第一条代码,此文件很简单只是跳转到实际入口_start16bit。并通过链接脚本固定到0xfffffff0。 239 | 240 | 跳转代码如下,使用硬编码编码跳转指令。 241 | ``` 242 | .section ".reset", "ax", %progbits 243 | .code16 244 | .globl _start 245 | _start: 246 | .byte 0xe9 247 | .int _start16bit - ( . + 2 ) 248 | /* Note: The above jump is hand coded to work around bugs in binutils. 249 | * 5 byte are used for a 3 byte instruction. This works because x86 250 | * is little endian and allows us to use supported 32bit relocations 251 | * instead of the weird 16 bit relocations that binutils does not 252 | * handle consistently between versions because they are used so rarely. 253 | */ 254 | .previous 255 | ``` 256 | 257 | 链接脚本如下,把.reset段固定到0xfffffff0处。0xfffffff0为x86第一条指令的执行地址,因为CS被重新赋值将进入真正的实模式(寻址访问0-1M),所以此段代码不能使用far jmp/call之类的指令,所以_start16bit必须在64K跳转范围内。 258 | ``` 259 | SECTIONS { 260 | /* Trigger an error if I have an unuseable start address */ 261 | _bogus = ASSERT(_start16bit >= 0xffff0000, "_start16bit too low. Please report."); 262 | _ROMTOP = 0xfffffff0; 263 | . = _ROMTOP; 264 | .reset . : { 265 | *(.reset); 266 | . = 15; 267 | BYTE(0x00); 268 | } 269 | } 270 | ``` 271 | 272 | ## entry16.inc 273 | 274 | 位置:src/cpu/x86/16bit/entry16.inc 275 | 276 | 此代码是进入保护模式代码的16位代码部分,主要执行以下操作 277 | 278 | - 失效页表缓存 279 | - 初始化全局描述符表和中断描述符表 280 | - 跳转到32位入口__protected_start(这时使用far jmp会自动初始化CS段寄存器) 281 | 282 | ## entry32.inc 283 | 284 | 位置:src/cpu/x86/32bit/entry32.inc 285 | 286 | 此代码是进入保护模式代码的32位部分,主要初始化除CS外的其他段寄存器 287 | 288 | # cache as ram 289 | 290 | intel平台下代码位于src/soc/intel/common/block/cpu/car/cache\_as\_ram.S 291 | 292 | 此段代码,主要是通过MTRRs设置一段内存属性为Write Back,此段内存大小不能超过cache空间的大小。最后禁止cache eviction(cache把dirty line同步到内存的过程),这样使访问这段内存都是访问cache。 293 | 294 | MTRRs初始状态应该是禁用的,首先需要判断MTRRs初始态是否正常 295 | ``` 296 | bootblock_pre_c_entry: 297 | 298 | post_code(0x20) 299 | 300 | /* 301 | * Use the MTRR default type MSR as a proxy for detecting INIT#. 302 | * Reset the system if any known bits are set in that MSR. That is 303 | * an indication of the CPU not being properly reset. 304 | */ 305 | check_for_clean_reset: 306 | mov $MTRR_DEF_TYPE_MSR, %ecx 307 | rdmsr 308 | and $(MTRR_DEF_TYPE_EN | MTRR_DEF_TYPE_FIX_EN), %eax 309 | cmp $0, %eax 310 | jz no_reset 311 | /* perform warm reset */ 312 | movw $0xcf9, %dx 313 | movb $0x06, %al 314 | outb %al, %dx 315 | 316 | no_reset: 317 | post_code(0x21) 318 | 319 | ``` 320 | 321 | 下面需要初始化MTRRs,清除fixed range MTRRs相关寄存器 322 | ``` 323 | /* Clear/disable fixed MTRRs */ 324 | mov $fixed_mtrr_list_size, %ebx 325 | xor %eax, %eax 326 | xor %edx, %edx 327 | 328 | clear_fixed_mtrr: 329 | add $-2, %ebx 330 | movzwl fixed_mtrr_list(%ebx), %ecx 331 | wrmsr 332 | jnz clear_fixed_mtrr 333 | 334 | post_code(0x22) 335 | 336 | ``` 337 | 其中fixed\_mtrr\_list,为一个表存放了寄存器的地址。fixed\_mtrr\_list\_size中存放了寄存器的个数 338 | ``` 339 | fixed_mtrr_list: 340 | .word MTRR_FIX_64K_00000 341 | .word MTRR_FIX_16K_80000 342 | .word MTRR_FIX_16K_A0000 343 | .word MTRR_FIX_4K_C0000 344 | .word MTRR_FIX_4K_C8000 345 | .word MTRR_FIX_4K_D0000 346 | .word MTRR_FIX_4K_D8000 347 | .word MTRR_FIX_4K_E0000 348 | .word MTRR_FIX_4K_E8000 349 | .word MTRR_FIX_4K_F0000 350 | .word MTRR_FIX_4K_F8000 351 | fixed_mtrr_list_size = . - fixed_mtrr_list 352 | ``` 353 | 354 | 接着需要清除variable range MTRRs相关寄存器,因为具体平台可配置的variable range数不一样,需要通过读取MTRR\_CAP\_MSR获取。 355 | ``` 356 | /* Figure put how many MTRRs we have, and clear them out */ 357 | mov $MTRR_CAP_MSR, %ecx 358 | rdmsr 359 | movzb %al, %ebx /* Number of variable MTRRs */ 360 | mov $MTRR_PHYS_BASE(0), %ecx 361 | xor %eax, %eax 362 | xor %edx, %edx 363 | 364 | clear_var_mtrr: 365 | wrmsr 366 | inc %ecx 367 | wrmsr 368 | inc %ecx 369 | dec %ebx 370 | jnz clear_var_mtrr 371 | 372 | post_code(0x23) 373 | ``` 374 | 其中MTRR\_PHYS\_BASE为variable range MTRRs配置寄存器的起始地址,一组配置需要两组寄存器。 375 | 376 | 接着配置默认的内存属性为uncacheable,即其他内存空间都不使用cache 377 | ``` 378 | /* Configure default memory type to uncacheable (UC) */ 379 | mov $MTRR_DEF_TYPE_MSR, %ecx 380 | rdmsr 381 | /* Clear enable bits and set default type to UC. */ 382 | and $~(MTRR_DEF_TYPE_MASK | MTRR_DEF_TYPE_EN | \ 383 | MTRR_DEF_TYPE_FIX_EN), %eax 384 | wrmsr 385 | ``` 386 | 387 | 接着通过cpuid指令获取计算机物理内存空间上限,并计算出一个掩码用于处理variable range MTRRs的配置寄存器。 388 | ``` 389 | /* Configure MTRR_PHYS_MASK_HIGH for proper addressing above 4GB 390 | * based on the physical address size supported for this processor 391 | * This is based on read from CPUID EAX = 080000008h, EAX bits [7:0] 392 | * 393 | * Examples: 394 | * MTRR_PHYS_MASK_HIGH = 00000000Fh For 36 bit addressing 395 | * MTRR_PHYS_MASK_HIGH = 0000000FFh For 40 bit addressing 396 | */ 397 | 398 | movl $0x80000008, %eax /* Address sizes leaf */ 399 | cpuid 400 | sub $32, %al 401 | movzx %al, %eax 402 | xorl %esi, %esi 403 | bts %eax, %esi 404 | dec %esi /* esi <- MTRR_PHYS_MASK_HIGH */ 405 | 406 | post_code(0x24) 407 | ``` 408 | 409 | 接着根据具体配置信息,配置variable range MTRRs 410 | ``` 411 | #if ((CONFIG_DCACHE_RAM_SIZE & (CONFIG_DCACHE_RAM_SIZE - 1)) == 0) 412 | /* Configure CAR region as write-back (WB) */ 413 | mov $MTRR_PHYS_BASE(0), %ecx 414 | mov $CONFIG_DCACHE_RAM_BASE, %eax 415 | or $MTRR_TYPE_WRBACK, %eax 416 | xor %edx,%edx 417 | wrmsr 418 | 419 | /* Configure the MTRR mask for the size region */ 420 | mov $MTRR_PHYS_MASK(0), %ecx 421 | mov $CONFIG_DCACHE_RAM_SIZE, %eax /* size mask */ 422 | dec %eax 423 | not %eax 424 | or $MTRR_PHYS_MASK_VALID, %eax 425 | movl %esi, %edx /* edx <- MTRR_PHYS_MASK_HIGH */ 426 | wrmsr 427 | #elif (CONFIG_DCACHE_RAM_SIZE == 768 * KiB) /* 768 KiB */ 428 | /* Configure CAR region as write-back (WB) */ 429 | mov $MTRR_PHYS_BASE(0), %ecx 430 | mov $CONFIG_DCACHE_RAM_BASE, %eax 431 | or $MTRR_TYPE_WRBACK, %eax 432 | xor %edx,%edx 433 | wrmsr 434 | 435 | mov $MTRR_PHYS_MASK(0), %ecx 436 | mov $(512 * KiB), %eax /* size mask */ 437 | dec %eax 438 | not %eax 439 | or $MTRR_PHYS_MASK_VALID, %eax 440 | movl %esi, %edx /* edx <- MTRR_PHYS_MASK_HIGH */ 441 | wrmsr 442 | 443 | mov $MTRR_PHYS_BASE(1), %ecx 444 | mov $(CONFIG_DCACHE_RAM_BASE + 512 * KiB), %eax 445 | or $MTRR_TYPE_WRBACK, %eax 446 | xor %edx,%edx 447 | wrmsr 448 | 449 | mov $MTRR_PHYS_MASK(1), %ecx 450 | mov $(256 * KiB), %eax /* size mask */ 451 | dec %eax 452 | not %eax 453 | or $MTRR_PHYS_MASK_VALID, %eax 454 | movl %esi, %edx /* edx <- MTRR_PHYS_MASK_HIGH */ 455 | wrmsr 456 | #else 457 | #error "DCACHE_RAM_SIZE is not a power of 2 and setup code is missing" 458 | #endif 459 | post_code(0x25) 460 | ``` 461 | 根据,基础知识里面提到的一组variable range MTRRs设置的内存必须4K起始地址对其,并且大小为4K*2^n,这里为了支持一种特别情况768K使用了两组配置寄存器配置了两段内存空间(512+256) 462 | 463 | 接着使能variable MTRRs和cache 464 | ``` 465 | /* Enable variable MTRRs */ 466 | mov $MTRR_DEF_TYPE_MSR, %ecx 467 | rdmsr 468 | or $MTRR_DEF_TYPE_EN, %eax 469 | wrmsr 470 | 471 | /* Enable caching */ 472 | mov %cr0, %eax 473 | and $~(CR0_CD | CR0_NW), %eax 474 | invd 475 | mov %eax, %cr0 476 | ``` 477 | 478 | 接着根据具体平台分支,对cache进行具体配置,禁止cache eviction(把dirty cache line同步到内存)。并且对映射的内存空间清零 479 | ``` 480 | .global car_nem 481 | car_nem: 482 | /* Disable cache eviction (setup stage) */ 483 | mov $MSR_EVICT_CTL, %ecx 484 | rdmsr 485 | or $0x1, %eax 486 | wrmsr 487 | 488 | post_code(0x26) 489 | 490 | /* Clear the cache memory region. This will also fill up the cache */ 491 | movl $CONFIG_DCACHE_RAM_BASE, %edi 492 | movl $CONFIG_DCACHE_RAM_SIZE, %ecx 493 | shr $0x02, %ecx 494 | xor %eax, %eax 495 | cld 496 | rep stosl 497 | 498 | post_code(0x27) 499 | 500 | /* Disable cache eviction (run stage) */ 501 | mov $MSR_EVICT_CTL, %ecx 502 | rdmsr 503 | or $0x2, %eax 504 | wrmsr 505 | 506 | post_code(0x28) 507 | 508 | jmp car_init_done 509 | ``` 510 | 511 | 接着合并分支,为进入C语言环境作准备:初始化SP、传递参数 512 | ``` 513 | .global car_init_done 514 | car_init_done: 515 | 516 | post_code(0x29) 517 | 518 | /* Setup bootblock stack */ 519 | mov $_car_stack_end, %esp 520 | 521 | /* Need to align stack to 16 bytes at call instruction. Account for 522 | the two pushes below. */ 523 | andl $0xfffffff0, %esp 524 | sub $8, %esp 525 | 526 | /*push TSC value to stack*/ 527 | movd %mm2, %eax 528 | pushl %eax /* tsc[63:32] */ 529 | movd %mm1, %eax 530 | pushl %eax /* tsc[31:0] */ 531 | 532 | before_carstage: 533 | post_code(0x2A) 534 | 535 | call bootblock_c_entry 536 | /* Never reached */ 537 | 538 | .halt_forever: 539 | post_code(POST_DEAD_CODE) 540 | hlt 541 | jmp .halt_forever 542 | ``` 543 | 544 | # bootblock 545 | 546 | bootblock\_c\_entry是各平台的入口,具体平台具体实现。主要是进行一些平台相关初始化,然后跳转到bootblock通用框架src/lib/bootblock.c。在skeylake下没有初始化工作,直接转到bootblock\_main\_with\_timestamp。 547 | 548 | bootblock框架提供了如下接口,具体平台根据具体情况实现 549 | ``` 550 | __attribute__((weak)) void bootblock_soc_early_init(void) { /* do nothing */ } 551 | __attribute__((weak)) void bootblock_mainboard_early_init(void) { /* no-op */ } 552 | __attribute__((weak)) void bootblock_soc_init(void) { /* do nothing */ } 553 | __attribute__((weak)) void bootblock_mainboard_init(void) { /* do nothing */ } 554 | ``` 555 | 556 | 初始化分为两个阶段早期初始化、初始化。每个阶段分别可以对soc和主板(mainboard)进行初始化。 557 | 558 | ## soc初始化 559 | 560 | soc初始化代码位于src/soc/intel/skylake/bootblock/bootblock.c中 561 | 562 | 在早期初始中soc完成了PCIe总线内存地址映射、pch配置空间内存地址映射、LPC配置使能部分外设、SPI及FLASH BIOS配置以及UART配置 563 | ``` 564 | void bootblock_soc_early_init(void) 565 | { 566 | bootblock_systemagent_early_init(); 567 | bootblock_pch_early_init(); 568 | bootblock_cpu_init(); 569 | pch_early_iorange_init(); 570 | 571 | if (IS_ENABLED(CONFIG_UART_DEBUG)) 572 | pch_uart_init(); 573 | } 574 | 575 | ``` 576 | ### PCIe内存地址映射 577 | 578 | 主要通过IO方式访问System Agent(PCIe根设备,设备编号00:0.0),设置其配置空间的PCIEXBAR、PCIEXBARH 579 | ``` 580 | void bootblock_systemagent_early_init(void) 581 | { 582 | uint32_t reg; 583 | uint8_t pciexbar_length; 584 | 585 | /* 586 | * The PCIEXBAR is assumed to live in the memory mapped IO space under 587 | * 4GiB. 588 | */ 589 | reg = 0; 590 | pci_io_write_config32(SA_DEV_ROOT, PCIEXBAR + 4, reg); 591 | 592 | /* Get PCI Express Region Length */ 593 | switch (CONFIG_SA_PCIEX_LENGTH) { 594 | case 256 * MiB: 595 | pciexbar_length = PCIEXBAR_LENGTH_256MB; 596 | break; 597 | case 128 * MiB: 598 | pciexbar_length = PCIEXBAR_LENGTH_128MB; 599 | break; 600 | case 64 * MiB: 601 | pciexbar_length = PCIEXBAR_LENGTH_64MB; 602 | break; 603 | default: 604 | pciexbar_length = PCIEXBAR_LENGTH_256MB; 605 | } 606 | reg = CONFIG_MMCONF_BASE_ADDRESS | (pciexbar_length << 1) 607 | | PCIEXBAR_PCIEXBAREN; 608 | pci_io_write_config32(SA_DEV_ROOT, PCIEXBAR, reg); 609 | 610 | /* 611 | * TSEG defines the base of SMM range. BIOS determines the base 612 | * of TSEG memory which must be at or below Graphics base of GTT 613 | * Stolen memory, hence its better to clear TSEG register early 614 | * to avoid power on default non-zero value (if any). 615 | */ 616 | pci_write_config32(SA_DEV_ROOT, TSEG, 0); 617 | } 618 | ``` 619 | 620 | 这里把PCIe映射到4G范围内,所以PCIEXBARH被设置为0 621 | 622 | TSEG是System Agent上用于设在内存映射的寄存器,这里设在为0只是为了防止此寄存器有值 623 | 624 | ### spi bios设置以及初始化P2SB 625 | 626 | spi bios设置在fast_spi_early_init中配置 627 | ``` 628 | void bootblock_pch_early_init(void) 629 | { 630 | fast_spi_early_init(SPI_BASE_ADDRESS); 631 | enable_p2sbbar(); 632 | } 633 | ``` 634 | 635 | 初始化P2SB主要是为了访问PCH的私有配置空间,即初始化SBREG_BAR寄存器把PCH的私有配置空间映射到内存空间。 636 | ``` 637 | static void enable_p2sbbar(void) 638 | { 639 | device_t dev = PCH_DEV_P2SB; 640 | 641 | /* Enable PCR Base address in PCH */ 642 | pci_write_config32(dev, PCI_BASE_ADDRESS_0, CONFIG_PCR_BASE_ADDRESS); 643 | 644 | /* Enable P2SB MSE */ 645 | pci_write_config8(dev, PCI_COMMAND, 646 | PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); 647 | /* 648 | * Enable decoding for HPET memory address range. 649 | * HPTC_OFFSET(0x60) bit 7, when set the P2SB will decode 650 | * the High Performance Timer memory address range 651 | * selected by bits 1:0 652 | */ 653 | pci_write_config8(dev, HPTC_OFFSET, HPTC_ADDR_ENABLE_BIT); 654 | } 655 | ``` 656 | 657 | ### 防止写BIOS空间 658 | 659 | 通过MTRRs配置BIOS缓存空间属性为写保护,防止写BIOS 660 | ``` 661 | void bootblock_cpu_init(void) 662 | { 663 | fast_spi_cache_bios_region(); 664 | } 665 | 666 | void fast_spi_cache_bios_region(void) 667 | { 668 | size_t bios_size; 669 | uint32_t alignment; 670 | const int type = MTRR_TYPE_WRPROT; 671 | uintptr_t base; 672 | 673 | /* Only the IFD BIOS region is memory mapped (at top of 4G) */ 674 | fast_spi_get_bios_region(&bios_size); 675 | 676 | if (!bios_size) 677 | return; 678 | 679 | /* Round to power of two */ 680 | alignment = 1 << (log2_ceil(bios_size)); 681 | bios_size = ALIGN_UP(bios_size, alignment); 682 | base = 4ULL*GiB - bios_size; 683 | 684 | if (ENV_RAMSTAGE) { 685 | mtrr_use_temp_range(base, bios_size, type); 686 | } else { 687 | int mtrr = get_free_var_mtrr(); 688 | 689 | if (mtrr == -1) 690 | return; 691 | 692 | set_var_mtrr(mtrr, base, bios_size, type); 693 | } 694 | } 695 | ``` 696 | 697 | ### LPC配置 698 | 699 | 主要用来使能LPC下的设备 700 | ``` 701 | void pch_early_iorange_init(void) 702 | { 703 | uint16_t dec_rng, dec_en = 0; 704 | 705 | /* IO Decode Range */ 706 | if (IS_ENABLED(CONFIG_DRIVERS_UART_8250IO) && 707 | IS_ENABLED(CONFIG_UART_DEBUG)) { 708 | dec_rng = COMA_RANGE | (COMB_RANGE << 4); 709 | dec_en = COMA_LPC_EN | COMB_LPC_EN; 710 | pci_write_config16(PCH_DEV_LPC, LPC_IO_DEC, dec_rng); 711 | pcr_write16(PID_DMI, PCR_DMI_LPCIOD, dec_rng); 712 | } 713 | 714 | /* IO Decode Enable */ 715 | dec_en |= SE_LPC_EN | KBC_LPC_EN | MC1_LPC_EN; 716 | pci_write_config16(PCH_DEV_LPC, LPC_EN, dec_en); 717 | pcr_write16(PID_DMI, PCR_DMI_LPCIOE, dec_en); 718 | } 719 | 720 | ``` 721 | 722 | ### UART初始化 723 | 724 | 初始化8250串口 725 | ``` 726 | void pch_uart_init(void) 727 | { 728 | uintptr_t base = uart_platform_base(CONFIG_UART_FOR_CONSOLE); 729 | 730 | uart_common_init(PCH_DEV_UART2, base, CLK_M_VAL, CLK_N_VAL); 731 | 732 | /* Put UART2 in byte access mode for 16550 compatibility */ 733 | if (!IS_ENABLED(CONFIG_DRIVERS_UART_8250MEM_32)) 734 | pcr_write32(PID_SERIALIO, PCR_SERIAL_IO_GPPRVRW7, 735 | PCR_SIO_PCH_LEGACY_UART2); 736 | 737 | gpio_configure_pads(uart2_pads, ARRAY_SIZE(uart2_pads)); 738 | } 739 | ``` 740 | 741 | ## 主板初始化 742 | 743 | 主板初始化主要位于src/mainboard/intel/kblrvp/bootblock.c中 744 | 745 | 此初始化主要为了初始化串口使用的两个IO口 746 | 747 | x86下GPIO配置位于PCH的私有空间。GPIO被分组,每组对应一个PCR的PortID,对应如下结构体。 748 | ``` 749 | struct pad_community { 750 | const char *name; 751 | const char *acpi_path; 752 | size_t num_gpi_regs;/* number of gpi registers in community */ 753 | size_t max_pads_per_group; /* number of pads in each group; 754 | Number of pads bit mapped in each GPI status/en and Host Own Reg */ 755 | gpio_t first_pad; /* first pad in community */ 756 | gpio_t last_pad; /* last pad in community */ 757 | uint16_t host_own_reg_0; /* offset to Host Ownership Reg 0 */ 758 | uint16_t gpi_smi_sts_reg_0; /* offset to GPI SMI EN Reg 0 */ 759 | uint16_t gpi_smi_en_reg_0; /* offset to GPI SMI STS Reg 0 */ 760 | uint16_t pad_cfg_base; /* offset to first PAD_GFG_DW0 Reg */ 761 | uint8_t gpi_status_offset; /* specifies offset in struct 762 | gpi_status */ 763 | uint8_t port; /* PCR Port ID */ 764 | const struct reset_mapping *reset_map; /* PADRSTCFG logical to 765 | chipset mapping */ 766 | size_t num_reset_vals; 767 | }; 768 | ``` 769 | pad_cfg_base是配置IO口的起始地址 770 | 771 | 每个IO口通过一个结构体描述其配置,结构如下 772 | ``` 773 | struct pad_config { 774 | int pad;/* offset of pad within community */ 775 | uint32_t pad_config[GPIO_NUM_PAD_CFG_REGS];/* 776 | Pad config data corresponding to DW0, DW1,.... */ 777 | }; 778 | ``` 779 | 780 | 然后系统定义了一些列的宏(PAD_CFG_XXX),用来描述一个IO的配置。 781 | 782 | kblrvp的串口默认配置在src/mainboard/intel/kblrvp/vatiants/rvp3/include/vatiant/gpio.h中 783 | 784 | ``` 785 | /* Early pad configuration in romstage. */ 786 | static const struct pad_config early_gpio_table[] = { 787 | /* UART2_RXD */ PAD_CFG_NF(GPP_C20, NONE, DEEP, NF1), 788 | /* UART2_TXD */ PAD_CFG_NF(GPP_C21, NONE, DEEP, NF1), 789 | }; 790 | ``` 791 | 792 | 主板初始化,主要初始化early_gpio_table描述的IO口 793 | ``` 794 | static void early_config_gpio(void) 795 | { 796 | /* This is a hack for FSP because it does things in MemoryInit() 797 | * which it shouldn't do. We have to prepare certain gpios here 798 | * because of the brokenness in FSP. */ 799 | gpio_configure_pads(early_gpio_table, ARRAY_SIZE(early_gpio_table)); 800 | } 801 | 802 | void bootblock_mainboard_init(void) 803 | { 804 | early_config_gpio(); 805 | } 806 | ``` 807 | 808 | # 下一阶段引导 809 | 810 | 在bootblock执行完成,通过run\_romstage运行下一阶段代码。 811 | 812 | run\_romstage由系统提供,此函数主要负责加载程序到内存并跳转到下一个阶段。 813 | 814 | 具体平台需要具体实现arch\_prog\_run 815 | ``` 816 | void arch_prog_run(struct prog *prog) 817 | { 818 | if (ENV_RAMSTAGE) 819 | try_payload(prog); 820 | __asm__ volatile ( 821 | #ifdef __x86_64__ 822 | "jmp *%%rdi\n" 823 | #else 824 | "jmp *%%edi\n" 825 | #endif 826 | 827 | :: "D"(prog_entry(prog)) 828 | ); 829 | } 830 | ``` 831 | # 参考 832 | 833 | [Intel® 64 and IA-32 Architectures Software Developer's Manual](https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf) 834 | 835 | [Conventional PCI](https://en.wikipedia.org/wiki/Conventional_PCI) 836 | 837 | [PCI Express](https://en.wikipedia.org/wiki/PCI_Express) 838 | 839 | [PCI configuration space](https://en.wikipedia.org/wiki/PCI_configuration_space) 840 | 841 | -------------------------------------------------------------------------------- /docs/x86/coreboot/4.coreboot-x86移植相关.md: -------------------------------------------------------------------------------- 1 | # bootblock 2 | 3 | bootblock框架位于`src/lib/bootblock.c`中。框架预留了如下几个接口由具体平台实现。 4 | ```c 5 | __attribute__((weak)) void bootblock_mainboard_early_init(void) { /* no-op */ } 6 | __attribute__((weak)) void bootblock_soc_early_init(void) { /* do nothing */ } 7 | __attribute__((weak)) void bootblock_soc_init(void) { /* do nothing */ } 8 | __attribute__((weak)) void bootblock_mainboard_init(void) { /* do nothing */ } 9 | ``` 10 | 11 | 在skylake平台下,实现了soc相关的初始化,位于`src/soc/intel/skylake/bootblock/bootblock.c`中。这样具体主板可以实现的方法只剩下如下两个: 12 | ```c 13 | __attribute__((weak)) void bootblock_mainboard_early_init(void) { /* no-op */ } 14 | __attribute__((weak)) void bootblock_mainboard_init(void) { /* do nothing */ } 15 | ``` 16 | 17 | 参考源码中所有现有主板,只实现了`void bootblock_mainboard_init(void)`。此函数一般用于初始化早期IO。 18 | 19 | 20 | ## IO 21 | 22 | GPIO为边带桥下的设备,边带桥为一个PCI设备。在边带桥配置空间中映射了一段16M的内存空间,被称为PCR空间。这段空间被分为256个块(16M = 256 * 64k),访问PCR空间需要块的编号(port)和偏移量。PCR空间访问在以下文件中实现。 23 | src/soc/intel/common/block/include/intelblocks/pcr.h 24 | src/soc/intel/common/block/pcr/pcr.c 25 | 26 | PCR空间中每一个port对应一个设备,其中GPIO占用多个port。一个port对应一组GPIO,被称为一个Community。每个Community下对应的GPIO个数不同。在100系intel芯片组下GPIO分布如下: 27 | | Port | Community | GPIO | 28 | |------|-----------|-------------------------------------| 29 | | 0xAF | 0 | GPP_A GPP_B | 30 | | 0xAE | 1 | GPP_C GPP_D GPP_E GPP_F GPP_G GPP_H | 31 | | 0xAD | 2 | GPD | 32 | | 0xAC | 3 | GPP_I | 33 | 34 | GPIO实现相关文件如下: 35 | src/soc/intel/common/block/include/intelblocks/gpio.h 定义了一些结构体 36 | src/soc/intel/common/block/gpio/gpio.c 定义了一些配置操作GPIO的方法 37 | src/soc/intel/skylake/include/soc/gpio.h 定义了一些宏用于方便实现GPIO相关配置 38 | src/soc/intel/skylake/include/soc/gpio_def.h 定义GPIO引脚编号(通过预处理包含具体实现)及引脚的IRQ 39 | src/soc/intel/skylake/include/soc/gpio\_pch\_h\_defs.h 定义pch平台GPIO引脚编号 40 | src/soc/intel/skylake/include/soc/gpio\_soc\_defs.h 定义soc平台GPIO引脚编号 41 | 42 | 系统把所有的GPIO顺序编号(0 - n),并定义一个类型表示GPIO。 43 | typedef uint32_t gpio_t; 44 | 45 | 每一个GPIO对应一组关键配置寄存器,每个寄存器32为DW0-DWn,n和具体平台有关。在每一个Community中第一个DW的偏移是一致的。 46 | ``` 47 | struct pad_community { 48 | const char *name; 49 | const char *acpi_path; 50 | size_t num_gpi_regs;/* number of gpi registers in community */ 51 | size_t max_pads_per_group; /* number of pads in each group; 52 | Number of pads bit mapped in each GPI status/en and Host Own Reg */ 53 | gpio_t first_pad; /* 第一个pin的编号 */ 54 | gpio_t last_pad; /* 最后一个pin的编号 */ 55 | uint16_t host_own_reg_0; /* offset to Host Ownership Reg 0 */ 56 | uint16_t gpi_smi_sts_reg_0; /* offset to GPI SMI EN Reg 0 */ 57 | uint16_t gpi_smi_en_reg_0; /* offset to GPI SMI STS Reg 0 */ 58 | uint16_t pad_cfg_base; /* offset to first PAD_GFG_DW0 Reg */ 59 | uint8_t gpi_status_offset; /* specifies offset in struct gpi_status */ 60 | uint8_t port; /* PCR Port ID */ 61 | const struct reset_mapping *reset_map; /* PADRSTCFG logical to chipset mapping */ 62 | size_t num_reset_vals; 63 | }; 64 | ``` 65 | 66 | 定义reset_mapping结构用于设定GPIO的初始状态(主要用于设定DW)。 67 | ``` 68 | struct reset_mapping { 69 | uint32_t logical; /* 复位值 */ 70 | uint32_t chipset; /* 初始值 */ 71 | }; 72 | ``` 73 | 74 | 定义pad_config用于初始化一个具体的pin脚 75 | ``` 76 | struct pad_config { 77 | int pad; /* GPIO引脚编号 */ 78 | uint32_t pad_config[GPIO_NUM_PAD_CFG_REGS]; /* DW配置值 */ 79 | }; 80 | ``` 81 | 82 | 移植主要要获取GPIO配置寄存器的值,这些值可以通过在linux下访问PCI空间获取 83 | 84 | # romstage 85 | 86 | romstage需要初始化内存,与如下内容有关:FSP / devicetree / spd 87 | 88 | ## FSP版本选择 89 | 90 | skylake支持两种FSP版本:2.0 / 1.1,具体主板在其目录中需要通过`select MAINBOARD_USES_FSP2_0`选择使用FSP2.0。FSP版本选择代码在src/soc/intel/skylake/Kconfig中实现,如下: 91 | ``` 92 | config MAINBOARD_USES_FSP2_0 93 | bool 94 | default n 95 | 96 | config USE_FSP2_0_DRIVER 97 | def_bool y 98 | depends on MAINBOARD_USES_FSP2_0 99 | select PLATFORM_USES_FSP2_0 100 | select ADD_VBT_DATA_FILE if RUN_FSP_GOP 101 | select POSTCAR_CONSOLE 102 | select POSTCAR_STAGE 103 | 104 | config USE_FSP1_1_DRIVER 105 | def_bool y 106 | depends on !MAINBOARD_USES_FSP2_0 107 | select PLATFORM_USES_FSP1_1 108 | select DISPLAY_FSP_ENTRY_POINTS 109 | ``` 110 | 111 | ## SPD 112 | 113 | SPD是内存条上的一块EEPROM,这块EEPROM中记录了内存的一些信息,并链接到SMBUS总线(一种IIC总线的变种)上。每个EEPROM有三个引脚用于设定总线地址,具体由主板内存插槽决定,一般0x50-0x57。 114 | 115 | 获取spd内存的地址 116 | ~ sudo modprobe i2c-dev 117 | ~ sudo modprobe i2c-i801 118 | ~ sudo i2cdetect -l 119 | i2c-0 i2c i915 gmbus dpc I2C adapter 120 | i2c-1 i2c i915 gmbus dpb I2C adapter 121 | i2c-2 i2c i915 gmbus dpd I2C adapter 122 | i2c-3 i2c DPDDC-A I2C adapter 123 | i2c-4 i2c DPDDC-B I2C adapter 124 | i2c-5 i2c DPDDC-C I2C adapter 125 | i2c-6 smbus SMBus I801 adapter at f040 SMBus adapter 126 | ~ sudo i2cdetect 6 # 选择smbus的编号 127 | WARNING! This program can confuse your I2C bus, cause data loss and worse! 128 | I will probe file /dev/i2c-6. 129 | I will probe address range 0x03-0x77. 130 | Continue? [Y/n] y 131 | 0 1 2 3 4 5 6 7 8 9 a b c d e f 132 | 00: -- -- -- -- -- 08 -- -- -- -- -- -- -- 133 | 10: -- -- -- -- -- -- -- -- -- -- -- -- -- 1d -- -- 134 | 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 135 | 30: 30 -- -- -- -- 35 36 -- -- -- -- -- -- -- -- -- 136 | 40: -- -- -- -- 44 -- -- -- -- -- -- -- -- -- -- -- 137 | 50: 50 -- 52 -- -- -- -- -- -- -- -- -- -- -- -- -- 138 | 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 139 | 70: -- -- -- -- -- -- -- -- 140 | 此PC的SPD地址为0x50 / 0x52 141 | 142 | SPD相关操作在如下文件中实现 143 | src/lib/spd_bin.c 144 | src/include/spd_bin.h 145 | 146 | 系统定义了一个全局静态数组用于保存SPD数据 147 | ```c 148 | static u8 spd_data[CONFIG_DIMM_MAX * CONFIG_DIMM_SPD_SIZE] CAR_GLOBAL; 149 | ``` 150 | 151 | 并定义了一个结构体来记录SPD数据 152 | ```c 153 | struct spd_block { 154 | u8 addr_map[CONFIG_DIMM_MAX]; /* 记录具体主板内存插槽SPD的SMBUS地址,CONFIG_DIMM_MAX主板插槽个数 */ 155 | u8 *spd_array[CONFIG_DIMM_MAX]; /* 记录每个插槽的spd数据,指向全局静态变量spd_data */ 156 | u16 len; /* 每一个SPD数据长度:DDR4为512字节,其他256字节 */ 157 | }; 158 | ``` 159 | 160 | 并导出如下函数 161 | ``` 162 | void print_spd_info(uint8_t spd[]); /* 打印SPD信息,一个内存条 */ 163 | void dump_spd_info(struct spd_block *blk); /* 打印SPD信息,一个主板的所有内存条信息 */ 164 | void get_spd_smbus(struct spd_block *blk); /* 从smbus读取SPD信息,地在通过blk传入 */ 165 | /* 从cbfs中读取SPD信息,对应那些直接焊接在主板上的内存 */ 166 | int get_spd_cbfs_rdev(struct region_device *spd_rdev, u8 spd_index); 167 | int read_ddr3_spd_from_cbfs(u8 *buf, int idx); /* 从cbfs中读取DDR3的SPD信息 */ 168 | ``` 169 | 170 | coreboot负责读出SPD数据并传递给FSP处理。SPD需要在内存初始化之前传递给FSP。FSP传参通过UPD数据结构实现,skylake下FSP2.0的内存初始化UPD数据结构在`src/vendorcode/intel/fsp.fsp2.0/skylake/FspmUpd.h`中定义。在`src/mainboard/intel/kblrvp/romstage.c`中通过FSPM_UPD->FspmConfig->MemorySpdPtr00 / FSPM_UPD->FspmConfig->MemorySpdPtr10 / FSPM_UPD->FspmConfig->MemorySpdDataLen,把SPD数据传递给FSP。 171 | 172 | ## devicetree 173 | 174 | devicetree是用于描述设备树,以及芯片配置的数据结构。coreboot提供编译器,把devicetree转换成c语言机构体。 175 | 176 | 这些结构体有两种链接模式,链表(用于链接所有的device),数(以总线的模式构成树) 177 | 178 | 179 | 主要结构 180 | 181 | ```c 182 | struct device { 183 | DEVTREE_CONST struct bus *bus; /* 设备所在总线 */ 184 | DEVTREE_CONST struct device *sibling; /* 同一个总线上的下一个设备 */ 185 | DEVTREE_CONST struct device *next; /* 所有的设备通过这个指针构成链表 */ 186 | 187 | struct device_path path; 188 | unsigned int vendor; 189 | unsigned int device; 190 | u16 subsystem_vendor; 191 | u16 subsystem_device; 192 | unsigned int class; /* 3 bytes: (base, sub, prog-if) */ 193 | unsigned int hdr_type; /* PCI header type */ 194 | unsigned int enabled : 1; /* set if we should enable the device */ 195 | unsigned int initialized : 1; /* 1 if we have initialized the device */ 196 | unsigned int on_mainboard : 1; 197 | struct pci_irq_info pci_irq_info[4]; 198 | u8 command; 199 | 200 | /* Base registers for this device. I/O, MEM and Expansion ROM */ 201 | DEVTREE_CONST struct resource *resource_list; 202 | 203 | /* links are (downstream) buses attached to the device, usually a leaf 204 | * device with no children has 0 buses attached and a bridge has 1 bus 205 | */ 206 | DEVTREE_CONST struct bus *link_list; /* 当前设备下的总线,通过bus->next构成链表 */ 207 | 208 | struct device_operations *ops; /* 具体设备有关的操作 */ 209 | #if !DEVTREE_EARLY 210 | struct chip_operations *chip_ops; /* 芯片相关操作 */ 211 | const char *name; 212 | #endif 213 | DEVTREE_CONST void *chip_info; 214 | }; 215 | 216 | struct bus { 217 | DEVTREE_CONST struct device *dev; /* This bridge device */ 218 | DEVTREE_CONST struct device *children; /* devices behind this bridge */ 219 | DEVTREE_CONST struct bus *next; /* The next bridge on this device */ 220 | unsigned int bridge_ctrl; /* Bridge control register */ 221 | uint16_t bridge_cmd; /* Bridge command register */ 222 | unsigned char link_num; /* The index of this link */ 223 | uint16_t secondary; /* secondary bus number */ 224 | uint16_t subordinate; /* max subordinate bus number */ 225 | unsigned char cap; /* PCi capability offset */ 226 | uint32_t hcdn_reg; /* For HyperTransport link */ 227 | 228 | unsigned int reset_needed : 1; 229 | unsigned int disable_relaxed_ordering : 1; 230 | unsigned int ht_link_up : 1; 231 | }; 232 | ``` 233 | 234 | 这些设备存在与芯片组下,芯片组相关的配置通过register关键字定义具体的值。 235 | 236 | pci_scan_bridge 237 | pci_scan_bus 238 | 239 | pci_domain_scan_bus 240 | pci_scan_bus 241 | 242 | pci_scan_bus 243 | pci_probe_dev 244 | set_pci_ops(通过读取VID/DID确定具体设备,给device->ops赋值) 245 | 246 | ## 中断路由与中断共享 247 | 248 | 中断路由指外设怎样链接到中断控制器的中断请求引脚,一个设备可以有4个中断引脚INTA / INTB / INTC / INTD,中断控制器只有有限的中断请求引脚(8259只有8个中断请求引脚,通过两片8259连级可以提供15个中断请求),为了共享中断请求引脚,中断通过电平触发,中断处理程序通过轮询依次处理同一个中断请求下的所有中断。 249 | 250 | 在PCI配置空间中有两个寄存器,int\_pin / int\_line。其中int\_pin由具体的设备决定,只读,表示当前功能使用哪一个中断引脚。其中int\_line为可写的,用于设置当前设备的中断发送给中断控制器的哪一个引脚。 251 | 252 | 在skylake下中断路由配置代码如下 253 | ```c 254 | static void pch_pirq_init(device_t dev) 255 | { 256 | device_t irq_dev; 257 | config_t *config = dev->chip_info; 258 | uint8_t pch_interrupt_routing[MAX_PXRC_CONFIG]; 259 | 260 | // 从devicetree中提取配置信息 261 | pch_interrupt_routing[0] = config->pirqa_routing; 262 | pch_interrupt_routing[1] = config->pirqb_routing; 263 | pch_interrupt_routing[2] = config->pirqc_routing; 264 | pch_interrupt_routing[3] = config->pirqd_routing; 265 | pch_interrupt_routing[4] = config->pirqe_routing; 266 | pch_interrupt_routing[5] = config->pirqf_routing; 267 | pch_interrupt_routing[6] = config->pirqg_routing; 268 | pch_interrupt_routing[7] = config->pirqh_routing; 269 | 270 | itss_irq_init(pch_interrupt_routing); 271 | 272 | // 遍历所有设备 273 | for (irq_dev = all_devices; irq_dev; irq_dev = irq_dev->next) { 274 | u8 int_pin = 0, int_line = 0; 275 | 276 | if (!irq_dev->enabled || irq_dev->path.type != DEVICE_PATH_PCI) 277 | continue; 278 | 279 | // 从配置空间读取PCI_INTERRUPT_PIN 280 | int_pin = pci_read_config8(irq_dev, PCI_INTERRUPT_PIN); 281 | 282 | switch (int_pin) { 283 | case 1: /* INTA# */ 284 | int_line = config->pirqa_routing; 285 | break; 286 | case 2: /* INTB# */ 287 | int_line = config->pirqb_routing; 288 | break; 289 | case 3: /* INTC# */ 290 | int_line = config->pirqc_routing; 291 | break; 292 | case 4: /* INTD# */ 293 | int_line = config->pirqd_routing; 294 | break; 295 | } 296 | 297 | if (!int_line) 298 | continue; 299 | 300 | // 设置PCI_INTERRUPT_LINE 301 | pci_write_config8(irq_dev, PCI_INTERRUPT_LINE, int_line); 302 | } 303 | } 304 | ``` 305 | 306 | # 获取FLASH存储空间大小 307 | 308 | ~ sudo dmidecode 309 | # dmidecode 3.0 310 | Getting SMBIOS data from sysfs. 311 | SMBIOS 3.0 present. 312 | 85 structures occupying 4095 bytes. 313 | Table at 0x8B2CD000. 314 | 315 | Handle 0x0000, DMI type 0, 24 bytes 316 | BIOS Information 317 | Vendor: American Megatrends Inc. 318 | Version: 2.80 319 | Release Date: 06/28/2017 320 | Address: 0xF0000 321 | Runtime Size: 64 kB 322 | ROM Size: 8192 kB -> 存储空间大小 323 | Characteristics: 324 | PCI is supported 325 | BIOS is upgradeable 326 | BIOS shadowing is allowed 327 | Boot from CD is supported 328 | Selectable boot is supported 329 | BIOS ROM is socketed 330 | EDD is supported 331 | 5.25"/1.2 MB floppy services are supported (int 13h) 332 | 3.5"/720 kB floppy services are supported (int 13h) 333 | 3.5"/2.88 MB floppy services are supported (int 13h) 334 | Print screen service is supported (int 5h) 335 | 8042 keyboard services are supported (int 9h) 336 | Serial services are supported (int 14h) 337 | Printer services are supported (int 17h) 338 | ACPI is supported 339 | USB legacy is supported 340 | BIOS boot specification is supported 341 | Targeted content distribution is supported 342 | UEFI is supported 343 | BIOS Revision: 5.12 344 | 345 | ... 346 | 347 | # superio 348 | 349 | superio是一个总线设备,其下可以提供:串口、并口、PS/2鼠标键盘、温度监测与风扇转速监测等 350 | 351 | superio通过io空间的两个寄存器访问index/data(0x2e/0x2f 0x4e/0x4f),具体使用0x2e/0x2f还是0x4e/0x4f由芯片决定 352 | 353 | index(0-0x2f),为全局信息和配置 354 | index(0x30-0xff),为具体逻辑设备的相关的配置 355 | 356 | superio有如下index 357 | 0x20 Device ID of superio (2 bytes) 358 | 0x07 logical device select 359 | 0x30 logical device enable 360 | 0x60 logical device address (2 bytes) 361 | 362 | 串口一般接在superio下,需要看一下数据手册根据具体情况修正。 363 | 364 | 365 | # PIC(8259) 366 | 367 | 传统的中断控制器,通过两片8259连级提供15个IRQ。刚开始每个IRQ固定链接一个外设,但随着PIC设备增多IRQ数目不够。这时引入了中断共享的概令,为了共享一个IRQ引脚,需要把中断设置为电平触发,当一个IRQ触发中断时,中断复位程序依次编列该IRQ连接的PCI设备处理完清除中断标志,直到所有的设备处理完成。 368 | 369 | 设备树需要配置中断路由,对应src/soc/intel/skylake/chip.h中soc_intel_skylake_config结构体的如下字段 370 | ```c 371 | uint8_t pirqa_routing; 372 | uint8_t pirqb_routing; 373 | uint8_t pirqc_routing; 374 | uint8_t pirqd_routing; 375 | uint8_t pirqe_routing; 376 | uint8_t pirqf_routing; 377 | uint8_t pirqg_routing; 378 | uint8_t pirqh_routing; 379 | ``` 380 | 381 | # ioapic 382 | 383 | ioapic是南桥上的一个设备,映射到内存空间[0xFEC00000-0xFECFFFFF] 384 | 其中有三个寄存器 385 | IDX (offset 00h, size 32bits) 386 | DAT (0ffset 10h, size 32bits) 387 | EOIR(offset 40h, size 32bits) 388 | 389 | 通过这三个寄存器可以间接访问ioapic空间,ioapic空间以4字节编址,即一个地址对应32bits。对DAT读写对应读取地址为IDX的32bits数据。 390 | 391 | 在coreboot中代码实现如下 392 | ```c 393 | // 读取ioapic空间,偏移量为reg的寄存器的值 394 | // ioapic_base对应ioapic映射到的内存地址 395 | // reg对应ioapic空间的偏移量 396 | u32 io_apic_read(void *ioapic_base, u32 reg) 397 | { 398 | write32(ioapic_base, reg); 399 | return read32(ioapic_base + 0x10); 400 | } 401 | 402 | // 写ioapic空间,偏移量为reg,值为value 403 | // ioapic_base对应ioapic映射到的内存地址 404 | // reg对应ioapic空间的偏移量 405 | // value对应要写入的数据 406 | void io_apic_write(void *ioapic_base, u32 reg, u32 value) 407 | { 408 | write32(ioapic_base, reg); 409 | write32(ioapic_base + 0x10, value); 410 | } 411 | ``` 412 | 413 | ioapic空间寄存器如下 414 | 415 | ID (offset 00h, size 32bits),APIC ID用于标示一个APIC设备(Local APIC / IO APIC) 416 | VER (offset 01h, size 32bits),APIC版本信息,以及中断重定向入口条目数(条目数在上电时可以读写一次,固件程序可以写入需要的值,把部分重定向入口保留给固件程序使用) 417 | RTEn (offset 10h + 2 * n, size 64bits) 418 | 63:56 Destination Field 目标APIC设备(CPU),根据Destination Field域的值意义不同,Physical Mode(Destination Mode = 0) 表示目标处理器的APIC ID,Logical Mode(Destination Mode = 1)表示一组处理器 419 | 16:16 Interrupt Mask 中断屏蔽位,为1时屏蔽中断脚 420 | 15:15 Trigger Mode 中断触发方式,0边沿触发,1电平触发 421 | 14:14 Remote IRR 只对电平触发中断有效,当IOAPIC接受中断后置位该位,在写EOI时清除该位 422 | 13:13 Interrupt Input Pin Polarity 中断触发的有效电平,0高电平,1低电平 423 | 12:12 Delivery Status 传送状态,0->当前没有中断;1->ioapic已经接受到中断,但由于某种原因中断还未传送给lapic 424 | 11:11 Destination Mode 0->Physical Mode,1->Logical Mode 425 | 10:8 Delivery Mode 传送模式,用于指定中断以何种放送发送给CPU 426 | 7:0 Interrupt Vector 中断向量 427 | 428 | 429 | # PMC(Power Management Configuration) 430 | 431 | 其中有一个GPE(General Puropse Event),用于唤醒处理器或者触发中断(触发中断跟GPIO的配置有关) 432 | 相关寄存器有 433 | GPE_STS:状态,写1清0 434 | GPE_EN:使能位 435 | GPIO_CFG:配置位,设置IO口映射到的GPE 436 | 437 | 设备树需要配置使能以及IO映射,对应src/soc/intel/skylake/chip.h中soc_intel_skylake_config结构体的如下字段 438 | ``` 439 | /* 使能位配置 */ 440 | uint32_t gpe0_en_1; /* GPE0_EN_31_0 */ 441 | uint32_t gpe0_en_2; /* GPE0_EN_63_32 */ 442 | uint32_t gpe0_en_3; /* GPE0_EN_95_64 */ 443 | uint32_t gpe0_en_4; /* GPE0_EN_127_96 / GPE_STD */ 444 | 445 | /* GPE映射到IO */ 446 | uint8_t gpe0_dw0; /* GPE0_31_0 STS/EN */ 447 | uint8_t gpe0_dw1; /* GPE0_63_32 STS/EN */ 448 | uint8_t gpe0_dw2; /* GPE0_95_64 STS/EN */ 449 | ``` 450 | 451 | # s0ix state 452 | 453 | 454 | s0ix状态是英特尔soc待机状态。s0ix状态下会关闭部分soc,在他们不需要使用时。s0ix是soc最深的休眠状态。 455 | 456 | 设备树可以使能s0ix状态,对应src/soc/intel/skylake/chip.h中soc_intel_skylake_config结构体的如下字段 457 | ``` 458 | /* Enable S0iX support */ 459 | int s0ix_enable; 460 | ``` 461 | 462 | 此配置主要用于生成ACPI表(SSDT表,其中记录了CPU状态以及配置信息) 463 | 464 | # DPTF(Intel ® Dynamic Platform and Thermal Framework) 465 | 466 | 英特尔热量动态管理平台 467 | 468 | 设备树可以使能DPTF,对应src/soc/intel/skylake/chip.h中soc_intel_skylake_config结构体的如下字段 469 | ``` 470 | /* Enable DPTF support */ 471 | int dptf_enable; 472 | ``` 473 | 此变量会在ACPI中使用,在SMI程序中也会使用。为此coreboot定义了一个结构GNVS,在SMI/ACPI以及OSPM中传递这些信息。 474 | 475 | 在C程序中定义的GNVS位于`src/soc/intel/skylake/include/soc/nvs.h`中 476 | 477 | 在ACPI中定义的GNVS位于`src/soc/intel/skylake/acpi/globalnvs.asl`中 478 | 479 | 在写ACPI表时访问dptf_enable为GNVS中的一个字段DPTE赋值,代码位于`src/soc/intel/skylake/acpi.c`中的acpi_create_gnvs函数中 480 | 481 | 在SOC的ACPI代码中使用,基本都用与DPTF相关设备STA函数中。基本格式都一样,如下: 482 | ``` 483 | Method (_STA) 484 | { 485 | If (LEqual (\DPTE, One)) { 486 | Return (0xF) 487 | } Else { 488 | Return (0x0) 489 | } 490 | } 491 | ``` 492 | 此函数是acpi中的一个重要函数,用于获取设备的状态,通过DPTE来使能DPTF相关设备 493 | bit0 - 置位,如果设备存在 494 | bit1 - 置位,如果设备启用并解码资源 495 | bit2 - 置位,如果需要在UI中显示 496 | bit3 - 置位,如果设备正常工作 497 | bit4 - 置位,如果设备使用电池供电 498 | 499 | 500 | # 电源门控策略 501 | 502 | 在PMC内存映射空间,有三个寄存器,用于配置在S3、S4、S5下的电源门控策略,它不直接影响电源,但其他硬件可以动态应用调节电源 503 | 504 | 设备树中有电源门控配置,对应src/soc/intel/skylake/chip.h中soc_intel_skylake_config结构体的如下字段 505 | ``` 506 | /* Deep SX enables */ 507 | int deep_s3_enable_ac; 508 | int deep_s3_enable_dc; 509 | int deep_s5_enable_ac; 510 | int deep_s5_enable_dc; 511 | 512 | /* 513 | * Deep Sx Configuration 514 | * DSX_EN_WAKE_PIN - Enable WAKE# pin 515 | * DSX_EN_LAN_WAKE_PIN - Enable LAN_WAKE# pin 516 | * DSX_EN_AC_PRESENT_PIN - Enable AC_PRESENT pin 517 | */ 518 | uint32_t deep_sx_config; 519 | ``` 520 | 521 | # CPU 热量相关的配置 522 | 523 | 此配置作用与CPU的MSR 524 | 525 | 设备树中有对应的热量控制配置,对应src/soc/intel/skylake/chip.h中soc_intel_skylake_config结构体的如下字段 526 | ``` 527 | /* TCC activation offset */ 528 | int tcc_offset; 529 | ``` 530 | 此字段最终被写入MSR_TEMPERATURE_TARGET[24:27],配置值可以通过读取MSR获取 531 | 532 | MSR读取(代码来自inteltool) 533 | ``` 534 | // 第一步加载内核模块 : modprobe msr 535 | 536 | static int open_and_seek(int cpu, unsigned long msr, int mode, int *fd) 537 | { 538 | char dev[512]; 539 | char temp_string[50]; 540 | 541 | snprintf(dev, sizeof(dev), "/dev/cpu/%d/msr", cpu); 542 | *fd = open(dev, mode); 543 | 544 | if (*fd < 0) { 545 | sprintf(temp_string, 546 | "open(\"%s\"): %s\n", dev, strerror(errno)); 547 | perror(temp_string); 548 | return -1; 549 | } 550 | 551 | if (lseek(*fd, msr, SEEK_SET) == (off_t)-1) { 552 | sprintf(temp_string, "lseek(%lu): %s\n", msr, strerror(errno)); 553 | perror(temp_string); 554 | close(*fd); 555 | return -1; 556 | } 557 | 558 | return 0; 559 | } 560 | 561 | msr_t rdmsr_from_cpu(int cpu, unsigned long addr) 562 | { 563 | int fd; 564 | msr_t msr = { 0xffffffff, 0xffffffff }; 565 | uint32_t buf[2]; 566 | char temp_string[50]; 567 | 568 | if (open_and_seek(cpu, addr, O_RDONLY, &fd) < 0) { 569 | sprintf(temp_string, "Could not read MSR for CPU#%d", cpu); 570 | perror(temp_string); 571 | } 572 | 573 | if (read(fd, buf, 8) == 8) { 574 | msr.lo = buf[0]; 575 | msr.hi = buf[1]; 576 | } 577 | 578 | close(fd); 579 | 580 | return msr; 581 | } 582 | 583 | int get_number_of_cpus(void) 584 | { 585 | return sysconf(_SC_NPROCESSORS_ONLN); 586 | } 587 | ``` 588 | 589 | # CPU热量限制 590 | 591 | 此配置用于控制CPU热量(设置MSR) 592 | 593 | 位于设备树中,对应src/soc/intel/skylake/chip.h中soc_intel_skylake_config结构体的如下字段 594 | ``` 595 | /* PL2 Override value in Watts */ 596 | u32 tdp_pl2_override; 597 | 598 | /* SysPL2 Value in Watts */ 599 | u32 tdp_psyspl2; 600 | ``` 601 | 相关代码位于src/soc/intel/skylake/cpu.c set_power_limits 602 | 603 | 可以参照代码,并根据机器的MSR推算出具体配置 604 | -------------------------------------------------------------------------------- /resources/images/docs/opentitan/alert_handler_esc_timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/alert_handler_esc_timer.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/alert_handler_ping_timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/alert_handler_ping_timer.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/hmac_core_fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/hmac_core_fsm.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/i2c_fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/i2c_fsm.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/ibex.md-controller_fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/ibex.md-controller_fsm.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/sha2_fifo_fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/sha2_fifo_fsm.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/sha2_hash_ctrl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/sha2_hash_ctrl.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/sha2_pad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/sha2_pad.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/spi_fwm_rxf_ctrl_fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/spi_fwm_rxf_ctrl_fsm.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/spi_fwm_txf_ctrl_fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/spi_fwm_txf_ctrl_fsm.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/usb_fs_nb_in_pe_fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/usb_fs_nb_in_pe_fsm.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/usb_fs_nb_out_pe_fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/usb_fs_nb_out_pe_fsm.png -------------------------------------------------------------------------------- /resources/images/docs/opentitan/usb_fs_tx_fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardenedlinux/embedded-iot_profile/20b7bbf6541a8a42d5391adb1be5241723eb29f5/resources/images/docs/opentitan/usb_fs_tx_fsm.png --------------------------------------------------------------------------------