試實踐:RISC-V從核電源時鐘與OpenSBI)
先說點背景。這篇文章是“Bringing Up Heterogeneous RISC-V on Allwinner SoCs”的第二部分。第一部分主要聊了怎么把最小系統(tǒng)跑起來比如串口、時鐘、DDR 初始化還有怎么把 OpenSBI 引導進內(nèi)核。這一部分重點完全不同我們要面對的是真正的深水區(qū)異構多核。全志的 SoC 上RISC-V 核心往往不是孤零零一個而是和 Arm 主核、DSP、NPU 混在一顆芯片里有各自的內(nèi)存視圖、中斷控制器和電源域。把其中一個 RISC-V 核拉起來只是開始讓它們協(xié)同工作、不掉鏈子才是真正考驗功夫的地方。這篇文章適合誰適合已經(jīng)在嵌入式 Linux 或 RTOS 里摸爬滾打、做過一些板級 bring-up但還沒系統(tǒng)性處理過異構多核的人。看完之后你應該能理清全志異構平臺的啟動鏈路知道電源域和時鐘域要按什么順序處理理解 SBI/HSM 和 CPU hotplug 在啟動從核時扮演的角色也知道內(nèi)存一致性和消息通信該怎么驗證。內(nèi)容會比較硬核但我會盡量把每一步的“為什么”講清楚而不是直接甩一段寄存器操作讓你照著抄。1. 異構架構與啟動鏈路全景解讀1.1 異構從哪來一顆SoC里住著幾套“CPU世界觀”先明確一個概念這里的“異構”不是簡單的“大核小核”同構多核比如四顆 Cortex-A53而是指同一顆 SoC 里集成了不同指令集架構、不同特權級設計、甚至不同總線主控權的 CPU 核心組合。在全志的平臺上你經(jīng)常能看到這樣的組合一顆或幾顆 Arm Cortex-A 系列核心作為主控跑 Linux一到兩個 RISC-V 核心常見的是平頭哥 E907、C906、C910 這類用來做協(xié)處理、實時控制、低功耗偵聽或者干脆做 DSP 替代外加 NPU、DSP 這類加速器。為什么這么設計典型的場景是Arm 核跑復雜的應用系統(tǒng)但實時性不夠穩(wěn)定RISC-V 核跑 FreeRTOS 或者裸機任務專門處理中斷密集的 I/O 和傳感器數(shù)據(jù)。兩者共享 DDR各自又有私有的 SRAM。從硬件視角看它們就是“兩個世界”——不同的啟動地址、不同的時鐘源頭、不同的中斷號分配甚至看到的外設地址空間都有一部分是私有的。這對做 bring-up 的人來說意味著什么意味著你不能再用“一顆 SoC 一套 CPU 一套外設”的思維。你要同時管理至少兩套啟動流程協(xié)調(diào)兩個運行域的資源還要保證它們沒有互相踩踏。很多人第一次做這種平臺時最容易犯的錯就是只盯著自己要帶起來的那顆 RISC-V 核忽略了它和主核之間共享的資源PLL、DDR 控制器、MBUS、中斷控制器其實早就被對方初始化過了。1.2 啟動鏈路拆解從 ROM 到多核協(xié)作的完整旅程全志異構 SoC 的典型啟動鏈路我把它拆成了下面這幾個階段階段執(zhí)行主體主要任務是否涉及 RISC-V 核ROM BootSoC 內(nèi)置 ROM從 boot device 加載下一級引導SPL/Boot0通常不ROM 固定只會拉起主控核SPL/Boot0Arm 主核初始化 PLL、DDR、串口、存儲控制器不直接涉及但會配置到 RISC-V 核的電源和時鐘門控固件層OpenSBI/U-BootArm 主核進入 EL2/M-mode加載內(nèi)核和設備樹準備啟動 Linux會用到 SBI 的 HSM 擴展來管理從核內(nèi)核啟動Arm 主核SMP 初始化時依次喚醒 RISC-V 從核從核通過 SBI 調(diào)用或 DT 里的 enable-method 被拉起運行期協(xié)作所有核核間通信、共享內(nèi)存、設備中斷分發(fā)多核同時運行需要一致性保障這里有個很關鍵的點全志的 ROM 和 BootROM 階段默認只負責把主控核一般是 Arm 核帶起來。RISC-V 從核在系統(tǒng)上電后處于什么狀態(tài)通常是電源域未開啟、時鐘被關閉、復位拉死。也就是說它并不是悄悄地“空轉”而是徹底“斷電休眠”。所以整個 RISC-V 從核的 bring-up本質(zhì)上就是在啟動后期由主核主動完成一次“上電、解復位、加載固件、釋放啟動”的完整動作。搞清楚這條鏈路后面所有工作都是倒著往前推先讓主核的系統(tǒng)穩(wěn)定跑起來再一點點把從核的資源打開。2. 電源域與時鐘把從核“通電”是第一步2.1 電源域管理為什么不能一開始就把所有核都上電很多第一次做的人會有一個直覺上電就把所有核心的電源打開不就省事了嗎全志的 SoC 設計恰恰相反——它把不同核心的電源域做成可以獨立控制的目的就是讓你在低功耗場景下可以隨時關掉用不到的核心。這也是“異構”的真正價值之一按需供電。在全志的電源管理框架里你需要注意幾類寄存器電源開關控制POWER_SW_CTRL控制某個 CPU 域或 CPU 邏輯域的開關。復位控制CPU_CFG / CPU_RST控制每個核的復位釋放。時鐘門控CLK_GATING控制 CPU 核的時鐘開關。以我調(diào)試過的平臺為例RISC-V 從核的電源域在默認狀態(tài)下是“已關閉且保持復位”的。你要按下面這個順序操作先把 CPU 電源域的供電開關打開等待電源穩(wěn)定通常需要輪詢電源狀態(tài)寄存器或者簡單延時幾十微秒再從時鐘樹里把 RISC-V 核的時鐘門控打開最后才釋放復位信號。這個順序不能亂。如果你先釋放復位再打開時鐘核心會處于“時鐘不存在卻開始執(zhí)行指令”的狀態(tài)輕則掛死重則總線訪問異常導致整個系統(tǒng)卡住。我曾經(jīng)因為圖快跳過電源穩(wěn)定等待在 10 次啟動里有 3 次出現(xiàn)從核取指異常后來查了好久才發(fā)現(xiàn)是電源未穩(wěn)就釋放了復位。2.2 時鐘樹與分頻配置從核跑多快不是你說了算全志的時鐘樹比較復雜但核心原則就一條CPU 核的時鐘來自 PLL 分頻而 PLL 通常已經(jīng)被主核初始化過了。你不需要自己創(chuàng)建一個新 PLL但你必須知道從核的時鐘源掛在哪個 PLL 的哪條分頻鏈路上。實操層面的步驟大致是找到該 SoC 用戶手冊里的時鐘樹框圖定位 RISC-V 核的時鐘源比如“CPU1_CLK_SRC”或“RISC-V_CLK”確認它的父時鐘是哪個 PLL以及當前頻率配置分頻系數(shù)讓從核運行在目標頻率打開時鐘門控并等待時鐘穩(wěn)定。這里最容易踩的坑是全志經(jīng)常把兩個核的時鐘源放在同一顆 PLL 上但允許獨立分頻。比如主核跑 1.2GHz從核可以通過分頻跑到 600MHz 或 400MHz。如果你在調(diào)整分頻系數(shù)時改錯了父 PLL很可能直接把主核的時鐘也帶偏整個系統(tǒng)瞬間死機。分享一個我常用的檢查方法改動任何時鐘相關寄存器之前先把當前的寄存器值完整 dump 出來并存到一個文件里。如果系統(tǒng)死機可以通過 JTAG 反復對比確認是哪一步改出了問題。不要憑記憶寫寄存器這種平臺寄存器的 bit 位定義五花八門同一個偏移在不同型號上可能含義完全不同。2.3 實操心得電源和時鐘操作的安全寫法按我現(xiàn)在的習慣凡是做從核電源/時鐘初始化的操作我都會擺成下面這個偽代碼結構。具體的寄存器基址和偏移以你手頭 SoC 的用戶手冊為準。void riscv_core_power_on(uint32_t core_id) { // 1. 打開核心電源域開關 writel(1U core_id, POWER_SW_CTRL_BASE CPU_POWER_ON_OFFSET); // 2. 等待電源域狀態(tài)確認 while (!(readl(POWER_STAT_BASE CPU_POWER_STAT_OFFSET) (1U core_id))) ; // 3. 配置時鐘源和分頻 writel(CLK_SRC_SEL_A 8 | DIV_VALUE, CLK_BASE RISC_V_CLK_CFG_OFFSET); // 4. 打開時鐘門控 writel(readl(CLK_BASE CLK_GATING_OFFSET) | (1U core_id), CLK_BASE CLK_GATING_OFFSET); // 5. 釋放復位 writel(readl(CPU_CFG_BASE CPU_RST_OFFSET) | (1U core_id), CPU_CFG_BASE CPU_RST_OFFSET); }這個順序我跑了多款全志平臺都很穩(wěn)。但請記住不同芯片的寄存器布局差異很大比如有的平臺通過“寫 1 清 0”的方式控制復位有的用“寫 1 置位”有的在電源域穩(wěn)定之后還要求先寫一個“ack 寄存器”。拿到新的開發(fā)板第一步永遠是先讀一遍參考固件fex/SCP 固件里對應的初始化序列而不是自己發(fā)明輪子。3. 固件層搭建OpenSBI、M-mode 與從核喚醒3.1 為什么要先談 M-mode 固件RISC-V 特權級模型RISC-V 和 Arm 的一個顯著區(qū)別在于它的特權級設計非常清晰M-mode機器模式、S-mode監(jiān)管模式、U-mode用戶模式。Linux 內(nèi)核跑在 S-modeOpenSBI 跑在 M-mode。這意味著所有涉及 CPU 硬件狀態(tài)的操作比如修改 mstatus、處理 IPI、管理內(nèi)存物理映射都不能由內(nèi)核直接做而要經(jīng)過 SBI 調(diào)用。全志平臺上的 RISC-V 從核跑系統(tǒng)時通常有兩種模式跑裸機或者 RTOS全程留在 M-mode不需要 OpenSBI自己直接操作所有硬件。跑 Linux必須先把 OpenSBI 加載到從核的 M-mode然后從核的 Linux 才能啟動到 S-mode。對于異構 Linux 場景主流方案是每個 RISC-V 核都跑一份 OpenSBI通過 SBI 的 HSMHibernate Suspend Mechanism擴展來協(xié)調(diào)啟動與休眠。HSM 擴展定義了一組標準調(diào)用比如sbi_hsm_hart_start、sbi_hsm_hart_stop用來讓一個 hart 啟動另一個 hart。這可比你自己寫一套跨核啟動協(xié)議要可靠得多。3.2 用 SBI HSM 擴展喚醒從核標準路徑怎么走假設你的系統(tǒng)現(xiàn)在這樣Arm 主核已經(jīng)跑起了 LinuxRISC-V 從核的電源和時鐘已經(jīng)準備好復位也已經(jīng)釋放。接下來就是要讓從核的 PC 跳到一段合法的啟動代碼。用 SBI HSM 的方式整個流程是主核上的軟件通常是內(nèi)核里的 cpu_ops發(fā)起sbi_hsm_hart_start調(diào)用OpenSBI 在 M-mode 檢查目標 hart 的狀態(tài)OpenSBI 將目標 hart 的啟動地址寫入它的stvec并設置啟動參數(shù)目標 hart 從復位向量開始執(zhí)行先進入 OpenSBI 的冷啟動或 warm start 路徑OpenSBI 初始化完成后跳轉到內(nèi)核的啟動入口。這個過程里最常出的問題是從核的復位向量和stvec之間的關系沒理清。RISC-V 從核上電后第一件事是從芯片規(guī)定的復位地址取指——這個地址通常是一個 Boot ROM 或者固定的 SRAM 地址。但 OpenSBI 接管后真正做的是把啟動入口寫到stvec而stvec必須指向 OpenSBI 自己的 trap handler。如果你直接讓從核跳到一個裸地址比如你編譯的 Linux 內(nèi)核入口除非你完全繞過了 OpenSBI否則幾乎不可能成功。3.3 啟動代碼細節(jié)從核第一次“睜眼”看到的世界從核的啟動匯編我習慣寫成非常樸素的版本先做最基本的棧指針初始化、bss 清零然后跳到 C 代碼。下面是一段典型的從核啟動入口以 RISC-V 匯編為例.section .text.init .globl _start _start: /* 關中斷 */ csrw mie, zero csrw mip, zero /* 設置臨時棧 */ la sp, _stack_top /* 清零 bss */ la a0, __bss_start la a1, __bss_end 1: bge a0, a1, 2f sd zero, 0(a0) addi a0, a0, 8 j 1b 2: /* 跳轉 C 初始化 */ call riscv_secondary_main loop: wfi j loop這段代碼本身沒什么玄機但有兩個細節(jié)需要特別留意。第一個細節(jié)是wfi死循環(huán)必須有。從核如果在初始化完成之前被踩到未定義狀態(tài)至少還有一個低功耗等待的退路如果沒有這個循環(huán)從核會直接亂跳把總線搞得烏煙瘴氣。第二個細節(jié)是棧指針。很多從核的私有 SRAM 非常小可能只有 64KB 或者 128KB。你如果在鏈接腳本里把棧放得太大從核一啟動就把 SRAM 踩穿接著蓋掉 OpenSBI 或者內(nèi)核的關鍵數(shù)據(jù)。我一般會把從核的棧放在私有 SRAM 的最高地址并且限定大小不超過 16KB因為從核啟動階段根本用不了那么多??臻g。4. Linux 內(nèi)核側適配設備樹與 SMP4.1 設備樹里怎么描述異構多核設備樹是 Linux 內(nèi)核了解硬件拓撲的主要途徑。異構多核要正常工作必須在 DT 里把每個核的cpunode 以及enable-method寫對。一段典型的多核描述長這樣cpus { #address-cells 1; #size-cells 0; cpu0: cpu0 { compatible arm,cortex-a53; reg 0x0; device_type cpu; enable-method psci; }; cpu1: cpu1 { compatible riscv; reg 0x1; device_type cpu; riscv,isa rv64imafdc; mmu-type riscv,sv39; enable-method sbi; cpu-release-addr 0x0 0x45000000; }; };注意幾個容易搞錯的地方。enable-method必須匹配實際使用的啟動方式。Arm 核繼續(xù)用psciRISC-V 核用sbi這倆不能混。riscv,isa必須和實際硬件一致。如果你寫的是rv64imafdc但實際核不支持 F 擴展內(nèi)核在檢測到非法指令時會直接 panic。用cat /proc/cpuinfo檢查實際導出的 ISA 是排查這類問題的第一步。cpu-release-addr只在舊式 spin-table 方式下需要。如果你走 SBI HSM這個字段可以省略但有些老內(nèi)核或者定制內(nèi)核還是會參考它。4.2 PSCI、SBI、spin-table三套 enable-method 怎么選全志平臺上的異構多核可能的組合我列個表方便你看組合主核RISC-V 從核典型 enable-method適用場景主核 Linux 從核裸機ArmRISC-V無從核直接跑裸機實時協(xié)處理、I/O 偵聽主核 Linux 從核 RTOSArmRISC-Vremoteproc 或自定義需要框架管理生命周期同構 RISC-V SMPRISC-VRISC-Vsbi通用 SMP LinuxArm Linux RISC-V LinuxArmRISC-Vsbi通過 OpenSBI雙系統(tǒng)異構 AMP實際操作中我見過最多的是第一種Arm 主核跑 LinuxRISC-V 從核跑裸機或 FreeRTOS 完成特定實時任務。這種場景并不一定需要在內(nèi)核設備樹里給從核建 node反而用 remoteproc 框架管理更合適一個固件文件負責描述加載地址、啟動地址和資源表。如果你堅持要讓從核也跑 Linux那就要走完整 SBI 路線。SBI HSM 比 spin-table 的優(yōu)勢在于它不僅能熱插拔還能處理休眠和低功耗狀態(tài)。但代價是每個從核都要有一份獨立的 OpenSBI而且在共享 DDR 上要處理好內(nèi)存空洞和保留區(qū)域否則主核 Linux 的內(nèi)存管理會覆蓋掉從核的代碼段。4.3 啟動日志解讀怎么看從核有沒有被真正拉起判斷從核是否成功啟動最直接的辦法是看主核內(nèi)核日志里有沒有類似下面的信息smp: Bringing up secondary CPUs ... [ 0.186093] smp: Booting CPU1 (cpu 1) [ 0.190216] CPU1: RISC-V CPU (rv64imafdc) running at 600 MHz如果你看到的是CPU1: failed to boot或者干脆沒有任何Booting CPU1的日志那么問題大概率出在三個地方電源和時鐘配置有問題從核根本沒有運行從核的啟動地址不正確OpenSBI 找不到入口SBI 調(diào)用失敗主核 OpenSBI 返回錯誤碼。排查時我一般會做兩件事第一在從核固件入口處加一個 GPIO 翻轉邏輯通過示波器或者邏輯分析儀看從核是否真的執(zhí)行到了 C 代碼。第二打開 OpenSBI 的詳細日志-DOPENSBI_ENABLE_DEBUG看主核發(fā)起 HSM 調(diào)用時返回了什么狀態(tài)。5. 內(nèi)存一致性與共享通信5.1 多核共享 DDR 的“假一致”陷阱異構平臺最容易出問題的不是性能而是內(nèi)存一致性。Arm 核和 RISC-V 核共享同一個 DDR 控制器時硬件上通常有一致性總線比如 CCI 或 MBUS但并不意味著 CPU 的 L1/L2 緩存天然一致。如果你的 RISC-V 核沒有參與硬件緩存一致性協(xié)議很多協(xié)處理核不支持那么主核對一段內(nèi)存的寫入從核讀到的可能是舊值。舉個真實例子。我在一個平臺上讓 Arm 核往共享內(nèi)存寫一批描述符然后通過 doorbell 中斷通知 RISC-V 核去讀。從核讀到的數(shù)據(jù)有一半是 0xff另一半是舊數(shù)據(jù)。查了總線協(xié)議、DMA 配置最后發(fā)現(xiàn)就是緩存一致性問題Arm 核的數(shù)據(jù)還留在 L2 cache 里沒有寫穿到內(nèi)存。解決這類問題的方式有三種共享內(nèi)存區(qū)域標注為 non-cacheable。在設備樹里用dma-coherent或者直接在頁表里關掉 cache bit。每次通信前做顯式 cache clean/invalidate。用硬件支持的一致性端口如果有的話把從核納入一致性域。最省事、也最穩(wěn)妥的方案是第一種犧牲一點性能換正確性。全志這類平臺的共享內(nèi)存通常本來就在 MBUS 和 SRAM 之間帶寬也不高強行追求 cache 優(yōu)化意義不大。5.2 緩存維護操作的“放之四海而皆準”寫法哪怕你決定走 cacheable 路線也要知道緩存維護指令怎么發(fā)。RISC-V 上常見的操作是fence.i和sfence.vma。前者用于指令緩存同步后者用于地址轉換緩存失效。void flush_icache_range(unsigned long start, unsigned long end) { asm volatile(fence.i ::: memory); }聽起來簡單但真正容易出問題的是跨核場景。Arm 核寫了一段從核要執(zhí)行的代碼然后你讓從核去取指——如果從核的 I-cache 里還緩存著舊指令它執(zhí)行的仍然是舊代碼。這種情況fence.i需要在從核上執(zhí)行一次才能清掉它自身的指令緩存。實際操作中我建議在共享代碼區(qū)更新之后除了主核做一次 clean還要讓從核在啟動路徑最開頭執(zhí)行一次fence.i。雖然會損失幾條指令的性能但換來的是“無論如何都不會執(zhí)行到舊代碼”的確定性。這在調(diào)試初期非常重要等你把整個鏈路調(diào)穩(wěn)定了再考慮優(yōu)化掉。5.3 核間通信選型mailbox、共享內(nèi)存還是信號量全志 SoC 上核間通信的方案不外乎下面幾種純共享內(nèi)存 標志位最簡單但需要自己處理并發(fā)和緩存維護。適合大數(shù)據(jù)量、低頻通知的場景。共享內(nèi)存 interruptdoorbell全志平臺每個核都有一組中斷控制器你可以在寫完成數(shù)據(jù)之后給對端核發(fā)一個 IPI。這是最常見的方案。硬件 mailbox部分型號有專用的 mailbox 硬件可以發(fā)送短消息比如最多 8 個字。適合控制類小消息不需要復雜緩存管理。我個人的建議是初期調(diào)試優(yōu)先選擇“共享內(nèi)存 IPI”靈活且可控。你先通過 GPIO 或者串口打印驗證 IPI 通路是否正常然后再去搞共享內(nèi)存協(xié)議。千萬不要一上來就搞一個復雜的消息框架否則一旦出錯你分不清是中斷沒到、內(nèi)存不一致還是協(xié)議解析踩空排查成本極高。6. 調(diào)試與驗證從“能跑”到“跑得穩(wěn)”6.1 調(diào)試手段選擇JTAG、串口日志、還是內(nèi)核輔助異構平臺的調(diào)試最大的痛苦在于“不知道從核到底在干什么”。串口日志是必須的但對核間這種時序敏感性極高的問題串口本身可能掩蓋問題——你加的打印延遲足夠把問題“治好”。我一般按下面優(yōu)先級組織調(diào)試手段串口日志用于確認從核啟動進度、打印關鍵分支GPIO 翻轉用示波器測時序判斷執(zhí)行路徑上幾個關鍵節(jié)點是否按預期到達JTAG/OpenOCD用于查看從核的 PC、堆棧、通用寄存器內(nèi)核側輔助比如從核跑 Linux 時用/sys/devices/system/cpu/cpu1/online控制熱插拔測試。調(diào)試時要有一個理念每次只改一個變量。比如你先驗證電源域和復位不要同時調(diào)時鐘和內(nèi)核配表。用 GPIO 在從核入口處翻轉一次如果翻出來了說明電源、時鐘、復位這條鏈路沒問題如果沒有你才需要往下查。6.2 驗證指標不只是“能開機”多核 bring-up 的驗證階段我給自己定的標準不是“能開機交差”而是下面這幾個指標驗證項目標驗證方法從核啟動時間從釋放復位到執(zhí)行第一個用戶代碼 50msGPIO/示波器測量核數(shù)識別內(nèi)核能看到所有預期核/proc/cpuinfo熱插拔穩(wěn)定性CPU off/on 循環(huán) 1000 次無失敗腳本自動化核間通信正確性100000 次消息傳輸零錯誤共享內(nèi)存回環(huán)測試長時間穩(wěn)定性雙核滿載運行 72 小時無崩潰stress 工具加自定義負載不要小看熱插拔測試。SBI HSM 的 warm start 路徑和冷啟動路徑往往不同很多平臺“冷啟動一切正常一執(zhí)行 CPU hotplug 就死機”。這個問題在異構平臺上尤其典型因為主核和從核共享了某些低功耗資源停核的時候沒有正確關掉該關的下次啟動時就會踩到未初始化狀態(tài)。6.3 壓力測試腳本逼出隱藏問題下面是一個簡單但實用的熱插拔測試腳本跑在 Linux 主核上#!/bin/bash CPU1 FAIL0 for i in $(seq 1 1000); do echo 1 /sys/devices/system/cpu/cpu${CPU}/online if [ $? -ne 0 ]; then echo online failed at iteration $i FAIL1 break fi sleep 0.01 echo 0 /sys/devices/system/cpu/cpu${CPU}/online if [ $? -ne 0 ]; then echo offline failed at iteration $i FAIL1 break fi done if [ $FAIL -eq 0 ]; then echo Hotplug test passed: 1000 cycles else echo Hotplug test failed fi跑這個腳本之前建議先在從核的固件里加一個計數(shù)器每次啟動時自增并寫到一個固定內(nèi)存地址。主核熱插拔一千次之后去讀那個地址確認從核確實全新啟動了一千次而不是只啟動了一次、后面都在假裝成功。這種“假成功”在 SBI 路徑上非常隱蔽我甚至遇到過從核第一次啟動后根本沒死只是被錯誤地標記為下線結果每次 on/off 操作只是在主核軟件層面打轉的情況。7. 常見問題與排查技巧實錄7.1 問題速查表我踩過的那些坑下面是我在多個全志異構平臺上實際遇到過的典型問題總結成表方便你遇到類似情況時快速定位現(xiàn)象可能原因排查方向從核一點反應都沒有電源域未打開/復位未釋放檢查電源控制寄存器狀態(tài)測量核心供電從核啟動后 PC 跑飛啟動地址錯誤或入口處中斷未關用 JTAG 看 PC 值確認 stvec 和復位向量從核代碼執(zhí)行到一半掛死私有 SRAM 溢出或棧溢出檢查鏈接腳本確認棧大小和內(nèi)存邊界主核內(nèi)核報 CPU1 failed to bootHSM 調(diào)用返回錯誤打開 OpenSBI 調(diào)試日志返回碼逐項比對從核能啟動但亂打印串口波特率/UART 時鐘配置不一致確認從核側 UART 時鐘源和主核側是否一致熱插拔第二次失敗停核時未正確關閉緩存/中斷檢查 SBI 停止路徑和緩存維護操作共享數(shù)據(jù)讀到舊值緩存一致性未處理共享內(nèi)存改 non-cacheable 或顯式 clean7.2 獨家避坑幾個文檔里不會寫的細節(jié)第一個坑是關于“從核啟動延遲”。很多時候你釋放復位后從核不會立刻開始取指而是要等幾十毫秒的 PLL 鎖定時間。如果你在釋放復位后立刻去檢查從核狀態(tài)寄存器的某個標志位大概率會失敗。正確做法是釋放復位后先等待一個固定的、足夠長的延時我一般用 100ms再去做后續(xù)檢查。第二個坑是內(nèi)存保留區(qū)。主核 Linux 跑起來后默認會管理整個 DDR它根本不知道哪一塊內(nèi)存是從核的固件和堆棧。如果你在設備樹里沒有用reserved-memory把這些區(qū)域圈出來Linux 的 page allocator 就可能把從核正在執(zhí)行的代碼頁面重新分配給其他進程后果就是從核毫無征兆地死掉。在你的 devicetree 里務必要有類似這樣的保留區(qū)reserved-memory { #address-cells 1; #size-cells 1; ranges; riscv_fw_mem: riscv-fw45000000 { reg 0x45000000 0x1000000; no-map; }; };no-map關鍵字非常關鍵它告訴內(nèi)核這塊內(nèi)存不允許做頁表映射從而避免任何虛擬地址別名問題。第三個坑是 IPI 中斷號。全志平臺上不同核的中斷控制器看到的 IRQ 編號空間可能不一樣。你在主核上發(fā) IPI 給從核時用的中斷號未必等于從核處理時看到的中斷號。一定要先在從核固件里通過csrr讀取中斷號確認值之后再寫正式處理函數(shù)不要想當然地復用同一個數(shù)字。7.3 排查方法論一步一步縮小范圍多核系統(tǒng)排查最忌諱的是“東改一下西試一下”。我建議你每次調(diào)試都按下面五步走確認目標核的硬件狀態(tài)電源是否打開、時鐘是否穩(wěn)定、復位是否釋放確認最小啟動路徑從核是否執(zhí)行到了我們加的 GPIO 翻轉點確認固件加載是否正確目標內(nèi)存區(qū)域的字節(jié)內(nèi)容是否和編譯產(chǎn)物一致確認 SBI/啟動協(xié)議交互主核的調(diào)用是否返回成功從核的入口地址是否正確確認系統(tǒng)軟件適配設備樹、DMA 內(nèi)存圈定、中斷路由是否和硬件一致。這五步一層層下來大部分問題都能在第五步之前暴露。我見過最多的失敗案例都是因為跳過前四步直接在內(nèi)核日志里找原因最后繞了一大圈發(fā)現(xiàn)只是某個 GPIO 復位信號沒拉高。8. 實測體會與后續(xù)擴展做異構多核 bring-up 這件事跟我們平時做普通單核或多核系統(tǒng)最大的不同是你要同時維護兩套“世界觀”。主核的世界里Linux 管理一切資源從核的世界里你可能只有一個裸機循環(huán)和幾個中斷處理函數(shù)。一條共享內(nèi)存報文從主核發(fā)到從核中間隔著的不僅是硬件總線還有兩個完全不同的軟件棧。我在實際調(diào)試中最深的體會是嚴格遵循“一次只動一個變量”的原則。異構平臺上變量太多了電源、時鐘、復位、固件地址、DT 配置、緩存屬性任何一個地方都有可能是問題。如果你同時改了三個地方然后系統(tǒng)跑起來了你永遠不知道自己到底是怎么調(diào)好的。反過來下次換一顆芯片你又要從頭開始踩坑。這套流程如果跑順了后面的擴展方向其實很豐富??梢酝?AMP 方向走讓 RISC-V 從核獨立跑 FreeRTOS負責實時控制也可以往 SMP 方向走讓多個 RISC-V 核共同分擔 Linux 負載還可以用 remoteproc 框架把從核的生命周期納入 Linux 管理實現(xiàn)運行時加載和卸載從核固件。我自己比較推薦先把“共享內(nèi)存 IPI”的通信基建打磨穩(wěn)這是所有后續(xù)功能的地基。最后再分享一個小技巧如果你手頭的全志平臺上有多個相同型號的板子務必在從核固件里加一個可配置的延時參數(shù)或者打印開關通過板子上的撥碼開關或調(diào)試串口輸入來調(diào)節(jié)。異構 bring-up 階段很多問題都是時序相關的能快速調(diào)節(jié)從核啟動時序比什么高級調(diào)試器都管用。祝你們調(diào)板順利。