【SemiDrive源码分析】【X9芯片启动流程】26 - R5 SafetyOS 之 LK_INIT_LEVEL_TARGET 阶段代码流程分析(TP Drvier、Audio Server初始化)
本 SemiDrive源码分析 之 Yocto源码分析 系列文章汇总如下:
- 《【SemiDrive源码分析】【Yocto源码分析】01 - yocto/base目录源码分析(编译环境初始化流程)》
- 《【SemiDrive源码分析】【Yocto源码分析】02 - yocto/meta-openembedded目录源码分析》
- 《【SemiDrive源码分析】【Yocto源码分析】03 - yocto/meta-semidrive目录及Yocto Kernel编译过程分析(上)》
- 《【SemiDrive源码分析】【Yocto源码分析】04 - yocto/meta-semidrive目录及Yocto Kernel编译过程分析(下)》
- 《【SemiDrive源码分析】【Yocto源码分析】05 - 找一找Yocto Kernel编译过程中所有Task的源码在哪定义的呢?》
- 《【SemiDrive源码分析】【Yocto源码分析】06 - Kernel编译生成的Image.bin、Image_nobt.dtb、modules.tgz 这三个文件分别是如何生成的?》
- 《【SemiDrive源码分析】【Yocto源码分析】07 - core-image-base-x9h_ref_serdes.rootfs.ext4 文件系统是如何生成的》
- 《【SemiDrive源码分析】【X9芯片启动流程】08 - X9平台 lk 目录源码分析 之 目录介绍》
- 《【SemiDrive源码分析】【X9芯片启动流程】09 - X9平台系统启动流程分析》
- 《【SemiDrive源码分析】【X9芯片启动流程】10 - BareMetal_Suite目录R5 DIL.bin 引导程序源代码分析》
- 《【SemiDrive源码分析】【X9芯片启动流程】11 - freertos_safetyos目录Cortex-R5 DIL2.bin 引导程序源代码分析》
- 《【SemiDrive源码分析】【X9芯片启动流程】12 - freertos_safetyos目录Cortex-R5 DIL2.bin 之 sdm_display_init 显示初始化源码分析》
- 《【SemiDrive源码分析】【X9芯片驱动调试】13 - GPIO 配置方法》
- 《【SemiDrive源码分析】【X9芯片启动流程】14 - freertos_safetyos目录Cortex-R5 SafetyOS/RTOS工作流程分析》
- 《【SemiDrive源码分析】【X9芯片启动流程】15 - freertos_safetyos目录 R5 SafetyOS 之 tcpip_init() 代码流程分析》
- 《【SemiDrive源码分析】【X9 Audio音频模块分析】16 - 音频模块框图及硬件原理图分析》
- 《【SemiDrive源码分析】【X9芯片启动流程】17 - R5 SafetyOS 之 LK_INIT_LEVEL_PLATFORM 阶段代码流程分析(上)dcf_init 核间通信初始化》
- 《【SemiDrive源码分析】【X9芯片启动流程】18 - R5 SafetyOS 之 LK_INIT_LEVEL_PLATFORM 阶段代码流程(下)启动QNX、Android》
- 《【SemiDrive源码分析】【X9芯片启动流程】19 - MailBox 核间通信机制介绍(理论篇)》
- 《【SemiDrive源码分析】【X9芯片启动流程】20 - MailBox 核间通信机制介绍(代码分析篇)之 MailBox for RTOS 篇》
- 《【SemiDrive源码分析】【X9芯片启动流程】21 - MailBox 核间通信机制介绍(代码分析篇)之 Mailbox for Linux 篇》
- 《【SemiDrive源码分析】【X9芯片启动流程】22 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-VIRTIO Kernel 篇》
- 《【SemiDrive源码分析】【X9芯片启动流程】23 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC Kernel 篇》
- 《【SemiDrive源码分析】【X9芯片启动流程】24 - MailBox 核间通信机制相关寄存器介绍》
- 《【SemiDrive源码分析】【X9芯片启动流程】25 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC RTOS & QNX篇》
- 《【SemiDrive源码分析】【X9芯片启动流程】26 - R5 SafetyOS 之 LK_INIT_LEVEL_TARGET 阶段代码流程分析(TP Drvier、Audio Server初始化)》
- 《【SemiDrive源码分析】【X9芯片启动流程】27 - R5 SafetyOS 之 .apps 应用启动代码流程分析》
- 《【SemiDrive源码分析】【X9芯片启动流程】28 - Android Preloader启动流程分析》
- 《【SemiDrive源码分析】【X9芯片启动流程】29 - MailBox 核间通信机制介绍(代码分析篇)之 Property篇》
- 《【SemiDrive源码分析】【X9芯片启动流程】30 - MailBox 核间通信机制介绍(代码分析篇)之 RPCall篇》
- 《【SemiDrive源码分析】【X9芯片启动流程】31 - MailBox 核间通信机制介绍(代码分析篇)之 Notify篇》
- 《【SemiDrive源码分析】【X9芯片启动流程】32 - MailBox 核间通信机制介绍(代码分析篇)之 Socket篇》
- 《【SemiDrive源码分析】【X9芯片启动流程】33 - MailBox 核间通信机制介绍(代码分析篇)之 /dev/vircan篇》
前面一段时间,我们在分析MailBox ,虽然没全分析完,但MailBox 的原理已经差不多了解了,
由于项目快开始了,考虑到时间已经不支持继续详细分析MailBox 了,所以从本文开始,继续回归初始化流程的分析,尽快把启动流程看完,至于MailBox 等后续再看吧。
在前面 《【SemiDrive源码分析】【X9芯片启动流程】14 - freertos_safetyos目录Cortex-R5 SafetyOS/RTOS工作流程分析》 中,
我们分析到,R5 SafetyOS 之 LK_INIT_LEVEL_TARGET 阶段主要是运行如下函数: register_touch_driver_entry() , hal_vpu_create_mutex() , cospi_early_init() , hal_crypto_init() , (lk_init_hook)res_fs_init() , (lk_init_hook)audio_server_init()
一、register_touch_driver_entry() 注册TP 驱动
可以看出,触摸驱动是通过 register_touch_driver() 来探测初始化TP 的,
注册时,先调用 get_touch_device(&tsc, &tsc_num); 获取 TP 相关的配置信息保存在tsc[i] 中, 然后调用驱动换 probe 函数,传参 tsc[i] 配置参数
# buildsystem\rtos\lk_boot\framework\service\input\include\touch_driver.h
#define register_touch_driver(drv) \
static void register_touch_driver_entry(uint level) \
{ \
__register_touch_driver(&drv); \
} \
LK_INIT_HOOK(touch_driver##drv, register_touch_driver_entry, LK_INIT_LEVEL_TARGET)
# buildsystem\rtos\lk_boot\framework\service\input\touch_driver.c
void __register_touch_driver(struct touch_driver *driver)
{
struct ts_board_config *tsc = NULL;
int tsc_num = 0;
get_touch_device(&tsc, &tsc_num);
for (int i = 0; i < tsc_num; i++) {
if (tsc[i].enable && driver->probe && !strcmp(tsc[i].device_name, driver->driver_name))
driver->probe(&tsc[i]);
}
}
1.1 TP 配置参数 ts_board_config
如下是 X9HP 默认的TP 配置参数,采用的是下LVDS3 和 LVDS4 两个接口
# buildsystem\rtos\lk_boot\target\reference_x9\safety\touch_device.c
static struct ts_board_config tsc[] = {
{
TS_ENABLE, "goodix", RES_I2C_I2C15, 0x5d,
TS_SUPPORT_CTRLPANEL_MAIN, CONTROLPANEL,
{1920, 720, 10, 0, 0, 0},
{false, 12, 0x75, TCA9539_P07},
{true, TI_SERDES, TI947_SINGLE, TI948, 0x1a, 0x2c, 3, 2},
{0},
{
PortConf_PIN_I2S_MC_SD7,
{0, 0}
}
},
{
TS_DISABLE, "goodix", RES_I2C_I2C14, 0x5d,
TS_SUPPORT_CTRLPANEL_AUX1, ENTERTAINMENT,
{1920, 720, 10, 0, 0, 0},
{false, 12, 0x75, TCA9539_P06},
{false, TI_SERDES, TI947_SINGLE, TI948, 0x0, 0x0, 0, 0},
{0},
{
PortConf_PIN_I2S_MC_SD6,
{0, 0}
}
},
};
其结构体定义如下:
# buildsystem\rtos\lk_boot\framework\service\input\include\touch_device.h
struct ts_board_config {
bool enable;
const char *device_name;
u32 res_id;
u16 i2c_addr;
u16 ts_domain_support;
enum DISPLAY_SCREEN screen_id;
struct ts_coord_config coord_config;
struct port_expand_config port_config;
struct ts_serdes_config serdes_config;
struct ts_pin_config reset_pin;
struct ts_pin_config irq_pin;
};
TP 使用串行、解串的芯片是 TI947_SINGLE 、TI948
# buildsystem\rtos\lk_boot\framework\service\input\include\touch_device.h
enum ts_serdes_type {
TI_SERDES,
TS_SERDES_MAX,
};
enum ts_ser_type {
TI941_SINGLE,
TI941_SECOND,
TI941_DUAL,
TI947_SINGLE,
TI947_SECOND,
TI947_DUAL,
TS_SER_MAX,
};
enum ts_des_type {
TI948,
TS_DES_MAX
};
1.2 探测函数 goodix_probe_device():初始化goodix->safe_ts_dev结构体,保存在全局safe_ts_dev[] 数组中,启动gt9xx_thread线程,注册中断函数
我们以 goodix.c 来举例,看看TP 初始化时做了什么:
# buildsystem\rtos\lk_boot\exdev\touch\src\goodix.c
static struct touch_driver goodix_driver = {
"goodix",
goodix_probe_device,
};
register_touch_driver(goodix_driver);
我们进入 goodix_probe_device() 函数,开始探测初始化:
- 申请并初始化
goodix 结构体,解析 dev_irq_pin_num 分别为 130 和 131 - 获取
I2C 句柄 instance , 保存在 goodix->i2c_handle 中 - 解析 串行、解串
conf 参数,配置串行解串链路: 检查 serdes 链路是否通、使能TI947_SINGLE 芯片、写寄存器 0x17 为0x9e (启动i2c pass ) - 检查
serdes 链路是否通,通过 I2C 直接读取 0x6 寄存器,在其中保存了对应的des 地址 - 重置设备,读取版本号。
- 更新汇顶固件
- 启动
gt9xx_thread 线程运行goodix_ts_work_func() 函数,及注册中断函数 goodix_irq_handler() - 申请
struct safe_ts_device 结构体,保存在goodix->safe_ts_dev 中 - 注册
goodix->safe_ts_dev 设备,将其保存在全局 safe_ts_dev[TS_DO_CTRLPANEL_MAIN] = dev 中
# buildsystem\rtos\lk_boot\exdev\touch\src\goodix.c
static int goodix_probe_device(const struct ts_board_config *conf)
{
struct goodix_ts_data *goodix = NULL;
goodix = malloc(sizeof(struct goodix_ts_data));
memset(goodix, 0, sizeof(struct goodix_ts_data));
goodix->conf = conf;
goodix->instance = goodix->conf->ts_domain_support;
goodix->dev_irq = goodix->conf->irq_pin.pin_num;
hal_i2c_creat_handle(&goodix->i2c_handle, goodix->conf->res_id);
=====> instance = hal_i2c_get_instance(i2c_res_glb_idx);
if (goodix->conf->serdes_config.enable) {
if (goodix->conf->serdes_config.serdes_type == TI_SERDES) {
enum ts_ser_type ser_type = goodix->conf->serdes_config.ser_type;
enum ts_des_type des_type = goodix->conf->serdes_config.des_type;
u16 ser_addr = goodix->conf->serdes_config.ser_addr;
u16 des_addr = goodix->conf->serdes_config.des_addr;
ret = ti_serdes_link_check(goodix->i2c_handle, ser_addr, ser_type, des_addr, des_type);
ret = ti_ser_enable_port(goodix->i2c_handle, ser_addr, ser_type);
ret = ti_ser_enable_i2c_passthrough(goodix->i2c_handle, ser_addr, ser_type);
}
}
do {
goodix_reset_device(goodix);
if (!goodix_read_version(goodix))
break;
} while (++count < 3);
ret = goodix_firmware_upgrade(goodix);
ret = goodix_config_device(goodix);
=====================>
+ event_init(&goodix->event, false, EVENT_FLAG_AUTOUNSIGNAL);
+ thread_t *tp_thread = thread_create("gt9xx_thread", goodix_ts_work_func, goodix, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+ thread_detach_and_resume(tp_thread);
+ register_gpio_int_handler(goodix->dev_irq, IRQ_TYPE_EDGE_FALLING, goodix_irq_handler, goodix);
+ unmask_gpio_interrupt(goodix->dev_irq);
<=====================
goodix->safe_ts_dev = safe_ts_alloc_device();
goodix->safe_ts_dev->instance = goodix->conf->ts_domain_support;
goodix->safe_ts_dev->screen_id = goodix->conf->screen_id;
goodix->safe_ts_dev->vinfo.id = goodix->id;
goodix->safe_ts_dev->vinfo.version = goodix->version;
goodix->safe_ts_dev->vinfo.vendor = goodix->vendor;
goodix->safe_ts_dev->vinfo.name = goodix->conf->device_name;
goodix->safe_ts_dev->vinfo.ser_addr = goodix->conf->serdes_config.ser_addr;
goodix->safe_ts_dev->vinfo.des_addr = goodix->conf->serdes_config.des_addr;
goodix->safe_ts_dev->vinfo.ts_addr = goodix->conf->i2c_addr;
goodix->safe_ts_dev->cinfo.max_touch_num = goodix->conf->coord_config.max_touch_num;
goodix->safe_ts_dev->cinfo.swapped_x_y = goodix->conf->coord_config.swapped_x_y;
goodix->safe_ts_dev->cinfo.inverted_x = goodix->conf->coord_config.inverted_x;
goodix->safe_ts_dev->cinfo.inverted_y = goodix->conf->coord_config.inverted_y;
goodix->safe_ts_dev->set_inited = goodix_set_inited;
goodix->safe_ts_dev->vendor_priv = goodix;
ret = safe_ts_register_device(goodix->safe_ts_dev);
dprintf(ALWAYS, "%s, instance=%#x, done ok\n", __func__, goodix->instance);
return 0;
}
1.3 gt9xx_thread线程函数goodix_ts_work_func():解析event事件,读取tp坐标
static int goodix_ts_work_func(void *arg)
{
struct goodix_ts_data *goodix = arg;
u8 pdata[81] = {0,};
int len, touch_num;
struct touch_report_data report_data;
while (1) {
event_wait(&goodix->event);
touch_num = goodix_ts_read_input_report(goodix, pdata);
if (gdx_dbg_flag) {
dprintf(ALWAYS, "%s: [%d] 0x%x, (0x%x, 0x%x, 0x%x, 0x%x)\n", __func__,
goodix->instance, pdata[0], pdata[3], pdata[2], pdata[5], pdata[4]);
}
report_data.key_value = pdata[0] & (1 << 4);
report_data.touch_num = touch_num;
for (int j = 0; j < touch_num; j++) {
report_data.coord_data[j].id = *((u8 *)(pdata + 1 + GOODIX_CONTACT_SIZE * j));
report_data.coord_data[j].x = *((u16 *)(pdata + 2 + GOODIX_CONTACT_SIZE * j));
report_data.coord_data[j].y = *((u16 *)(pdata + 4 + GOODIX_CONTACT_SIZE * j));
report_data.coord_data[j].w = *((u16 *)(pdata + 6 + GOODIX_CONTACT_SIZE * j));
}
len = 2 + touch_num * TS_COORD_METADATA_SIZE;
safe_ts_report_data(goodix->safe_ts_dev, &report_data, len);
unmask_gpio_interrupt(goodix->dev_irq);
}
return 0;
}
1.4 中断函数 goodix_irq_handler():在中断中触发 event事件
static enum handler_return goodix_irq_handler(void *arg)
{
struct goodix_ts_data *goodix = arg;
mask_gpio_interrupt(goodix->dev_irq);
event_signal(&goodix->event, false);
return INT_RESCHEDULE;
}
二、hal_vpu_create_mutex()
# buildsystem\rtos\lk_boot\hal\vpu_hal\codaj12\src\vpu_hal.c
void hal_vpu_create_mutex(uint level) { mutex_init(&vpu_mutex); }
三、cospi_early_init(): Nor Flash 读写服务初始化
启动了一个线程,不停的检测 xfer 事件,收到事件时, 根据事件的类型 SPI_NOR_OPS_READ 或SPI_NOR_OPS_WRITE 做相应的读写动作。
# buildsystem\rtos\lk_boot\chipdev\spi_nor\host\cadence_ospi.c
static void cospi_early_init(uint level)
{
struct cospi_pdata *cospi = &s_cospi;
memset(cospi, 0, sizeof(struct cospi_pdata));
mutex_init(&cospi->bus_mutex);
event_init(&cospi->dma_event, false, EVENT_FLAG_AUTOUNSIGNAL);
event_init(&cospi->complete_event, false, EVENT_FLAG_AUTOUNSIGNAL);
event_init(&cospi->xfer_start_event, false, EVENT_FLAG_AUTOUNSIGNAL);
event_init(&cospi->xfer_done_event, false, EVENT_FLAG_AUTOUNSIGNAL);
dprintf(INFO, "creat cospi thread!\n");
thread_t *thread = thread_create("cospi_thread", cospi_data_thread, (void *)cospi, HIGH_PRIORITY, DEFAULT_STACK_SIZE);
thread_detach(thread);
thread_resume(thread);
}
static int cospi_data_thread(void *arg)
{
u32 ret;
struct cospi_pdata *cospi = arg;
struct spi_nor *nor = cospi->priv;
struct spi_nor_handle *handle = NULL;
for (;;) {
event_wait(&cospi->xfer_start_event);
nor = cospi->priv;
ret = cospi_do_xfer(nor, &nor->data_cmd);
if (nor->async_mode) {
handle = nor->parent;
if (handle && handle->event_handle) {
handle->opt_type = nor->data_cmd.type;
handle->opt_result = ret ? SPI_NOR_OPT_FAILED : SPI_NOR_OPT_COMPLETE;
handle->event_handle(handle->opt_type, handle->opt_result);
}
}
else {
nor->data_error = ret;
event_signal(&cospi->xfer_done_event, false);
}
}
return 0;
}
static int cospi_do_xfer(struct spi_nor *nor, struct spi_nor_cmd *cmd)
{
int ret = 0;
struct cospi_pdata *cospi = nor->priv_data;
uint32_t remaining = cmd->size;
uint8_t *xfer_buf = (uint8_t *)cmd->buf;
const uint32_t max_xfer_length = cospi->fifo_depth * cospi->fifo_width * 128;
uint32_t xfer_len;
while(remaining) {
switch (cmd->type) {
case SPI_NOR_OPS_READ:
ret = cospi_indirect_read(nor, &nor->data_cmd, xfer_buf, xfer_len);
break;
case SPI_NOR_OPS_WRITE:
ret = cospi_indirect_write(nor, &nor->data_cmd, xfer_buf, xfer_len);
break;
}
xfer_buf += xfer_len;
nor->data_cmd.addr += xfer_len;
remaining -= xfer_len;
}
nor->data_present = 0;
return ret;
}
四、hal_crypto_init(): 加密环境CE初始化
# rtos\lk_boot\hal\crypto_hal\src\crypto_hal.c
void hal_crypto_init(uint level){
ce_globle_init();
return ;
}
# rtos\lk_boot\chipdev\crypto\silex\ce.c
int32_t ce_globle_init(void)
{
LTRACEF("ce_globle_init enter\n");
event_init(&g_trng_signal, false, 0);
register_int_handler(ZONE_TRNG_INT, &trng_irq_handle, (void *)0);
sram_config();
init_vce_key_interface();
g_ce_inited = true;
return 0;
}
五、(lk_init_hook)res_fs_init()
static int res_fs_init(void)
{
char diskd[3] = {DISKD+0x30,':',0};
verified_res_image();
r = f_mount(&fsd,diskd,1);
return r;
}
六、(lk_init_hook)audio_server_init():音频服务初始化
# rtos\lk_boot\framework\audio\am\src\am_server.c
int audio_server_init(void)
{
if (!is_str_resume(STR_AP1)) {
am_init();
} else {
am_resume((void*)STR_AM_PATHS_ADDR);
}
return 0;
}
LK_INIT_HOOK(au_mgr, (lk_init_hook)audio_server_init,
LK_INIT_LEVEL_TARGET + 2);
am_init() 函数定义在 buildsystem\rtos\freertos_safetyos\framework\audio\am\lib\libam_lib.a 中,看不到。
有关RTOS 的启动流程就看到这吧, 目前知道这个过程干了啥就够了,有关具体模块的内容,我也省略了, 一方面因为时间不太够,另一方面,这些具体模块,后面会单独分析。
本来下篇文章,按计划是要写《【SemiDrive源码分析】【X9芯片启动流程】27 - R5 SafetyOS 之 .apps 应用启动代码流程分析》的, 主要包含如下app:
# build-safety-x9plus-ref-serdes/safety-x9plus-ref-serdes.elf.map
# grep -rsn "__apps_start" ./build-safety-x9plus-ref-serdes/
.apps 0x0000000037908e24 0x168
0x0000000037908e24 __apps_start = .
*(.apps)
.apps 0x0000000037908e24 0x14 ./build-safety-x9plus-ref-serdes/platform/kunlun/safety.mod.o
0x0000000037908e24 _app_sysd_reboot_probe
.apps 0x0000000037908e38 0x14 ./build-safety-x9plus-ref-serdes/application/services/pfmon.mod.o
0x0000000037908e38 _app_pfm
.apps 0x0000000037908e4c 0x14 ./build-safety-x9plus-ref-serdes/application/system/pvt.mod.o
0x0000000037908e4c _app_pvt_entry
.apps 0x0000000037908e60 0x14 ./build-safety-x9plus-ref-serdes/application/test/spi_nor.mod.o
0x0000000037908e60 _app_spi_nor_test
.apps 0x0000000037908e74 0x28 ./build-safety-x9plus-ref-serdes/framework/service/audio.mod.o
0x0000000037908e74 _app_i2s_rpc
0x0000000037908e88 _app_audio_stream_rpc
.apps 0x0000000037908e9c 0x14 ./build-safety-x9plus-ref-serdes/framework/service/camera.mod.o
0x0000000037908e9c _app_cam_srv
.apps 0x0000000037908eb0 0x28 ./build-safety-x9plus-ref-serdes/framework/service/can_proxy.mod.o
0x0000000037908eb0 _app_canproxy
0x0000000037908ec4 _app_can_proxy_test
.apps 0x0000000037908ed8 0x14 ./build-safety-x9plus-ref-serdes/framework/service/cluster_bridge.mod.o
0x0000000037908ed8 _app_cluster_app
.apps 0x0000000037908eec 0x14 ./build-safety-x9plus-ref-serdes/framework/service/display.mod.o
0x0000000037908eec _app_displayd
.apps 0x0000000037908f00 0x14 ./build-safety-x9plus-ref-serdes/framework/service/update_monitor.mod.o
0x0000000037908f00 _app_update_monitor
.apps 0x0000000037908f14 0x28 ./build-safety-x9plus-ref-serdes/framework/test/dcf.mod.o
0x0000000037908f14 _app_dcf_sample
0x0000000037908f28 _app_test_posix
.apps 0x0000000037908f3c 0x14 ./build-safety-x9plus-ref-serdes/lib/shell.mod.o
0x0000000037908f3c _app_shell
.apps 0x0000000037908f50 0x14 ./build-safety-x9plus-ref-serdes/application/early_app/BootAnimation.mod.o
0x0000000037908f50 _app_ba_replay
.apps 0x0000000037908f64 0x14 ./build-safety-x9plus-ref-serdes/application/early_app/controlpanel.mod.o
0x0000000037908f64 _app_sysd_controlpanel
.apps 0x0000000037908f78 0x14 ./build-safety-x9plus-ref-serdes/application/early_app/main.mod.o
0x0000000037908f78 _app_USM
0x0000000037908f8c __apps_end = .
实话说,对目前来说,看这个对我没任何作用,有兴趣的兄弟可以自行找到对应的代码去阅读。 后面项目中会涉及到的,如开机动画等,这些,后面涉及到时,我会单独起文章写。
从《【SemiDrive源码分析】【X9芯片启动流程】18 - R5 SafetyOS 之 LK_INIT_LEVEL_PLATFORM 阶段代码流程(下)启动QNX、Android》 中, 我们知道 R5 是通过调用 reboot_ap1() 来启动 android preloader 的,那下篇文章,我们正式进入 android 分析 preloader 源码
|