深入,并且广泛
-沉默犀牛
此篇博客原博客来自freebuf,原作者SetRet。原文链接:https://www.freebuf.com/news/135084.html
写在前面的话
写这篇文章之前,我只好假定你所知道的跟我一样浅薄(针对本文这一方面),所以如果你看到一些实在是太小儿科的内容,请你多加担待,这确实就是我目前的水平,谢谢。
这里就开始啦!
上一篇我们已经分析过Kmain函数了,现在来回顾一遍发现Kmain显示初始化了一些硬件资源,包括线程初始化,CPU的一些设置,平台初始化(board、clk、qgic、scm),和串口调试,开启这些资源就是为LK和kernel的运行准备环境。(这一点从完成了这些初始化之后才打印 welcome lk 也能看得出来)接下来就是一些软件的初始化,包括为bootstate赋值(bootloader的启动时间),堆初始化,dpc初始化,定时器初始化。做完这些准备工作之后,就启动了bootstrap2线程,我们这篇文章就看一下这个线程做了什么。
大致描述bootstrap2
本文介绍一下两个函数,其他两个函数在很多平台是空函数
target_init(); // 初始化spmi、emmc,检测pwr key按键和震动等
apps_init(); // 调用所有apps字段的init函数
target_init
void target_init(void)
{
uint32_t base_addr;
uint8_t slot;
dprintf(INFO, "target_init()\n");
/*初始化 SPMI(system power management interface) 系统电源管理结构的控制器*/
spmi_init(PMIC_ARB_CHANNEL_NUM, PMIC_ARB_OWNER_ID);
/*获取并设置 音量上键 和 音量下键 的状态,而所有按键的状态都保存在全局数组 key_bitmap 中*/
target_keystatus();
target_sdc_init(); //初始化 emmc
if (partition_read_table()) //读取分区表
{
dprintf(CRITICAL, "Error reading the partition table info\n");
ASSERT(0);
}
#if LONG_PRESS_POWER_ON
shutdown_detect(); //检测开机时间不足够长则关机
#endif
#if PON_VIB_SUPPORT
vib_timed_turn_on(VIBRATE_TIME); //开启手机震动 4/1 秒,提示用户手机开启
#endif
if (target_use_signed_kernel())
target_crypto_init_params(); //初始化加密解密引擎,用于解密内核
}
现在针对这个函数做一些说明:
1.emmc
emmc 是目前手机领域流行的存储设备,相当于 pc 端的 ssd 硬盘,这里涉及到一个比较重要的全局数据 static struct mmc_device *dev:
/*
* sdhci host structure, holding information about host
* controller parameters
*/
struct sdhci_host {
uint32_t base; /* Base address for the host */
uint32_t cur_clk_rate; /* Running clock rate */
uint32_t timing; /* current timing for the host */
bool tuning_in_progress; /* Tuning is being executed */
uint8_t major; /* host controller minor ver */
uint16_t minor; /* host controller major ver */
bool use_cdclp533; /* Use cdclp533 calibration circuit */
event_t* sdhc_event; /* Event for power control irqs */
struct host_caps caps; /* Host capabilities */
struct sdhci_msm_data *msm_host; /* MSM specific host info */
};
/* mmc card register */
struct mmc_card {
uint32_t rca; /* Relative addres of the card*/
uint32_t ocr; /* Operating range of the card*/
uint32_t block_size; /* Block size for the card */
uint32_t wp_grp_size; /* WP group size for the card */
uint64_t capacity; /* card capacity */
uint32_t type; /* Type of the card */
uint32_t status; /* Card status */
uint8_t *ext_csd; /* Ext CSD for the card info */
uint32_t raw_csd[4]; /* Raw CSD for the card */
uint32_t raw_scr[2]; /* SCR for SD card */
uint32_t rpmb_size; /* Size of rpmb partition */
uint32_t rel_wr_count; /* Reliable write count */
struct mmc_cid cid; /* CID structure */
struct mmc_csd csd; /* CSD structure */
struct mmc_sd_scr scr; /* SCR structure */
struct mmc_sd_ssr ssr; /* SSR Register */
};
/* mmc device config data */
struct mmc_config_data {
uint8_t slot; /* Sdcc slot used */
uint32_t pwr_irq; /* Power Irq from card to host */
uint32_t sdhc_base; /* Base address for the sdhc */
uint32_t pwrctl_base; /* Base address for power control registers */
uint16_t bus_width; /* Bus width used */
uint32_t max_clk_rate; /* Max clock rate supported */
uint8_t hs200_support; /* SDHC HS200 mode supported or not */
uint8_t hs400_support; /* SDHC HS400 mode supported or not */
uint8_t use_io_switch; /* IO pad switch flag for shared sdc controller */
};
/* mmc device structure */
struct mmc_device {
struct sdhci_host host; /* Handle to host controller */
struct mmc_card card; /* Handle to mmc card */
struct mmc_config_data config; /* Handle for the mmc config data */
};
static struct mmc_device *dev;
2.读取分区表
读取分区表,不论是 MBR 还是 GPT 分区格式,读取后都存放在以下结构中:
struct partition_entry {
unsigned char type_guid[PARTITION_TYPE_GUID_SIZE];
unsigned dtype;
unsigned char unique_partition_guid[UNIQUE_PARTITION_GUID_SIZE];
unsigned long long first_lba;
unsigned long long last_lba;
unsigned long long size;
unsigned long long attribute_flag;
unsigned char name[MAX_GPT_NAME_SIZE];
uint8_t lun;
};
struct partition_entry *partition_entries;
3.检测pwr key
在开启了长按开机键开机的设置后才会生效, 很多平台默认开启了此选项,到这里检测开机时间不足够长则关机,按照这里的代码理解,关机后,每次按下开机键,系统其实已经启动到 shutdown_detect 这个位置了,不过由于按键时间不长,所以没有屏幕没有点亮,系统没有完全启动。
apps_init
extern const struct app_descriptor __apps_start;
extern const struct app_descriptor __apps_end;
struct app_descriptor {
const char *name;
app_init init;
app_entry entry;
unsigned int flags;
};
static int app_thread_entry(void *arg)
{
const struct app_descriptor *app = (const struct app_descriptor *)arg;
app->entry(app, NULL);
return 0;
}
static void start_app(const struct app_descriptor *app)
{
thread_t *thr;
printf("starting app %s\n", app->name);
thr = thread_create(app->name, &app_thread_entry, (void *)app, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
if(!thr)
{
return;
}
thread_resume(thr);
}
void apps_init(void)
{
const struct app_descriptor *app;
/* call all the init routines */
for (app = &__apps_start; app != &__apps_end; app++) {
if (app->init)
app->init(app);
}
/* start any that want to start on boot */
for (app = &__apps_start; app != &__apps_end; app++) {
if (app->entry && (app->flags & APP_FLAG_DONT_START_ON_BOOT) == 0) {
start_app(app);
}
}
}
整个遍历 app 并启动的过程并不复杂,有趣的是 __apps_start 和 __apps_end 的定义,这两个变量符号在所有源文件中并不存在,而是在 arch/arm/*.ld 链接脚本中存在,这样类似的结构在前面 heap_init 中已经遇到过,区别在于 __apps_start 和 __apps_end 是自定义的两个符号。代表了自定义段 .apps 的开始位置和结束位置。也就是说所有的 app 都通过在特殊的段 .apps 中注册实现了一套插件系统,是一个十分精巧的设计。后续的任何新的 app 只需要使用以下两个宏声明即可注册到 .apps 段中:
#define __SECTION(x) __attribute((section(x)))
#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname,
#define APP_END };
下面是 aboot app 声明的 app 的实际例子:
APP_START(aboot)
.init = aboot_init,
APP_END
宏展开后的实际效果如下:
struct app_descriptor _app_aboot ____attribute((section(".apps"))) = {.name = "aboot", .init = aboot_init};
这样通过遍历 .apps 段就可以获取 aboot 的描述信息,调用 aboot 的 init 函数了