0. 什么是minidump?
各个subsystem 都会注册在 memory 映射表中,当system 发⽣crash的时候,boot subsystem 会去加密并保存注册过的memory信息,保存到RAM EMMC 分区
一、MINIDUMP流程图
二、验证方法
Enable
echo mini > /sys/kernel/dload/dload_mode: To enable only minidump
echo full > /sys/kernel/dload/dload_mode: To enable full dump
echo both > /sys/kernel/dload/dload_mode: To enable both
echo 1 > /sys/kernel/dload/emmc_dload: download to emmc/qpst/sd-card option
三、理论验证结果
开机时内存中会为它保留⼀部分空间⽤来存,这个部分空间存的是下⾯三种log:
- kernel log 2MB
- logcat 2MB
- tz_log
PS:此效果需要上层适配,opengrok 搜索关键词“ minidump64 ”, " vendor.minidump.is.valid "
四、MINIDUMP代码流程
4.1 xbl阶段
从高通Android启动代码流程分析(SBL->ABL) 可知,在开机过程中,代码会执行到boot_dload_check()->boot_dload_entry,而本节从此函数开始介绍mimidump的xbl流程。
void boot_dload_check
(
bl_shared_data_type *bl_shared_data
)
{
//...
if ( boot_dload_entry( ) == TRUE ) //检查TCSR寄存器
{
/* Check the UEFI ram dump cookie, we enter download mode
only if UEFI ram dump cookie is NOT set*/
if ( !( boot_shared_imem_cookie_ptr != NULL &&
boot_shared_imem_cookie_ptr->uefi_ram_dump_magic ==
UEFI_CRASH_DUMP_MAGIC_NUM ) )
{
/* Enter downloader for QPST */
sbl_dload_entry(); //入口
}
//...
}
4.1.1 boot_dload_entry
/*
FUNCTION BOOT_DLOAD_ENTRY
DESCRIPTION
Determines if identifier is present in BOOT_MISC_DETECT register to tell
SBL to enter the boot downloader, instead on continuing the normal boot
process.
DEPENDENCIES
Data in BOOT_MISC_DETECT (or GCC_WIND_DOWN_TIMER for v1) is retained across a reset.
RETURN VALUE
TRUE indicates downloader should be entered.
SIDE EFFECTS
None
===========================================================================*/
boolean boot_dload_entry( void )
{
/* Check to see if download ID is present.
For Bear family the cookie is now stored in the register BOOT_MISC_DETECT
and not IMEM. This register is shared with PBL and maybe others. Only
one bit is needed and the mask SBL_BOOT_MISC_DETECT_MASK defines what
section of the register SBL owns. */
char dbg_info[40];
uint32 dload_flag =0x0;
boolean status = FALSE;
do
{
//这边就是来验证TCSR寄存器是否设置了DLOAD_MODE和MINIDUMP mode的
dload_flag = HWIO_TCSR_TCSR_BOOT_MISC_DETECT_INM(SBL_DLOAD_MODE_BIT_MASK | SBL_MINIDUMP_MODE_BIT_MASK);
boot_dload_flag_val = dload_flag;
if (dload_flag)
{
snprintf(dbg_info, 40, "TCSR reg value 0x%x ", dload_flag);
boot_log_message(dbg_info);
/* Clear ID so the downloader is not entered again on the next boot. */
//清除TCSR寄存器值,避免再次进入
HWIO_TCSR_TCSR_BOOT_MISC_DETECT_OUTM(SBL_DLOAD_MODE_BIT_MASK | SBL_MINIDUMP_MODE_BIT_MASK,0);
status = TRUE;
break;
}
}while(0);
return status;
} /* boot_dload_entry() */
这里需要提到一点TCSR寄存器,既然从寄存器里取值,需要知道寄存器地址,这边研究了一下,将寄存器地址简单说明一下:
#define HWIO_TCSR_TCSR_BOOT_MISC_DETECT_ADDR (TCSR_TCSR_REGS_REG_BASE + 0x00013000)
#define TCSR_TCSR_REGS_REG_BASE (CORE_TOP_CSR_BASE + 0x000c0000)
#define CORE_TOP_CSR_BASE 0x00300000
{% tip success %}
SBL_MINIDUMP_MODE_BIT_MASK = 0x40 后面会用到
由此可得到TCSR寄存器地址为:0x00300000+0x000c0000+0x00013000=0x003D3000
此地址后面会提到!!
{% endtip %}
如果此时检测到TCSR寄存器中的DLOAD_MODE和MINIDUMP的bit被设置了,如果使能就会进⼊sbl1_dload_entry()→XBLRamDumpMain()
4.1.2 XBLRamDumpMain
void sbl1_dload_entry ()
{
//...
//SCL_RAMDUMP_CODE_BASE 指向的是一个ld文件地址
((void (*)())(uintnt)(SCL_RAMDUMP_CODE_BASE))();
}
---->
RAMDUMP_ROM SCL_RAMDUMP_CODE_BASE:
{
*ModuleEntryPoint.o* (.text) //这里会进入.s汇编
KEEP(*(.dll_path_section))
*(.text .stub .text.* .rela.text .relaRAMDUMP_ROM*)
*(BOOT_UTIL_ASM)
*(RO)
*(ARM_MMU)
*(.gcc_except_table .got .got.plt )
/* RO DATA */
*(.constdata .rodata .rodata.* .gnu.linkonce.r.*)
ASSERT(SIZEOF(RAMDUMP_ROM) <= SCL_RAMDUMP_CODE_SIZE, "Invalid size of RAMDUMP_ROM Section");
} : RAMDUMP_CODE_ROM
---->
ModuleEntryPoint.asm
_ModuleEntryPoint
b XBLRamDumpMain
之后就会跳转到XBLRamDumpMain执行
VOID XBLRamDumpMain( VOID )
{
// ...
/*-----------------------------------------------------------------------
* Ram dump to eMMC raw partition, this function will reset device
* after successful dump collection if cookie is set
*----------------------------------------------------------------------*/
boot_ram_dump_to_raw_parition(); //写⼊dump log 写到 emmc raw partition
#ifdef FEATURE_BOOT_RAMDUMPS_TO_SD_CARD
/*----------------------------------------------------------------------
* Take the Ramdumps to SD card if cookie file is
* present in SD card
*---------------------------------------------------------------------*/
boot_ram_dumps_to_sd_card(); //写入ramdump log 到 sd card
#endif /*FEATURE_BOOT_RAMDUMPS_TO_SD_CARD*/
//...
}
4.1.3 boot_ram_dump_to_raw_parition
4.1.3.1 dload_mem_debug_init
通过dload_mem_debug_init()->dload_mem_debug_target_init()->dload_add_minidump_regions()初始化minidump分区:
通过boot_dload_read_saved_cookie() & SBL_MINIDUMP_MODE_BIT_MASK的值来判断minidump是否使能,
boot_dload_read_saved_cookie()直接返回boot_dload_flag_val变量(也就是tscr寄存器保存的值)。
void dload_mem_debug_target_init(void)
{
boolean add_region_status = FALSE;
uint64 start_addr =0;
uint64 size =0;
uint32 index =0;
sbl_if_shared_ddr_device_info_type *available_ddr;
char ddr_string_info[DLOAD_DEBUG_STRLEN_BYTES],ddr_filename[DLOAD_DEBUG_STRLEN_BYTES];
uint64 file_cnt =0 ;
struct memory_region dump_regions[] = {MEMORY_REGION_TABLE};
/* Set memory region table to be fixed length, required by sahara*/
dload_mem_debug_len_init();
/* Check if device is retail unlocked. Do not dump internal memories
in retail unlock scenario. Also zero peripheral memory so it does not
appear in DDR dump. */
if (dload_mem_debug_is_device_retail_unlocked())
{
dump_regions[0].region_base = 0x0;
dump_regions[0].region_size = 0x0;
dump_regions[0].desc = NULL;
dump_regions[0].filename = NULL;
dload_mem_debug_zero_peripheral_memory();
}
dload_flag = boot_dload_read_saved_cookie();
/*
********************************WARNING**************************************
Please make sure all dump region file names follow the 8.3 file name format!
*****************************************************************************
*/
/* Check if DLOAD flag is set, if not only minidump regions are to be dumped */
if(dload_flag & SBL_DLOAD_MODE_BIT_MASK)
{
index = 0;
/* RAM-DUMP table defined in .builds file */
while ( dump_regions[index].region_base != 0x0 )
{
add_region_status = dload_debug_add_region(OPTIONAL_DEF_SAVE,
dump_regions[index].region_base,
dump_regions[index].region_size,
dump_regions[index].desc,
dump_regions[index].filename
);
if(add_region_status == FALSE)
{
break;
}
index++;
}
BL_VERIFY((add_region_status == TRUE),
BL_ERR_RAM_DUMP_FAIL|BL_ERROR_GROUP_BOOT);
/* Add RPM MSG RAM as a restricted region as USB controller cannot access it */
dload_debug_add_restricted_region(SCL_RPM_MSG_RAM_BASE,
SCL_RPM_MSG_RAM_SIZE);
dload_debug_add_restricted_region(SCL_DDR0_DTTS_REGION0_BASE,
SCL_DDR0_DTTS_REGION0_SIZE);
dload_debug_add_restricted_region(SCL_DDR0_DTTS_REGION1_BASE,
SCL_DDR0_DTTS_REGION1_SIZE);
/* Add IPA memory dumps */
dload_add_ipa_memory_regions();
/* Add Pmic power on reason*/
dload_add_pmic_info(FALSE);
/* Add reset status */
dload_add_reset_status(FALSE);
/* Add ddr training data */
dload_add_ddr_training_data();
/* Add pImem region */
dload_add_pimem_region();
/* Add the gcc register information */
dload_add_gcc_regs();
index = 0;
available_ddr = boot_get_ddr_info();
while(index < available_ddr->noofddr)
{
start_addr = available_ddr->ddr_info[index].cs_addr;
size = available_ddr->ddr_info[index].ramsize << CONVERT_TO_MB_SHIFT;
/* Define DDR Memory Region EBI1 CS0/CS1 etc */
file_cnt = 0;
do {
qmemset(ddr_string_info,0,DLOAD_DEBUG_STRLEN_BYTES);
qmemset(ddr_filename,0,DLOAD_DEBUG_STRLEN_BYTES);
qsnprintf (ddr_string_info, DLOAD_DEBUG_STRLEN_BYTES, " DDR CS%u part%u Memory", index, file_cnt);
qsnprintf (ddr_filename, DLOAD_DEBUG_STRLEN_BYTES, "DDRCS%u_%u.BIN", index, file_cnt);
if(size >= 0x80000000) {
add_region_status = dload_debug_add_region(OPTIONAL_DEF_SAVE, start_addr + 0x80000000 * file_cnt, 0x80000000,
ddr_string_info, ddr_filename);
BL_VERIFY((add_region_status == TRUE),
BL_ERR_RAM_DUMP_FAIL|BL_ERROR_GROUP_BOOT);
size -= 0x80000000;
}
else {
add_region_status = dload_debug_add_region(OPTIONAL_DEF_SAVE, start_addr + 0x80000000 * file_cnt, size,
ddr_string_info, ddr_filename);
BL_VERIFY((add_region_status == TRUE),
BL_ERR_RAM_DUMP_FAIL|BL_ERROR_GROUP_BOOT);
size -= size;
}
file_cnt++;
} while(size) ;
index++;
}
}
/* Add the minidump regions, if enabled */
if(dload_flag & SBL_MINIDUMP_MODE_BIT_MASK)
{
dload_add_minidump_regions();
}
}
可以看到dload_flag是很重要的一个值,他是确保minidump流程成功必要的一个变量
static void dload_add_minidump_regions(void)
{
/* Add additional regions for Minidump case, pass info on oem key, so that HLOS regions
can be zeroed */
#ifdef FEATURE_XIAOMI_DUMP_LASTLOG
dload_log("MINIDUMP: dload_add_minidump_regions enter!");
dload_add_last_kmsg(TRUE);
#else
add_minidump_regions();
start_index_default = dload_mem_debug_num_ent();
add_default_smem_entries();
dload_add_pmic_info(TRUE);
dload_add_reset_status(TRUE);
dload_debug_add_region(OPTIONAL_DEF_SAVE,
(uint64)SHARED_IMEM_BASE,
SHARED_IMEM_SIZE,
"Shared IMEM", "MD_SHRDIMEM.BIN");
#endif
}
从适配的角度来看,我们舍弃了原先的设计,只执行dload_add_last_kmsg
4.1.3.3 dload_add_last_kmsg
#ifdef FEATURE_XIAOMI_DUMP_LASTLOG
static void dload_add_last_kmsg(boolean md)
{
uint32 index = 0;
ram_buffer *rb = NULL;
uint32 tmp = 0;
//这个就是新加的dump region log_dump_regions_md
struct log_memory_region* log_dump_regions = log_dump_regions_md;
while ( log_dump_regions[index].region_base != 0x0 )
{
dload_log("MINIDUMP: dload_add_last_kmsg() log_dump_regions[%d].region_base = 0x%x", index, log_dump_regions[index].region_base);
rb = (ram_buffer *)(log_dump_regions[index].region_base);
if( rb->sig != 0x43474244){
index++;
continue;
}
dload_log("MINIDUMP: dload_add_last_kmsg() rb->data = 0x%x", (uint64)&rb->data);
dload_log("MINIDUMP: dload_add_last_kmsg() rb->start = 0x%x", rb->start);
dload_log("MINIDUMP: dload_add_last_kmsg() rb->size = 0x%x", rb->size);
dload_log("MINIDUMP: dload_add_last_kmsg() log_dump_regions[%d].region_size = 0x%x", index, log_dump_regions[index].region_size);
dload_log("MINIDUMP: dload_add_last_kmsg() sizeof(ram_buffer) = 0x%x", sizeof(ram_buffer));
if (rb->size == (log_dump_regions[index].region_size - sizeof(ram_buffer))
&& rb->start != rb->size) {
dload_debug_add_region(OPTIONAL_DEF_SAVE,
(uint64)&rb->data + rb->start,
log_dump_regions[index].region_size - rb->start,
log_dump_regions[index].desc,
log_dump_regions[index].filename
);
dload_debug_add_region(OPTIONAL_DEF_SAVE,
(uint64)&rb->data,
rb->start,
log_dump_regions[index].desc,
log_dump_regions[index].filename
);
}
else
{
dload_debug_add_region(OPTIONAL_DEF_SAVE,
(uint64)&rb->data,
rb->size,
log_dump_regions[index].desc,
log_dump_regions[index].filename
);
}
index++;
tmp = rb->data[rb->size]; //save Original data
rb->data[rb->size] = '\0'; //set end char,for dload_display_kernel_keyword find keyword
//if (display_kernel_keyword == FALSE)
//{
//dload_display_kernel_keyword((const char *)&rb->data);
//display_kernel_keyword = TRUE;
//}
rb->data[rb->size] = tmp; //Restore Original data
}
}
#endif
而log_dump_regions_md的定义是在BOOT.XF.4.1/boot_images/QcomPkg/XBLLoader/boot_dload_mi_ramdump.h
struct log_memory_region log_dump_regions_md[] = {
{0x5d100000, 0x100000, "console region", "md_kmsg"},
{0x5d200000, 0x200000, "logcat region", "md_pmsg"},
{0x0, 0x0, NULL, NULL}
};
由此可见,我们只定义了两个region,md_kmsg和md_pmsg
4.1.3.4 dload_debug_add_region
boolean dload_debug_add_region
(
dload_save_pref_type save_pref,
uint64 mem_base,
uint64 length,
char *desc,
char *filename
)
{
boolean status = FALSE;
uint32 i = real_num_regions;
uint32 desc_length = strlen(desc);
uint32 filename_length = strlen(filename);
/* Make sure we dont overrun array and align memory regions */
if ((desc_length < DLOAD_DEBUG_STRLEN_BYTES) &&
(filename_length < DLOAD_DEBUG_STRLEN_BYTES) &&
(i < NUM_REGIONS))
{
dload_log("MINIDUMP: memory region old mem_base: 0x%x", mem_base);
dload_debug_info[i].save_pref = (byte)save_pref;
dload_debug_info[i].mem_base = mem_base & ~3;
dload_debug_info[i].length = length & ~3;
strlcpy(dload_debug_info[i].desc, desc, DLOAD_DEBUG_STRLEN_BYTES);
strlcpy(dload_debug_info[i].filename, filename, DLOAD_DEBUG_STRLEN_BYTES);
real_num_regions++;
status = TRUE;
}
dload_log("==================================================");
dload_log("MINIDUMP: add memory region");
dload_log("MINIDUMP: memory region mem_base: 0x%x", mem_base);
dload_log("MINIDUMP: memory region length: %x", length);
dload_log("MINIDUMP: memory region desc : %s", desc);
dload_log("MINIDUMP: memory region filename : %s", filename);
dload_log("==================================================");
return status;
}
4.1.4 boot_handle_lastlog_header
- 初始化lastlog_header
#ifdef FEATURE_XIAOMI_DUMP_LASTLOG
#define LASTLOG_HEADER_SIZE 1024
#define MAX_LASTLOG_COUNT 10
/* kmsg 1M , logcat 2M , and section header & boot_raw_parition_dump_header_v2 */
#define LASTLOG_DUMP_SIZE 0x400000
#define LASTLOG_MAGIC 0x6c6f676d61676963
struct PACK(boot_lastlog_header)
{
uint64 magic;
uint32 next_index; //the index of next dump
uint32 dump_count; //the num of dumps
uint64 header_size; //the offset of 1st dump
uint64 dump_size; //size of one dump
//TBD
};
#endif
#ifdef FEATURE_XIAOMI_DUMP_LASTLOG
struct boot_lastlog_header log_header; //定义log_header 为boot_lastlog_header
/* Pointer points to the partition offset of current lastlog to write data */
static uint64 lastlog_curr_offset;
#endif
#ifdef FEATURE_XIAOMI_DUMP_LASTLOG
boolean boot_handle_lastlog_header()
{
boolean ret = FALSE;
ret = dev_sdcc_read_by_imgid(&log_header, 0,
sizeof(struct boot_lastlog_header), GEN_IMG); //因为lastlog的地址属于最上⾯的,第⼆个参数就是source address in bytes from
if(ret == FALSE) {
//boot_display_message_3x("Read error,Rawdump abort,Try next dump!");
boot_log_message("Read error,Rawdump abort,Try next dump!");
} else {
if(log_header.magic != LASTLOG_MAGIC) {
/* It's maybe the first rawdump, the flash with wrong data */
log_header.magic = LASTLOG_MAGIC;
log_header.next_index = 0;
log_header.dump_count = 0;
log_header.dump_size = LASTLOG_DUMP_SIZE; //设置dump size
log_header.header_size = LASTLOG_HEADER_SIZE; //设置header size
} //初始化lastlog_header
if(log_header.next_index >= MAX_LASTLOG_COUNT)
log_header.next_index = 0;
/* Setup offset of current dump */
lastlog_curr_offset = LASTLOG_HEADER_SIZE +
log_header.next_index * LASTLOG_DUMP_SIZE; //将 dump_header 写到 minidump裸分区 lastlog_curr_offset 指向的位置,⼤⼩为 DUMP_HEADE
//之后只要调整这个偏移即可
}
return ret;
}
#endif
4.1.5 boot_process_raw_ram_dump
- 计算 headers_required_size (dump_header 和 sections_header)
- 初始化 dump_header
- 将 dump_header 写到 minidump裸分区 lastlog_curr_offset 指向的位置,⼤⼩为 DUMP_HEADER_SIZE
- 将sections_header写到 minidump 裸分区 lastlog_curr_offset+ DUMP_HEADER_SIZE指向的位置,⼤⼩为 SECTION_HEADER_SIZE * real_rd_sections
- 设置每⼀个section_header(即update section_header)
- 将内存中的dump 写到 minidump裸分区执⾏的offset的位置
- update dump_header and sections_header
- update lastlog_headers
static void boot_process_raw_ram_dump()
{
uint32 headers_required_size = 0;
#ifdef FEATURE_XIAOMI_DUMP_LASTLOG
uint32 i = 0;
#endif
/* We first need space to store overall header and section header*/
ram_dump_sections_num = dload_mem_debug_num_ent(); //获取dump section的数量
#ifdef FEATURE_XIAOMI_DUMP_LASTLOG
/* check the real section num of mini dump*/
for(; i < ram_dump_sections_num ; i++)
{
if(is_dump_file_need(i, TRUE))
real_rd_sections++;
}
#endif
/* Make sure the number of sections we need to dump doesn't exceed
the max we support */
if(ram_dump_sections_num > MAX_RAW_DUMP_SECTION_NUM)
{
boot_raw_ram_dump_error_handler();
}
headers_required_size = DUMP_HEADER_SIZE +
#ifndef FEATURE_XIAOMI_DUMP_LASTLOG
(SECTION_HEADER_SIZE * ram_dump_sections_num);
#else
(SECTION_HEADER_SIZE * real_rd_sections); //计算 headers_required_size (dump_header 和 sections_header)
//这边看到的设计,可以看到其实已经将minidump其他的section完全舍去了,只保留我们设置
#endif
/* Initialize the header */
boot_ram_dump_header_init(); //初始化 dump_header
#ifdef FEATURE_XIAOMI_DUMP_LASTLOG
update_lastlog_headers();
#else
/* Write a fresh copy of overall header first to indicate new ram dump */
dump_overall_headers();
#endif
if(partition_size > headers_required_size)
{
boot_toggle_led_init();
/* We can at least dump all the headers */
raw_dump_header.dump_size = headers_required_size;
/* Write a fresh copy of all the section headers to indicate new ram dump*/
dump_section_headers();
/* Dump each sections */
if(boot_process_raw_ram_dump_sections() == TRUE) //到这边就是步骤5了
{
/* If all sections finished successully set header to valid */
raw_dump_header.validity_flag |= RAM_DUMP_VALID_MASK;
}
else
{
/* if it returns false we know there's not enough space*/
raw_dump_header.validity_flag |= RAM_DUMP_INSUFFICIENT_STORAGE_MASK;
}
}
else
{
/* There is not enough space to store the section headers.
Set the insufficent storage bit */
raw_dump_header.dump_size = DUMP_HEADER_SIZE;
raw_dump_header.validity_flag |= RAM_DUMP_INSUFFICIENT_STORAGE_MASK;
}
/* Now dump is finished, update the overall header */
dump_overall_headers();
}
将sections_header写到 minidump 裸分区 lastlog_curr_offset+ DUMP_HEADER_SIZE指向的位置,⼤⼩为 SECTION_HEADER_SIZE * real_rd_sections.
4.1.6 boot_process_raw_ram_dump_sections
在boot_process_raw_ram_dump_sections()函数中会遍历dload_debug_info数组保存的各个memory region,is_dump_file_need()函数会根据debug_filename前三个字节是否为“md_”或“MD_”从⽽跳过fulldump相关的memory region,最后调⽤boot_ram_dump_coldplug_write()函数将dload_debug_info数组中的memory region dump到相应的⽂件中。
4.2 kernel阶段
kernel阶段主要是往设置好的minidump的内存地址里写数据以及设置TCSR寄存器
这部分主要涉及的源码文件如下:
qcom-dload-mode.c
msm-poweroff.c
qcom-scm.h
Kernel-5.15需要将源码编译成ko,而不是编译进内核。需要打开以下宏
CONFIG\_LAST\_LOG\_MINIDUMP=y # 这是我们自定义的需要使用的
CONFIG\_POWER\_RESET\_MSM=m
CONFIG\_POWER\_RESET\_QCOM\_DOWNLOAD\_MODE=m
CONFIG\_POWER\_RESET\_QCOM\_DOWNLOAD\_MODE\_DEFAULT=y
4.2.1 dts匹配
static const struct of_device_id of_msm_restart_match[] = {
{ .compatible = "qcom,pshold", },
{},
};
对应的设备树
restart@440b000 {
compatible = "qcom,pshold";
reg = <0x440b000 0x4>,
<0x03d3000 0x4>;
reg-names = "pshold-base", "tcsr-boot-misc-detect";
};
4.2.2 probe函数
static int msm_restart_probe(struct platform_device *pdev)
{
//...
setup_dload_mode_support(); //设置dload_mode以及emmc_dload节点
np = of_find_compatible_node(NULL, NULL,
"qcom,msm-imem-restart_reason");
/*
restart_reason@65c {
compatible = "qcom,msm-imem-restart_reason";
reg = <0x65c 0x4>;
};
*/
//...
mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pshold-base");
/*
mem->start = 0x440b000
*/
//...
mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"tcsr-boot-misc-detect");
/*
mem->start = 0x03d3000
*/
set_dload_mode(download_mode);
if (!download_mode)
qcom_scm_disable_sdi();
force_warm_reboot = of_property_read_bool(dev->of_node,
"qcom,force-warm-reboot");
return 0;
//...
}
setup_dload_mode_support:主要用来设置dload_mode和emmc_dload节点,从用户空间设置dload_mode传到底层,设置minidump的流程
set_dload_mode(download_mode):设置dload_mode,默认值download_mode为1
static void set_dload_mode(int on)
{
if (dload_mode_addr) {
__raw_writel(on ? 0xE47B337D : 0, dload_mode_addr);
__raw_writel(on ? 0xCE14091A : 0,
dload_mode_addr + sizeof(unsigned int));
/* Make sure the download cookie is updated */
mb();
}
//如果on为1, 传入dload_type
// tcsr_boot_misc_detect为真,这个值是从dts中读取的
//所以这句其实执行的是qcom_scm_set_download_mode(dload_type, tcsr_boot_misc_detect)
qcom_scm_set_download_mode(on ? dload_type : 0,
tcsr_boot_misc_detect ? : 0);
dload_mode_enabled = on;
}
void qcom_scm_set_download_mode(enum qcom_download_mode mode, phys_addr_t tcsr_boot_misc)
{
bool avail;
int ret = 0;
struct device *dev = __scm ? __scm->dev : NULL;
avail = __qcom_scm_is_call_available(dev,
QCOM_SCM_SVC_BOOT,
QCOM_SCM_BOOT_SET_DLOAD_MODE);
if (avail) {
ret = __qcom_scm_set_dload_mode(dev, mode);
} else if (tcsr_boot_misc || (__scm && __scm->dload_mode_addr)) {
ret = qcom_scm_io_writel(tcsr_boot_misc ? : __scm->dload_mode_addr, mode);
} else {
dev_err(dev,
"No available mechanism for setting download mode\n");
}
if (ret)
dev_err(dev, "failed to set download mode: %d\n", ret);
}
这里为了梳理代码流程,增加了log
Kernel log如下:
[ 308.043708] __scm->dload_mode_addr = 0x3d3000
[ 308.043728] 222222
[ 308.043735] 4444444
[ 308.043741] addr=0x3d3000
[ 308.043747] val = 64
从上述可知,此函数最终调用的是qcom_scm_io_writel()
,往addr=0x3d3000写入dload_type,
这个地方的addr,是指scm->dload_mode_addr,是从qcom_scm_find_dload_address函数获取到的。
static int qcom_scm_find_dload_address(struct device *dev, u64 *addr)
{
struct device_node *tcsr;
struct device_node *np = dev->of_node;
struct resource res;
u32 offset;
int ret;
//获取节点
tcsr = of_parse_phandle(np, "qcom,dload-mode", 0);
if (!tcsr)
return 0;
//获取节点中的reg address,存到res中
ret = of_address_to_resource(tcsr, 0, &res);
of_node_put(tcsr);
if (ret)
return ret;
ret = of_property_read_u32_index(np, "qcom,dload-mode", 1, &offset);
if (ret < 0)
return ret;
//最终的地址就是res起始地址+偏移量
*addr = res.start + offset;
return 0;
}
下面说明一下addr的计算:
qcom_scm {
compatible = "qcom,scm";
qcom,dload-mode = <&tcsr 0x13000>;
};
从函数可知,此时res.start = &tcsr ,offset = 0x13000
addr = &tcsr + 0x13000
tcsr: syscon@0x003C0000 {
compatible = "syscon";
reg = <0x003C0000 0x40000>;
};
tcsr = 0x003C0000
故 addr = 0x3c0000+0x13000=0x3d3000
这个地址和在xbl阶段,第4.1.1章节中提到的03d3000相对应。
上面讲的set_dload_mode是默认的驱动加载时执行的,默认的dload_type为
#ifdef CONFIG_LAST_LOG_MINIDUMP
static int dload_type = SCM_DLOAD_BOTHDUMPS;//默认为BOTHDUMP
#else
static int dload_type = SCM_DLOAD_FULLDUMP;
#endif
#define SCM_DLOAD_FULLDUMP QCOM_DOWNLOAD_FULLDUMP
#define SCM_EDLOAD_MODE QCOM_DOWNLOAD_EDL
#define SCM_DLOAD_MINIDUMP QCOM_DOWNLOAD_MINIDUMP
#define SCM_DLOAD_BOTHDUMPS (SCM_DLOAD_FULLDUMP | SCM_DLOAD_MINIDUMP)
enum qcom_download_mode {
QCOM_DOWNLOAD_NODUMP = 0x00,
QCOM_DOWNLOAD_EDL = 0x01,
QCOM_DOWNLOAD_FULLDUMP = 0x10,
#if IS_ENABLED(CONFIG_LAST_LOG_MINIDUMP)
QCOM_DOWNLOAD_MINIDUMP = 0x40, //minidump的值改为0x40
#else
QCOM_DOWNLOAD_MINIDUMP = 0x20,
#endif
};
{% tip success %}
QCOM_DOWNLOAD_MINIDUMP 和之前在xbl中提到的 SBL_MINIDUMP_MODE_BIT_MASK值要保证一致为0x40
{% endtip %}
4.2.3 show_dload_mode和store_dload_mode
static ssize_t show_dload_mode(struct kobject *kobj, struct attribute *attr,
char *buf)
{
return scnprintf(buf, PAGE_SIZE, "DLOAD dump type: %s\n",
(dload_type == SCM_DLOAD_BOTHDUMPS) ? "both" :
((dload_type == SCM_DLOAD_MINIDUMP) ? "mini" : "full"));
}
static size_t store_dload_mode(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
if (sysfs_streq(buf, "full")) {
dload_type = SCM_DLOAD_FULLDUMP;
} else if (sysfs_streq(buf, "mini")) {
if (!msm_minidump_enabled()) {
pr_err("Minidump is not enabled\n");
return -ENODEV;
}
dload_type = SCM_DLOAD_MINIDUMP;
} else if (sysfs_streq(buf, "both")) {
if (!msm_minidump_enabled()) {
pr_err("Minidump not enabled, setting fulldump only\n");
dload_type = SCM_DLOAD_FULLDUMP;
return count;
}
dload_type = SCM_DLOAD_BOTHDUMPS;
} else {
pr_err("Invalid Dump setup request..\n");
pr_err("Supported dumps:'full', 'mini', or 'both'\n");
return -EINVAL;
}
pr_err("%s: dload_type=0x%x\n", __func__, dload_type);
mutex_lock(&tcsr_lock);
/*Overwrite TCSR reg*/
set_dload_mode(dload_type);
mutex_unlock(&tcsr_lock);
return count;
}
#endif /* CONFIG_QCOM_MINIDUMP */
此函数的功能就是在/sys/kernel/dload/生成dload_mode的节点,赋予读与写的函数接口。
当我们从用户空间写入
echo mini > /sys/kernel/dload/dload_mode
dload_type=SCM_DLOAD_MINIDUMP
然后调用set_dload_mode(dload_type),将写入的值(0x40)写到0x3d3000地址。
五、kmsg和pmsg原理
这部分代码是在kernel的pstore中创建的
kernel_platform/msm-kernel/fs/pstore/ram.c
probe函数中解析设备树
parse_u32("mem-type", pdata->record_size, pdata->mem_type);
parse_u32("record-size", pdata->record_size, 0);
parse_u32("console-size", pdata->console_size, 0);
parse_u32("ftrace-size", pdata->ftrace_size, 0);
parse_u32("pmsg-size", pdata->pmsg_size, 0);
parse_u32("ecc-size", pdata->ecc_info.ecc_size, 0);
parse_u32("flags", pdata->flags, 0);
parse_u32("max-reason", pdata->max_reason, pdata->max_reason);
// 创建dmsg memeory ramoops
err = ramoops_init_przs("dmesg", dev, cxt, &cxt->dprzs, &paddr,
dump_mem_sz, cxt->record_size,
&cxt->max_dump_cnt, 0, 0);
// 创建pmsg memory ramoops
err = ramoops_init_prz("pmsg", dev, cxt, &cxt->mprz, &paddr,
cxt->pmsg_size, 0);
{% tip success %}
这里需要提到的这个函数persistent_raw_new,此函数会在创建这个memory ramoops的时候使用固定的sig值(0x43474244),这个值是和底层bp的值相对应的,在增加kmsg以及pmsg的时候需要对sig值做判断
{% endtip %}
而需要做到sig值匹配的一个重要点就是,内存地址的匹配,在kernel中设定的record-size,console-size,pms-size以及start地址都需要在底层完全匹配!
dts:
/* reserve for pstore */
ramoops_mem: ramoops@5D000000 {
compatible = "ramoops";
reg = <0x0 0x5D000000 0x0 0x00200000>;
record-size = <0x40000>;
pmsg-size = <0x100000>;
console-size = <0x80000>;
};
这里插一个,ramoops的地址0x5D000000在BP侧的uefiplat.cfg中的要对应上
0x5D000000, 0x00200000, "LAST LOG", AddMem, SYS\_MEM, SYS\_MEM\_CAP, Reserv, WRITE\_BACK\_XN
20241218更新:部分代码有oops分区也是一样的,如果没有需要添加
abl:
/*
Ramoops address maps
|----------------| <------Ramoops start address 0x5D00000
| record1 | Recordsize *2 = 0x40000 * 2
|----------------|
| record2 |
|----------------| <------Last kmsg start address 0x5D080000
| console | console size = 0x80000;
|----------------| <------Last pmsg start address 0x5D100000
| pmsg | pmsg size = 0x100000;
|________________|
*/
CONST UINT64 RamoopsAddress=0x5D000000;
CONST UINT64 RamoopsSize = 0x200000;
CONST UINT64 LastKmsgSize = 0x80000;
CONST UINT64 LastPmsgSize = 0x100000;
CONST UINT64 RecordSize = 0x40000;
bp:
struct log_memory_region log_dump_regions_md[] = {
{0x5d080000, 0x80000, "console region", "md_kmsg"},
{0x5d100000, 0x100000, "logcat region", "md_pmsg"},
{0x0, 0x0, NULL, NULL}
};
如此可以得到以下的串口log:
B - 3897150 - MINIDUMP: dload_add_last_kmsg() log_dump_regions[0].region_base = 0x5d080000
B - 3905184 - MINIDUMP: dload_add_last_kmsg() rb->sig = 0x5d080000
B - 3913359 - MINIDUMP: dload_add_last_kmsg() rb->sig = 0x43474244
B - 3919460 - MINIDUMP: dload_add_last_kmsg() rb->data = 0x5d08000c
B - 3925561 - MINIDUMP: dload_add_last_kmsg() rb->start = 0x39edf
B - 3931749 - MINIDUMP: dload_add_last_kmsg() rb->size = 0x39edf
B - 3937763 - MINIDUMP: dload_add_last_kmsg() log_dump_regions[0].region_size = 0x80000
B - 3943698 - MINIDUMP: dload_add_last_kmsg() sizeof(ram_buffer) = 0xc
B - 3951616 - MINIDUMP: memory region old mem_base: 0x5d08000c
B - 3958063 - ==================================================
B - 3963815 - MINIDUMP: add memory region
B - 3964193 - MINIDUMP: memory region mem_base: 0x5d08000c
B - 3968118 - MINIDUMP: memory region length: 39edf
B - 3973510 - MINIDUMP: memory region desc : console region
B - 3978300 - MINIDUMP: memory region filename : md_kmsg
B - 3983778 - ==================================================
B - 3983778 - ==================================================
B - 3994544 - MINIDUMP: dload_add_last_kmsg() log_dump_regions[1].region_base = 0x5d100000
B - 4000481 - MINIDUMP: dload_add_last_kmsg() rb->sig = 0x5d100000
B - 4008656 - MINIDUMP: dload_add_last_kmsg() rb->sig = 0x43474244
B - 4014757 - MINIDUMP: dload_add_last_kmsg() rb->data = 0x5d10000c
B - 4020858 - MINIDUMP: dload_add_last_kmsg() rb->start = 0x5dabe
B - 4027046 - MINIDUMP: dload_add_last_kmsg() rb->size = 0xffff4
B - 4033059 - MINIDUMP: dload_add_last_kmsg() log_dump_regions[1].region_size = 0x100000
B - 4038996 - MINIDUMP: dload_add_last_kmsg() sizeof(ram_buffer) = 0xc
B - 4079075 - ==================================================
B - 4089841 - MINIDUMP: memory region old mem_base: 0x5d10000c
B - 4095769 - ==================================================
B - 4101523 - MINIDUMP: add memory region
B - 4101900 - MINIDUMP: memory region mem_base: 0x5d10000c
B - 4105826 - MINIDUMP: memory region length: 5dabe
B - 4111218 - MINIDUMP: memory region desc : logcat region
B - 4116007 - MINIDUMP: memory region filename : md_pmsg
B - 4121399 - ==================================================
B - 4132176 - MINIDUMP: memory region old mem_base: 0x45ebb104
B - 4138092 - ==================================================
B - 4143844 - MINIDUMP: add memory region
B - 4144221 - MINIDUMP: memory region mem_base: 0x45ebb104
B - 4148147 - MINIDUMP: memory region length: ed
B - 4153537 - MINIDUMP: memory region desc : CMM Script
B - 4158067 - MINIDUMP: memory region filename : load.cmm
B - 4163201 - ==================================================
B - 4176191 - RawDump Free space:0x3fff08, Dump start address:0x5d08000c, size 0x39edc
B - 4192608 - RawDump Free space:0x323aec, Dump start address:0x5d10000c, size 0x5dabc
B - 4202771 - RawDump successfully, Reset the device
六、总结
整体的设计思路就是,
- 在开机后通过用户空间设置dload_mode=mini,将0x40的值写入到0x3d3000
- 如果手机触发了dump,在手机第二次开机的时候会去读取0x3d3000的值,如果设置为minidump的模式,则在xbl中走相应的流程读取存在md_kmsg 和md_pmsg地址的kmsg log和logcat log,将log存于minidump分区
- 开机后使用dd指令,导出minidump分区,使用ultraedit就可以看到log了
七、如何验证?
1. set the mini dump to emmc
adb root
adb wait-for-device
adb shell "echo mini > /sys/kernel/dload/dload_mode"
adb shell "cat /sys/kernel/dload/dload_mode"
adb shell "echo 1 > /sys/module/subsystem_restart/parameters/enable_ramdumps"
adb shell "echo 1 > /sys/module/subsystem_restart/parameters/enable_mini_ramdumps"
adb shell "echo 1 > /sys/module/subsystem_restart/parameters/enable_debug"
adb shell "echo 'file ramdump.c +p' > /sys/kernel/debug/dynamic_debug/control"
adb shell "echo 1 >/sys/kernel/dload/emmc_dload"
adb shell "cat /sys/kernel/dload/emmc_dload"
2. adb shell "echo c > /proc/sysrq-trigger" to get a dump
in the uart, you could see log as below to save the minidump to emmc
B - 3299490 - RawDump Free space:0x4c2fa20, Dump start address:0x858c1000, size 0x2000
B - 3310348 - RawDump Free space:0x4c2da20, Dump start address:0x86007210, size 0xf8
B - 3320565 - RawDump Free space:0x4c2d928, Dump start address:0x86001030, size 0x1000
B - 3331423 - RawDump Free space:0x4c2c928, Dump start address:0x85eac400, size 0x8
B - 3340787 - RawDump Free space:0x4c2c920, Dump start address:0x85e97000, size 0xcc
B - 3350150 - RawDump Free space:0x4c2c854, Dump start address:0x85eac408, size 0x4
B - 3359483 - RawDump Free space:0x4c2c850, Dump start address:0x146aa000, size 0x1000
B - 3370311 - RawDump Free space:0x4c2b850, Dump start address:0x85ebad04, size 0x1c08
B - 3381931 - RawDump successfully, Reset the device
3. pull the minidump from device
adb wait-for-device
adb root
adb wait-for-device
adb shell "dd if=/dev/block/bootdevice/by-name/minidump of=/sdcard/minidump.bin"
adb pull /sdcard/minidump.bin .
八、Debug log
MINIDUMP: dload_add_last_kmsg() log_dump_regions[0].region_base = 0x5d100000
MINIDUMP: dload_add_last_kmsg() rb->data = 0x5d10000c
MINIDUMP: dload_add_last_kmsg() rb->start = 0x4fb1a
MINIDUMP: dload_add_last_kmsg() rb->size = 0x4fb1a
MINIDUMP: dload_add_last_kmsg() log_dump_regions[0].region_size = 0x100000
MINIDUMP: dload_add_last_kmsg() sizeof(ram_buffer) = 0xc
MINIDUMP: memory region old mem_base: 0x5d10000c
==================================================
MINIDUMP: add memory region
MINIDUMP: memory region mem_base: 0x5d10000c
MINIDUMP: memory region length: 4fb1a
MINIDUMP: memory region desc : console region
MINIDUMP: memory region filename : md_kmsg
==================================================
MINIDUMP: dload_add_last_kmsg() log_dump_regions[1].region_base = 0x5d200000
MINIDUMP: dload_add_last_kmsg() rb->data = 0x5d20000c
MINIDUMP: dload_add_last_kmsg() rb->start = 0x2a7b5
MINIDUMP: dload_add_last_kmsg() rb->size = 0x1ffff4
MINIDUMP: dload_add_last_kmsg() log_dump_regions[1].region_size = 0x200000
MINIDUMP: dload_add_last_kmsg() sizeof(ram_buffer) = 0xc
MINIDUMP: memory region old mem_base: 0x5d22a7c1
==================================================
MINIDUMP: add memory region
MINIDUMP: memory region mem_base: 0x5d22a7c1
MINIDUMP: memory region length: 1d584b
MINIDUMP: memory region desc : logcat region
MINIDUMP: memory region filename : md_pmsg
==================================================
MINIDUMP: memory region old mem_base: 0x5d20000c
==================================================
MINIDUMP: add memory region
MINIDUMP: memory region mem_base: 0x5d20000c
MINIDUMP: memory region length: 2a7b5
MINIDUMP: memory region desc : logcat region
MINIDUMP: memory region filename : md_pmsg
==================================================
MINIDUMP: memory region old mem_base: 0x45ebb104
==================================================
MINIDUMP: add memory region
MINIDUMP: memory region mem_base: 0x45ebb104
MINIDUMP: memory region length: ed
MINIDUMP: memory region desc : CMM Script
MINIDUMP: memory region filename : load.cmm
==================================================
RawDump Free space:0x3fff08, Dump start address:0x5d10000c, size 0x4fb18
RawDump Free space:0x3b03f0, Dump start address:0x5d22a7c0, size 0x1d5848
RawDump Free space:0x1daba8, Dump start address:0x5d20000c, size 0x2a7b4
RawDump successfully, Reset the device
九、参考change
- 拉私change
https://gerrit.odm.mioffice.cn/c/kernel/msm-5.15/+/282351
https://gerrit.odm.mioffice.cn/c/platform/vendor/qcom/non-hlos-sm6225/+/281057
- 非拉私change
https://gerrit.odm.mioffice.cn/c/kernel/msm-5.15/+/282351
https://gerrit.odm.mioffice.cn/c/platform/vendor/qcom/non-hlos/BOOT.XF.4.1/+/283972
十、其它
这里没有将md_kmsg 和 md_pmsg的地址存的kmsg 以及 logcat的log是如何存进去的,可以参考如下的change:
https://gerrit.odm.mioffice.cn/c/kernel/msm-5.15/+/285234
https://gerrit.odm.mioffice.cn/c/platform/vendor/qcom-proprietary/devicetree/+/268824
Fastboot 读取last kmsg
https://gerrit.odm.mioffice.cn/c/abl/tianocore/edk2/+/268673
https://gerrit.odm.mioffice.cn/c/platform/vendor/qcom/non-hlos-sm6225/+/274882