blob: 8d31041db64e10c9d59b6baf0cceae2f71d8e84c [file] [log] [blame]
/* Copyright 2018 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/fsnotify.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/of_fdt.h>
#include <linux/bldr_debug_tools.h>
#include <linux/uaccess.h>
#define RAMLOG_COMPATIBLE_NAME "qcom,bldr_log"
#define RAMLOG_LAST_RSE_NAME "bl_old_log"
#define RAMLOG_CUR_RSE_NAME "bl_log"
/* Header structure defined in LK */
struct bldr_log_header {
__u32 magic;
__u32 offset;
__u32 rotate_flag;
};
static char *bl_last_log_buf, *bl_cur_log_buf;
static unsigned long bl_last_log_buf_size, bl_cur_log_buf_size;
static int bldr_log_check_header(struct bldr_log_header *header,
unsigned long bldr_log_size)
{
/* Check magic first */
if (header->magic == BOOT_DEBUG_MAGIC) {
/* Check offset range */
if (header->offset >= sizeof(struct bldr_log_header) &&
header->offset < sizeof(struct bldr_log_header) +
bldr_log_size)
return 0;
}
return -EINVAL;
}
static void bldr_log_parser(const char *bldr_log, char *bldr_log_buf,
unsigned long bldr_log_size, unsigned long *bldr_log_buf_size)
{
struct bldr_log_header *header = (struct bldr_log_header *)bldr_log;
if (bldr_log_check_header(header, bldr_log_size)) {
pr_warn("bldr_log_parser: bldr_log invalid in %p\n", bldr_log);
return;
}
if (header->rotate_flag) {
char *bldr_log_buf_ptr = bldr_log_buf;
unsigned long bldr_log_bottom_size, bldr_log_top_size;
/* Bottom half part */
bldr_log_bottom_size = bldr_log_size - header->offset;
memcpy(bldr_log_buf_ptr, bldr_log + header->offset,
bldr_log_bottom_size);
bldr_log_buf_ptr += bldr_log_bottom_size;
/* Top half part */
bldr_log_top_size = header->offset - sizeof(*header);
memcpy(bldr_log_buf_ptr, bldr_log + sizeof(*header),
bldr_log_top_size);
*bldr_log_buf_size = bldr_log_bottom_size + bldr_log_top_size;
} else {
*bldr_log_buf_size = header->offset - sizeof(*header);
memcpy(bldr_log_buf, bldr_log + sizeof(*header),
*bldr_log_buf_size);
}
pr_debug("bldr_log_parser: size %ld\n", *bldr_log_buf_size);
}
ssize_t bldr_last_log_read_once(char __user *userbuf, ssize_t klog_size)
{
ssize_t len = 0;
len = (klog_size > bl_last_log_buf_size) ?
(ssize_t)bl_last_log_buf_size : 0;
if (len > 0) {
if (copy_to_user(userbuf, bl_last_log_buf, len)) {
pr_warn("bldr_last_log_read_once, copy_to_user failed\n");
return -EFAULT;
}
}
return len;
}
ssize_t bldr_log_read_once(char __user *userbuf, ssize_t klog_size)
{
ssize_t len = 0;
len = (klog_size > bl_cur_log_buf_size) ?
(ssize_t)bl_cur_log_buf_size : 0;
if (len > 0) {
if (copy_to_user(userbuf, bl_cur_log_buf, len)) {
pr_warn("bldr_log_read_once, copy_to_user failed\n");
return -EFAULT;
}
}
return len;
}
/**
* Read last bootloader logs, kernel logs, current bootloader logs in order.
*
* Handle reads that overlap different regions so the file appears like one
* contiguous file to the reader.
*/
ssize_t bldr_log_read(const void *lastk_buf, ssize_t lastk_size,
char __user *userbuf, size_t count, loff_t *ppos)
{
loff_t pos;
ssize_t total_len = 0;
ssize_t len;
int i;
struct {
const char *buf;
const ssize_t size;
} log_regions[] = {
{ .buf = bl_last_log_buf, .size = bl_last_log_buf_size },
{ .buf = lastk_buf, .size = lastk_size },
{ .buf = bl_cur_log_buf, .size = bl_cur_log_buf_size },
};
pos = *ppos;
if (pos < 0)
return -EINVAL;
if (!count)
return 0;
for (i = 0; i < ARRAY_SIZE(log_regions); ++i) {
if (pos < log_regions[i].size && log_regions[i].buf != NULL) {
len = simple_read_from_buffer(userbuf, count, &pos,
log_regions[i].buf, log_regions[i].size);
if (len < 0)
return len;
count -= len;
userbuf += len;
total_len += len;
}
pos -= log_regions[i].size;
if (pos < 0)
break;
}
*ppos += total_len;
return total_len;
}
ssize_t bldr_log_total_size(void)
{
return bl_last_log_buf_size + bl_cur_log_buf_size;
}
int bldr_log_setup(phys_addr_t bldr_phy_addr, size_t bldr_log_size,
bool is_last_bldr)
{
char *bldr_base;
int ret = 0;
if (!bldr_log_size) {
ret = EINVAL;
goto _out;
}
bldr_base = ioremap(bldr_phy_addr, bldr_log_size);
if (!bldr_base) {
pr_warn("%s: failed to map last bootloader log buffer\n",
__func__);
ret = -ENOMEM;
goto _out;
}
if (is_last_bldr) {
bl_last_log_buf = kmalloc(bldr_log_size, GFP_KERNEL);
if (!bl_last_log_buf) {
pr_warn("%s: failed to alloc last bootloader log buffer\n",
__func__);
goto _unmap;
} else {
pr_debug("bootloader_log: allocate for last bootloader log, size: %zu\n",
bldr_log_size);
bldr_log_parser(bldr_base, bl_last_log_buf,
bldr_log_size, &bl_last_log_buf_size);
}
} else {
bl_cur_log_buf = kmalloc(bldr_log_size, GFP_KERNEL);
if (!bl_cur_log_buf) {
pr_warn("%s: failed to alloc last bootloader log buffer\n",
__func__);
goto _unmap;
} else {
pr_debug("bootloader_log: allocate buffer for bootloader log, size: %zu\n",
bldr_log_size);
bldr_log_parser(bldr_base, bl_cur_log_buf,
bldr_log_size, &bl_cur_log_buf_size);
}
}
_unmap:
iounmap(bldr_base);
_out:
return ret;
}
int bldr_log_init(void)
{
struct device_node *np;
struct resource temp_res;
int num_reg = 0;
np = of_find_compatible_node(NULL, NULL, RAMLOG_COMPATIBLE_NAME);
if (np) {
while (of_address_to_resource(np, num_reg, &temp_res) == 0) {
if (!strcmp(temp_res.name, RAMLOG_LAST_RSE_NAME))
bldr_log_setup(temp_res.start,
resource_size(&temp_res), true);
else if (!strcmp(temp_res.name, RAMLOG_CUR_RSE_NAME))
bldr_log_setup(temp_res.start,
resource_size(&temp_res), false);
else
pr_warn("%s: unknown bldr resource %s\n",
__func__, temp_res.name);
num_reg++;
}
if (!num_reg)
pr_warn("%s: can't find address resource\n", __func__);
} else {
pr_warn("%s: can't find compatible '%s'\n",
__func__, RAMLOG_COMPATIBLE_NAME);
}
return num_reg;
}
void bldr_log_release(void)
{
kfree(bl_last_log_buf);
kfree(bl_cur_log_buf);
}