[RK3288][Android6.0] 系统重启后保存上一次kernel log机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kris_fei/article/details/80341626

Platform: RK3288
OS: Android 6.0
Kernel: 3.10.92

背景:

最近遇到如下现象:

  1. 自动随机重启
  2. 板子连接adb和uart无响应

这两个问题都需要抓取上一次的kernel log来查找问题。

虽然我有在系统上做了重启后log的保存机制,但是由于是在用户空间使用脚本做的处理,
如果是kernel crash此类issue,就有可能无法保存到log文件中。

后来得知rk在rk3288 android6.0上有实现了类似机制,此代码也适用到其他Linux平台。


查看方法:

#cat /proc/last_kmsg


注意事项:

  1. 针对现象1如果怀疑是reset键引起,那么可以硬件上先屏蔽掉物理按键缩小问题范围。
  2. 针对现象2手动重启如按reset按键必须要保证ddr不掉电,可以直接外接电源到ddr上。
  3. 如果查看/proc/last_kmsg里的内容是乱码,那么说明ddr掉过电了。

源代码分析:

源代码路径:
kernel/arch/arm/mach-rockchip/last_log.c

原理:
从printk中的buf中copy一份到buf中,如果系统重启,由于ddr没掉电,因此buf中的数据没有丢失,然后将这个buf拷贝到另一个buf中,当用户读取/proc/last_kmsg或者/proc/last_log(前者的链接),输出另一个buf即可。

代码说明:

//LOG_BUF_SHIFT和LOG_BUF_LEN以及LOG_BUF_PAGE_ORDER和printk中的buf存放地址和大小保持一致。
#define LOG_BUF_SHIFT   CONFIG_LOG_BUF_SHIFT
#define LOG_BUF_LEN (1 << LOG_BUF_SHIFT)
#define LOG_BUF_PAGE_ORDER  (LOG_BUF_SHIFT - PAGE_SHIFT)

//保存上一次的log buf地址
static char *last_log_buf;
//保存当前log buf地址
static char *log_buf;
//存放到buf的具体位置
static size_t log_pos;
//在此模块初始化之前,log肯定就会有,因为log_buf还未分配,所以先放到early_log_buf中,最大保留8k数据。
static char early_log_buf[8192];

rk_last_log_init():

static int __init rk_last_log_init(void)
{
    size_t early_log_size;
    char *buf;
    struct proc_dir_entry *entry;
    phys_addr_t buf_phys;

    buf = (char *)__get_free_pages(GFP_KERNEL, LOG_BUF_PAGE_ORDER);
    if (!buf) {
        pr_err("failed to __get_free_pages(%d)\n", LOG_BUF_PAGE_ORDER);
        return 0;
    }
    //把上次重启之前的log数据copy到last_log_buf中
    memcpy(last_log_buf, buf, LOG_BUF_LEN);
    //如果执行此模块初始化时log已经超过了8k了,那么就最大取8k,否则取到log_pos大小。
    early_log_size = log_pos > sizeof(early_log_buf) ? sizeof(early_log_buf) : log_pos;
    //把early_log_buf中数据copy到log_buf中
    memcpy(log_buf, early_log_buf, early_log_size);
    memset(log_buf + early_log_size, 0, LOG_BUF_LEN - early_log_size);

    pr_info("%pa map to 0x%p and copy to 0x%p, size 0x%x early 0x%zx (version 3.1)\n", &buf_phys, log_buf, last_log_buf, LOG_BUF_LEN, early_log_size);
    //创建/proc/last_kmsg文件,供用户空间操作读取log
    entry = proc_create("last_kmsg", S_IRUSR, NULL, &last_log_fops);
    if (!entry) {
        pr_err("failed to create proc entry\n");
        return 0;
    }
    proc_set_size(entry, LOG_BUF_LEN);
    //proc/last_log只是链接而已。
    proc_symlink("last_log", NULL, "last_kmsg");

    return 0;
}

rk_last_log_text():

void rk_last_log_text(char *text, size_t size)
{
    //此模块未初始化之前使用early_log_buf暂存,初始化完成后使用log_buf
    char *buf = log_buf ? log_buf : early_log_buf;
    size_t log_size = log_buf ? LOG_BUF_LEN : sizeof(early_log_buf);
    size_t pos;

    /* Check overflow */
    pos = log_pos & (log_size - 1);
    if (likely(size + pos <= log_size))
        memcpy(&buf[pos], text, size);
    else {
        size_t first = log_size - pos;
        size_t second = size - first;
        memcpy(&buf[pos], text, first);
        memcpy(&buf[0], text + first, second);
    }
    //更新当前未存放log的起始位置
    log_pos += size;
}

rk_last_log_text()函数用于保存printk以及其他模块中的log到此模块的log_buf中。
例如pm_rk3288.c中就有调用。

static void  ddr_printch(char byte)
{
    uart_printch(byte);  

#ifdef CONFIG_RK_LAST_LOG
    rk_last_log_text(&byte, 1);

    if (byte == '\n') {
        byte = '\r';
        rk_last_log_text(&byte, 1);
    }
#endif
        pll_udelay(2);
}

last_log_fops:

static const struct file_operations last_log_fops = {
    .owner = THIS_MODULE,
    .read = last_log_read,
};

last_log_read:

static ssize_t last_log_read(struct file *file, char __user *buf,
                    size_t len, loff_t *offset)
{
    loff_t pos = *offset;
    ssize_t count;

    if (pos >= LOG_BUF_LEN)
        return 0;

    count = min(len, (size_t)(LOG_BUF_LEN - pos));
    //直接返回上次log给用户空间。
    if (copy_to_user(buf, &last_log_buf[pos], count))
        return -EFAULT;

    *offset += count;
    return count;
}

另外,顺便提一下,rk3399 android 7.1上此方法不再适用,而是用了pstore,以下文字摘自网络

On Android, diagnosing kernel crashes often requires a developer to read the /proc/last_kmsg file, which stores previous kernel logs after a reboot. Unfortunately it went missing in Android 6.0.
It turns out that it was moved to /sys/fs/pstore/console-ramoops . It still works the same way as before (cat /sys/fs/pstore/console-ramoops will display the previous kernel log)


参考:

New location of last_kmsg on Android 6.0 and above: /sys/fs/pstore/console-ramoops

阅读更多

没有更多推荐了,返回首页