系統(tǒng)與在線升級(jí)實(shí)踐)
FPGA上跑Linux當(dāng)年我第一次看到這個(gè)說(shuō)法時(shí)確實(shí)挺好奇的。等自己動(dòng)手折騰了快一個(gè)月才明白CPU核只是冰山一角真正花時(shí)間的地方全在總線、DDR、啟動(dòng)鏈路和系統(tǒng)裁剪上。這篇文章記錄一下我完成的一套方案一個(gè)核心代碼量?jī)H5000行左右的RISC-V軟核在FPGA上跑起輕量級(jí)Linux并在此基礎(chǔ)上做了現(xiàn)場(chǎng)可用的在線升級(jí)機(jī)制。如果你是FPGA工程師想往RISC-V和嵌入式Linux方向擴(kuò)展或者你本身是做嵌入式Linux的想搞明白FPGA側(cè)的門(mén)道這篇應(yīng)該都能幫上忙。我不會(huì)只貼“成功日志”更多是講清楚每一步為什么這么做、有哪些坑可以提前避開(kāi)。1. 為什么是RISC-V軟核選型與設(shè)計(jì)思路1.1 方案選型能跑Linux的軟核不是“隨便寫(xiě)個(gè)CPU”FPGA上做軟核CPU通常想到的是MicroBlaze或者NiosII。這兩個(gè)確實(shí)是成熟方案問(wèn)題是商業(yè)授權(quán)、封閉生態(tài)、定制成本都比較高。換成RISC-V之后開(kāi)源、指令集精簡(jiǎn)而且相關(guān)工具鏈已經(jīng)非常完整很適合自己做軟核。但“能在FPGA上跑Linux”有一個(gè)硬性門(mén)檻處理器必須帶MMU也就是內(nèi)存管理單元。沒(méi)有MMU的處理器Linux內(nèi)核根本不愿意配合因?yàn)樗倪M(jìn)程管理、虛擬內(nèi)存、fork、exec、用戶態(tài)與內(nèi)核態(tài)切換全都建立在頁(yè)表映射和地址保護(hù)之上。我之前用過(guò)PicoRV32這類(lèi)只有一兩千行代碼的小核跑裸機(jī)或者RTOS很輕松但要跑Linux就不行了——它壓根沒(méi)有S模式也沒(méi)有MMU硬上Linux只能跑uClinux很多應(yīng)用和驅(qū)動(dòng)都會(huì)出問(wèn)題。所以選型時(shí)我把重點(diǎn)放在“有MMU、有S特權(quán)級(jí)、有足夠穩(wěn)定的總線接口”這幾個(gè)條件上。最終采用的方案是圍繞VexRiscv框架擴(kuò)展出來(lái)的一個(gè)RV32軟核支持Sv32頁(yè)表、支持M/S/U三級(jí)特權(quán)模式核心RTL代碼量大概5000行。這個(gè)規(guī)模不算大但足夠把Linux內(nèi)核啟動(dòng)起來(lái)。有一點(diǎn)要提前說(shuō)清楚5000行指的是CPU核心也就是流水線、寄存器堆、MMU、中斷和總線接口這些關(guān)鍵部分整個(gè)SoC還包括DDR控制器、Flash控制器、UART等外設(shè)這些往往通過(guò)LiteX這類(lèi)框架自動(dòng)生成。不要指望一個(gè)文件就撐起整個(gè)系統(tǒng)。1.2 架構(gòu)拆解處理器內(nèi)部是怎么設(shè)計(jì)的這個(gè)軟核采用的是單發(fā)射、順序執(zhí)行的5級(jí)流水線結(jié)構(gòu)。對(duì)比那些支持亂序執(zhí)行、多發(fā)射的高性能核它的優(yōu)勢(shì)是時(shí)序容易收斂面積小邏輯簡(jiǎn)單出問(wèn)題好排查。在FPGA上流水線深度和分支預(yù)測(cè)策略都會(huì)直接影響最高時(shí)鐘頻率順序發(fā)射的代價(jià)是IPC低一點(diǎn)但換來(lái)的是更簡(jiǎn)單的實(shí)現(xiàn)和更穩(wěn)定的時(shí)序這個(gè)取舍對(duì)FPGA上的軟核來(lái)說(shuō)非常合理。為了跑Linux軟核必須包含三個(gè)關(guān)鍵機(jī)制特權(quán)級(jí)切換M模式運(yùn)行OpenSBI或BootROMS模式運(yùn)行Linux內(nèi)核U模式運(yùn)行用戶進(jìn)程。這樣內(nèi)核和用戶程序可以被硬件隔離。中斷和異常使用RISC-V標(biāo)準(zhǔn)的PLIC和CLINT中斷控制器提供外部中斷和定時(shí)器中斷。Linux的進(jìn)程調(diào)度依賴(lài)定時(shí)器中斷沒(méi)有這部分系統(tǒng)啟動(dòng)到一半就會(huì)卡住。MMU這個(gè)核實(shí)現(xiàn)了Sv32頁(yè)表格式能處理32位虛擬地址到物理地址的轉(zhuǎn)換。沒(méi)有它Linux就只能退化成uClinux方案。我實(shí)際跑起來(lái)的時(shí)鐘頻率在90MHz左右性能當(dāng)然比不上硬核CPU但跑輕量級(jí)Linux、跑基礎(chǔ)網(wǎng)絡(luò)服務(wù)、跑業(yè)務(wù)邏輯都?jí)蛴?。很多?yīng)用場(chǎng)景其實(shí)根本不缺那點(diǎn)性能缺的是能自由改代碼的CPU。1.3 為什么代碼量能控制在5000行左右“5000行Verilog跑Linux”聽(tīng)起來(lái)像魔術(shù)其實(shí)是設(shè)計(jì)上做了大量取舍。RISC-V指令集本身就是典型RISC設(shè)計(jì)指令數(shù)量少編碼規(guī)則整齊這是先天優(yōu)勢(shì)。其次這個(gè)核沒(méi)有做浮點(diǎn)單元浮點(diǎn)運(yùn)算交給軟件模擬硬要算浮點(diǎn)會(huì)慢但對(duì)輕量級(jí)Linux來(lái)說(shuō)完全可以接受。也沒(méi)有做復(fù)雜的亂序執(zhí)行和深度緩存cache用簡(jiǎn)單直連映射大小也就2KB到8KB夠用就行。最關(guān)鍵的一點(diǎn)是大量“處理器外圍”工作被外包給了Bus Matrix和現(xiàn)成IP。處理器只需要實(shí)現(xiàn)取指、譯碼、執(zhí)行、訪存、寫(xiě)回這幾個(gè)核心環(huán)節(jié)然后通過(guò)AXI接口掛到總線上剩下的DDR控制器、SPI Flash控制器、UART控制器都可以用現(xiàn)成方案。這樣核心代碼自然就壓到5000行左右不會(huì)膨脹成一個(gè)幾萬(wàn)行的怪物。2. 硬件平臺(tái)搭建從RTL到可運(yùn)行SoC2.1 FPGA板卡選型與存儲(chǔ)資源規(guī)劃跑Linux對(duì)資源的要求比裸機(jī)高不少首要瓶頸不是邏輯單元而是存儲(chǔ)。我用的是Artix-7級(jí)別的板卡帶有128MB DDR3和16MB SPI NOR Flash。35T這個(gè)量級(jí)的邏輯資源已經(jīng)足夠放軟核加各種外設(shè)LUT利用率一般也就60%左右布線壓力不大。如果板卡沒(méi)有DDR只在FPGA片上BRAM里跑Linux那會(huì)很痛苦。Linux內(nèi)核解壓后要占用內(nèi)存initramfs要解壓到內(nèi)存進(jìn)程棧、堆、頁(yè)表全都要內(nèi)存效應(yīng)期的片上BRAM只有1.8Mbit左右遠(yuǎn)遠(yuǎn)不夠。所以選板卡時(shí)必須確認(rèn)有DDR/SDRAM最好是DDR3容量至少64MB才比較好跑一個(gè)稍微完整的文件系統(tǒng)。Flash容量也要提前規(guī)劃。一個(gè)FPGA bitstream文件大約幾MBOpenSBI大約幾十到一百多KBLinux內(nèi)核壓縮鏡像幾MBrootfs取決于你放多少工具BusyBox initramfs可以壓到幾MB如果放完整工具鏈就會(huì)超過(guò)十幾MB。16MB Flash能裝但比較緊湊有條件建議直接上32MB或64MB給后續(xù)在線升級(jí)留出空間。2.2 外設(shè)與總線搭建CPU之外的另一半工作CPU核心本身不產(chǎn)生價(jià)值它必須能訪問(wèn)DDR、能從Flash啟動(dòng)、能和外界通信這就要靠總線把它們串起來(lái)。這個(gè)SoC采用AXI4總線作為主干CPU作為主設(shè)備DDR、Flash、UART、Timer、中斷控制器都掛在總線上作為從設(shè)備。總線矩陣選擇了LiteX自動(dòng)生成省去了大量手寫(xiě)狀態(tài)機(jī)的工作。地址映射是整個(gè)系統(tǒng)最需要提前想清楚的事。我用的映射大約是外設(shè)地址范圍說(shuō)明DDR30x40000000 - 0x47FFFFFF主內(nèi)存Linux運(yùn)行于此SPI Flash0x20000000 - 0x20FFFFFF存放bitstream、固件、文件系統(tǒng)UART0xF0000000串口控制臺(tái)與升級(jí)通道CLINT/PLIC0xF0010000附近定時(shí)器與中斷控制器DDR控制器是系統(tǒng)里最大的坑。自己用Verilog寫(xiě)一個(gè)DDR3控制器從PHY層到Bank調(diào)度工作量不是一兩個(gè)月能穩(wěn)定收尾的。實(shí)際做法是使用現(xiàn)成方案比如Xilinx MIG IP或者LiteDRAM生成的可綜合控制器。這些方案經(jīng)過(guò)大量驗(yàn)證時(shí)序和訓(xùn)練邏輯相對(duì)可靠你只需要在約束文件里填對(duì)時(shí)鐘頻率、引腳位置和DDR型號(hào)即可。UART模塊在方案里相對(duì)簡(jiǎn)單但也不能掉以輕心。串口控制臺(tái)是聯(lián)調(diào)階段最核心的觀測(cè)手段如果UART都有問(wèn)題后面全盲調(diào)很難受。2.3 啟動(dòng)方式與Flash布局FPGA上電后首先加載的是bitstream。bitstream存在SPI Flash里FPGA廠商的配置引擎會(huì)自動(dòng)從Flash地址0讀取配置。配置完成后軟核CPU開(kāi)始復(fù)位取指。這時(shí)候就會(huì)出現(xiàn)一個(gè)關(guān)鍵問(wèn)題CPU從哪里開(kāi)始執(zhí)行第一條指令我是把OpenSBI放在Flash里的固定偏移CPU復(fù)位后通過(guò)BootROM把OpenSBI加載到內(nèi)存然后OpenSBI初始化S模式環(huán)境再跳轉(zhuǎn)到Linux內(nèi)核入口。Flash布局大致是這樣區(qū)域偏移內(nèi)容FPGA配置區(qū)0x00000000bitstreamOpenSBI區(qū)0x00500000左右引導(dǎo)固件內(nèi)核A區(qū)0x00600000左右Linux內(nèi)核鏡像DTB A區(qū)0x00A00000左右設(shè)備樹(shù)內(nèi)核B區(qū)0x00B00000備用內(nèi)核鏡像DTB B區(qū)0x00F00000備用設(shè)備樹(shù)rootfs區(qū)0x01000000BusyBox文件系統(tǒng)這個(gè)分區(qū)表在項(xiàng)目一開(kāi)始就要定好因?yàn)楹竺嬖诰€升級(jí)全靠它。我最初沒(méi)重視后面改分區(qū)偏移差點(diǎn)把整個(gè)系統(tǒng)又折騰了一遍。3. 輕量級(jí)Linux系統(tǒng)的構(gòu)建與啟動(dòng)3.1 軟件棧OpenSBI Linux內(nèi)核 initramfs跑Linux需要在FPGA里跑一套完整的軟件棧這里用的是OpenSBI、Linux內(nèi)核、BusyBox initramfs三件套。OpenSBI運(yùn)行在M模式下負(fù)責(zé)最底層的硬件初始化、中斷轉(zhuǎn)發(fā)和SBI調(diào)用服務(wù)相當(dāng)于RISC-V世界里的“固件”。OpenSBI跑完后會(huì)跳到S模式啟動(dòng)Linux內(nèi)核。選擇OpenSBI而不是傳統(tǒng)的BBL原因是它維護(hù)更活躍、功能更完整社區(qū)主流基本都轉(zhuǎn)向它了。Linux內(nèi)核直接使用主線內(nèi)核源碼配置成RISC-V架構(gòu)。我這里因?yàn)槭荝V32所以工具鏈前綴是riscv32-unknown-linux-gnu-如果是RV64則用riscv64-linux-gnu-。工具鏈可以直接下載預(yù)編譯版本也可以讓Buildroot自己編后者更省心。initramfs是最省事的根文件系統(tǒng)方案。它把BusyBox和必要的腳本打包進(jìn)一個(gè)cpio歸檔再和內(nèi)核一起編譯成鏡像。啟動(dòng)時(shí)內(nèi)核會(huì)把它解壓到內(nèi)存當(dāng)作根文件系統(tǒng)。好處是不用管SD卡、不用寫(xiě)Flash根文件系統(tǒng)驅(qū)動(dòng)非常適合軟核起步階段。用Buildroot構(gòu)建的過(guò)程很簡(jiǎn)單配置目標(biāo)架構(gòu)為RISC-V選擇BusyBox指定內(nèi)核源碼路徑和內(nèi)核defconfig然后build。一次構(gòu)建會(huì)同時(shí)生成工具鏈、內(nèi)核鏡像和rootfs歸檔省去很多手工步驟。3.2 內(nèi)核裁剪的關(guān)鍵配置Linux內(nèi)核默認(rèn)配置打開(kāi)的功能特別多覆蓋了各種硬件平臺(tái)在FPGA這種資源受限環(huán)境下必須裁剪。以下幾個(gè)配置我認(rèn)為非常關(guān)鍵CONFIG_MMUy CONFIG_SERIAL_8250y CONFIG_SERIAL_8250_CONSOLEy CONFIG_RD_GZIPy CONFIG_INITRAMFS_SOURCErootfs.cpio.gz CONFIG_CMDLINEconsolettyS0,115200 root/dev/ram0CONFIG_MMU必須開(kāi)啟沒(méi)有MMU就別想跑標(biāo)準(zhǔn)Linux。CONFIG_SERIAL_8250提供串口驅(qū)動(dòng)console輸出全靠它。CONFIG_INITRAMFS_SOURCE指向打包好的rootfs內(nèi)核會(huì)把它內(nèi)嵌進(jìn)鏡像。CONFIG_CMDLINE用于指定啟動(dòng)參數(shù)。如果設(shè)備樹(shù)里也有chosen/bootargs兩者可能會(huì)沖突調(diào)試時(shí)要留意必要時(shí)用CONFIG_CMDLINE_FORCE強(qiáng)制覆蓋設(shè)備樹(shù)里的參數(shù)。編譯前盡量關(guān)閉不用的驅(qū)動(dòng)大量的網(wǎng)絡(luò)協(xié)議、聲卡驅(qū)動(dòng)、USB主機(jī)驅(qū)動(dòng)、GPU驅(qū)動(dòng)等統(tǒng)統(tǒng)不要。裁剪不是靠猜而是先編一版查看鏡像大小再啟動(dòng)看內(nèi)核輸出的設(shè)備注冊(cè)信息把用不到的模塊關(guān)掉。這樣一輪輪下來(lái)內(nèi)核鏡像可以從幾MB壓到2-3MB甚至更小。3.3 啟動(dòng)流程與日志解析正常啟動(dòng)時(shí)串口應(yīng)該能看到這樣的輸出順序OpenSBI v1.2 ____ _____ ____ _____ / __ \ / ____| _ \_ _| ... [ 0.000000] Linux version 6.1.0-riscv [ 0.000000] Kernel command line: consolettyS0,115200 root/dev/ram0 ... [ 1.200000] Run /init as init process看到“Run /init as init process”意味著內(nèi)核已經(jīng)成功掛載rootfs并啟動(dòng)了用戶態(tài)第一個(gè)進(jìn)程。如果到這里接著出現(xiàn)BusyBox的啟動(dòng)日志和命令行提示符整條鏈路就通了。我踩過(guò)一次很典型的坑日志卡在內(nèi)核解壓完、剛跳轉(zhuǎn)到內(nèi)核入口就沒(méi)輸出。這種問(wèn)題通常不是內(nèi)核本身而是MMU初始化或內(nèi)存映射參數(shù)有問(wèn)題。定位方法是先把earlycon打開(kāi)讓它盡早輸出再就是檢查DDR地址是否和鏈接地址匹配。4. FPGA上的在線升級(jí)實(shí)現(xiàn)4.1 在線升級(jí)到底在更新什么既然系統(tǒng)能在現(xiàn)場(chǎng)跑起來(lái)那么“在線升級(jí)”這個(gè)概念就需要拆開(kāi)看。在FPGALinux這種組合里升級(jí)的對(duì)象至少分三層FPGA邏輯升級(jí)更新bitstream即CPU、總線、外設(shè)的硬件邏輯。固件升級(jí)更新OpenSBI、Linux內(nèi)核鏡像、設(shè)備樹(shù)DTB。應(yīng)用升級(jí)更新rootfs里的業(yè)務(wù)程序、配置腳本等。傳統(tǒng)做法是接JTAG線把電腦拿到設(shè)備旁邊燒Flash這在研發(fā)調(diào)試階段沒(méi)問(wèn)題但真正的工業(yè)現(xiàn)場(chǎng)不可能這么干。在線升級(jí)的意義在于系統(tǒng)運(yùn)行狀態(tài)下通過(guò)網(wǎng)絡(luò)或串口把新版本傳到設(shè)備由系統(tǒng)自身完成Flash更新然后重啟生效。需要注意在線升級(jí)不等于“熱切換”。Linux內(nèi)核本身不能在家里運(yùn)行、原地把自己替換成新內(nèi)核操作系統(tǒng)啟動(dòng)過(guò)程必須是完整重來(lái)一次。所以在線升級(jí)的本質(zhì)是“更新持久化鏡像 重啟加載新鏡像”。4.2 Flash分區(qū)設(shè)計(jì)與A/B備份策略在線升級(jí)里最怕的不是升級(jí)失敗而是升級(jí)失敗后沒(méi)有退路。為此我采用了A/B雙分區(qū)機(jī)制。所謂A/B就是把內(nèi)核和rootfs同時(shí)準(zhǔn)備兩份分別放在不同F(xiàn)lash區(qū)域。系統(tǒng)正常從A區(qū)啟動(dòng)升級(jí)時(shí)只寫(xiě)B(tài)區(qū)。寫(xiě)完B區(qū)后校驗(yàn)文件頭和數(shù)據(jù)校驗(yàn)和再修改啟動(dòng)標(biāo)記告訴引導(dǎo)程序下一次從B區(qū)啟動(dòng)。如果B區(qū)啟動(dòng)失敗引導(dǎo)程序自動(dòng)回退到A區(qū)。MTD分區(qū)用起來(lái)大概是這樣的概念mtdpartsspi0.0:1M(openSBI),4M(kernel_a),4M(kernel_b),1M(dtb_a),1M(dtb_b),8M(rootfs_a),8M(rootfs_b)Linux起來(lái)后用cat /proc/mtd查看實(shí)際分區(qū)# cat /proc/mtd dev: size erasesize name mtd0: 00100000 00010000 openSBI mtd1: 00400000 00010000 kernel_a mtd2: 00400000 00010000 kernel_b mtd3: 00100000 00010000 dtb_a mtd4: 00100000 00010000 dtb_b mtd5: 00800000 00010000 rootfs_a mtd6: 00800000 00010000 rootfs_bA/B分區(qū)會(huì)多占用一倍Flash容量這是最直接的代價(jià)。如果Flash實(shí)在緊張至少要對(duì)內(nèi)核分區(qū)做雙備份因?yàn)檫@是升級(jí)中最常改、最容易寫(xiě)壞的部分。OpenSBI和bitstream可以只保留一個(gè)黃金版本正常使用中很少更新。4.3 升級(jí)流程實(shí)戰(zhàn)串口、網(wǎng)絡(luò)與腳本當(dāng)前系統(tǒng)正常啟動(dòng)時(shí)在線升級(jí)最直接的做法就是在Linux里寫(xiě)Flash分區(qū)。假設(shè)備份內(nèi)核已經(jīng)通過(guò)scp或者串口傳到了/tmp/Image執(zhí)行flashcp -v /tmp/Image /dev/mtd2flashcp會(huì)先擦除mtd2分區(qū)再把數(shù)據(jù)寫(xiě)進(jìn)去比直接dd到/dev/mtd2更安全因?yàn)樗粫?huì)讓你誤擦超范圍。寫(xiě)完內(nèi)核后設(shè)備樹(shù)也要一起更新flashcp -v /tmp/system.dtb /dev/mtd4然后修改啟動(dòng)標(biāo)記讓引導(dǎo)程序下次從B區(qū)啟動(dòng)。如果是U-Boot可以用fw_setenvfw_setenv bootpart b sync reboot如果系統(tǒng)已經(jīng)完全起不來(lái)只能進(jìn)引導(dǎo)程序恢復(fù)那么串口就是最后一條命。在U-Boot或自研bootloader里用Ymodem協(xié)議接收文件再寫(xiě)入Flashloady 0x40000000 erase 0x0B000000 0x400000 cp.b 0x40000000 0x0B000000 0x400000這里的地址要格外小心一定是引導(dǎo)程序視角的Flash地址而不是Linux視角的地址。混淆地址是我在調(diào)試中犯過(guò)的錯(cuò)誤一次寫(xiě)錯(cuò)就可能導(dǎo)致系統(tǒng)無(wú)法啟動(dòng)。如果沒(méi)有把握先把舊的Flash內(nèi)容備份到內(nèi)存或者用md命令讀幾個(gè)字節(jié)確認(rèn)分區(qū)首部特征再動(dòng)手。整個(gè)升級(jí)過(guò)程最好腳本化腳本里強(qiáng)制做校驗(yàn)md5sum /tmp/Image md5sum /tmp/Image.remote # 不一致則退出沒(méi)有校驗(yàn)的升級(jí)腳本就是在拿現(xiàn)場(chǎng)設(shè)備做盲測(cè)。4.4 掉電保護(hù)與回滾機(jī)制在線升級(jí)最大的敵人是寫(xiě)著寫(xiě)著斷電。如果正在擦寫(xiě)Flash的過(guò)程中斷電該分區(qū)內(nèi)容既不是舊版本也不是新版本系統(tǒng)可能直接變磚。所以升級(jí)策略的底線是永遠(yuǎn)不要先擦當(dāng)前啟動(dòng)分區(qū)。先把新鏡像寫(xiě)到B區(qū)校驗(yàn)成功后再切換啟動(dòng)標(biāo)記。這樣就算升級(jí)過(guò)程斷電A區(qū)還是好的下次上電還是從A區(qū)啟動(dòng)。再搭配一個(gè)bootcount機(jī)制每次引導(dǎo)程序啟動(dòng)一個(gè)分區(qū)時(shí)在Flash里記錄啟動(dòng)次數(shù)加1。用戶程序正常運(yùn)行后通過(guò)命令把計(jì)數(shù)清零。如果新分區(qū)啟動(dòng)時(shí)反復(fù)重啟計(jì)數(shù)會(huì)持續(xù)累積達(dá)到閾值后引導(dǎo)程序自動(dòng)切換回舊分區(qū)。這是嵌入式領(lǐng)域通用的回滾方案。FPGA側(cè)還有個(gè)特殊的坑bitstream在Flash里如果被寫(xiě)壞FPGA會(huì)配置失敗整塊板子直接起不來(lái)。我的辦法是做一個(gè)極簡(jiǎn)的“golden”bitstream放在Flash最前面現(xiàn)場(chǎng)需要更新邏輯時(shí)只更新后面主bitstream區(qū)域如果主bitstream校驗(yàn)失敗FPGA硬件引擎會(huì)回退到golden配置。golden版本只做基礎(chǔ)功能保證系統(tǒng)能啟動(dòng)、能繼續(xù)做恢復(fù)操作這相當(dāng)于給硬件上了一道保險(xiǎn)。5. 踩坑實(shí)錄常見(jiàn)問(wèn)題與排查技巧5.1 串口完全沒(méi)有任何輸出遇到“上電后串口死寂”的情況第一反應(yīng)不要怪內(nèi)核先確認(rèn)硬件和RTL。按這個(gè)順序排查會(huì)更高效確認(rèn)時(shí)鐘和復(fù)位。用邏輯分析儀或ILA抓一下CPU時(shí)鐘是否在跑復(fù)位釋放時(shí)間是否正常。確認(rèn)UART波特率。串口終端必須和RTL里的波特率分頻一致。RTL里如果寫(xiě)成9600終端卻設(shè)成115200自然沒(méi)輸出。確認(rèn)UART引腳綁定。很多板卡USB轉(zhuǎn)串口芯片的TX/RX方向容易搞反或者共地沒(méi)接好。確認(rèn)內(nèi)核命令行真的傳到了。很多時(shí)候不是沒(méi)輸出而是consolettyS0,115200沒(méi)生效內(nèi)核輸出沒(méi)走串口。其中“硬件方向接反”這種低級(jí)錯(cuò)誤最容易讓人白折騰半天。遇到串口沒(méi)輸出我會(huì)先用萬(wàn)用表或者示波器看USB轉(zhuǎn)串口芯片的TX引腳確認(rèn)有沒(méi)有波形再考慮軟件問(wèn)題。5.2 內(nèi)核啟動(dòng)到一半卡住或重啟如果串口日志能打出來(lái)但卡在“Uncompressing Linux... done, booting the kernel”之后通常是MMU和內(nèi)存的問(wèn)題。這里有個(gè)典型的連鎖反應(yīng)MMU初始化時(shí)頁(yè)表設(shè)置不正確內(nèi)核第一條訪問(wèn)虛擬內(nèi)存的指令就觸發(fā)異常異常處理又沒(méi)準(zhǔn)備好于是掛死。我遇到過(guò)幾次這樣的卡頓最后定位都是DDR訓(xùn)練參數(shù)不匹配。解決思路是確認(rèn)DDR控制器的時(shí)序參數(shù)和真實(shí)內(nèi)存顆粒匹配特別是tRCD、tRP、tCL這些值。在OpenSBI里加一個(gè)簡(jiǎn)單的內(nèi)存讀寫(xiě)測(cè)試啟動(dòng)時(shí)就對(duì)DDR地址范圍做一遍踏步讀寫(xiě)提前暴露問(wèn)題。確認(rèn)內(nèi)核鏈接地址和DDR物理地址對(duì)應(yīng)。如果CPU從0x40000000取指內(nèi)核鏡像在編譯時(shí)也必須指定運(yùn)行地址在該區(qū)域兩者不一致時(shí)表現(xiàn)就是各種莫名卡死。5.3 Flash寫(xiě)入失敗或者升級(jí)后無(wú)法啟動(dòng)升級(jí)腳本執(zhí)行完文件也顯示寫(xiě)入成功重啟后系統(tǒng)卻起不來(lái)。這種情況大概率是分區(qū)偏移錯(cuò)了。Flash廠商的擦除塊大小不統(tǒng)一有的256KB有的64KB。如果分區(qū)定義和Flash實(shí)際擦除塊沒(méi)對(duì)齊寫(xiě)入時(shí)可能把別的分區(qū)擦掉。查看/proc/mtd里的erasesize再對(duì)一下分區(qū)偏移就能發(fā)現(xiàn)很多問(wèn)題。還有一次是升級(jí)后忘記sync。Linux有緩存機(jī)制flashcp寫(xiě)完后數(shù)據(jù)還在緩存立刻斷電可能丟失。升級(jí)腳本里必須加sync最好再延時(shí)等緩存落盤(pán)。這是在線升級(jí)最容易被忽略的細(xì)節(jié)。為了救磚我強(qiáng)烈建議在Flash最前面留一塊完全不被業(yè)務(wù)升級(jí)觸碰的“急救區(qū)”放一個(gè)能通過(guò)串口下載文件并寫(xiě)Flash的最小引導(dǎo)程序。配置好這個(gè)后再折騰在線升級(jí)心里會(huì)踏實(shí)很多——最壞情況下頂多回退或者串口重刷不需要開(kāi)蓋拆Flash用編程器。5.4 地址映射導(dǎo)致的“靈異現(xiàn)象”調(diào)試過(guò)程中我遇到過(guò)一個(gè)很隱蔽的現(xiàn)象內(nèi)核跑起來(lái)了命令行也能執(zhí)行但偶爾訪問(wèn)某個(gè)外設(shè)時(shí)系統(tǒng)重啟。后來(lái)發(fā)現(xiàn)是總線地址映射有重疊區(qū)CPU發(fā)出去的外設(shè)訪問(wèn)地址同時(shí)命中了Flash和UART兩個(gè)從設(shè)備。總線仲裁器看到多個(gè)設(shè)備同時(shí)應(yīng)答直接掛死。這類(lèi)問(wèn)題排查起來(lái)非常煩因?yàn)楝F(xiàn)象隨機(jī)。建議在SoC搭建階段就要一份清晰的總線地址分配表檢查是否有重疊區(qū)域。另外在RTL里給總線加一個(gè)默認(rèn)slave一旦地址沒(méi)匹配到任何設(shè)備就返回一個(gè)錯(cuò)誤響應(yīng)而不是讓總線掛在未知狀態(tài)。這個(gè)默認(rèn)slave設(shè)計(jì)雖小卻能幫我省下大量的摸黑調(diào)試時(shí)間。最后再分享一個(gè)小技巧在線升級(jí)這套機(jī)制不要等整個(gè)系統(tǒng)全部調(diào)通了才做最好在第一次Linux能啟動(dòng)時(shí)就順手把Flash分區(qū)和升級(jí)腳本寫(xiě)好。早期分區(qū)改動(dòng)成本低等業(yè)務(wù)代碼和上層驅(qū)動(dòng)都依賴(lài)固定地址后再改分區(qū)才是真的傷筋動(dòng)骨。先把升級(jí)通道打通后面不管調(diào)內(nèi)核還是調(diào)應(yīng)用都能省很多事。