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:

  1. kernel log 2MB
  2. logcat 2MB
  3. 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

  1. 初始化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

  1. 计算 headers_required_size (dump_header 和 sections_header)
  2. 初始化 dump_header
  3. 将 dump_header 写到 minidump裸分区 lastlog_curr_offset 指向的位置,⼤⼩为 DUMP_HEADER_SIZE
  4. 将sections_header写到 minidump 裸分区 lastlog_curr_offset+ DUMP_HEADER_SIZE指向的位置,⼤⼩为 SECTION_HEADER_SIZE * real_rd_sections
  5. 设置每⼀个section_header(即update section_header)
  6. 将内存中的dump 写到 minidump裸分区执⾏的offset的位置
  7. update dump_header and sections_header
  8. 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

六、总结

整体的设计思路就是,

  1. 在开机后通过用户空间设置dload_mode=mini,将0x40的值写入到0x3d3000
  2. 如果手机触发了dump,在手机第二次开机的时候会去读取0x3d3000的值,如果设置为minidump的模式,则在xbl中走相应的流程读取存在md_kmsg 和md_pmsg地址的kmsg log和logcat log,将log存于minidump分区
  3. 开机后使用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

  1. 拉私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

  1. 非拉私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