Gaviar Handheld (小志掌機)
-
【碟】照来了,丑了点。但功能验证应该够用了。没有司徒 大佬的加藤鹰手法,右边的接口焊接失败了,。@steward

我要尽快跟上你的节奏了@lovehex99 -
@yofa2008 在 Gaviar Handheld (小志掌機) 中说:
【碟】照来了,丑了点。但功能验证应该够用了。没有司徒 大佬的加藤鹰手法,右边的接口焊接失败了,。
你这个看着像个黑莓手机,上面一个屏幕,下面好多按键
-
-
@yofa2008 你自己打版喔 厲害
-
@steward 在 Gaviar Handheld (小志掌機) 中说:
編譯步驟
換了16G
root@TinaLinux:/etc# mount /dev/mmcblk0p8 /mnt/UDISK/
mount /dev/mmcblk0p8 /mnt/UDISK/
mount: mounting /dev/mmcblk0p8 on /mnt/UDISK/ failed: Input/output error
只有沒對 /dev/sdb8 進行擴充但UART還是開不起來
我編譯步驟是照這個帖子的
Starting kernel ...[02.156][mmc]: MMC Device 2 not found
[02.160][mmc]: mmc 2 not find, so not exit -
-
@lovehex99 你没打板如何 搭硬件环境的?是有现成的板子买吗?
@xiaowenge 都是想尽可能匹配‘小志’的硬件外设,布局按键在CAD里转来转去花了些时间。哈哈 -
@yofa2008 厉害厉害。。怎么用CAD的?
-
@yofa2008 買的喔
-
@steward 在 Gaviar Handheld (小志掌機) 中说:
兩個問題問一下:
- 你是如何燒錄檔案到 MicroSD ?
- 因為預設是使用 adb shell 進入操作(USB Type-C), 你有使用 adb shell 試試嗎?
1.用PhoenixCardv4.2.7.7z
2.有的 ADB SHELL可以操作 -
解開後, 使用 dd 命令燒錄到 MicroSD 卡後, 插入開機, 就會開始播放 Nokia 手機鈴聲~
-
@lovehex99 在 Gaviar Handheld (小志掌機) 中说:
@steward 在 Gaviar Handheld (小志掌機) 中说:
兩個問題問一下:
- 你是如何燒錄檔案到 MicroSD ?
- 因為預設是使用 adb shell 進入操作(USB Type-C), 你有使用 adb shell 試試嗎?
1.用PhoenixCardv4.2.7.7z
2.有的 ADB SHELL可以操作@steward 请问提示Is not Boot mode!是什么意思?我只写了uart4的那个img,没有在adb下找到devices哦
by the way.重新烧写了一下img解决,work type选择start up.

-
播放wav文件,拷贝了一个windows自带的wav.

-
@lovehex99 @steward
今天测试了一张256MB的卡,能正常播放wav。UART4也可正常交互

-
@yofa2008
你是直接燒司徒給的那個img吧?
我是從github下載自己編譯的 -
@lovehex99 对的。建议您也先排除简单的问题,再上手改比较容易。
-
@yofa2008 我硬件是用買的 所以應該沒問題
最近也忙 等後續教程多點再來玩 -
最近在幫 Miyoo Mini/Miyoo Mini Plus 移植 drastic NDS 模擬器, 目前已經快完善了, 這邊先晚一點更新 ~
-
@steward 许久没见大佬跟贴,猜测一定是在忙大事。目前我遇到了诡异的问题:为何用dd的命令复制到TF卡的img不写入,而phoenix写的img就能写入。是dd命令的问题??
见图
-
@yofa2008 img不是磁盘镜像文件,是专有的压缩格式,不带分区表。可以用OpenixCard转换为dd可以写入的带分区表的镜像
-
@awwwwa 啊,原来这样。感谢告知,司徒大佬在帖里提了一下,没说细节。有空我试一下了
-
@xiaowenge 三明治结构的全键盘工程机来了,准备就绪。就等司徒大佬回来传授一下如何替换屏驱动开机了@steward,linux开发不会搞,跟大神学会
图:
-
@steward 在 Gaviar Handheld (小志掌機) 中说:
司徒接下來介紹點屏的步驟,而在點屏之前,我們需要先了解一下屏的一些特性,這樣才可以跑順後面的開發,一般常說的 8080 屏、i80 屏、MCU 屏,指的就是傳輸資料的方式是使用 Intel 8080 的讀寫方式,其實早期是有兩大派別,分別是 Intel 8080 和 Motorola 6800,至於為何後來都用 Intel 8080,這應該不用多說,8080 和 6800 的格式可以從下圖知曉

那為何 i80 屏需要做初始化呢 ?因為裡面有專門用來做顯示處理的 MCU,因此,需要設定一些參數,如:width=320, height=240, fps=60 等參數,所以傳送給屏的資料會分成 Data 和 Command,當然,有些特殊制定規格的屏就不須要初始化,因為參數都已經寫死並且固定在 MCU 裡,不過,市面上比較難購買到這類產品

MCU 在顯示圖像時,會固定從 RAM(或稱呼:顯示記憶體、Graphic RAM、GRAM)讀取資料並顯示在屏上面
那該如何初始化呢?目前市面上的屏大約可以分成兩種作法:1. 使用 DB0~DB15 傳送初始化 Data 和 Command, 2. 使用 I2C 傳送 Data 和 Command

兩種方式都可以達到初始化的目的,因此,在購買 i80 屏後,記得跟賣家索取初始化命令
那問題來了,當我們正在刷屏(寫入 RAM)時,MCU 是否有可能正在讀取 RAM ?答案:沒錯,這種狀況一定會發生,假如寫入跟讀取沒有一個同步機制,那就會發生這種狀況,而這種狀況就是一般俗稱:撕裂、閃屏、Screen Tearing,司徒畫了一張流程圖,下圖是 MCU 讀取 RAM 並且顯示在屏上的流程

當 F133 透過 TCON_LCD i80 介面傳送資料給屏時(黃色的像素),由於共用同一塊 RAM,因此,RAM 的內容被更新成黃色,同時,屏的 MCU 也正在更新圖像到屏上,因此,變成第一個畫面(紅色)和第二個畫面(黃色)的交疊狀況,如下圖

那 i80 屏的撕裂問題如何解決呢?其實,一般 i80 屏都會拉出 TE/FRAME 腳位,這個腳位一般不使用,因為它跟時序高度相關,需要同步處理,同常 TE/FRAME 腳位都會懸空,不過,TE/FRAME 腳位其實是用來告知屏的 MCU 是否正在更新圖像(讀取 RAM),因此,為了避免撕裂,F133 TCON_LCD 可以在 MCU 空閒時,傳送更新的資料,這樣就可以避免撕裂

那是不是搞定 TE/FRAME 腳位後,屏就不會撕裂?答案:不是,因為,從模擬器、顯示驅動到硬件傳送都可能會有撕裂問題,所以要確保顯示品質,任何經過的地方都必須仔細處理才可以保證畫面不撕裂

根據司徒的經驗,在不同地方的所造成的撕裂,將會有不一樣的結果,下圖是司徒整理的有趣現象,模擬器因為是像素更新居多,因此,覆蓋一般是從起始像素開始,所以撕裂比較像是上下分層,而顯示驅動一般使用區塊複製,因此,撕裂是屬於左右分層,屏的撕裂,由於更新跟寫入是屬於追跑的現象,因此,撕裂屬於三角分層

LCD 電路


main.s.global _start .equ GPIO_BASE, 0x02000000 .equ PD_CFG0, 0x0090 .equ PD_CFG1, 0x0094 .equ PD_CFG2, 0x0098 .equ PD_DAT, 0x00a0 .equ LCD_RST, (1 << 0) .equ LCD_WR, (1 << 18) .equ LCD_RS, (1 << 19) .equ LCD_RD, (1 << 20) .equ LCD_CS, (1 << 21) .equ LCD_BL, (1 << 22) .equ _250MS, 50000000 .equ _500MS, 100000000 .equ _1S, 200000000 .text .long 0x4000006f .byte 'e','G','O','N','.','B','T','0' .long 0x5F0A6C39 .long 0x8000 .long 0, 0 .long 0, 0, 0, 0, 0, 0, 0, 0 .long 0, 0, 0, 0, 0, 0, 0, 0 .org 0x0400 _start: li t0, 0x11111111 li a0, GPIO_BASE + PD_CFG0 sw t0, 0(a0) li a0, GPIO_BASE + PD_CFG1 sw t0, 0(a0) li a0, GPIO_BASE + PD_CFG2 sw t0, 0(a0) li t0, 0xffffffff li a0, GPIO_BASE + PD_DAT sw t0, 0(a0) jal lcd_rst li t0, 0xb2 jal lcd_cmd li t0, 0x5c jal lcd_dat li t0, 0x5c jal lcd_dat li t0, 0x00 jal lcd_dat li t0, 0x33 jal lcd_dat li t0, 0x33 jal lcd_dat li t0, 0xb7 jal lcd_cmd li t0, 0x35 jal lcd_dat li t0, 0x21 jal lcd_cmd li t0, 0x11 jal lcd_cmd li t0, _250MS jal delay li t0, 0xe0 jal lcd_cmd li t0, 0xd0 jal lcd_dat li t0, 0x06 jal lcd_dat li t0, 0x0b jal lcd_dat li t0, 0x07 jal lcd_dat li t0, 0x07 jal lcd_dat li t0, 0x24 jal lcd_dat li t0, 0x2e jal lcd_dat li t0, 0x32 jal lcd_dat li t0, 0x46 jal lcd_dat li t0, 0x37 jal lcd_dat li t0, 0x13 jal lcd_dat li t0, 0x13 jal lcd_dat li t0, 0x2d jal lcd_dat li t0, 0x33 jal lcd_dat li t0, 0xe1 jal lcd_cmd li t0, 0xd0 jal lcd_dat li t0, 0x02 jal lcd_dat li t0, 0x06 jal lcd_dat li t0, 0x09 jal lcd_dat li t0, 0x08 jal lcd_dat li t0, 0x05 jal lcd_dat li t0, 0x29 jal lcd_dat li t0, 0x44 jal lcd_dat li t0, 0x42 jal lcd_dat li t0, 0x38 jal lcd_dat li t0, 0x14 jal lcd_dat li t0, 0x14 jal lcd_dat li t0, 0x2a jal lcd_dat li t0, 0x30 jal lcd_dat li t0, 0x36 jal lcd_cmd li t0, 0xb0 jal lcd_dat li t0, 0x2a jal lcd_cmd li t0, 0x00 jal lcd_dat li t0, 0x00 jal lcd_dat li t0, 0x01 jal lcd_dat li t0, 0x3f jal lcd_dat li t0, 0x2b jal lcd_cmd li t0, 0x00 jal lcd_dat li t0, 0x00 jal lcd_dat li t0, 0x00 jal lcd_dat li t0, 0xef jal lcd_dat li t0, 0x3a jal lcd_cmd li t0, 0x55 jal lcd_dat li t0, 0x29 jal lcd_cmd li t0, 0x2c jal lcd_cmd li t6, 320 * 80 red: li t0, 0xf800 jal lcd_dat addi t6, t6, -1 bgtz t6, red li t6, 320 * 80 green: li t0, 0x7e0 jal lcd_dat addi t6, t6, -1 bgtz t6, green li t6, 320 * 80 blue: li t0, 0x1f jal lcd_dat addi t6, t6, -1 bgtz t6, blue 1: j 1b lcd_wr: li a0, GPIO_BASE + PD_DAT li t4, 0x00ff and t2, t0, t4 li t4, 0xff00 and t3, t0, t4 sll t2, t2, 1 sll t3, t3, 2 move t5, t1 or t5, t5, t2 or t5, t5, t3 sw t5, 0(a0) li t0, LCD_WR or t5, t5, t0 sw t5, 0(a0) jr ra lcd_dat: move a1, ra li t1, LCD_RS | LCD_RD | LCD_BL | LCD_RST jal lcd_wr jr a1 lcd_cmd: move a1, ra li t1, LCD_RD | LCD_BL | LCD_RST jal lcd_wr jr a1 lcd_rst: move a1, ra li t0, 0x00000000 li a0, GPIO_BASE + PD_DAT sw t0, 0(a0) li t0, _250MS jal delay li t0, 0xffffffff li a0, GPIO_BASE + PD_DAT sw t0, 0(a0) li t0, _250MS jal delay jr a1 delay: addi t0, t0, -1 bgtz t0, delay jr ra .end
完成

请教@steward 这个main.s文件 怎么加载到启动完成的系统中去执行啊,目前在配置lcd需要在上面 修改些参数
-
@lovehex99
请问哪里买的?谢 -
-
-
@yofa2008 你好,蓝色的有按钮的板是哪里买的?
-
@johnaa 临时自己打板做的,用它来完成gpio转接
-
@lovehex99 你github下载的工程能编译出可运行的img吗? 在没有任何改动的情况下,编译出的img串口启动时提示出错,我用的是ubuntu20.04。

有没有什么思路? -
@yofa2008
很久没玩了 我在等晕哥的安卓掌机 -
@lovehex99 别啊,这就弃坑了吗?我的F1C100都还在坑里呢
-
我用MangoPI官网的github包编译出能正常启动的img,网址(https://mangopi.org/mqr);
!
但用司徒大神的包编译的img始终不能正常引导,提示如之前反馈的错误,大致意思是两个ERROR:没有找到uboot.fex头和opensbi.fex头;
!
对比了一些目录发现差异还是比较多的,特别是lichee\brandy-2.0\spl下面:
司徒大神的包里面没有找到sboot文件夹,不知道是啥原因;

我按@steward 你的包编译的固件日志第二行直接调用了include\commit_info.h的CI_INFO那个hash,但你编译好的Img第二行是sh脚本调用log文件生成的。能提示可能是什么原因吗?
MangoPI使用的是lichee\brandy-2.0\目录下的spl-pub,里面内容和你的spl文件目录相差还是比较大。目前已经重新在github上多次下载并编译,问题仍然一致。基本可以排除文件 不一致造成的 -
@yofa2008 司徒大神都鴿了 暈哥的安卓掌機也...
-
@lovehex99 啊嘻,这……哈哈哈原来是这样,
-
司徒前些日子真的相當相當忙碌, 真的相當抱歉, 三年後的今天才又開始更新這個貼子~也相當欣慰 bankbank 還有持續聯繫司徒, 因此, 等後半段需要高手逆向 NeoGeo ROM 時, 司徒再來呼叫 bankbank, 哈, 這機器, 司徒已經三年沒有碰了, 因此, 每當夜裡總是會夢見暈哥在他的豪宅呼喚司徒, 該起來動一動了, 哈, 七月半, 不怕不怕, 哈, Gaviar 掌機還是處於開發階段, 因此, 最終樣貌會變成如何, 尚不得而知, 不過為了可以更方便的 Debug, 司徒決定先小改一下, 因此, OpenSCAD 畫了一個簡易底座
$fn = 50; module panel(ch) { hull() { cd = 3; translate([0, 0, 0]) cylinder(ch, cd, cd); translate([81.85, 0, 0]) cylinder(ch, cd, cd); translate([0, 32.75, 0]) cylinder(ch, cd, cd); translate([81.85, 32.75, 0]) cylinder(ch, cd, cd); } } difference() { union() { difference() { union() { hull() { ch = 4; cd = 3; translate([0, 0, 0]) cylinder(ch, cd, cd); translate([81.85 + 2, 0, 0]) cylinder(ch, cd, cd); translate([0, 32.75 + 2, 0]) cylinder(ch, cd, cd); translate([81.85 + 2, 32.75 + 2, 0]) cylinder(ch, cd, cd); } hull() { ch = 4; cd = 1; translate([-2, 0, 0]) cylinder(ch, cd, cd); translate([16, 0, 0]) cylinder(ch, cd, cd); translate([-2, -19, 0]) cylinder(ch, cd, cd); translate([16, -19, 0]) cylinder(ch, cd, cd); } translate([69.85, 0, 0]) hull() { ch = 4; cd = 1; translate([-2, 0, 0]) cylinder(ch, cd, cd); translate([16, 0, 0]) cylinder(ch, cd, cd); translate([-2, -19, 0]) cylinder(ch, cd, cd); translate([16, -19, 0]) cylinder(ch, cd, cd); } } translate([1, 1, 1]) panel(10); // socket translate([-2, -19, 0]) cube([18, 16, 4]); translate([67.85, -19, 0]) cube([18, 16, 4]); } // type-c translate([-2, -19, 0]) difference() { cube([19, 17, 4]); translate([0.25, 0, 1]) cube([17.5, 16, 10]); } translate([67.85, -19, 0]) union() { difference() { cube([19, 17, 4]); translate([0.25, 0, 1]) cube([17.5, 16, 10]); } } } // type-c translate([10.75, -10, 1]) cube([5, 10, 5]); translate([-5, -15.75, 2.5]) cube([10, 10, 5]); // speaker translate([73.5, -10, 1]) cube([6, 10, 5]); }預計外觀是這樣

拓竹3D印表機還是相當給力

使用三秒膠將該上的元件都補齊

將CH340E的+5V連接到這裡

相當短的接線

正面

下邊

側邊

上邊

側邊

完美的開發機

-
由於全志針對F133的開機設計流程過於複雜,因此,為了可以更精簡以及方便維護程式碼,司徒特別針對小志掌機修改了一版OpenSBI,修改後的流程如下所示

編譯步驟:
$ cd $ wget https://github.com/steward-fu/website/releases/download/gaviar/thead_toolchain.tar.gz $ tar xvf thead_toolchain.tar.gz $ sudo mv thead /opt $ export PATH=/opt/thead/bin:$PATH $ cd $ wget https://github.com/steward-fu/website/releases/download/gaviar/opensbi_20260906.tar.gz $ tar xvf opensbi_20260906.tar.gz $ cd opensbi $ export PLATFORM=allwinner/f133 $ export CROSS_COMPILE=riscv64-unknown-linux-gnu- $ make distclean $ CONFIG=BROM make $ python3 scripts/gen_checksum.py ./build/platform/allwinner/f133/firmware/fw_jump.bin fw_jump.bin $ dd if=fw_jump.bin of=payload/brom.bin bs=1024 count=32 $ rm -rf fw_jump.bin $ make distclean $ make $ python3 scripts/gen_checksum.py ./build/platform/allwinner/f133/firmware/fw_jump.bin payload/fw_jump.bin $ python3 scripts/gen_checksum.py ./build/platform/allwinner/f133/firmware/fw_payload.bin payload/fw_payload.bin $ sudo dd if=payload/brom.bin of=/dev/sdX bs=1024 seek=8 $ sudo dd if=payload/fw_jump.bin of=/dev/sdX bs=1024 seek=40 $ sudo dd if=dtb of=/dev/sdX bs=1024 seek=300 $ sudo dd if=Image of=/dev/sdX bs=1024 seek=400由於目前Kernel程式碼尚未完成,因此,司徒使用LED閃爍程式替代,開機後,OpenSBI會跑LED閃爍程式

Baudrate 115200bps
[UART] Gaviar Handheld [DRAM] ZQ value = 0x2f [DRAM] get_pmu_exist() = -1 [DRAM] ddr_efuse_type = 0xa [DRAM] trefi = 7.8ms [DRAM] single rank and full DQ! [DRAM] ddr_efuse_type = 0xa [DRAM] trefi = 7.8ms [DRAM] rank 0 row = 13 [DRAM] rank 0 bank = 4 [DRAM] rank 0 page size = 2 KB [DRAM] DRAM BOOT DRIVE INFO: v0.33 [DRAM] DRAM CLK = 528 MHz [DRAM] DRAM Type = 2 (2:DDR2, 3:DDR3) [DRAM] DRAMC read ODT off [DRAM] DRAM ODT off [DRAM] ddr_efuse_type = 0xa [DRAM] DRAM SIZE = 64M [DRAM] dram_tpr4 = 0x0 [DRAM] PLL_DDR_CTRL_REG = 0xf8002b00 [DRAM] DRAM_CLK_REG = 0xc0000000 [DRAM] MR2 = 0x0 [DRAM] DRAM simple test OK [eMMC] MicroSD size = 59688MB [eMMC] Copy DTB to 0x41500000 (size = 65536)... [eMMC] Copy Kernel to 0x40008000 (size = 8388608)... [eMMC] Copy OpenSBI to 0x41000000 (size = 262144)... Welcome to Gaviar handheld OpenSBI v1.3 ____ _____ ____ _____ / __ \ / ____| _ \_ _| | | | |_ __ ___ _ __ | (___ | |_) || | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | | |__| | |_) | __/ | | |____) | |_) || |_ \____/| .__/ \___|_| |_|_____/|____/_____| | | |_| Platform Name : Allwinner F133 Platform Features : medeleg Platform HART Count : 1 Platform IPI Device : --- Platform Timer Device : --- @ 0Hz Platform Console Device : f133_uart Platform HSM Device : --- Platform PMU Device : --- Platform Reboot Device : --- Platform Shutdown Device : --- Platform Suspend Device : --- Platform CPPC Device : --- Firmware Base : 0x41000000 Firmware Size : 194 KB Firmware RW Offset : 0x20000 Firmware RW Size : 66 KB Firmware Heap Offset : 0x28000 Firmware Heap Size : 34 KB (total), 2 KB (reserved), 8 KB (used), 23 KB (free) Firmware Scratch Size : 4096 B (total), 728 B (used), 3368 B (free) Runtime SBI Version : 1.0 Domain0 Name : root Domain0 Boot HART : 0 Domain0 HARTs : 0* Domain0 Region00 : 0x0000000041000000-0x000000004101ffff M: (R,X) S/U: () Domain0 Region01 : 0x0000000041020000-0x000000004103ffff M: (R,W) S/U: () Domain0 Region02 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X) Domain0 Next Address : 0x0000000040008000 Domain0 Next Arg1 : 0x0000000041500000 Domain0 Next Mode : S-mode Domain0 SysReset : yes Domain0 SysSuspend : yes Boot HART ID : 0 Boot HART Domain : root Boot HART Priv Version : v1.11 Boot HART Base ISA : rv64imafdcvx Boot HART ISA Extensions : zicntr Boot HART PMP Count : 8 Boot HART PMP Granularity : 2048 Boot HART PMP Address Bits: 38 Boot HART MHPM Count : 0 Boot HART MHPM Mask : 0x0 Boot HART MIDELEG : 0x0000000000000222 Boot HART MEDELEG : 0x000000000000b109 Jump to kernel (0x40008000) ... -
司徒列出幫小志掌機修改的部份

為何RISC-V需要有這些模式呢?可以從下圖得知,Linux/OS一般跑在S-Mode,但是當需要控制底層時,就需要切換M-Mode,這也是SBI介面的由來,當然,如果不用Linux/OS,就可以自己實做這一部份

P.S. 司徒目前幫小志掌機創見一個暫時 Git Repo (https://github.com/steward-fu/gaviar), 修改的 OpenSBI 也都在這個 Repo 裡面, 到時候, 等小志掌機都開發完成後, 司徒就會 Archive 到 https://github.com/steward-fu/website
-
為了讓OpenSBI具有開機執行的能力,司徒小改了一下fw_base.S,在程式進入點_gaviar加入判斷程式,如果是從0x20000的位址執行,則執行第一次初始化的動作,包含初始化UART、DRAM、MMC等,並且加載OpenSBI、Kernel、DTB到DRAM,至於為何是判斷位址0x20000呢?這是因為F133 BROM一上電後,會先從MicroSD 8KB位址處加載32KB程式到RAM 0x20000並且執行,所以司徒在0x20000處做標記(F133_TAG),接著等第二次執行時,這時候位址已經改成從0x41000000執行,所以就會直接跑_start的程式,因此我們需要兩份fw_jump.bin,一份是給0x20000用的(32KB),另一份則是完整的fw_jump.bin,司徒使用CONFIG=brom make用來編譯0x20000的fw_jump.bin,詳細的執行流程如下所示:

-
司徒目前幫小志掌機規劃的 MicroSD Layout 如下

檔案更新的方式如下
$ sudo dd if=fw_jump.bin of=/dev/sdX bs=1024 seek=8 $ sudo dd if=fw_jump.bin of=/dev/sdX bs=1024 seek=40 $ sudo dd if=dtb of=/dev/sdX bs=1024 seek=300 $ sudo dd if=Image of=/dev/sdX bs=1024 seek=400 -
由於目前司徒目前使用LED閃爍程式替代Kernel,那是否代表可以修改這個LED閃爍程式來呼叫SBI Interface呢?答案肯定是可以的,程式碼如下:
.global _start .text .long 0x05c0006f .byte 'e','G','O','N','.','B','T','0' .long 0x5f0a6c39 .long 0x8000 .long 0, 0 .long 0, 0, 0, 0, 0, 0, 0, 0 .long 0, 0, 0, 0, 0, 0, 0, 0 _start: la a0, msg call sbi_puts 1: j 1b sbi_puts: mv t0, a0 1: lbu a0, 0(t0) beqz a0, 2f li a7, 1 ecall addi t0, t0, 1 j 1b 2: ret msg: .asciz "\n\nHello, world!\n" .end編譯、燒錄
$ riscv64-unknown-linux-gnu-as -o main.o main.s $ riscv64-unknown-linux-gnu-ld -T main.ld -o main.elf main.o $ riscv64-unknown-linux-gnu-objcopy -O binary main.elf tmp.bin $ python3 gen_checksum.py tmp.bin main.bin $ sudo dd if=main.bin of=/dev/sdb bs=1024 seek=400開機後就可以從UART看到Hello, world!字串,如果想從屏幕上印出字串,只要修改lib/sbi/sbi_ecall_legacy.c,將#if 0改成#if 1後,重新編譯燒錄即可
static int sbi_ecall_legacy_handler(unsigned long extid, unsigned long funcid, const struct sbi_trap_regs *regs, unsigned long *out_val, struct sbi_trap_info *out_trap) { int ret = 0; struct sbi_tlb_info tlb_info; u32 source_hart = current_hartid(); ulong hmask = 0; switch (extid) { case SBI_EXT_0_1_SET_TIMER: #if __riscv_xlen == 32 sbi_timer_event_start((((u64)regs->a1 << 32) | (u64)regs->a0)); #else sbi_timer_event_start((u64)regs->a0); #endif break; case SBI_EXT_0_1_CONSOLE_PUTCHAR: sbi_putc(regs->a0); #if 0 /* Added by Steward for Gaviar handheld */ void f133_slcd_putc(char ch); f133_slcd_putc(regs->a0); #endif break;
-
Legacy SBI Interface有如下幾個Function,上面的那個main.s範例就是使用EID 0x01的sbi_console_putchar()印出字串,藉由上面的main.s範例,讓我們可以更了解SBI Interface的運作原理

-
呼叫的流程如下:

-
司徒目前已經將 Kernel 程式碼打包放到 https://github.com/steward-fu/gaviar, 這份 Kernel 是從 Tina-Linux 抽取出來的源代碼, 尚未做修改, 因此, 目前還無法正常開機進入系統, 司徒心想剛好紀錄整個除錯過程, 將來可以當作參考, 編譯步驟如下:
$ cd $ wget https://github.com/steward-fu/website/releases/download/gaviar/thead_toolchain.tar.gz $ tar xvf thead_toolchain.tar.gz $ sudo mv thead /opt $ export PATH=/opt/thead/bin:$PATH $ git clone https://github.com/steward-fu/gaviar $ cd gaviar/kernel $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make distclean $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make gaviar_defconfig $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make menuconfig $ ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- make Image dtbs -j4 $ sudo dd if=arch/riscv/boot/Image of=/dev/sdX bs=1024 seek=400 $ sudo dd if=arch/riscv/boot/dts/sunxi/board.dtb of=/dev/sdX bs=1024 seek=300開機問題如下:
[ 0.000000] Linux version 5.4.61 (steward@debian) (riscv64-unknown-linux-gnu-gcc (C-SKY RISCV Tools V1.8.4 B20200702) 8.1.0, GNU ld (GNU Binutils) 2.32) #3 PREEMPT Tue Sep 8 21:27:48 CST 2026 [ 0.000000] earlycon: sbi0 at I/O port 0x0 (options '') [ 0.000000] printk: bootconsole [sbi0] enabled [ 0.000000] cma: Reserved 16 MiB at 0x0000000047000000 [ 0.000000] Zone ranges: [ 0.000000] DMA32 [mem 0x0000000040000000-0x0000000047ffffff] [ 0.000000] Normal empty [ 0.000000] Movable zone start for each node [ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000040000000-0x0000000047ffffff] [ 0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x0000000047ffffff] [ 0.000000] elf_hwcap is 0x20112d [ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 32320 [ 0.000000] Kernel command line: console=ttyS4,115200n8 debug loglevel=7,initcall_debug=1 init=/init earlycon=sbi [ 0.000000] Dentry cache hash table entries: 16384 (order: 5, 131072 bytes, linear) [ 0.000000] Inode-cache hash table entries: 8192 (order: 4, 65536 bytes, linear) [ 0.000000] Sorting __ex_table... [ 0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off [ 0.000000] Memory: 104568K/131072K available (4909K kernel code, 448K rwdata, 1909K rodata, 160K init, 234K bss, 10120K reserved, 16384K cma-reserved) [ 0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1 [ 0.000000] rcu: Preemptible hierarchical RCU implementation. [ 0.000000] Tasks RCU enabled. [ 0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 10 jiffies. [ 0.000000] NR_IRQS: 0, nr_irqs: 0, preallocated irqs: 0 [ 0.000000] Oops - load access fault [#1] [ 0.000000] Modules linked in: [ 0.000000] CPU: 0 PID: 0 Comm: swapper Not tainted 5.4.61 #3 [ 0.000000] sepc: ffffffe000205a12 ra : ffffffe00000b832 sp : ffffffe0006d7e10 [ 0.000000] gp : ffffffe000741948 tp : ffffffe0006dc670 t0 : ffffffe0006d7d70 [ 0.000000] t1 : 0000000000000005 t2 : 000000000000043f s0 : ffffffe0006d7ef0 [ 0.000000] s1 : 0000000000000001 a0 : ffffffd000002080 a1 : 0000000000000002 [ 0.000000] a2 : 0000000000000000 a3 : ffffffd000000000 a4 : 0000000000000020 [ 0.000000] a5 : ffffffffffffffff a6 : ffffffe000577be0 a7 : 0000000000000000 [ 0.000000] s2 : ffffffe0006f4bd8 s3 : ffffffe006e0b318 s4 : ffffffe00075d150 [ 0.000000] s5 : 0000000000000000 s6 : 0000000000000002 s7 : ffffffe0005f3c78 [ 0.000000] s8 : ffffffe000621920 s9 : ffffffe0007488a8 s10: 0000000000000001 [ 0.000000] s11: 0000000000200000 t3 : ffffffd004000000 t4 : 9000000004fffcc7 [ 0.000000] t5 : 0000000000000003 t6 : ffffffe0006d7d3c [ 0.000000] sstatus: 0000000200000100 sbadaddr: 0000000010002080 scause: 0000000000000005 [ 0.000000] random: get_random_bytes called from init_oops_id+0x26/0x38 with crng_init=0 [ 0.000000] ---[ end trace 0000000000000000 ]--- [ 0.000000] Kernel panic - not syncing: Attempted to kill the idle task! [ 0.000000] ---[ end Kernel panic - not syncing: Attempted to kill the idle task! ]--- -
司徒最終使出吃奶的力氣跟 AI 努力奮鬥後, 終於把這個 Crash 問題找出來, 首先看到 Crash PC位址是位於 0x205a12
[ 0.000000] sepc: ffffffe000205a12 ra : ffffffe00000b832 sp : ffffffe0006d7e10 [ 0.000000] sstatus: 0000000200000100 sbadaddr: 0000000010002080 scause: 0000000000000005定位副程式
$ riscv64-unknown-linux-gnu-addr2line -e vmlinux -f -C 0xffffffe000205a12 plic_toggle.isra.2 irq-sifive-plic.c:?查看 Device Tree
$ vim arch/riscv/boot/dts/sunxi/sun20iw1p1.dtsi plic0: interrupt-controller@10000000 {從 OpenSBI 列印 0x10002080 內容,發現內容都是 0
M-mode read 0x10002080 => 0 S-mode read 0x10002080 => 0開始定位權限問題,發現 PLIC_CTRL_REG 需要設定才可以存取 PLIC 內容

於是在 OpenSBI 跳轉到 Kernel 前,先設定 PLIC 存取權限,就可以解掉 Crash 問題
*(volatile uint32_t *)0x101ffffc = 1;Baudrate 115200bps
[ 0.000000] Linux version 5.4.61 (steward@debian) (riscv64-unknown-linux-gnu-gcc (C-SKY RISCV Tools V1.8.4 B20200702) 8.1.0, GNU ld (GNU Binutils) 2.32) #1 PREEMPT Thu Sep 10 22:35:38 CST 2026 [ 0.000000] earlycon: sbi0 at I/O port 0x0 (options '') [ 0.000000] printk: bootconsole [sbi0] enabled [ 0.000000] cma: Reserved 16 MiB at 0x0000000047000000 [ 0.000000] Zone ranges: [ 0.000000] DMA32 [mem 0x0000000040000000-0x0000000047ffffff] [ 0.000000] Normal empty [ 0.000000] Movable zone start for each node [ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000040000000-0x0000000047ffffff] [ 0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x0000000047ffffff] [ 0.000000] elf_hwcap is 0x20112d [ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 32320 [ 0.000000] Kernel command line: console=ttyS4,115200n8 debug loglevel=7,initcall_debug=1 init=/init earlycon=sbi [ 0.000000] Dentry cache hash table entries: 16384 (order: 5, 131072 bytes, linear) [ 0.000000] Inode-cache hash table entries: 8192 (order: 4, 65536 bytes, linear) [ 0.000000] Sorting __ex_table... [ 0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off [ 0.000000] Memory: 104568K/131072K available (4909K kernel code, 449K rwdata, 1909K rodata, 160K init, 234K bss, 10120K reserved, 16384K cma-reserved) [ 0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1 [ 0.000000] rcu: Preemptible hierarchical RCU implementation. [ 0.000000] Tasks RCU enabled. [ 0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 10 jiffies. [ 0.000000] NR_IRQS: 0, nr_irqs: 0, preallocated irqs: 0 [ 0.000000] plic: mapped 200 interrupts with 1 handlers for 2 contexts. [ 0.000000] riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [0] [ 0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x588fe9dc0, max_idle_ns: 440795202592 ns [ 0.000125] sched_clock: 64 bits at 24MHz, resolution 41ns, wraps every 4398046511097ns [ 0.008238] riscv_timer_clockevent depends on broadcast, but no broadcast function available [ 0.021298] clocksource: timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 79635851949 ns [ 0.043416] Calibrating delay loop (skipped), value calculated using timer frequency.. 48.00 BogoMIPS (lpj=240000) [ 0.053948] pid_max: default: 32768 minimum: 301 [ 0.060648] Mount-cache hash table entries: 512 (order: 0, 4096 bytes, linear) [ 0.068014] Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes, linear) [ 0.089491] ASID allocator initialised with 65536 entries [ 0.095919] rcu: Hierarchical SRCU implementation. [ 0.105840] devtmpfs: initialized [ 0.342131] random: get_random_u32 called from bucket_table_alloc.isra.27+0x10a/0x12c with crng_init=0 [ 0.349219] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns [ 0.368572] futex hash table entries: 256 (order: 0, 6144 bytes, linear) [ 0.384847] pinctrl core: initialized pinctrl subsystem [ 0.401201] NET: Registered protocol family 16 [ 0.425372] DMA: preallocated 256 KiB pool for atomic allocations [ 0.435759] cpuidle: using governor menu [ 0.864565] rtc_ccu: sunxi ccu init OK [ 0.943050] clock: sunxi ccu init OK [ 0.955128] clock: sunxi ccu init OK [ 1.281595] sun6i-dma 3002000.dma-controller: sunxi dma probed [ 1.316158] iommu: Default domain type: Translated [ 1.322135] sunxi iommu: irq = 4 [ 1.336606] SCSI subsystem initialized [ 1.343999] usbcore: registered new interface driver usbfs [ 1.350350] usbcore: registered new interface driver hub [ 1.356590] usbcore: registered new device driver usb [ 1.362847] videodev: Linux video capture interface: v2.00 [ 1.376396] Advanced Linux Sound Architecture Driver Initialized. [ 1.389471] Bluetooth: Core ver 2.22 [ 1.393475] NET: Registered protocol family 31 [ 1.397995] Bluetooth: HCI device and connection manager initialized [ 1.404467] Bluetooth: HCI socket layer initialized [ 1.409454] Bluetooth: L2CAP socket layer initialized [ 1.414767] Bluetooth: SCO socket layer initialized [ 1.421697] pwm module init! [ 1.438662] g2d 5410000.g2d: Adding to iommu group 0 [ 1.447448] G2D: rcq version initialized.major:252 [ 1.459566] clocksource: Switched to clocksource riscv_clocksource [ 1.644760] sun8iw20-pinctrl 2000000.pinctrl: initialized sunXi PIO driver [ 1.679602] NET: Registered protocol family 2 [ 1.693720] tcp_listen_portaddr_hash hash table entries: 256 (order: 0, 4096 bytes, linear) [ 1.702402] TCP established hash table entries: 1024 (order: 1, 8192 bytes, linear) [ 1.710560] TCP bind hash table entries: 1024 (order: 1, 8192 bytes, linear) [ 1.718069] TCP: Hash tables configured (established 1024 bind 1024) [ 1.725104] UDP hash table entries: 256 (order: 1, 8192 bytes, linear) [ 1.732064] UDP-Lite hash table entries: 256 (order: 1, 8192 bytes, linear) [ 1.740853] NET: Registered protocol family 1 [ 1.771587] workingset: timestamp_bits=62 max_order=15 bucket_order=0 [ 1.892320] squashfs: version 4.0 (2009/01/31) Phillip Lougher [ 1.901056] ntfs: driver 2.1.32 [Flags: R/W]. [ 2.831877] io scheduler mq-deadline registered [ 2.836472] io scheduler kyber registered [ 2.848914] [DISP]disp_module_init [ 2.858080] disp 5000000.disp: Adding to iommu group 0 [ 3.319244] display_fb_request,fb_id:0 [ 3.325176] [DISP] Fb_copy_boot_fb,line:1449: [ 3.325230] no boot_fb0 [ 3.335539] disp_al_manager_apply ouput_type:0 [ 3.341621] [DISP] lcd_clk_config,line:774: [ 3.341861] disp 0, clk: pll(72000000),clk(72000000),dclk(6000000) dsi_rate(72000000) [ 3.341861] clk real:pll(288000000),clk(288000000),dclk(6000000) dsi_rate(0) [ 3.366295] [DISP]disp_module_init finish [ 3.376543] sunxi_sid_init()783 - insmod ok [ 3.397789] uart uart0: uart0 supply uart not found, using dummy regulator [ 3.407627] sw_uart_request_gpio()1273 - UART0 devm_pinctrl_get() failed! return -19 [ 3.415548] uart0: ttyS0 at MMIO 0x2500000 (irq = 18, base_baud = 1500000) is a SUNXI [ 3.429644] sun8iw20-pinctrl 2000000.pinctrl: 2000000.pinctrl supply vcc-pb not found, using dummy regulator [ 3.444651] uart uart4: uart4 supply uart not found, using dummy regulator [ 3.454608] uart4: ttyS4 at MMIO 0x2501000 (irq = 22, base_baud = 1500000) is a SUNXI [ 3.471324] printk: console [ttyS4] enabled setup baud 115200 parity n bits 8, flow n [ 3.471324] printk: console [ttyS4] enabled [ 3.480395] printk: bootconsole [sbi0] disabled [ 3.480395] printk: bootconsole [sbi0] disabled [ 3.499934] misc dump reg init [ 3.523752] libphy: Fixed MDIO Bus: probed [ 3.536492] ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver [ 3.544162] sunxi-ehci: EHCI SUNXI driver [ 3.554895] get ehci0-controller wakeup-source is fail. [ 3.562094] sunxi ehci0-controller don't init wakeup source [ 3.568767] [sunxi-ehci0]: probe, pdev->name: 4101000.ehci0-controller, sunxi_ehci: 0xffffffe00076b730, 0x:ffffffd004067000, irq_no:2e [ 3.582892] [sunxi-ehci0]: Not init ehci0 [ 3.590936] get ehci1-controller wakeup-source is fail. [ 3.598042] sunxi ehci1-controller don't init wakeup source [ 3.604712] [sunxi-ehci1]: probe, pdev->name: 4200000.ehci1-controller, sunxi_ehci: 0xffffffe00076bec0, 0x:ffffffd00406b000, irq_no:31 [ 3.619088] sunxi-ehci 4200000.ehci1-controller: 4200000.ehci1-controller supply drvvbus not found, using dummy regulator [ 3.634144] sunxi-ehci 4200000.ehci1-controller: 4200000.ehci1-controller supply hci not found, using dummy regulator [ 3.649553] sunxi-ehci 4200000.ehci1-controller: EHCI Host Controller [ 3.657523] sunxi-ehci 4200000.ehci1-controller: new USB bus registered, assigned bus number 1 [ 3.668805] sunxi-ehci 4200000.ehci1-controller: irq 49, io mem 0x04200000 -
司徒幫小志掌機規劃的Memory Layout

Device Tree保留這些區域避免被覆蓋
memory@40000000 { device_type = "memory"; reg = <0x0 0x40000000 0x0 0x04000000>; /* 64 MB */ }; reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; opensbi_reserved: opensbi@43e00000 { reg = <0x0 0x43e00000 0x0 0x00080000>; /* 512 KiB */ no-map; }; dtb_reserved: dtb@43d00000 { reg = <0x0 0x43d00000 0x0 0x00010000>; /* 64 KiB */ no-map; }; };開機後的Log
[ 0.000000] Linux version 5.4.61 (steward@debian) (riscv64-unknown-linux-gnu-gcc (C-SKY RISCV Tools V1.8.4 B20200702) 8.1.0, GNU ld (GNU Binutils) 2.32) #1 PREEMPT Fri Sep 11 07:05:18 CST 2026 [ 0.000000] earlycon: sbi0 at I/O port 0x0 (options '') [ 0.000000] printk: bootconsole [sbi0] enabled [ 0.000000] cma: Reserved 16 MiB at 0x0000000042c00000 [ 0.000000] Zone ranges: [ 0.000000] DMA32 [mem 0x0000000040000000-0x0000000043ffffff] [ 0.000000] Normal empty [ 0.000000] Movable zone start for each node [ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000040000000-0x0000000043cfffff] [ 0.000000] node 0: [mem 0x0000000043d10000-0x0000000043dfffff] [ 0.000000] node 0: [mem 0x0000000043e80000-0x0000000043ffffff] [ 0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x0000000043ffffff] -
Kernel Crash 問題已經解決,但是從 Log 的結果來看,Kernel 並沒有進入到最後的階段,因為我們尚未描述 rootfs 位置,理應有 VFS Crash 的 Log 才對,所以我們還要繼續追問題
ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver sunxi-ehci: EHCI SUNXI driver get ehci0-controller wakeup-source is fail. sunxi ehci0-controller don't init wakeup source [sunxi-ehci0]: probe, pdev->name: 4101000.ehci0-controller, sunxi_ehci: 0xffffffe00051a130, 0x:ffffffd004078000, irq_no:2e [sunxi-ehci0]: Not init ehci0 get ehci1-controller wakeup-source is fail. sunxi ehci1-controller don't init wakeup source [sunxi-ehci1]: probe, pdev->name: 4200000.ehci1-controller, sunxi_ehci: 0xffffffe00051a8c0, 0x:ffffffd00407c000, irq_no:31 sunxi-ehci 4200000.ehci1-controller: 4200000.ehci1-controller supply drvvbus not found, using dummy regulator sunxi-ehci 4200000.ehci1-controller: 4200000.ehci1-controller supply hci not found, using dummy regulator sunxi-ehci 4200000.ehci1-controller: EHCI Host Controller sunxi-ehci 4200000.ehci1-controller: new USB bus registered, assigned bus number 1 sunxi-ehci 4200000.ehci1-controller: irq 49, io mem 0x04200000先把 USB 的 DTS 描述進行關閉來定位問題
&ehci0 { status = "disabled"; }; &ohci0 { status = "disabled"; };關閉後的 Kernel Log
ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver sunxi-ehci: EHCI SUNXI driver ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver sunxi-ohci: OHCI SUNXI driver usbcore: registered new interface driver uas usbcore: registered new interface driver usb-storage sunxi-rtc 7090000.rtc: errata__fix_alarm_day_reg_default_value(): ALARM0_DAY_REG=0, set it to 1 sunxi-rtc 7090000.rtc: Warning: Using internal RC 16M clock source. Time may be inaccurate! sunxi-rtc 7090000.rtc: Warning: Using internal RC 16M clock source. Time may be inaccurate! sunxi-rtc 7090000.rtc: Warning: Using internal RC 16M clock source. Time may be inaccurate! sunxi-rtc 7090000.rtc: registered as rtc0 sunxi-rtc 7090000.rtc: Warning: Using internal RC 16M clock source. Time may be inaccurate! sunxi-rtc 7090000.rtc: setting system clock to 1970-01-01T00:00:11 UTC (11) sunxi-rtc 7090000.rtc: sunxi rtc probed i2c /dev entries driver sunxi-mmc 4020000.sdmmc: SD/MMC/SDIO Host Controller Driver(v4.25 2022-6-21 13:40) sunxi-mmc 4020000.sdmmc: ***ctl-spec-caps*** 8 sunxi-mmc 4020000.sdmmc: No vmmc regulator found sunxi-mmc 4020000.sdmmc: No vqmmc regulator found sunxi-mmc 4020000.sdmmc: No vdmmc regulator found sunxi-mmc 4020000.sdmmc: No vd33sw regulator found sunxi-mmc 4020000.sdmmc: No vd18sw regulator found sunxi-mmc 4020000.sdmmc: No vq33sw regulator found sunxi-mmc 4020000.sdmmc: No vq18sw regulator found sunxi-mmc 4020000.sdmmc: Got CD GPIO sunxi-mmc 4020000.sdmmc: set cd-gpios as 24M fail sunxi-mmc 4020000.sdmmc: sdc set ios:clk 0Hz bm PP pm UP vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: no vqmmc,Check if there is regulator接著在 Kernel menuconfig 也將 MMC 關閉,DTS 裡面除了 UART4 的 SDC 也關閉
misc dump reg init brd: module loaded loop: module loaded i2c /dev entries driver ashmem: initialized但是 Kernel 還是沒有進入載入 init 的階段,因此,從 initcall 印出 Log
$ vim init/main.c 1012 static void __init do_initcall_level(int level) 1013 { 1014 int ret = 0; 1015 initcall_entry_t *fn; 1016 1017 strcpy(initcall_command_line, saved_command_line); 1018 parse_args(initcall_level_names[level], 1019 initcall_command_line, __start___param, 1020 __stop___param - __start___param, 1021 level, level, 1022 NULL, &repair_env_string); 1023 1024 trace_initcall_level(initcall_level_names[level]); 1025 for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) { 1026 printk("### INITCALL BEGIN: level=%d fn=%ps ###\n", level, initcall_from_entry(fn)); 1027 do_one_initcall(initcall_from_entry(fn)); 1028 printk("### INITCALL END: level=%d fn=%ps ret=%d ###\n", level, initcall_from_entry(fn), ret); 1029 } 1030 }最後出問題的地方在 clk_disable_unused
### INITCALL END: level=6 fn=i2c_dev_init ret=0 ### ### INITCALL BEGIN: level=6 fn=sunxi_led_driver_init ### ### INITCALL END: level=6 fn=sunxi_led_driver_init ret=0 ### ### INITCALL BEGIN: level=6 fn=timer_led_trigger_init ### ### INITCALL END: level=6 fn=timer_led_trigger_init ret=0 ### ### INITCALL BEGIN: level=6 fn=defon_led_trigger_init ### ### INITCALL END: level=6 fn=defon_led_trigger_init ret=0 ### ### INITCALL BEGIN: level=6 fn=ashmem_init ### ### INITCALL END: level=6 fn=ashmem_init ret=0 ### ### INITCALL BEGIN: level=6 fn=sunxi_sid_driver_init ### ### INITCALL END: level=6 fn=sunxi_sid_driver_init ret=0 ### ### INITCALL BEGIN: level=7 fn=init_oops_id ### ### INITCALL END: level=7 fn=init_oops_id ret=0 ### ### INITCALL BEGIN: level=7 fn=pm_qos_power_init ### ### INITCALL END: level=7 fn=pm_qos_power_init ret=0 ### ### INITCALL BEGIN: level=7 fn=pm_debugfs_init ### ### INITCALL END: level=7 fn=pm_debugfs_init ret=0 ### ### INITCALL BEGIN: level=7 fn=wakeup_reason_init ### ### INITCALL END: level=7 fn=wakeup_reason_init ret=0 ### ### INITCALL BEGIN: level=7 fn=printk_late_init ### ### INITCALL END: level=7 fn=printk_late_init ret=0 ### ### INITCALL BEGIN: level=7 fn=init_srcu_module_notifier ### ### INITCALL END: level=7 fn=init_srcu_module_notifier ret=0 ### ### INITCALL BEGIN: level=7 fn=tk_debug_sleep_time_init ### ### INITCALL END: level=7 fn=tk_debug_sleep_time_init ret=0 ### ### INITCALL BEGIN: level=7 fn=fault_around_debugfs ### ### INITCALL END: level=7 fn=fault_around_debugfs ret=0 ### ### INITCALL BEGIN: level=7 fn=max_swapfiles_check ### ### INITCALL END: level=7 fn=max_swapfiles_check ret=0 ### ### INITCALL BEGIN: level=7 fn=prandom_reseed ### ### INITCALL END: level=7 fn=prandom_reseed ret=0 ### ### INITCALL BEGIN: level=7 fn=clk_debug_init ### ### INITCALL END: level=7 fn=clk_debug_init ret=0 ### ### INITCALL BEGIN: level=7 fn=sync_state_resume_initcall ### ### INITCALL END: level=7 fn=sync_state_resume_initcall ret=0 ### ### INITCALL BEGIN: level=7 fn=deferred_probe_initcall ### ### INITCALL END: level=7 fn=deferred_probe_initcall ret=0 ### ### INITCALL BEGIN: level=7 fn=ubi_init ### ### INITCALL END: level=7 fn=ubi_init ret=0 ### ### INITCALL BEGIN: level=7 fn=of_fdt_raw_init ### ### INITCALL END: level=7 fn=of_fdt_raw_init ret=0 ### ### INITCALL BEGIN: level=7 fn=clk_disable_unused ###忽略 CLK 錯誤檢查
chosen { bootargs = "console=ttyS4,115200n8 earlycon=sbi ignore_loglevel loglevel=8 init=/init clk_ignore_unused"; };終於進入載入 init 的階段,這才是 Kernel 完成開機的階段,當然中間忽略的問題還要一一找出來
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) CPU: 0 PID: 1 Comm: swapper Not tainted 5.4.61 #5 Call Trace: [<ffffffe0000211c4>] walk_stackframe+0x0/0x98 [<ffffffe00002137c>] show_stack+0x2a/0x34 [<ffffffe000287892>] dump_stack+0x20/0x28 [<ffffffe000025882>] panic+0xec/0x27a [<ffffffe000000f22>] mount_block_root+0x214/0x27a [<ffffffe00000110a>] mount_root+0x86/0x90 [<ffffffe000001288>] prepare_namespace+0x174/0x17e [<ffffffe000000b62>] kernel_init_freeable+0x174/0x1c8 [<ffffffe000297ff8>] kernel_init+0x12/0xee [<ffffffe00001ff30>] ret_from_exception+0x0/0xc ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---移除 CLK 錯誤檢查
chosen { bootargs = "console=ttyS4,115200n8 earlycon=sbi ignore_loglevel loglevel=8 init=/init"; };在 CLK 裡面添加 Debug Log
1230 static void clk_disable_unused_subtree(struct clk_core *core) 1231 { 1232 struct clk_core *child; 1233 unsigned long flags; 1234 1235 printk( 1236 "CLK %s: prepare=%d enable=%d protect=%d parent=%s\n", 1237 core->name, 1238 core->prepare_count, 1239 core->enable_count, 1240 core->protect_count, 1241 core->parent ? core->parent->name : "NULL");定位到 PLL-CPU 問題
### CLK UNUSED END: pll-ddr0 ### CLK pll-cpux: prepare=0 enable=0 protect=0 parent=dcxo24M CLK riscv: prepare=0 enable=0 protect=0 parent=pll-cpux CLK riscv-axi: prepare=0 enable=0 protect=0 parent=riscv ### CLK UNUSED END: riscv-axi ### ### CLK UNUSED END: riscv ### CLK pll-cpux-div: prepare=0 enable=0 protect=0 parent=pll-cpux ### CLK UNUSED END: pll-cpux-div ###將 SUN8IW20_PLL_CPUX_REG 設定成 CLK_IS_CRITICAL
$ vim drivers/clk/sunxi-ng/ccu-sun8iw20.c 37 #define SUN8IW20_PLL_CPUX_REG 0x000 38 static struct ccu_mult pll_cpux_clk = { 39 .enable = BIT(27), 40 .lock = BIT(28), 41 .mult = _SUNXI_CCU_MULT_MIN(8, 8, 12), 42 .common = { 43 .reg = 0x000, 44 .hw.init = CLK_HW_INIT("pll-cpux", "dcxo24M", 45 &ccu_mult_ops, 46 CLK_SET_RATE_UNGATE | CLK_IS_CRITICAL), 47 }, 48 };添加 BOOT 開機選項,然後使用 rootwait 故意讓 Kernel 停在等待 rootfs Ready 的狀態
chosen { bootargs = "console=ttyS4,115200n8 earlycon=sbi ignore_loglevel loglevel=8 root=/dev/mmcblk0p1 rootwait rw rootfstype=fat init=/init"; };Kernel 已經可以正常開機
printk: console [ttyS4] enabled printk: bootconsole [sbi0] disabled printk: bootconsole [sbi0] disabled misc dump reg init brd: module loaded loop: module loaded i2c /dev entries driver ashmem: initialized Waiting for root device /dev/mmcblk0p1...司徒當初考慮使用 Tina-Linux 的原因是 Allwinner F133 相關的移植都已經完成, 我們應該可以很快就將 Kernel 移植使用, 但是目前看來, Tina-Linux 太高度客製化, 導致在還沒有很熟悉配置設定前, 可能需要花一些時間來除錯問題, 不過這個投資可能還是比從頭移植 Allwinner F133 到 main stream 上來得快一點~划算划算~哈
-
司徒看了一下目前Tina-Linux裡面的Video Framebuffer Driver,發現這個Video Driver是為了可以透過初始化參數的設定就可以點屏,架構相對來說比較複雜一點,對於初期可以很快拿來使用,但是後期我們要做最佳化時,會變成是一個瓶頸,司徒覺得不適合我們短小精幹的小志掌機,為此,司徒將小志掌機的顯示驅動拆成如下三個階段製作:
階段一:使用I/O Toggle方式顯示 階段二:使用DMA方式顯示 階段三:加上INT中斷掃屏framebuffer位於drivers/video/fbdev資料夾,因此,我們創建一個新檔案st7789.c
/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option)any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/cdev.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/fb.h> #include <linux/dma-mapping.h> #include <linux/device.h> #include <linux/backlight.h> #include <linux/platform_device.h> #include <linux/uaccess.h> #include <linux/pm_runtime.h> #include <linux/interrupt.h> #include <linux/wait.h> #include <linux/clk.h> #include <linux/cpufreq.h> #include <linux/console.h> #include <linux/spinlock.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/lcm.h> #include <linux/clk-provider.h> #include <video/of_display_timing.h> #include <linux/gpio.h> #include <linux/omapfb.h> #include <linux/compiler.h> #include <linux/workqueue.h> #include <linux/sched.h> #include <asm/io.h> #include <asm/irq.h> #define PALETTE_SIZE 256 #define DRIVER_NAME "gaviar-fb" #define GPIO_BASE 0x02000000 #define PD_CFG0 0x0090 #define PD_CFG1 0x0094 #define PD_CFG2 0x0098 #define PD_DAT 0x00a0 #define LCD_RST (1 << 0) #define LCD_WR (1 << 18) #define LCD_RS (1 << 19) #define LCD_RD (1 << 20) #define LCD_CS (1 << 21) #define LCD_BL (1 << 22) struct myfb_par { struct device *dev; struct platform_device *pdev; void *vram_virt; uint32_t vram_size; dma_addr_t vram_phys; u32 pseudo_palette[16]; struct fb_videomode mode; }; struct iomm { uint8_t *gpio; }; static struct iomm myio = { 0 }; static struct fb_var_screeninfo myfb_var = { 0 }; static struct fb_fix_screeninfo myfb_fix = { .id = DRIVER_NAME, .type = FB_TYPE_PACKED_PIXELS, .type_aux = 0, .visual = FB_VISUAL_TRUECOLOR, .xpanstep = 0, .ypanstep = 1, .ywrapstep = 0, .accel = FB_ACCEL_NONE }; static void lcd_write(uint32_t ctl, uint32_t dat) { uint32_t r = ctl | ((dat & 0xff) << 1) | ((dat & 0xff00) << 2); writel(r, myio.gpio + PD_DAT); r |= LCD_WR; writel(r, myio.gpio + PD_DAT); } static void lcd_write_cmd(uint32_t cmd) { lcd_write(LCD_RD | LCD_BL | LCD_RST, cmd); } static void lcd_write_dat(uint32_t dat) { lcd_write(LCD_RS | LCD_RD | LCD_BL | LCD_RST, dat); } static void lcd_reset(void) { uint32_t r = 0; r = readl(myio.gpio + PD_DAT); r &= ~LCD_RST; writel(r, myio.gpio + PD_DAT); mdelay(150); r |= LCD_RST; writel(r, myio.gpio + PD_DAT); mdelay(150); } static int lcd_init(struct myfb_par *mypar) { writel(0x11111111, myio.gpio + PD_CFG0); writel(0x11111111, myio.gpio + PD_CFG1); writel(0x11111111, myio.gpio + PD_CFG2); writel(0xffffffff, myio.gpio + PD_DAT); lcd_reset(); lcd_write_cmd(0xb2); lcd_write_dat(0x5c); lcd_write_dat(0x5c); lcd_write_dat(0x00); lcd_write_dat(0x33); lcd_write_dat(0x33); lcd_write_cmd(0xb7); lcd_write_dat(0x35); lcd_write_cmd(0x21); lcd_write_cmd(0x11); mdelay(150); lcd_write_cmd(0xe0); lcd_write_dat(0xd0); lcd_write_dat(0x06); lcd_write_dat(0x0b); lcd_write_dat(0x07); lcd_write_dat(0x07); lcd_write_dat(0x24); lcd_write_dat(0x2e); lcd_write_dat(0x32); lcd_write_dat(0x46); lcd_write_dat(0x37); lcd_write_dat(0x13); lcd_write_dat(0x13); lcd_write_dat(0x2d); lcd_write_dat(0x33); lcd_write_cmd(0xe1); lcd_write_dat(0xd0); lcd_write_dat(0x02); lcd_write_dat(0x06); lcd_write_dat(0x09); lcd_write_dat(0x08); lcd_write_dat(0x05); lcd_write_dat(0x29); lcd_write_dat(0x44); lcd_write_dat(0x42); lcd_write_dat(0x38); lcd_write_dat(0x14); lcd_write_dat(0x14); lcd_write_dat(0x2a); lcd_write_dat(0x30); lcd_write_cmd(0x36); lcd_write_dat(0xb0); lcd_write_cmd(0x2a); lcd_write_dat(0x00); lcd_write_dat(0x00); lcd_write_dat(0x01); lcd_write_dat(0x3f); lcd_write_cmd(0x2b); lcd_write_dat(0x00); lcd_write_dat(0x00); lcd_write_dat(0x00); lcd_write_dat(0xef); lcd_write_cmd(0x3a); lcd_write_dat(0x55); lcd_write_cmd(0x29); lcd_write_cmd(0x2c); { int i = 0; uint16_t *p = mypar->vram_virt; for (i = 0; i < 320 * 240; i++) { lcd_write_dat(*p++); } } return 0; } static int myfb_setcolreg(unsigned n, unsigned r, unsigned g, unsigned b, unsigned t, struct fb_info *info) { #define CNVT_TOHW(val, width) ((((val) << (width)) + 0x7FFF - (val)) >> 16) r = CNVT_TOHW(r, info->var.red.length); b = CNVT_TOHW(b, info->var.blue.length); g = CNVT_TOHW(g, info->var.green.length); ((u32 *)(info->pseudo_palette))[n] = (r << info->var.red.offset) | (g << info->var.green.offset) | (b << info->var.blue.offset); return 0; } static int myfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { int bpp = var->bits_per_pixel >> 3; struct myfb_par *par = info->par; unsigned long line_size = var->xres_virtual * bpp; if ((var->xres != 320) || (var->yres != 240) || (var->bits_per_pixel != 16)) { return -EINVAL; } var->transp.offset = 0; var->transp.length = 0; var->red.offset = 11; var->red.length = 5; var->green.offset = 5; var->green.length = 6; var->blue.offset = 0; var->blue.length = 5; var->red.msb_right = 0; var->green.msb_right = 0; var->blue.msb_right = 0; var->transp.msb_right = 0; if (line_size * var->yres_virtual > par->vram_size) { var->yres_virtual = par->vram_size / line_size; } if (var->yres > var->yres_virtual) { var->yres = var->yres_virtual; } if (var->xres > var->xres_virtual) { var->xres = var->xres_virtual; } if (var->xres + var->xoffset > var->xres_virtual) { var->xoffset = var->xres_virtual - var->xres; } if (var->yres + var->yoffset > var->yres_virtual) { var->yoffset = var->yres_virtual - var->yres; } return 0; } static int myfb_set_par(struct fb_info *info) { struct myfb_par *par = info->par; fb_var_to_videomode(&par->mode, &info->var); info->fix.visual = FB_VISUAL_TRUECOLOR; info->fix.line_length = 320 * 2; return 0; } static int myfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { info->var.xoffset = var->xoffset; info->var.yoffset = var->yoffset; return 0; } static struct fb_ops myfb_ops = { .owner = THIS_MODULE, .fb_check_var = myfb_check_var, .fb_set_par = myfb_set_par, .fb_setcolreg = myfb_setcolreg, .fb_pan_display = myfb_pan_display, .fb_fillrect = sys_fillrect, .fb_copyarea = sys_copyarea, .fb_imageblit = sys_imageblit, }; static int myfb_probe(struct platform_device *device) { int ret = 0; struct fb_info *info = NULL; struct myfb_par *par = NULL; struct fb_videomode *mode = NULL; mode = devm_kzalloc(&device->dev, sizeof(struct fb_videomode), GFP_KERNEL); if (mode == NULL) { return -ENOMEM; } mode->name = "320x240"; mode->xres = 320; mode->yres = 240; mode->vmode = FB_VMODE_NONINTERLACED; pm_runtime_enable(&device->dev); pm_runtime_get_sync(&device->dev); info = framebuffer_alloc(sizeof(struct myfb_par), &device->dev); if (!info) { return -ENOMEM; } par = info->par; par->pdev = device; par->dev = &device->dev; fb_videomode_to_var(&myfb_var, mode); par->vram_size = (320 * 240 * 2 * 4) + 4096; par->vram_virt = dma_alloc_coherent(&device->dev, par->vram_size, (resource_size_t *)&par->vram_phys, GFP_KERNEL | GFP_DMA); if (!par->vram_virt) { return -EINVAL; } info->screen_base = (char __iomem *)par->vram_virt; myfb_fix.smem_start = par->vram_phys; myfb_fix.smem_len = par->vram_size; myfb_fix.line_length = 320 * 2; myfb_var.grayscale = 0; myfb_var.bits_per_pixel = 16; info->flags = FBINFO_FLAG_DEFAULT; info->fix = myfb_fix; info->var = myfb_var; info->fbops = &myfb_ops; info->pseudo_palette = par->pseudo_palette; info->fix.visual = (info->var.bits_per_pixel <= 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; ret = fb_alloc_cmap(&info->cmap, PALETTE_SIZE, 0); if (ret) { return -EINVAL; } info->cmap.len = 32; myfb_var.activate = FB_ACTIVATE_FORCE; fb_set_var(info, &myfb_var); dev_set_drvdata(&device->dev, info); if (register_framebuffer(info) < 0) { return -EINVAL; } lcd_init(par); return 0; } static int myfb_remove(struct platform_device *dev) { struct fb_info *info = dev_get_drvdata(&dev->dev); struct myfb_par *par = info->par; if (info) { flush_scheduled_work(); unregister_framebuffer(info); fb_dealloc_cmap(&info->cmap); dma_free_coherent(NULL, par->vram_size, par->vram_virt, par->vram_phys); framebuffer_release(info); } iounmap(myio.gpio); return 0; } static int myfb_suspend(struct platform_device *dev, pm_message_t state) { struct fb_info *info = platform_get_drvdata(dev); console_lock(); fb_set_suspend(info, 1); pm_runtime_put_sync(&dev->dev); console_unlock(); return 0; } static int myfb_resume(struct platform_device *dev) { struct fb_info *info = platform_get_drvdata(dev); console_lock(); pm_runtime_get_sync(&dev->dev); fb_set_suspend(info, 0); console_unlock(); return 0; } static const struct of_device_id fb_of_match[] = { { .compatible = "allwinner,sunxi-disp" }, {} }; MODULE_DEVICE_TABLE(of, fb_of_match); static struct platform_driver fb_driver = { .probe = myfb_probe, .remove = myfb_remove, .suspend = myfb_suspend, .resume = myfb_resume, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(fb_of_match), }, }; static void sunxi_ioremap(void) { myio.gpio = (uint8_t *)ioremap(GPIO_BASE, 1024); } static void sunxi_iounmap(void) { iounmap(myio.gpio); } static int __init myfb_init(void) { sunxi_ioremap(); return platform_driver_register(&fb_driver); } static void __exit myfb_cleanup(void) { platform_driver_unregister(&fb_driver); sunxi_iounmap(); } module_init(myfb_init); module_exit(myfb_cleanup); MODULE_DESCRIPTION("framebuffer driver for Gaviar handheld"); MODULE_AUTHOR("Steward Fu <steward.fu@gmail.com>"); MODULE_LICENSE("GPL");drivers/video/fbdev/Kconfig
config FB_ST7789 tristate "ST7789 framebuffer support" depends on FB select FB_SYS_FOPS select FB_SYS_FILLRECT select FB_SYS_COPYAREA select FB_SYS_IMAGEBLIT ---help--- Frame buffer driver for the Gaviar handhelddrivers/video/fbdev/Makefile
obj-$(CONFIG_FB_ST7789) += st7789.o選擇ST7789

編譯下載後就可以看到Linux Boot Logo

如果不想手動編譯,可以使用司徒做好的測試Image,如果要自己手動編譯,Commit使用44f254c025465ea58a2f024482f572f59567cb2b
$ cd $ wget https://github.com/steward-fu/website/releases/download/gaviar/kernel_test_20260912.img $ sudo dd if=kernel_test_20260912.img of=/dev/sdX bs=1M -
司徒畫了一張Framebuffer的流程圖,這樣可以更知道整個Framebuffer Driver的運作,至於司徒為何還是使用Framebuffer Driver而不是其它更新的Video Driver Model呢?因為太難的司徒也不會...

-
司徒是打算先使用 Timer 做屏幕刷新,這樣就可以先移植輸入驅動程式,這才發現 Linux Kernel 啟動後,Timer 是無法正常運作的,往回找問題,才意識到當初在移植 OpenSBI 時,忘記做Timer、IPI、IRQ 初始化,因此,再度更新 OpenSBI Platform 初始化的部份,修改後的方塊圖如下

P.S. 修改的 Commit 57c01448d198b3ed53aa83951195f550e0658428 -
司徒先添加 Timer 充當 DMA 刷屏使用,流程變成如下:

修改後的代碼
/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option)any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/cdev.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/fb.h> #include <linux/dma-mapping.h> #include <linux/device.h> #include <linux/backlight.h> #include <linux/platform_device.h> #include <linux/uaccess.h> #include <linux/pm_runtime.h> #include <linux/interrupt.h> #include <linux/wait.h> #include <linux/clk.h> #include <linux/cpufreq.h> #include <linux/console.h> #include <linux/spinlock.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/lcm.h> #include <linux/clk-provider.h> #include <video/of_display_timing.h> #include <linux/gpio.h> #include <linux/omapfb.h> #include <linux/compiler.h> #include <linux/workqueue.h> #include <linux/sched.h> #include <asm/io.h> #include <asm/irq.h> #define DRIVER_NAME "gaviar-fb" #define CCU_BASE 0x02001000 #define GPIO_BASE 0x02000000 #define LCDC_BASE 0x05461000 #define PLL_CPU_CTRL_REG 0x0000 #define PLL_PERI_CTRL_REG 0x0020 #define PLL_VIDEO0_CTRL_REG 0x0040 #define PLL_VIDEO1_CTRL_REG 0x0048 #define DRAM_BGR_REG 0x080C #define G2D_CLK_REG 0x0630 #define G2D_BGR_REG 0x063C #define HSTIMER_BGR_REG 0x073C #define TCONLCD_CLK_REG 0x0B60 #define TCONLCD_BGR_REG 0x0B7C #define RISC_CLK_REG 0x0D00 #define RISC_GATING_REG 0x0D04 #define RISC_CFG_BGR_REG 0x0D0C #define PD_CFG0 0x0090 #define PD_CFG1 0x0094 #define PD_CFG2 0x0098 #define PD_DAT 0x00a0 #define LCD_RST (1 << 0) #define LCD_WR (1 << 18) #define LCD_RS (1 << 19) #define LCD_RD (1 << 20) #define LCD_CS (1 << 21) #define LCD_BL (1 << 22) struct myfb_par { struct device *dev; struct platform_device *pdev; void *vram_virt; uint32_t vram_size; dma_addr_t vram_phys; uint32_t pseudo_palette[16]; struct fb_videomode mode; }; struct iomm { uint8_t *ccu; uint8_t *gpio; uint8_t *lcdc; }; static struct iomm myio = { 0 }; static struct myfb_par *mypar = NULL; static struct fb_var_screeninfo myfb_var = { 0 }; static struct fb_fix_screeninfo myfb_fix = { .id = DRIVER_NAME, .type = FB_TYPE_PACKED_PIXELS, .type_aux = 0, .visual = FB_VISUAL_TRUECOLOR, .xpanstep = 0, .ypanstep = 1, .ywrapstep = 0, .accel = FB_ACCEL_NONE }; static void lcd_write(uint32_t ctl, uint32_t dat) { uint32_t r = ctl | ((dat & 0xff) << 1) | ((dat & 0xff00) << 2); writel(r, myio.gpio + PD_DAT); r |= LCD_WR; writel(r, myio.gpio + PD_DAT); } static void lcd_write_cmd(uint32_t cmd) { lcd_write(LCD_RD | LCD_BL | LCD_RST, cmd); } static void lcd_write_dat(uint32_t dat) { lcd_write(LCD_RS | LCD_RD | LCD_BL | LCD_RST, dat); } static void lcd_reset(void) { uint32_t r = 0; r = readl(myio.gpio + PD_DAT); r &= ~LCD_RST; writel(r, myio.gpio + PD_DAT); mdelay(150); r |= LCD_RST; writel(r, myio.gpio + PD_DAT); mdelay(150); } static int lcd_init(struct myfb_par *mypar) { writel(0x11111111, myio.gpio + PD_CFG0); writel(0x11111111, myio.gpio + PD_CFG1); writel(0x11111111, myio.gpio + PD_CFG2); writel(0xffffffff, myio.gpio + PD_DAT); lcd_reset(); lcd_write_cmd(0xb2); lcd_write_dat(0x5c); lcd_write_dat(0x5c); lcd_write_dat(0x00); lcd_write_dat(0x33); lcd_write_dat(0x33); lcd_write_cmd(0xb7); lcd_write_dat(0x35); lcd_write_cmd(0x21); lcd_write_cmd(0x11); mdelay(150); lcd_write_cmd(0xe0); lcd_write_dat(0xd0); lcd_write_dat(0x06); lcd_write_dat(0x0b); lcd_write_dat(0x07); lcd_write_dat(0x07); lcd_write_dat(0x24); lcd_write_dat(0x2e); lcd_write_dat(0x32); lcd_write_dat(0x46); lcd_write_dat(0x37); lcd_write_dat(0x13); lcd_write_dat(0x13); lcd_write_dat(0x2d); lcd_write_dat(0x33); lcd_write_cmd(0xe1); lcd_write_dat(0xd0); lcd_write_dat(0x02); lcd_write_dat(0x06); lcd_write_dat(0x09); lcd_write_dat(0x08); lcd_write_dat(0x05); lcd_write_dat(0x29); lcd_write_dat(0x44); lcd_write_dat(0x42); lcd_write_dat(0x38); lcd_write_dat(0x14); lcd_write_dat(0x14); lcd_write_dat(0x2a); lcd_write_dat(0x30); lcd_write_cmd(0x36); lcd_write_dat(0xb0); lcd_write_cmd(0x2a); lcd_write_dat(0x00); lcd_write_dat(0x00); lcd_write_dat(0x01); lcd_write_dat(0x3f); lcd_write_cmd(0x2b); lcd_write_dat(0x00); lcd_write_dat(0x00); lcd_write_dat(0x00); lcd_write_dat(0xef); lcd_write_cmd(0x3a); lcd_write_dat(0x55); lcd_write_cmd(0x29); lcd_write_cmd(0x2c); return 0; } static int myfb_setcolreg(unsigned n, unsigned r, unsigned g, unsigned b, unsigned t, struct fb_info *info) { #define CNVT_TOHW(val, width) ((((val) << (width)) + 0x7FFF - (val)) >> 16) r = CNVT_TOHW(r, info->var.red.length); b = CNVT_TOHW(b, info->var.blue.length); g = CNVT_TOHW(g, info->var.green.length); ((u32 *)(info->pseudo_palette))[n] = (r << info->var.red.offset) | (g << info->var.green.offset) | (b << info->var.blue.offset); return 0; } static int myfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { struct myfb_par *par = info->par; unsigned int line_size = var->xres_virtual * 2; if ((var->xres != 320) || (var->yres != 240) || (var->bits_per_pixel != 16)) { return -EINVAL; } var->transp.offset = 0; var->transp.length = 0; var->red.offset = 11; var->red.length = 5; var->green.offset = 5; var->green.length = 6; var->blue.offset = 0; var->blue.length = 5; var->red.msb_right = 0; var->green.msb_right = 0; var->blue.msb_right = 0; var->transp.msb_right = 0; if (line_size * var->yres_virtual > par->vram_size) { var->yres_virtual = par->vram_size / line_size; } if (var->yres > var->yres_virtual) { var->yres = var->yres_virtual; } if (var->xres > var->xres_virtual) { var->xres = var->xres_virtual; } if (var->xres + var->xoffset > var->xres_virtual) { var->xoffset = var->xres_virtual - var->xres; } if (var->yres + var->yoffset > var->yres_virtual) { var->yoffset = var->yres_virtual - var->yres; } return 0; } static int myfb_set_par(struct fb_info *info) { struct myfb_par *par = info->par; fb_var_to_videomode(&par->mode, &info->var); info->fix.visual = FB_VISUAL_TRUECOLOR; info->fix.line_length = 320 * 2; return 0; } static int myfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { info->var.xoffset = var->xoffset; info->var.yoffset = var->yoffset; return 0; } static struct fb_ops myfb_ops = { .owner = THIS_MODULE, .fb_set_par = myfb_set_par, .fb_setcolreg = myfb_setcolreg, .fb_check_var = myfb_check_var, .fb_pan_display = myfb_pan_display, .fb_fillrect = sys_fillrect, .fb_copyarea = sys_copyarea, .fb_imageblit = sys_imageblit, }; static struct timer_list my_timer; static void timer_handler(struct timer_list *timer) { static unsigned int cc = 0; if (mypar && mypar->vram_virt) { int i = 0; uint16_t *p = mypar->vram_virt; for (i = 0; i < 320 * 240; i++) { lcd_write_dat(*p++); } } printk("%d\n", cc++); mod_timer(&my_timer, jiffies + msecs_to_jiffies(500)); } static int myfb_probe(struct platform_device *device) { int ret = 0; struct fb_info *info = NULL; struct myfb_par *par = NULL; struct fb_videomode *mode = NULL; mode = devm_kzalloc(&device->dev, sizeof(struct fb_videomode), GFP_KERNEL); if (mode == NULL) { return -ENOMEM; } mode->name = "320x240"; mode->xres = 320; mode->yres = 240; mode->vmode = FB_VMODE_NONINTERLACED; info = framebuffer_alloc(sizeof(struct myfb_par), &device->dev); if (!info) { return -ENOMEM; } par = info->par; par->pdev = device; par->dev = &device->dev; fb_videomode_to_var(&myfb_var, mode); par->vram_size = 320 * 240 * 2 * 2; par->vram_virt = dma_alloc_coherent(&device->dev, par->vram_size, (resource_size_t *)&par->vram_phys, GFP_KERNEL | GFP_DMA ); if (!par->vram_virt) { return -EINVAL; } info->screen_base = (char __iomem *)par->vram_virt; myfb_fix.smem_start = par->vram_phys; myfb_fix.smem_len = par->vram_size; myfb_fix.line_length = 320 * 2; myfb_var.grayscale = 0; myfb_var.bits_per_pixel = 16; info->flags = FBINFO_FLAG_DEFAULT; info->fix = myfb_fix; info->var = myfb_var; info->fbops = &myfb_ops; info->pseudo_palette = par->pseudo_palette; info->fix.visual = (info->var.bits_per_pixel <= 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; ret = fb_alloc_cmap(&info->cmap, 256, 0); if (ret) { return -EINVAL; } info->cmap.len = 32; myfb_var.activate = FB_ACTIVATE_FORCE; fb_set_var(info, &myfb_var); dev_set_drvdata(&device->dev, info); if (register_framebuffer(info) < 0) { return -EINVAL; } lcd_init(par); mypar = par; timer_setup(&my_timer, timer_handler, 0); mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000)); return 0; } static int myfb_remove(struct platform_device *dev) { struct fb_info *info = dev_get_drvdata(&dev->dev); struct myfb_par *par = info->par; if (info) { flush_scheduled_work(); unregister_framebuffer(info); fb_dealloc_cmap(&info->cmap); dma_free_coherent(NULL, par->vram_size, par->vram_virt, par->vram_phys); framebuffer_release(info); } iounmap(myio.gpio); return 0; } static int myfb_suspend(struct platform_device *dev, pm_message_t state) { struct fb_info *info = platform_get_drvdata(dev); console_lock(); fb_set_suspend(info, 1); console_unlock(); return 0; } static int myfb_resume(struct platform_device *dev) { struct fb_info *info = platform_get_drvdata(dev); console_lock(); fb_set_suspend(info, 0); console_unlock(); return 0; } static const struct of_device_id fb_of_match[] = { { .compatible = "allwinner,sunxi-disp" }, {} }; MODULE_DEVICE_TABLE(of, fb_of_match); static struct platform_driver fb_driver = { .probe = myfb_probe, .resume = myfb_resume, .remove = myfb_remove, .suspend = myfb_suspend, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(fb_of_match), }, }; static void sunxi_ioremap(void) { myio.ccu = (uint8_t *)ioremap(CCU_BASE, 4096); myio.gpio = (uint8_t *)ioremap(GPIO_BASE, 4096); myio.lcdc = (uint8_t *)ioremap(LCDC_BASE, 4096); printk("%s\n", __func__); writel((1 << 31) | (1 << 24) | (1 << 16) | (1 << 0), myio.ccu + G2D_CLK_REG); writel((1 << 16) | (1 << 0), myio.ccu + G2D_BGR_REG); writel((1 << 31) | (1 << 24), myio.ccu + TCONLCD_CLK_REG); writel((1 << 16) | (1 << 0), myio.ccu + TCONLCD_BGR_REG); writel((1 << 16) | (1 << 0), myio.ccu + HSTIMER_BGR_REG); } static void sunxi_iounmap(void) { iounmap(myio.ccu); iounmap(myio.gpio); iounmap(myio.lcdc); } static int __init myfb_init(void) { sunxi_ioremap(); return platform_driver_register(&fb_driver); } static void __exit myfb_cleanup(void) { platform_driver_unregister(&fb_driver); sunxi_iounmap(); } module_init(myfb_init); module_exit(myfb_cleanup); MODULE_DESCRIPTION("framebuffer driver for Gaviar handheld"); MODULE_AUTHOR("Steward Fu <steward.fu@gmail.com>"); MODULE_LICENSE("GPL");更新 Kernel 後,Log 就會自動更新到屏幕上

P.S. 測試檔案 https://github.com/steward-fu/website/releases/download/gaviar/kernel_test_20260918.img -
補一張 Linux Timer 呼叫到 OpenSBI 的流程圖

-
來點輕鬆一點的改造分享,這是司徒最愛的 XT897 改造,主要改造的原因是按鍵爛掉了,然後喇叭是後置,那聲音真是有夠小,基於這些原因,司徒必須好好改造一下這小寶貝~
OpenSCAD
$fn = 50; module frame(off) { hull() { c = 3; h = 7; // left-bottom translate([0 - off, 4 - off / 2, 0]) cylinder(h, c, c); translate([6.5 - off / 2, 0 - off, 0]) cylinder(h, c, c); // right-bottom translate([112.5 + off / 2, 0 - off, 0]) cylinder(h, c, c); translate([118.5 + off, 4 - off / 2, 0]) cylinder(h, c, c); // left-top translate([6.5 - off / 2, 57 + off, 0]) cylinder(h, c, c); translate([0 - off, 53.5 + off / 2, 0]) cylinder(h, c, c); // right-top translate([118.5 + off, 53.5 + off / 2, 0]) cylinder(h, c, c); translate([112.5 + off / 2, 57 + off, 0]) cylinder(h, c, c); } } module earphone() { rotate([180, 0, 90]) difference() { translate([0, 0, 0]) cube([16, 22, 5]); translate([2.3, 2, 1]) cube([13, 18, 10]); } } module typec() { rotate([180, 0, 90]) difference() { translate([0, 0, 0]) cube([16.5, 13.5, 5]); translate([1.8, 1, 1]) cube([13.5, 11.5, 10]); } } module btn() { rotate([0, 180, 0]) difference() { translate([0, 0, 0]) cube([6, 16.5, 5]); translate([0, 6, 1]) cube([6, 6, 5]); } } module speaker() { difference() { translate([0, 0, 4]) cylinder(5, 8.5, 8.5); translate([0, 0, 3]) cylinder(5, 7.65, 7.65); } } module aux() { x = -1.6; y = 53; h = 21.6 + 0.8; w = 120.1; difference() { hull() { translate([x, y, 0.1]) cylinder(8.5, 3, 3); translate([w, y, 0.1]) cylinder(8.5, 3, 3); translate([x, y + h, 0.1]) cylinder(8.5, 3, 3); translate([w, y + h, 0.1]) cylinder(8.5, 3, 3); } translate([0, 0, -1]) hull() { off = 1.6; translate([x + off, y + off, -off]) cylinder(8.5, 3, 3); translate([w - off, y + off, -off]) cylinder(8.5, 3, 3); translate([x + off, y + h - off, -off]) cylinder(8.5, 3, 3); translate([w - off, y + h - off, -off]) cylinder(8.5, 3, 3); } translate([-5, 45, -1]) cube([130, 9, 10]); translate([3, 50, -1]) cube([110, 10, 10]); hull() { c = 3; h = 7; off = 0; translate([6.5 - off / 2, 57 + off, 0]) cylinder(h, c, c); translate([0 - off, 53.5 + off / 2, 0]) cylinder(h, c, c); translate([118.5 + off, 53.5 + off / 2, 0]) cylinder(h, c, c); translate([112.5 + off / 2, 57 + off, 0]) cylinder(h, c, c); } } } module back(h, off, extra) { c0 = 3; c1 = 3; translate([118.5, 0, 0]) rotate([0, 180, 0]) difference() { hull() { translate([0 - off, 4 - off / 2, 0]) cylinder(h, c0, c0); translate([6 - off / 2, 0 - off, 0]) cylinder(h, c0, c0); translate([112 + off / 2, 0 - off, 0]) cylinder(h, c0, c0); translate([118.5 + off, 4 - off / 2, 0]) cylinder(h, c0, c0); translate([0 - off, (74.4 + off / 2) + extra, 0]) cylinder(h, c0, c0); translate([118.5 + off, (74.4 + off / 2) + extra, 0]) cylinder(h, c0, c0); translate([0 - off, 4 - off / 2, 0]) sphere(c1); translate([6 - off / 2, 0 - off, 0]) sphere(c1); translate([112 + off / 2, 0 - off, 0]) sphere(c1); translate([118.5 + off, 4 - off / 2, 0]) sphere(c1); translate([0 - off, (74.4 + off / 2) + extra, 0]) sphere(c1); translate([118.5 + off, (74.4 + off / 2) + extra, 0]) sphere(c1); } } } module case(ch, cw, ce) { difference() { h = 7; union() { holder_z = 1; holder_h = h - holder_z; difference() { union() { difference() { frame(1.6); frame(0); } translate([0, 0, 11]) { back(ch, cw, ce); } // keyboard holder translate([8, 60.8, -0.3]) hull() { translate([0, 0, 0]) sphere(0.8); translate([101, 0, 0]) sphere(0.8); } } translate([8, 59, -0.3]) cube([4, 2, 5]); translate([12, 58.5, -0.3]) cube([88, 2, 5]); translate([98, 59, -0.3]) cube([5, 2, 5]); // sdcard translate([63.5, -6, 0]) cube([12, 5, 4]); // wires translate([11, 58, 5]) cube([3, 6, 2]); translate([38, 58, 5]) cube([3, 6, 2]); translate([91, 58, 5]) cube([3, 6, 2]); translate([103, 58, 5]) cube([3, 6, 2]); } // holder translate([-3, 23, holder_z]) cube([2, 11, holder_h]); translate([16.5, -3, holder_z]) cube([23, 1.5, holder_h]); translate([89, -3, holder_z]) cube([12, 1.5, holder_h]); difference() { translate([0, 0, h - 0.1]) frame(1.6); translate([-10, -10, h + 1.6]) cube([200, 200, 10]); // wires - battery translate([80, 54, -2]) cube([26, 10, 10]); translate([11, 54, -2]) cube([30, 10, 10]); } difference() { union() { aux(); translate([11, 61, 8]) btn(); translate([16, 61.5, 8]) earphone(); translate([75, 61.5, 8]) typec(); translate([100, 61, 8]) btn(); translate([112, 61, 8]) btn(); translate([127 / 2 - 2.5, 69.2, -1]) speaker(); } // earphone translate([20, 75, -5]) cube([6, 6, 10]); // speaker translate([45, 66.5, -4.1]) cube([10, 6, 10]); // typec translate([76.75, 75, -5]) cube([10, 6, 10]); } } // battery translate([15, 3, 0]) cube([82, 51, 12.8]); translate([2, 9, 0]) cube([15, 25, 10]); // screw c0 = 1; h0 = 50; z0 = -20; translate([3.5, 2, z0]) cylinder(h0, c0, c0); translate([46.5, 0.5, z0]) cylinder(h0, c0, c0); translate([109.5, 1, z0]) cylinder(h0, c0, c0); //translate([1, 37.3, z0]) cylinder(h0, c0, c0); //translate([115, 23, z0]) cylinder(h0, c0, c0); translate([1, 50, z0]) cylinder(h0, c0, c0); translate([47, 57.5, z0]) cylinder(h0, c0, c0); translate([109.8, 56.7, z0]) cylinder(h0, c0, c0); // screw c1 = 1.8; h1 = 20; z1 = 11; translate([3.5, 2, z1]) cylinder(h1, c1, c1); translate([46.5, 0.5, z1]) cylinder(h1, c1, c1); translate([109.5, 1, z1]) cylinder(h1, c1, c1); //translate([1, 37.3, z1]) cylinder(h1, c1, c1); //translate([115, 23, z1]) cylinder(h1, c1, c1); translate([1, 50, z1]) cylinder(h1, c1, c1); translate([47, 57.5, z1]) cylinder(h1, c1, c1); translate([109.8, 56.7, z1]) cylinder(h1, c1, c1); } } module upper() { difference() { case(3, 1, 0); translate([-10, -10, 6.9]) cube([200, 200, 50]); // screw translate([0.5, 72, -2]) cylinder(10, 0.5, 0.5); translate([45, 72, -2]) cylinder(10, 0.5, 0.5); translate([118, 72, -2]) cylinder(10, 0.5, 0.5); } } module lower() { difference() { case(5, 2.4, 0.6); translate([0, 0, -3]) { difference() { translate([0, 0, 11]) { difference() { translate([0, 0, 0]) { back(10, 1.65, 0.2); } translate([-10, -10, 0]) { cube([200, 200, 10]); } } } } } translate([-10, -10, -12]) cube([200, 200, 16.9]); // screw translate([0.5, 72, 0]) cylinder(10, 0.5, 0.5); translate([45, 72, 0]) cylinder(10, 0.5, 0.5); translate([118, 72, 0]) cylinder(10, 0.5, 0.5); // earphone translate([18, 75, -3]) cube([10, 6, 10]); // typec translate([74, 75, -3]) cube([15.5, 6, 10]); } } module shell() { ch = 3; cw = 1; ce = 0; difference() { h = 7; translate([-4, 50, 3]) cube([126, 27, 1]); union() { holder_z = 1; holder_h = h - holder_z; difference() { union() { difference() { frame(1.6); frame(0); } } } difference() { translate([0, 0, h - 0.1]) frame(1.6); translate([-10, -10, h + 1.6]) cube([200, 200, 10]); } aux(); } // btn translate([8 - 0.5, 70, 1]) cylinder(5, 2.25, 2.25); translate([8 + 89, 70, 1]) cylinder(5, 2.25, 2.25); translate([8 + 101, 70, 1]) cylinder(5, 2.25, 2.25); // speaker translate([8 + 53.5, 69.5, 1]) cylinder(5, 5, 5); // earphone translate([8 + 12, 60, 2]) cube([6.5, 20, 5]); } // support translate([0.5, 67, 3]) cylinder(5.8, 2, 2); translate([42, 67, 3]) cylinder(5.8, 2, 2); translate([72, 70, 3]) cylinder(5.8, 2, 2); translate([118, 68, 3]) cylinder(5.8, 2, 2); // speaker translate([8 + 53.5, 69.5, 3]) difference() { cylinder(1.5, 6, 6); cylinder(1.5, 5, 5); } } upper(); lower(); shell();


3D 列印



解焊

跳線

固定元件

走線

-
正面

耳機座需要小改一下,用來偵測耳機插入的狀態

耳機腳位

XT897耳機腳位

蓋上背蓋

兩顆電池

背蓋使用 M1.6x10 螺絲

耳機座末端使用插入偵測墊片

改上前蓋面板

完成

-
最後一版OpenSCAD
$fn = 50; module frame(off) { hull() { c = 3; h = 7; // left-bottom translate([0 - off, 4 - off / 2, 0]) cylinder(h, c, c); translate([6.5 - off / 2, 0 - off, 0]) cylinder(h, c, c); // right-bottom translate([112.5 + off / 2, 0 - off, 0]) cylinder(h, c, c); translate([118.5 + off, 4 - off / 2, 0]) cylinder(h, c, c); // left-top translate([6.5 - off / 2, 57 + off, 0]) cylinder(h, c, c); translate([0 - off, 53.5 + off / 2, 0]) cylinder(h, c, c); // right-top translate([118.5 + off, 53.5 + off / 2, 0]) cylinder(h, c, c); translate([112.5 + off / 2, 57 + off, 0]) cylinder(h, c, c); } } module earphone() { rotate([180, 0, 90]) difference() { translate([0, 0, 0]) cube([16, 22, 5]); translate([2.3, 2, 1]) cube([13, 18, 10]); } } module typec() { rotate([180, 0, 90]) difference() { translate([0, 0, 0]) cube([16.5, 13.5, 5]); translate([1.8, 1, 1]) cube([13.5, 11.5, 10]); } } module btn() { rotate([0, 180, 0]) difference() { translate([0, 0, 0]) cube([6, 16.5, 5]); translate([0, 6, 3.5]) cube([6, 6, 5]); } } module aux() { x = -1.6; y = 53; h = 21.6 + 0.8; w = 120.1; difference() { hull() { translate([x, y, 0.1]) cylinder(8.5, 3, 3); translate([w, y, 0.1]) cylinder(8.5, 3, 3); translate([x, y + h, 0.1]) cylinder(8.5, 3, 3); translate([w, y + h, 0.1]) cylinder(8.5, 3, 3); } translate([0, 0, -1]) hull() { off = 1.6; translate([x + off, y + off, -off]) cylinder(8.5, 3, 3); translate([w - off, y + off, -off]) cylinder(8.5, 3, 3); translate([x + off, y + h - off, -off]) cylinder(8.5, 3, 3); translate([w - off, y + h - off, -off]) cylinder(8.5, 3, 3); } translate([-5, 45, -1]) cube([130, 9, 10]); translate([3, 50, -1]) cube([110, 10, 10]); hull() { c = 3; h = 7; off = 0; translate([6.5 - off / 2, 57 + off, 0]) cylinder(h, c, c); translate([0 - off, 53.5 + off / 2, 0]) cylinder(h, c, c); translate([118.5 + off, 53.5 + off / 2, 0]) cylinder(h, c, c); translate([112.5 + off / 2, 57 + off, 0]) cylinder(h, c, c); } } } module back(h, off, extra) { c0 = 3; c1 = 3; translate([118.5, 0, 0]) rotate([0, 180, 0]) difference() { hull() { translate([0 - off, 4 - off / 2, 0]) cylinder(h, c0, c0); translate([6 - off / 2, 0 - off, 0]) cylinder(h, c0, c0); translate([112 + off / 2, 0 - off, 0]) cylinder(h, c0, c0); translate([118.5 + off, 4 - off / 2, 0]) cylinder(h, c0, c0); translate([0 - off, (74.4 + off / 2) + extra, 0]) cylinder(h, c0, c0); translate([118.5 + off, (74.4 + off / 2) + extra, 0]) cylinder(h, c0, c0); translate([0 - off, 4 - off / 2, 0]) sphere(c1); translate([6 - off / 2, 0 - off, 0]) sphere(c1); translate([112 + off / 2, 0 - off, 0]) sphere(c1); translate([118.5 + off, 4 - off / 2, 0]) sphere(c1); translate([0 - off, (74.4 + off / 2) + extra, 0]) sphere(c1); translate([118.5 + off, (74.4 + off / 2) + extra, 0]) sphere(c1); } } } module case(ch, cw, ce) { difference() { h = 7; union() { holder_z = 1; holder_h = h - holder_z; difference() { union() { difference() { frame(1.6); frame(0); } translate([0, 0, 11]) { back(ch, cw, ce); } // keyboard holder translate([8, 60.8, -0.3]) hull() { translate([0, 0, 0]) sphere(0.8); translate([101, 0, 0]) sphere(0.8); } } translate([8, 59, -0.3]) cube([4, 2, 5]); translate([12, 58.5, -0.3]) cube([88, 2, 5]); translate([98, 59, -0.3]) cube([5, 2, 5]); // sdcard translate([63.5, -6, 0]) cube([12, 5, 4]); // wires translate([11, 58, 5]) cube([3, 6, 2]); translate([21, 58, 5]) cube([3, 6, 2]); translate([31, 58, 5]) cube([3, 6, 2]); translate([60, 58, 5]) cube([3, 6, 2]); translate([89, 58, 5]) cube([3, 6, 2]); translate([92, 58, 5]) cube([3, 6, 2]); translate([108, 58, 5]) cube([3, 6, 2]); } // holder translate([-3, 23, holder_z]) cube([2, 11, holder_h]); translate([16.5, -3, holder_z]) cube([23, 1.5, holder_h]); translate([89, -3, holder_z]) cube([12, 1.5, holder_h]); difference() { translate([0, 0, h - 0.1]) frame(1.6); translate([-10, -10, h + 1.6]) cube([200, 200, 10]); // wires - battery translate([80, 54, -2]) cube([26, 10, 10]); translate([11, 54, -2]) cube([30, 10, 10]); } difference() { union() { aux(); translate([11, 61, 8]) btn(); translate([21, 61, 8]) btn(); translate([31, 61, 8]) btn(); translate([38, 61.5, 8]) earphone(); translate([95, 61.5, 8]) typec(); // speaker translate([66, 61, 3]) cube([1, 17, 3]); // switch translate([108, 65, 3]) cube([5, 2.5, 3]); translate([112.5, 65, 3]) cube([2, 3.5, 3]); translate([112.5, 76, 3]) cube([2, 2, 3]); } // earphone translate([50, 63.8, -4.1]) cube([10, 10, 10]); translate([42, 75, -5]) cube([6, 6, 10]); // typec translate([90, 63.3, -4.1]) cube([10, 5, 10]); translate([96.75, 75, -5]) cube([10, 6, 10]); } } // battery translate([15, 3, 0]) cube([82, 51, 12.8]); translate([2, 9, 0]) cube([15, 25, 10]); // screw c0 = 1; h0 = 50; z0 = -20; translate([3.5, 2, z0]) cylinder(h0, c0, c0); translate([46.5, 0.5, z0]) cylinder(h0, c0, c0); translate([109.5, 1, z0]) cylinder(h0, c0, c0); //translate([1, 37.3, z0]) cylinder(h0, c0, c0); //translate([115, 23, z0]) cylinder(h0, c0, c0); translate([1, 50, z0]) cylinder(h0, c0, c0); translate([47, 57.5, z0]) cylinder(h0, c0, c0); translate([109.8, 56.7, z0]) cylinder(h0, c0, c0); // screw for aux //translate([0.5, 64, z0]) cylinder(h0, c0, c0); translate([35, 64, z0]) cylinder(h0, c0, c0); //translate([63, 64, z0]) cylinder(h0, c0, c0); //translate([88, 64, z0]) cylinder(h0, c0, c0); //translate([118, 64, z0]) cylinder(h0, c0, c0); translate([0.5, 74, z0]) cylinder(h0, c0, c0); //translate([35, 74, z0]) cylinder(h0, c0, c0); translate([63, 74, z0]) cylinder(h0, c0, c0); translate([92, 74, z0]) cylinder(h0, c0, c0); translate([118, 74, z0]) cylinder(h0, c0, c0); // screw c1 = 1.8; h1 = 20; z1 = 11; translate([3.5, 2, z1]) cylinder(h1, c1, c1); translate([46.5, 0.5, z1]) cylinder(h1, c1, c1); translate([109.5, 1, z1]) cylinder(h1, c1, c1); //translate([1, 37.3, z1]) cylinder(h1, c1, c1); //translate([115, 23, z1]) cylinder(h1, c1, c1); translate([1, 50, z1]) cylinder(h1, c1, c1); translate([47, 57.5, z1]) cylinder(h1, c1, c1); translate([109.8, 56.7, z1]) cylinder(h1, c1, c1); // screw for aux //translate([0.5, 64, z1]) cylinder(h1, c1, c1); translate([35, 64, z1]) cylinder(h1, c1, c1); //translate([63, 64, z1]) cylinder(h1, c1, c1); //translate([88, 64, z1]) cylinder(h1, c1, c1); //translate([118, 64, z1]) cylinder(h1, c1, c1); translate([0.5, 74, z1]) cylinder(h1, c1, c1); //translate([35, 74, z1]) cylinder(h1, c1, c1); translate([63, 74, z1]) cylinder(h1, c1, c1); translate([92, 74, z1]) cylinder(h1, c1, c1); translate([118, 74, z1]) cylinder(h1, c1, c1); } } module keyboard_holder() { difference() { case(3, 1, 0); translate([-10, -10, 6.9]) cube([200, 200, 50]); // screw translate([0.5, 64, -2]) cylinder(10, 0.5, 0.5); translate([35, 64, -2]) cylinder(10, 0.5, 0.5); translate([63, 64, -2]) cylinder(10, 0.5, 0.5); translate([92, 64, -2]) cylinder(10, 0.5, 0.5); translate([118, 64, -2]) cylinder(10, 0.5, 0.5); translate([0.5, 74, -2]) cylinder(10, 0.5, 0.5); translate([35, 74, -2]) cylinder(10, 0.5, 0.5); translate([63, 74, -2]) cylinder(10, 0.5, 0.5); translate([92, 74, -2]) cylinder(10, 0.5, 0.5); translate([118, 74, -2]) cylinder(10, 0.5, 0.5); } } module battery_cover() { difference() { case(5, 2.4, 0.6); translate([0, 0, -3]) { difference() { translate([0, 0, 11]) { difference() { translate([0, 0, 0]) { back(10, 1.65, 0.2); } translate([-10, -10, 0]) { cube([200, 200, 10]); } } } } } translate([-10, -10, -12]) cube([200, 200, 16.9]); // screw z0 = -1; translate([0.5, 64, z0]) cylinder(10, 0.5, 0.5); translate([35, 64, z0]) cylinder(10, 0.5, 0.5); translate([63, 64, z0]) cylinder(10, 0.5, 0.5); translate([92, 64, z0]) cylinder(10, 0.5, 0.5); translate([118, 64, z0]) cylinder(10, 0.5, 0.5); translate([0.5, 74, z0]) cylinder(10, 0.5, 0.5); translate([35, 74, z0]) cylinder(10, 0.5, 0.5); translate([63, 74, z0]) cylinder(10, 0.5, 0.5); translate([92, 74, z0]) cylinder(10, 0.5, 0.5); translate([118, 74, z0]) cylinder(10, 0.5, 0.5); // earphone translate([40, 75, -3]) cube([10, 6, 10]); // typec translate([94, 75, -3]) cube([15.5, 6, 10]); } } module aux_shell() { ch = 3; cw = 1; ce = 0; difference() { h = 7; translate([-4, 50, 3]) cube([126, 27, 1.5]); union() { holder_z = 1; holder_h = h - holder_z; difference() { union() { difference() { frame(1.6); frame(0); } } } difference() { translate([0, 0, h - 0.1]) frame(1.6); translate([-10, -10, h + 1.6]) cube([200, 200, 10]); } aux(); } // btn translate([4.8, 66.85 - 0.25, 2]) cube([6.5, 6.5, 5]); translate([4.8 + 10, 66.85 - 0.25, 2]) cube([6.5, 6.5, 5]); translate([4.8 + 20, 66.85 - 0.25, 2]) cube([6.5, 6.5, 5]); // earphone translate([8 + 34, 61.5 + 2.5, 3]) cube([6.5, 20, 5]); translate([8 + 34, 61.5, 3 + 1]) cube([6.5, 20, 5]); // speaker translate([79, 69.5, 1]) resize([20, 10, 5]) cylinder(5, 5, 5); // typec translate([96.75, 68, 4.2]) cube([17, 10, 5]); translate([109, 70, 3]) cube([3, 5, 5]); } difference() { union() { z0 = 3; h0 = 6; translate([33, 64 - 1.75, z0]) cube([4, 8, h0]); translate([-1.5, 68.5, z0]) cube([4, 7.5, h0 - 0.2]); translate([61, 69, z0]) cube([4, 7.5, h0]); translate([91, 71, z0]) cube([3, 5.5, h0]); translate([116, 68.7, z0]) cube([4, 7.5, h0 - 0.2]); translate([119.5, 58.5, z0]) cube([2, 15, h0 - 0.2]); translate([-3, 58.5, z0]) cube([2, 15, h0 - 0.2]); } // screw z1 = 2; translate([35, 64, z1]) cylinder(10, 0.5, 0.5); translate([0.5, 74, z1]) cylinder(10, 0.5, 0.5); translate([63, 74, z1]) cylinder(10, 0.5, 0.5); translate([92, 74, z1]) cylinder(10, 0.5, 0.5); translate([118, 74, z1]) cylinder(10, 0.5, 0.5); } } keyboard_holder(); battery_cover(); aux_shell();
完美~

-
司徒接著來說明一下如何做 Input 驅動程式的移植,Input 驅動程式支援很多種類型的裝置,包含:鍵盤、滑鼠、觸控等,司徒幫小志掌機設定的類型是鍵盤,因為是比較單純的裝置,相當適合我們的小志掌機,之後要移植的模擬器就只要處理鍵盤訊息即可,這樣的架構就很單純,所以我們要實作的部份如下紅色部份

-
小志掌機的按鍵電路圖

對應的按鍵如下:
PE0 => UP PE7 => DOWN PE6 => LEFT PE4 => RIGHT PE13 => A PE12 => B PE11 => X PE2 => Y PE8 => SELECT PE9 => START PE10 => R1 PE1 => L1 PE3 => MENU -
drivers/input/keyboard/gaviar-keypad.c
/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option)any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/fs.h> #include <linux/kobject.h> #include <linux/cdev.h> #include <linux/init.h> #include <linux/device.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/timer.h> #include <linux/gpio.h> #include <linux/input.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/backlight.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/tlv.h> #include <sound/initval.h> #include <sound/dmaengine_pcm.h> #include <linux/gpio.h> #include <asm/irq.h> static int myperiod = 30; static struct input_dev *mydev; static struct timer_list mytimer; static uint32_t R_UP = 0x0001; static uint32_t R_DOWN = 0x0002; static uint32_t R_LEFT = 0x0004; static uint32_t R_RIGHT = 0x0008; static uint32_t R_A = 0x0010; static uint32_t R_B = 0x0020; static uint32_t R_X = 0x0040; static uint32_t R_Y = 0x0080; static uint32_t R_SELECT = 0x0100; static uint32_t R_START = 0x0200; static uint32_t R_L1 = 0x0400; static uint32_t R_R1 = 0x0800; static uint32_t R_MENU = 0x1000; static uint32_t I_UP = 0; static uint32_t I_DOWN = 0; static uint32_t I_LEFT = 0; static uint32_t I_RIGHT = 0; static uint32_t I_A = 0; static uint32_t I_B = 0; static uint32_t I_X = 0; static uint32_t I_Y = 0; static uint32_t I_SELECT = 0; static uint32_t I_START = 0; static uint32_t I_L1 = 0; static uint32_t I_R1 = 0; static uint32_t I_MENU = 0; static int do_input_request(uint32_t pin, const char *name) { if (gpio_request(pin, name) < 0) { printk("failed to request gpio: %s\n", name); return -1; } gpio_direction_input(pin); return 0; } static void print_key(uint32_t val, uint8_t is_pressed) { uint32_t i = 0; uint32_t map_val[] = { R_UP, R_DOWN, R_LEFT, R_RIGHT, R_A, R_B, R_X, R_Y, R_SELECT, R_START, R_L1, R_R1, R_MENU, -1 }; char *map_key[] = { "UP", "DOWN", "LEFT", "RIGHT", "A", "B", "X", "Y", "SELECT", "START", "L1", "R1", "MENU", "" }; for (i = 0; map_val[i] != -1; i++) { if (map_val[i] == val) { printk("%s: %s\n", map_key[i], is_pressed ? "DOWN" : "UP"); break; } } } static void report_key(uint32_t btn, uint32_t mask, uint8_t key) { static uint32_t btn_pressed = 0; static uint32_t btn_released = 0xffff; if(btn & mask) { btn_released &= ~mask; if((btn_pressed & mask) == 0) { btn_pressed |= mask; input_report_key(mydev, key, 1); print_key(mask, 1); } } else { btn_pressed &= ~mask; if((btn_released & mask) == 0) { btn_released |= mask; input_report_key(mydev, key, 0); print_key(mask, 0); } } } static void keypad_handler(struct timer_list *timer) { uint32_t val = 0; static uint32_t pre = 0; if (gpio_get_value(I_UP) == 0) val |= R_UP; if (gpio_get_value(I_DOWN) == 0) val |= R_DOWN; if (gpio_get_value(I_LEFT) == 0) val |= R_LEFT; if (gpio_get_value(I_RIGHT) == 0) val |= R_RIGHT; if (gpio_get_value(I_A) == 0) val |= R_A; if (gpio_get_value(I_B) == 0) val |= R_B; if (gpio_get_value(I_X) == 0) val |= R_X; if (gpio_get_value(I_Y) == 0) val |= R_Y; if (gpio_get_value(I_L1) == 0) val |= R_L1; if (gpio_get_value(I_R1) == 0) val |= R_R1; if (gpio_get_value(I_SELECT) == 0) val |= R_SELECT; if (gpio_get_value(I_START) == 0) val |= R_START; if (gpio_get_value(I_MENU) == 0) val |= R_MENU; if (pre != val) { pre = val; report_key(pre, R_UP, KEY_UP); report_key(pre, R_DOWN, KEY_DOWN); report_key(pre, R_LEFT, KEY_LEFT); report_key(pre, R_RIGHT, KEY_RIGHT); report_key(pre, R_A, KEY_LEFTCTRL); report_key(pre, R_B, KEY_LEFTALT); report_key(pre, R_X, KEY_SPACE); report_key(pre, R_Y, KEY_LEFTSHIFT); report_key(pre, R_L1, KEY_TAB); report_key(pre, R_R1, KEY_BACKSPACE); report_key(pre, R_SELECT, KEY_ESC); report_key(pre, R_START, KEY_ENTER); report_key(pre, R_MENU, KEY_RIGHTCTRL); input_sync(mydev); } mod_timer(&mytimer, jiffies + msecs_to_jiffies(myperiod)); } static int __init kbd_init(void) { uint32_t ret = 0; I_UP = ((32 * 4) + 0); I_DOWN = ((32 * 4) + 7); I_LEFT = ((32 * 4) + 6); I_RIGHT = ((32 * 4) + 4); I_A = ((32 * 4) + 13); I_B = ((32 * 4) + 12); I_X = ((32 * 4) + 11); I_Y = ((32 * 4) + 2); I_SELECT = ((32 * 4) + 8); I_START = ((32 * 4) + 9); I_R1 = ((32 * 4) + 10); I_L1 = ((32 * 4) + 1); I_MENU = ((32 * 4) + 3); do_input_request(I_MENU, "menu"); do_input_request(I_UP, "up"); do_input_request(I_DOWN, "down"); do_input_request(I_LEFT, "left"); do_input_request(I_RIGHT, "right"); do_input_request(I_A, "a"); do_input_request(I_B, "b"); do_input_request(I_X, "x"); do_input_request(I_Y, "y"); do_input_request(I_SELECT, "select"); do_input_request(I_START, "start"); do_input_request(I_L1, "l1"); do_input_request(I_R1, "r1"); mydev = input_allocate_device(); set_bit(EV_KEY, mydev-> evbit); set_bit(KEY_UP, mydev->keybit); set_bit(KEY_DOWN, mydev->keybit); set_bit(KEY_LEFT, mydev->keybit); set_bit(KEY_RIGHT, mydev->keybit); set_bit(KEY_LEFTCTRL, mydev->keybit); set_bit(KEY_LEFTALT, mydev->keybit); set_bit(KEY_SPACE, mydev->keybit); set_bit(KEY_LEFTSHIFT, mydev->keybit); set_bit(KEY_ENTER, mydev->keybit); set_bit(KEY_ESC, mydev->keybit); set_bit(KEY_TAB, mydev->keybit); set_bit(KEY_BACKSPACE, mydev->keybit); set_bit(KEY_RIGHTCTRL, mydev->keybit); mydev->name = "gaviar-keypad"; mydev->id.bustype = BUS_HOST; ret = input_register_device(mydev); timer_setup(&mytimer, keypad_handler, 0); mod_timer(&mytimer, jiffies + msecs_to_jiffies(myperiod)); return 0; } static void __exit kbd_exit(void) { input_unregister_device(mydev); del_timer(&mytimer); } module_init(kbd_init); module_exit(kbd_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Steward Fu <steward.fu@gmail.com>"); MODULE_DESCRIPTION("keypad driver for Gaviar handheld");drivers/input/keyboard/Kconfig
config KEYBOARD_GAVIAR tristate "keypad driver for Gaviar handheld" help Say Y here if you want to use keypad for Gaviar handheld.drivers/input/keyboard/Makefile
obj-$(CONFIG_KEYBOARD_GAVIAR) += gaviar-keypad.omenuconfig 選擇 Gaviar Keypad

完成

P.S. commit ec03a6057,測試檔案 https://github.com/steward-fu/website/releases/download/gaviar/kernel_test_20260919.img另外,司徒都是使用 drawio 製作圖片,如果有需要,可以從這邊下載:https://github.com/steward-fu/gaviar/tree/main/drawio
-
小志掌機的Keypad驅動程式,其運作流程如下

-
小志掌機的 Keypad 驅動程式只負責註冊 InputDevice,接著設定一個 Timer 做 GPIO 輪詢的操作,輪詢的間隔是 30ms,比較好的作法其實是中斷+輪詢,不過,簡單製作的話,輪詢方式其實已經很足夠使用了,值得一提是,GPIO 驅動程式,因為 Tina-Linux 已經移植完成,所以我們可以直接透過 GPIO 驅動程式來取得按鍵的狀態,假如沒有 GPIO 驅動程式的支援,我們就需要改用 I/O Mapping,然後自己去讀取暫存器(Register)的值做處理,兩者都可以達到目的,而處理完的結果,Keypad 驅動程式會透過 input_report_key() 回報給 Input Core 驅動程式,這樣上層的應用程式就可以透過讀取 /dev/input/event0 來取得我們回報的按鍵狀態,至於按鍵的回報,司徒採用以前丁果 A320 的按鍵映射方式,這些按鍵的選擇倒是沒有硬性規定,不過,根據司徒的觀察,以前這些高手的選擇都是經過細心思考且基於模擬器的相容問題來設計,像是 DOSBox 模擬器,加入 Enter、Escape 的按鍵映射,可以更加方便的操作

-
具備了按鍵和堪用的顯示後,小志掌機已經具備可以進入系統的門票,接著司徒將 MMC 驅動打開

Crash Log
sunxi-mmc 4020000.sdmmc: SD/MMC/SDIO Host Controller Driver(v4.25 2022-6-21 13:40) sunxi-mmc 4020000.sdmmc: ***ctl-spec-caps*** 8 sunxi-mmc 4020000.sdmmc: No vmmc regulator found sunxi-mmc 4020000.sdmmc: No vqmmc regulator found sunxi-mmc 4020000.sdmmc: No vdmmc regulator found sunxi-mmc 4020000.sdmmc: No vd33sw regulator found sunxi-mmc 4020000.sdmmc: No vd18sw regulator found sunxi-mmc 4020000.sdmmc: No vq33sw regulator found sunxi-mmc 4020000.sdmmc: No vq18sw regulator found sunxi-mmc 4020000.sdmmc: Cann't get sleep pinstate,check if needed sunxi-mmc 4020000.sdmmc: Cann't get pin bias hs pinstate,check if needed sunxi-mmc 4020000.sdmmc: sdc set ios:clk 0Hz bm PP pm UP vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: no vqmmc,Check if there is regulator sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: detmode:manually by software sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B [perf] PMU initialization done sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B mmc0: host does not support reading read-only switch, assuming write-enable sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing SD-HS(SDR25) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 25000000Hz bm PP pm ON vdd 21 width 1 timing SD-HS(SDR25) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 25000000Hz bm PP pm ON vdd 21 width 4 timing SD-HS(SDR25) dt B mmc0: new high speed SDHC card at address aaaa mmcblk0: mmc0:aaaa SC16G 14.8 GiB mmcblk0: p1 VFS: Cannot open root device "mmcblk0p1" or unknown-block(179,1): error -19 Please append a correct "root=" boot option; here are the available partitions: 0100 4096 ram0 (driver?) 0101 4096 ram1 (driver?) 0102 4096 ram2 (driver?) 0103 4096 ram3 (driver?) 0104 4096 ram4 (driver?) 0105 4096 ram5 (driver?) 0106 4096 ram6 (driver?) 0107 4096 ram7 (driver?) 0108 4096 ram8 (driver?) 0109 4096 ram9 (driver?) 010a 4096 ram10 (driver?) 010b 4096 ram11 (driver?) 010c 4096 ram12 (driver?) 010d 4096 ram13 (driver?) 010e 4096 ram14 (driver?) 010f 4096 ram15 (driver?) b300 15558144 mmcblk0 driver: mmcblk b301 15541248 mmcblk0p1 6a255dd9-01 Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(179,1) CPU: 0 PID: 1 Comm: swapper Not tainted 5.4.61 #2 Call Trace: [<ffffffff800241c4>] walk_stackframe+0x0/0x98 [<ffffffff8002437c>] show_stack+0x2a/0x34 [<ffffffff8028153a>] dump_stack+0x20/0x28 [<ffffffff8002a1cc>] panic+0xec/0x28a [<ffffffff80000ef6>] 0xffffffff80000ef6 [<ffffffff800010de>] 0xffffffff800010de [<ffffffff8000125c>] 0xffffffff8000125c [<ffffffff80000b58>] 0xffffffff80000b58 [<ffffffff80291c60>] kernel_init+0x12/0xee [<ffffffff80022f30>] ret_from_exception+0x0/0xc ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(179,1) ]---Crash 是正常的,因為我們還沒有準備任何 RootFS 讓 Linux Kernel 使用,不過修復 OpenSBI Timer 問題後,SD 卡已經可以正常運作,這是一個相當好的進展,畢竟我們不用花太多時間去找尋為何 SD 卡沒有抓到,而如果要更好的讓 Linux 運作,MMC 的這些 Error Log 我們應該適當去了解一下,看看有無機會修掉,不過至少 SD 卡可以正常運作了~灑花~
mmc0: new high speed SDHC card at address aaaa mmcblk0: mmc0:aaaa SC16G 14.8 GiB mmcblk0: p1 -
追了下 MMC Error Log, 發現是 Regulator 問題導致,小志掌機並沒有電源管理晶片(PMU),因此,司徒使用 Dummy Regulator 替代,Device Tree 如下
/dts-v1/; #include "sun20iw1p1.dtsi" / { compatible = "allwinner,d1", "arm,sun20iw1p1", "allwinner,sun20iw1p1", "allwinner,gaviar"; chosen { bootargs = "console=ttyS4,115200n8 console=tty ignore_loglevel loglevel=8 root=/dev/mmcblk0p1 rootwait rw rootfstype=fat init=/init"; }; memory@40000000 { device_type = "memory"; reg = <0x0 0x40000000 0x0 0x04000000>; }; reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; opensbi_reserved: opensbi@43e00000 { reg = <0x0 0x43e00000 0x0 0x00080000>; no-map; }; dtb_reserved: dtb@43d00000 { reg = <0x0 0x43d00000 0x0 0x00010000>; no-map; }; }; aliases { dsp0 = &dsp0; dsp0_gpio_int = &dsp0_gpio_int; }; dsp0: dsp0 { compatible = "allwinner,sun20iw1-dsp"; status = "okay"; }; dsp0_gpio_int: dsp0_gpio_int { compatible = "allwinner,sun20iw1-dsp-gpio-int"; pin-group = "PB", "PC", "PD", "PE"; status = "disabled"; }; reg_vdd_cpu: vdd-cpu { compatible = "regulator-fixed"; regulator-name = "vdd_cpu"; regulator-min-microvolt = <920000>; regulator-max-microvolt = <920000>; regulator-always-on; regulator-boot-on; status = "okay"; }; reg_sd_dummy: regulator-sd-dummy { compatible = "regulator-fixed"; regulator-name = "sd-dummy"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; regulator-always-on; }; }; &CPU0 { cpu-supply = <®_vdd_cpu>; }; &pio { sdc0_pins_a: sdc0@0 { allwinner,pins = "PF0", "PF1", "PF2", "PF3", "PF4", "PF5"; allwinner,function = "sdc0"; allwinner,muxsel = <2>; allwinner,drive = <1>; allwinner,pull = <1>; pins = "PF0", "PF1", "PF2", "PF3", "PF4", "PF5"; function = "sdc0"; drive-strength = <30>; bias-pull-up; power-source = <3300>; }; uart4_pins_a: uart4_pins@0 { pins = "PB2", "PB3"; function = "uart4"; muxsel = <7>; drive-strength = <10>; bias-pull-up; }; }; &uart4 { pinctrl-names = "default"; pinctrl-0 = <&uart4_pins_a>; status = "okay"; }; &sdc0 { pinctrl-names = "default", "mmc_1v8", "sleep"; pinctrl-0 = <&sdc0_pins_a>; pinctrl-1 = <&sdc0_pins_a>; pinctrl-2 = <&sdc0_pins_a>; bus-width = <4>; cap-sd-highspeed; no-sdio; no-mmc; disable-wp; sunxi-power-save-mode; ctl-spec-caps = <0x8>; max-frequency = <150000000>; vmmc-supply = <®_sd_dummy>; vqmmc-supply = <®_sd_dummy>; vdmmc-supply = <®_sd_dummy>; vdmmc33sw-supply = <®_sd_dummy>; vqmmc33sw-supply = <®_sd_dummy>; vdmmc18sw-supply = <®_sd_dummy>; vqmmc18sw-supply = <®_sd_dummy>; status = "okay"; };修正後的 Kernel Log 如下:
sunxi-mmc 4020000.sdmmc: SD/MMC/SDIO Host Controller Driver(v4.25 2022-6-21 13:40) sunxi-mmc 4020000.sdmmc: ***ctl-spec-caps*** 8 sunxi-mmc 4020000.sdmmc: sdc set ios:clk 0Hz bm PP pm UP vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: Switching to 3.3V signalling voltage ok sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: detmode:manually by software sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing LEGACY(SDR12) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 400000Hz bm PP pm ON vdd 21 width 1 timing SD-HS(SDR25) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 50000000Hz bm PP pm ON vdd 21 width 1 timing SD-HS(SDR25) dt B sunxi-mmc 4020000.sdmmc: sdc set ios:clk 50000000Hz bm PP pm ON vdd 21 width 4 timing SD-HS(SDR25) dt B mmc0: new high speed SDHC card at address aaaa mmcblk0: mmc0:aaaa SC16G 14.8 GiB mmcblk0: p1 -
司徒接著說明一下移植系統 UI 這個部份,一般市面上常見的 UI 包含:LVGL、Funkey-OS、Onion 等,這些 UI 都是屬於頂層應用程式,使用的元件都是基於 Buildroot 建構出來,而 Buildroot 是一套用來建制精簡 Linux 系統的工具,因此,移植系統 UI 指的就是將這些 UI 重新編譯並且跑在我們期望的平台上,因為某些UI會針對不同晶片或者 Kernel 做客製化,因此,這部份需要先找出來並且修改成我們想要的樣子,這些層級關係,司徒整理成如下圖片

-
那問題來了,Kernel 究竟是如何載入 Buildroot File System(rootfs) 並且跑起來的呢?一般作法是將 rootfs 放到一個磁區,例如:Partition 2,然後在 Kernel 的啟動參數裡面,描述 rootfs 位於何處以及磁區的類別是什麼,例如:ext4,然後說明第一個 init 程式位於何處,所以當 Kernel 開始執行時,MMC 驅動程式就會負責偵測 SD卡,然後 FileSystem 驅動程式負責磁區的識別,最後 Kernel 會執行 init 程式,整個過程如下圖所示:

-
接著我們讓小志掌機透過 Buildroot 開機到 Console 畫面試試, 步驟如下:
$ cd $ wget https://buildroot.org/downloads/buildroot-2026.08.tar.gz $ tar xvf buildroot-2026.08.tar.gz $ cd buildroot-2026.08 $ make menuconfig選擇 RISC-V

選擇 glibc、設定 kernel、選擇 C++

將編譯工具安裝到 /opt/gaviar, 稍後可以用來編譯一個 SDL 測試程式

設定 System banner (開機歡迎字樣)

選擇全部 SDL 套件

選擇 squashfs、tar

編譯
$ make司徒將 SD 卡格式化成一個 ext4 磁區

描述開機磁區位置以及磁區類型, 還有第一個程式位置 (init=/sbin/init)
bootargs = "console=ttyS4,115200n8 console=tty ignore_loglevel loglevel=8 root=/dev/mmcblk0p1 rootwait rw rootfstype=ext4 init=/sbin/init";將 output/images/rootfs.tar 解壓縮到第一磁區,開機後就可以看到 Welcome to Gaviar

-
接著我們來寫一個測試的 UI 應用程式試試, 程式碼如下:
#include <stdio.h> #include <stdlib.h> #include <SDL.h> int main(int argc, char **argv) { SDL_Rect rt = { 0 }; SDL_Surface *screen = NULL; SDL_Init(SDL_INIT_VIDEO); screen = SDL_SetVideoMode(320, 240, 16, SDL_SWSURFACE); SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0xff, 0x00, 0x00)); SDL_ShowCursor(0); rt.x = 50; rt.y = 50; rt.w = 30; rt.h = 30; SDL_FillRect(screen, &rt, SDL_MapRGB(screen->format, 0x00, 0xff, 0x00)); rt.x = 100; rt.y = 100; rt.w = 50; rt.h = 100; SDL_FillRect(screen, &rt, SDL_MapRGB(screen->format, 0x00, 0x00, 0xff)); SDL_Flip(screen); SDL_Delay(30000); SDL_Quit(); return 0; }編譯 (使用 Buildroot 編譯出來的編譯器)
$ /opt/gaviar/bin/riscv64-linux-gcc main.c -o main -I/opt/gaviar/riscv64-Gaviar-linux-gnu/sysroot/usr/include/SDL -lSDL設定開機啟動這個 UI 應用程式, 在 SDCard 裡面的 /etc/init.d/rcS, 附加如下指令並且將編譯後的 main 複製到 SDCard 的 /bin 資料夾底下
SDL_NOMOUSE=1 SDL_VIDEODRIVER=fbcon /bin/main開機後,就會顯示測試程式的UI畫面

P.S. 之後如果有時間移植 LVGL、Funkey-OS 或者 Onion,他們就是這個 UI 應用程式角色
Copyright © 2024 深圳全志在线有限公司 粤ICP备2021084185号 粤公网安备44030502007680号