高通平台aboot通过shared memory保存uart log到kernel

该博客详细介绍了如何在高通平台上,通过共享内存机制将aboot阶段的uart日志保存,并最终写入内核进行持久化。首先,aboot阶段打印日志,然后保存这些日志,接着将日志数据写入共享内存区域,最后由内核读取并处理这些日志信息。

文档目的

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();
#endif
void 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>;
		};



流程总览图:


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值