最近弄到了一个安卓系统的老年机,模仿的是Nokia基于Kaios的3310机型的重置版,这样的一台小屏幕老年机如果能跑主线Linux应该挺有趣的。
信息收集
这台手机具体型号是广信EF33,经过一番调查,一些基本的信息如下。
- msm8909 骁龙210 4核A7(arm32)
- 512MB RAM & 4GB ROM
- 大喇叭+耳机孔
- 单摄
- 没有传感器
- 没有触摸屏的小lcd
- 没有 Secure Boot
- 2.4 Ghz Wifi (wcn3610v1)
本来想通过原厂的fastboot来把内核调起来再去想替换Bootloader的事情,先去试试fastboot下有哪些指令能用,没想到的是厂家禁用了fastboot模式下所有的指令,就连reboot指令都使用不了,这意味着想要把主线linux跑起来,就必须先把lk移植上。
$ fastboot reboot
Rebooting FAILED (remote: 'unknown command')
fastboot: error: Command failed
进入到系统,得到root权限后挂载debugfs,debugfs里的gpio文件也可以得到一些外设连接的信息。
$ mount -t debugfs debugfs /sdcard/debug
$ cat /sdcard/debug/gpio
<省略一些不必要的数据>
GPIOs 911-1023, platform/1000000.pinctrl, msm_tlmm_gpio:
gpio-911 (camera_vana_2v8 ) out lo
gpio-912 (PA_ENABLE ) out lo
gpio-925 (matrix_kbd_col ) out lo
gpio-926 (matrix_kbd_col ) out lo
gpio-927 (matrix_kbd_col ) out lo
gpio-928 (dc-gpios ) out hi
gpio-933 (matrix_kbd_col ) out lo
gpio-942 (flashlight ) out lo
gpio-943 (matrix_kbd_col ) out lo
gpio-947 (matrix_kbd_row ) in hi
gpio-949 (matrix_kbd_row ) in hi
gpio-963 (button-backlight ) out lo
gpio-1001 (volume_up ) in hi
gpio-1002 (sos_key ) in hi
gpio-1006 (screen_lock_irq_gpio) in lo
gpio-1008 (matrix_kbd_row ) in hi
gpio-1009 (matrix_kbd_row ) in hi
gpio-1010 (troch ) out lo
gpio-1021 (matrix_kbd_row ) in hi
gpio-1022 (red ) out lo
gpio-1023 (green ) out hi
大体上知道数字键盘是纯gpio实现的,键盘驱动部分也可以使用主线linux现成的驱动,把外设基本跑起来应该问题不大。
通过lk向内核的参数也能确定一些信息,比如屏幕类型(需要root)。
$ cat /proc/cmdline
sched_enable_hmp=1 console=ttyHSL0,115200,n8 androidboot.console=ttyHSL0
androidboot.hardware=qcom user_debug=31 msm_rtb.filter=0x3F
ehci-hcd.park=3 androidboot.bootdevice=7824900.sdhci lpm_levels.sleep_disabled=1 earlyprintk androidboot.emmc=true androidboot.serialno=245df60
androidboot.baseband=msm
mdss_mdp3.panel=0:spi:0:qcom,mdss_spi_st7789v_ctc_qvga_nrx_cmd
不难看出这个手机用的是一块st7789v的spi屏。
继续在某网站上搜索一些猛士上传的文档,可以看出msm8909和msm8916的差别其实不是很大,主要是显示处理模块由主线能驱动的MDP5换成没驱动MDP3,可能把mipi驱动起来还是比较困难的,不过好在这个机器使用的是spi屏,应该能够通过tinydrm把屏幕跑起来。
使用edl把手机的固件导出来,得到lk的二进制文件,高通的Bootloader都是带ELF头的,使用反编译工具就可以直接根据函数符号来确定功能,这使逆向工作容易了许多,用Ghidra能快速锁定这个屏幕的初始化指令涉及到的数据结构。
另外一些信息在设备树里面,可以通过解包boot.img得到,这里就不再赘述。
至此,基本的信息收集工作就完成了,接下来就是lk的移植。
移植Littlekernel Bootloader
首先为了验证msm8909是否和msm8916用的是一套签名机制,这里先使用qtestsign工具对edl提取出的sbl1进行重新签名,这样即使签名不对也会进入9008模式,不至于使用工程线才能救回。(然而之后调spi的时候把lk调崩了还是花15买了条工程线。。。)
$ ./qtestsign.py sbl1 sbl1.bin
$ edl w sbl1 sbl1.bin
$ edl reset
重启后手机正常亮屏进入系统,证明了msm8909的Bootloader部分也可以使用qtestsign工具进行签名。
ArchLinux上的gcc11编译一些老项目时会有各种问题,这里就不再使用高通官方开源的littlekernel,而是lk2nd的expermental-rebase分支,虽然lk2nd部分还没有彻底完工,但是一些gcc版本相关的修复让整个开发过程的麻烦减少很多。
拉下来直接编译msm8909的工程,先直接不做任何修改签名后在实机上跑跑看,遇到问题再去一个个修复。
$ make TOOLCHAIN_PREFIX=arm-none-eabi- msm8909 -j8
得到的产物emmc_appsboot.mbn
会出现在build-msm8909
文件夹里。
用qtestsign签名后使用edl烧入aboot分区,手机白屏,fastboot所有指令能够正常使用,但是重启不了。
观察lk的相关日志得知是TrustZone的问题,找到一个TrustZone版本号较高的随身wifi的备份刷入后除屏幕外一切正常。
接下来就是如何为这个lk加入一块新的spi屏。
在各大网站里翻了翻,做过高通方案的大佬们貌似都是用高通给的工具通过xml直接生成的,msm8916-mainline项目也有类似的项目linux-mdss-dsi-panel-driver-generator能通过设备树直接生成主线内核用的屏幕驱动和lk用的头文件,查看源码后发现不支持spi屏的转换,也只能照着其他类似的头文件用ghidra
逆向的结果一条条手搓了。
没有啥逆向的经验,找到lk中已经编译进去但是没有使用过的屏幕与已知的头文件进行对比,找出编译后这些结构的储存规律。
就以st7789v_qvga
这块屏为例,在头文件中,用于表示颜色的结构体数据如下。
static struct color_info st7789v_qvga_cmd_color = {
24, 0, 0xff, 0, 0, 0
};
在ghidra中相关数据结构如下所示。
不难看出结构体成员编译后的储存方式,按照这个规律边猜边做完成了另外几个结构体的逆向。
但是在ghidra中的数据虽然结构体成员这种挺好分析出来值,像初始化指令数组这种成员个数不确定的数据结构给我造成了一定的困难。
比如这样的初始化指令
static char st7789v_qvga_cmd_on_cmd1[] = {
0x36,0x00,
};
反编译后的结果如下
不过它是用C语言写的,就必然有一个数据结构是描述它的长度的。
分析已知的源代码,发现所有的初始化指令被集中储存于XXX_on_command
结构体数组中。
static struct mdss_spi_cmd st7789v_qvga_cmd_on_command[] = {
{0x01, st7789v_qvga_cmd_on_cmd0, 0x78},
{0x02, st7789v_qvga_cmd_on_cmd1, 0x00},
{0x02, st7789v_qvga_cmd_on_cmd2, 0x00},
{0x01, st7789v_qvga_cmd_on_cmd3, 0x00},
{0x02, st7789v_qvga_cmd_on_cmd4, 0x00},
{0x02, st7789v_qvga_cmd_on_cmd5, 0x00},
{0x02, st7789v_qvga_cmd_on_cmd6, 0x00},
{0x02, st7789v_qvga_cmd_on_cmd7, 0x00},
{0x02, st7789v_qvga_cmd_on_cmd8, 0x00},
{0x02, st7789v_qvga_cmd_on_cmd9, 0x00},
{0x03, st7789v_qvga_cmd_on_cmd10, 0x00},
{0x02, st7789v_qvga_cmd_on_cmd11, 0x00},
{0x0F, st7789v_qvga_cmd_on_cmd12, 0x00},
{0x0F, st7789v_qvga_cmd_on_cmd13, 0x00},
{0x01, st7789v_qvga_cmd_on_cmd14, 0x78},
{0x01, st7789v_qvga_cmd_on_cmd15, 0x00},
{0x01, st7789v_qvga_cmd_on_cmd16, 0x00},
};
最后,通过mdss_spi_cmd
这个结构体的定义确定了第一个成员就是一条初始化指令的长度。
struct mdss_spi_cmd {
int size; // <-- 长度
char *payload;
int wait;
uint8_t cmds_post_tg;
};
虽然,确定了长度的储存位置,但此时长度在这个结构体数组里仍然很难提取出,仔细分析之后发现还是有规律可循的。(就是我找不到这个规律就是了~~)
于是,切换思路对设备树下手。
在设备树中,st7789v_qvga
的初始化参数如下:
qcom,mdss-spi-on-command = [
96 01 11
00 02 36 00
00 02 3A 05
00 02 35 00
00 06 B2 0C 0C 00 33 33
00 02 B7 75
00 02 BB 3D
00 02 C2 01
00 02 C3 19
00 02 04 20
00 02 C6 0F
00 03 D0 A4 A1
00 0F E0 70 04 08 09 09 05 2A 33
41 07 13 13 29 2F
00 0F E1 70 03 09 0A 09 06 2B 34
41 07 12 14 28 2E
00 01 21
00 01 29
00 05 2A 00 00 00 EF
00 05 2B 00 00 00 EF
00 01 2C];
观察lk中的头文件后,大体了解初始化指令的结构为以下形式
<wait> + <size> + <payload>
不难得出所有的屏幕初始化指令的长度。
弄出了屏幕初始化参数头文件后,在target/msm8909/oem_panel.c
中加入头文件的定义
@@ -54,6 +56,8 @@
#include "include/panel_auo_390p_cmd.h"
#include "include/panel_st7789v2_qvga_spi_cmd.h"
#include "include/panel_gc9305_qvga_spi_cmd.h"
+#include "include/panel_st7789v_qvga_spi_cmd.h"
+#include "include/panel_st7789v_ctc_qvga_nrx_cmd.h"
#pragma GCC diagnostic pop
#define DISPLAY_MAX_PANEL_DETECTION 2
@@ -86,6 +90,8 @@ enum {
AUO_390P_CMD_PANEL,
ST7789v2_QVGA_SPI_CMD_PANEL,
GC9305_QVGA_SPI_CMD_PANEL,
+ ST7789V_QVGA_SPI_CMD_PANEL,
+ ST7789V_CTC_QVGA_NRX_CMD_PANEL,
UNKNOWN_PANEL
};
@@ -108,10 +114,23 @@ static struct panel_list supp_panels[] = {
{"auo_390p_cmd", AUO_390P_CMD_PANEL},
{"ST7789V2_qvga_cmd", ST7789v2_QVGA_SPI_CMD_PANEL},
{"gc9305_qvga_cmd", GC9305_QVGA_SPI_CMD_PANEL},
+ {"st7789v_qvga_cmd", ST7789V_QVGA_SPI_CMD_PANEL},
+ {"st7789v_ctc_qvga_nrx_cmd", ST7789V_CTC_QVGA_NRX_CMD_PANEL},
};
在init_panel_data()
中将屏幕结构体指针相应成员和前面逆向出来头文件里的数据结构对应上。
+ case ST7789V_CTC_QVGA_NRX_CMD_PANEL:
+ panelstruct->paneldata = &st7789v_ctc_qvga_nrx_cmd_panel_data;
+ panelstruct->panelres = &st7789v_ctc_qvga_nrx_cmd_panel_res;
+ panelstruct->color = &st7789v_ctc_qvga_nrx_cmd_color;
+ panelstruct->panelresetseq = &st7789v_ctc_qvga_nrx_cmd_reset_seq;
+ panelstruct->backlightinfo = &st7789v_ctc_qvga_nrx_cmd_backlight;
+ pinfo->spi.panel_cmds = st7789v_ctc_qvga_nrx_cmd_on_command;
+ pinfo->spi.num_of_panel_cmds = ST7789V_CTC_QVGA_NRX_CMD_ON_COMMAND;
+ pan_type = PANEL_TYPE_SPI;
+ break;
最后,在oem_panel_select()
中修改屏幕的选择逻辑,强制选择前面逆向出来的屏幕。
@ -502,20 +544,22 @@ int oem_panel_select(const char *panel_name, struct panel_struct *panelstruct,
uint32_t platform_type = board_platform_id();
uint32_t platform_subtype = board_hardware_subtype();
int32_t panel_override_id;
+ int oem_panel_id = oem_read_panel_id();
+ dprintf(INFO, "[HACK] oem_panel_id :%d\n",oem_panel_id);
- if (panel_name) {
+ if (1) {
panel_override_id = panel_name_to_id(supp_panels,
- ARRAY_SIZE(supp_panels), panel_name);
+ ARRAY_SIZE(supp_panels), "st7789v_ctc_qvga_nrx_cmd");
if (panel_override_id < 0) {
dprintf(CRITICAL, "Not able to search the panel:%s\n",
- panel_name);
+ "st7789v_ctc_qvga_nrx_cmd");
} else if (panel_override_id < UNKNOWN_PANEL) {
/* panel override using fastboot oem command */
panel_id = panel_override_id;
dprintf(INFO, "OEM panel override:%s\n",
- panel_name);
+ "st7789v_ctc_qvga_nrx_cmd");
goto panel_init;
}
}
烧录进机器后,开机仍然是白屏,但是log已经显示逆向出来的屏幕被选择并初始化了。
这个阶段僵持了比较久的时间,主要是认为手机厂商跑spi屏一定会用公版设计,没有怀疑lk默认的屏幕spi接口和这个手机不一样(关键是背光亮了。。),在翻了lk中spi屏幕初始化的一整套过程后,尝试了很多种方法,仍然是白屏,最后看反编译的设备树,发现屏幕接在BLSP1的QUP5上,而lk2nd的是QUP4。(BLSP是高通搞得一种黑科技,可以将QUP配成i2c、spi等各种低速接口,但是一个QUP只能配置一种功能)
qcom,mdss_spi_client {
reg = <0x00>;
compatible = "qcom,mdss-spi-client";
label = "MDSS SPI QUP5 CLIENT"; // 《--- QUP5
dc-gpio = <0x95 0x11 0x00>;
spi-max-frequency = <0x2faf080>;
};
所以只需要加入QUP5的spi支持就可以了。但是lk2nd并没有定义qub5上的spi,照着寄存器手册试着写了一下。
diff --git a/platform/msm8909/include/platform/iomap.h b/platform/msm8909/include/platform/iomap.h
index 59919972..fe3a2dec 100755
--- a/platform/msm8909/include/platform/iomap.h
+++ b/platform/msm8909/include/platform/iomap.h
@@ -152,6 +152,13 @@
#define GCC_BLSP1_QUP5_SPI_APPS_N (CLK_CTL_BASE + 0x6030)
#define GCC_BLSP1_QUP5_SPI_APPS_D (CLK_CTL_BASE + 0x6034)
+#define GCC_BLSP1_QUP6_SPI_APPS_CBCR (CLK_CTL_BASE + 0x701C)
+#define GCC_BLSP1_QUP6_SPI_APPS_CMD_RCGR (CLK_CTL_BASE + 0x7024)
+#define GCC_BLSP1_QUP6_SPI_CFG_RCGR (CLK_CTL_BASE + 0x7028)
+#define GCC_BLSP1_QUP6_SPI_APPS_M (CLK_CTL_BASE + 0x702C)
+#define GCC_BLSP1_QUP6_SPI_APPS_N (CLK_CTL_BASE + 0x7030)
+#define GCC_BLSP1_QUP6_SPI_APPS_D (CLK_CTL_BASE + 0x7034)
+
/* GPLL */
#define GPLL0_STATUS (CLK_CTL_BASE + 0x21024)
#define GPLL0_MODE (CLK_CTL_BASE + 0x21000)
diff --git a/platform/msm8909/msm8909-clock.c b/platform/msm8909/msm8909-clock.c
index cfca2bdc..a175b064 100644
--- a/platform/msm8909/msm8909-clock.c
+++ b/platform/msm8909/msm8909-clock.c
@@ -657,6 +657,33 @@ static struct branch_clk gcc_blsp1_qup5_spi_apps_clk = {
},
};
+static struct rcg_clk gcc_blsp1_qup6_spi_apps_clk_src =
+{
+ .cmd_reg = (uint32_t *) GCC_BLSP1_QUP6_SPI_APPS_CMD_RCGR,
+ .cfg_reg = (uint32_t *) GCC_BLSP1_QUP6_SPI_CFG_RCGR,
+ .m_reg = (uint32_t *) GCC_BLSP1_QUP6_SPI_APPS_M,
+ .n_reg = (uint32_t *) GCC_BLSP1_QUP6_SPI_APPS_N,
+ .d_reg = (uint32_t *) GCC_BLSP1_QUP6_SPI_APPS_D,
+ .set_rate = clock_lib2_rcg_set_rate_mnd,
+ .freq_tbl = ftbl_gcc_blsp1_qup1_spi_apps_clk,
+ .current_freq = &rcg_dummy_freq,
+
+ .c = {
+ .dbg_name = "gcc_blsp1_qup6_spi_apps_clk_src",
+ .ops = &clk_ops_rcg,
+ },
+};
+
+static struct branch_clk gcc_blsp1_qup6_spi_apps_clk = {
+ .cbcr_reg = GCC_BLSP1_QUP6_SPI_APPS_CBCR,
+ .parent = &gcc_blsp1_qup6_spi_apps_clk_src.c,
+
+ .c = {
+ .dbg_name = "gcc_blsp1_qup6_spi_apps_clk",
+ .ops = &clk_ops_branch,
+ },
+};
+
/* Display clocks */
static struct clk_freq_tbl ftbl_mdss_esc0_1_clk[] = {
F_MM(19200000, cxo, 1, 0, 0),
@@ -814,6 +841,8 @@ static struct clk_lookup msm_clocks_msm8909[] =
CLK_LOOKUP("gcc_blsp1_qup4_spi_apps_clk", gcc_blsp1_qup4_spi_apps_clk.c),
CLK_LOOKUP("gcc_blsp1_qup5_spi_apps_clk_src", gcc_blsp1_qup5_spi_apps_clk_src.c),
CLK_LOOKUP("gcc_blsp1_qup5_spi_apps_clk", gcc_blsp1_qup5_spi_apps_clk.c),
+ CLK_LOOKUP("gcc_blsp1_qup6_spi_apps_clk_src", gcc_blsp1_qup6_spi_apps_clk_src.c),
+ CLK_LOOKUP("gcc_blsp1_qup6_spi_apps_clk", gcc_blsp1_qup6_spi_apps_clk.c),
CLK_LOOKUP("mdp_ahb_clk", mdp_ahb_clk.c),
CLK_LOOKUP("mdss_esc0_clk", mdss_esc0_clk.c),
另外还需要在platform/msm8909/gpio.c
中把原来的qup5上的i2c功能取消掉,并初始化qup5的spi引脚。
--- a/platform/msm8909/gpio.c
+++ b/platform/msm8909/gpio.c
@@ -124,13 +124,6 @@ void gpio_config_blsp_i2c(uint8_t blsp_id, uint8_t qup_id)
GPIO_8MA, GPIO_DISABLE);
break;
case QUP_ID_5:
- /* configure I2C SDA gpio */
- gpio_tlmm_config(18, 3, GPIO_OUTPUT, GPIO_NO_PULL,
- GPIO_8MA, GPIO_DISABLE);
-
- /* configure I2C SCL gpio */
- gpio_tlmm_config(19, 3, GPIO_OUTPUT, GPIO_NO_PULL,
- GPIO_8MA, GPIO_DISABLE);
break;
default:
@@ -147,51 +140,33 @@ void gpio_config_blsp_spi(uint8_t blsp_id, uint8_t qup_id)
{
if(blsp_id == BLSP_ID_1) {
switch (qup_id) {
-
case QUP_ID_3:
- /* configure SPI MOSI gpio */
- gpio_tlmm_config(12, 1, GPIO_OUTPUT, GPIO_NO_PULL,
- GPIO_16MA, GPIO_DISABLE);
-
- /* configure SPI MISO gpio */
- gpio_tlmm_config(13, 1, GPIO_OUTPUT, GPIO_NO_PULL,
- GPIO_16MA, GPIO_DISABLE);
-
- /* configure SPI CS_N gpio */
- gpio_tlmm_config(14, 1, GPIO_OUTPUT, GPIO_NO_PULL,
- GPIO_16MA, GPIO_DISABLE);
-
- /* configure SPI CLK gpio */
- gpio_tlmm_config(15, 1, GPIO_OUTPUT, GPIO_NO_PULL,
- GPIO_16MA, GPIO_DISABLE);
break;
-
case QUP_ID_4:
+ break;
+ case QUP_ID_0:
+ break;
+ case QUP_ID_1:
+ break;
+ case QUP_ID_2:
+ break;
+ case QUP_ID_5:
/* configure SPI MOSI gpio */
- gpio_tlmm_config(16, 1, GPIO_OUTPUT, GPIO_NO_PULL,
+ gpio_tlmm_config(8, 1, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_16MA, GPIO_DISABLE);
/* configure SPI MISO gpio */
- gpio_tlmm_config(17, 1, GPIO_OUTPUT, GPIO_NO_PULL,
+ gpio_tlmm_config(9, 1, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_16MA, GPIO_DISABLE);
/* configure SPI CS_N gpio */
- gpio_tlmm_config(18, 1, GPIO_OUTPUT, GPIO_NO_PULL,
+ gpio_tlmm_config(10, 1, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_16MA, GPIO_DISABLE);
/* configure SPI CLK gpio */
- gpio_tlmm_config(19, 1, GPIO_OUTPUT, GPIO_NO_PULL,
+ gpio_tlmm_config(11, 1, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_16MA, GPIO_DISABLE);
- break;
-
- case QUP_ID_0:
- case QUP_ID_1:
-
- case QUP_ID_2:
-
-
-
- case QUP_ID_5:
+ break;
default:
dprintf(CRITICAL, "Incorrect QUP id %d\n",qup_id);
ASSERT(0);
最后修改target/msm8909/include/target/display.h
中定义的qup_id
-#define SPI_QUP_ID 4
+#define SPI_QUP_ID 5
编译,签名,烧录,企鹅!
至此,一个调试用的Bootloader大功告成,接下来就是主线内核的移植了!