文档目的
shared memory即共享内存,在不同的进程之间通过一小段内存来共享信息,实现进程间通信。本文主要介绍高通aboot(lk) 和内核(kernel)间的信息传递。
在传统的lk-》kernel信息传递使用的是cmdline,但是这种方法只适合传递简短的信息,对于很大段的信息(比如aboot中的所有uart log)却无法传递。所以此处我们使用共享内存来传递,先把aboot中uart log全部保存在某一段内存中,然后无损传递到内核中,在内核中读出来。
具体实现
aboot打印log -> log保存 -> log写入内存 -> 启动内核 -> 读取log
1.aboot打印log
aboot中的log打印函数为dprintf,其原型定义在bootable/bootloader/lk/include/debug.h中:
#define dprintf(level, x...) do { if ((level) <= DEBUGLEVEL) { _dprintf(x); } } while (0)其又调用_dprintf(x),定义在bootable/bootloader/lk/lib/debug/debug.c中:
int _dprintf(const char *fmt, ...)
{
char buf[256];
char ts_buf[13];
int err;
snprintf(ts_buf, sizeof(ts_buf), "[%u] ",(unsigned int)current_time()); //串口log前面加上时间戳
dputs(ALWAYS, ts_buf);
va_list ap;
va_start(ap, fmt);
err = vsnprintf(buf, sizeof(buf), fmt, ap); //解析格式
va_end(ap);
dputs(ALWAYS, buf);
return err;
}调用到dputs函数,bootable/bootloader/lk/include/debug.h:#define dputs(level, str) do { if ((level) <= DEBUGLEVEL) { _dputs(str); } } while (0)再看_dputs(str)函数,bootable/bootloader/lk/lib/debug/debug.c:int _dputs(const char *str)
{
while(*str != 0) {
_dputc(*str++);
}
return 0;
}可以看到此处会把char的字符串拆分成一个char一个char的通过_dputc输出,bootable/bootloader/lk/platform/msm_shared/debug.c:void _dputc(char c)
{
#if WITH_DEBUG_LOG_BUF
log_putc(c);
#endif
#if WITH_DEBUG_DCC
if (c == '\n') {
write_dcc('\r');
}
write_dcc(c) ;
#endif
#if WITH_DEBUG_UART
uart_putc(0, c);
#endif
#if WITH_DEBUG_FBCON && WITH_DEV_FBCON
fbcon_putc(c);
#endif
#if WITH_DEBUG_JTAG
jtag_dputc(c);
#endif
}此处定义了众多的输出方式,我们平时看到的uart log,就是通过上述uart_putc(0, c)输出的,这里我们不在深入。我们只要在这个方法里面获取所有的char即可。2.log保存
上述方法中的参数都是char类型的,比如我们调用dprintf(ALWAYS, "test\n");那么_dputc就会被调用5次,参数分别为't'、'e'、's'、't'、'\n',此处我们参考log_putc的方法,使用一个char字符数组来依次保存这些char:
#ifdef SUPPORT_LK_UART_TO_KERNEL
kernel_log_putc(c);
#endif其中上述宏在相关mk文件中定义。再看一下kernel_log_putc的相关实现:#define UART_LOG_BUF_SIZE (31*1024) /* align on 31k */ // 此处我们申请31K的空间来保存
bool freed = false;
struct uart_log { // 保存uart的结构体
struct uart_log_header {
unsigned max_size; // 最大值
unsigned size_written;
unsigned idx;
} header;
char data[UART_LOG_BUF_SIZE]; // char数组,使用此数组来保存char
};
static struct uart_log log = { //结构体初始化
.header = {
.max_size = sizeof(log.data),
.size_written = 0,
.idx = 0,
},
.data = {0}
};
void kernel_log_putc(char c) { // 重要函数,重要函数,重要函数
if (!freed) {
log.data[log.header.idx] = c; // 字符数组赋值
log.header.size_written++; // 写入的字符个数
log.header.idx++; // 字符数组下标递增1
if (unlikely(log.header.idx >= log.header.max_size))
log.header.idx = 0;
}
}aboot中的每一句log都会被拆成char保存在上述log结构体的data这个字符数组中,最终这个字符数组会是一个非常长的数组。3.log写入内存
上述的结构体是一个临时变量,我们需要做的就是在启动内核之前把这个结构体中的data数组写入到内存中,并保存这片内存不会被其他进程申请使用。
首先我们要在内存中找到一片空白的内存,为了安全起见,我们把device tree和ramdisk加载的地址往后偏移32k(0x8000),留出32k给我们保存log使用:
其中各地址起始位置是在 bootable/bootloader/lk/platform/msm8952/include/platform/iomap.h:
#ifdef SUPPORT_LK_UART_TO_KERNEL // DDR_START即0x10000000
#define ABOOT_FORCE_UART_ADDR DDR_START + 0x3400000 /* 31k */
#define ABOOT_FORCE_UART_COUNT_ADDR DDR_START + 0x3407c00 /* 1k */
#define ABOOT_FORCE_TAGS_ADDR DDR_START + 0x3408000 // 偏移0x8000
#define ABOOT_FORCE_RAMDISK_ADDR DDR_START + 0x3608000 // 偏移0x8000
#else
#define ABOOT_FORCE_TAGS_ADDR DDR_START + 0x3400000
#define ABOOT_FORCE_RAMDISK_ADDR DDR_START + 0x3600000
#endif在aboot中boot_linux结束之前把data数组写入到内存中:#ifdef SUPPORT_LK_UART_TO_KERNEL
send_uart_to_kernel();
#endifvoid send_uart_to_kernel() {
char cnt_buf[6];
snprintf((char *)cnt_buf, sizeof(cnt_buf), "%d", log.header.idx);
memcpy((void*)ABOOT_FORCE_UART_ADDR, (char*)log.data, sizeof(log.data));
memcpy((void*)ABOOT_FORCE_UART_COUNT_ADDR, cnt_buf, sizeof(cnt_buf));
freed = true;
}上述函数有两个作用:a.使用memcpy把字符数组写入到ABOOT_FORCE_UART_ADDR这个地址 (0x13400000)
b.使用memcpy把字符的个数写入到ABOOT_FORCE_UART_COUNT_ADDR这个地址 (0x13407c00)
接着启动内核kernel.
4.log读取
在内核中,可以新建一个proc节点,然后在这个proc节点的read方法中去实现读取log的操作。(如何新建proc节点不在本文范围内)
下面是实现方法:
#define PAGE_BUF_SIZE 1024
#define UART_LOG_TOTAL_SIZE 0x7c00 /* 0x7c00 is 31k, refers to aboot */
static int aboot_uart_show(struct seq_file *m, void *v)
{
unsigned long uart_start_phy_addr, uart_cnt_phy_addr;
void * uart_start_virt_addr;
void * uart_cnt_virt_addr;
char buf[PAGE_BUF_SIZE], cnt[8];
unsigned long count;
int page = 0;
uart_start_phy_addr = simple_strtoul(aboot_add_buf, NULL, 0); // 0x13400000
uart_cnt_phy_addr = uart_start_phy_addr + UART_LOG_TOTAL_SIZE; // 0x13407c00
uart_start_virt_addr = phys_to_virt(uart_start_phy_addr); // 转化为虚拟地址
uart_cnt_virt_addr = phys_to_virt(uart_cnt_phy_addr); // 转化为虚拟地址
memcpy(cnt, uart_cnt_virt_addr, sizeof(cnt)); // 初始化
count = simple_strtoul(cnt, NULL, 0);
seq_printf(m, "total buf count = %lu\n", count);
seq_printf(m, "\n>>>>>>>>>>>>>>> lk uart log <<<<<<<<<<<<<<<\n\n");
for (page = 0; page <= (int)(count/PAGE_BUF_SIZE); page++) {
if (page == (int)(count/PAGE_BUF_SIZE)) { // 最后一次读取
memcpy(buf, uart_start_virt_addr + page * PAGE_BUF_SIZE,
(int)(count%PAGE_BUF_SIZE));
buf[count%PAGE_BUF_SIZE -1] = '\0';
seq_printf(m, "%s\n", buf);
} else { // 每次读取PAGE_BUF_SIZE个字节(1024)
memcpy(buf, uart_start_virt_addr + page * PAGE_BUF_SIZE, PAGE_BUF_SIZE);
buf[PAGE_BUF_SIZE -1] = '\0';
seq_printf(m, "%s", buf);
}
}
return 0;
}先获取aboot中data数组的起始物理地址、字符count存储的起始地址,然后都转换为虚拟地址,接着从data起始地址开始,每次循环读取1024字节,并地址指针偏移1024字节,直到读取count个字符为止。另外,为了防止0x13400000 - 0x13408000这段地址被重新分配,我们要把这段保留起来,修改kernel/msm-3.18/arch/arm64/boot/dts/msm8937.dtsi,添加如下语句:
aboot_uart_mem: aboot_uart_region@13400000 {
compatible = "removed-dma-pool";
no-map;
reg = <0x0 0x13400000 0x0 0x7c00>;
};
aboot_uart_cnt_mem: aboot_uart_cnt_region@0 {
compatible = "removed-dma-pool";
no-map;
reg = <0x0 0x13407c00 0x0 0x400>;
};流程总览图:
该博客详细介绍了如何在高通平台上,通过共享内存机制将aboot阶段的uart日志保存,并最终写入内核进行持久化。首先,aboot阶段打印日志,然后保存这些日志,接着将日志数据写入共享内存区域,最后由内核读取并处理这些日志信息。

4374

被折叠的 条评论
为什么被折叠?



