1165 lines
28 KiB
C
1165 lines
28 KiB
C
/*
|
|
* drivers/misc/emmc_ipanic.c
|
|
*
|
|
* Copyright (C) 2011 Intel Corp
|
|
* Author: dongxing.zhang@intel.com
|
|
* Author: jun.zhang@intel.com
|
|
* Author: chuansheng.liu@intel.com
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/device.h>
|
|
#include <linux/types.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/wakelock.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/preempt.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/nmi.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/genhd.h>
|
|
#include <linux/panic_gbuffer.h>
|
|
#include "emmc_ipanic.h"
|
|
|
|
#include <linux/kmsg_dump.h>
|
|
|
|
static char *part_label = "";
|
|
module_param(part_label, charp, 0);
|
|
MODULE_PARM_DESC(part_label, "IPanic mmc partition device label (panic)");
|
|
|
|
static u32 disable_emmc_ipanic;
|
|
core_param(disable_emmc_ipanic, disable_emmc_ipanic, uint, 0644);
|
|
|
|
static struct mmc_emergency_info emmc_info = {
|
|
.init = mmc_emergency_init,
|
|
.write = mmc_emergency_write,
|
|
.part_label = CONFIG_EMMC_IPANIC_PLABEL,
|
|
};
|
|
|
|
static unsigned char *ipanic_proc_entry_name[PROC_MAX_ENTRIES] = {
|
|
"emmc_ipanic_header",
|
|
"emmc_ipanic_console",
|
|
"emmc_ipanic_threads",
|
|
"emmc_ipanic_gbuffer"
|
|
};
|
|
|
|
static int in_panic;
|
|
static struct emmc_ipanic_data drv_ctx;
|
|
static struct work_struct proc_removal_work;
|
|
static int log_offset[IPANIC_LOG_MAX];
|
|
static int log_len[IPANIC_LOG_MAX]; /* sector count */
|
|
static int log_size[IPANIC_LOG_MAX]; /* byte count */
|
|
static size_t log_head[IPANIC_LOG_MAX];
|
|
static size_t log_woff[IPANIC_LOG_MAX];
|
|
static unsigned char last_chunk_buf[SECTOR_SIZE];
|
|
static int last_chunk_buf_len;
|
|
static DEFINE_MUTEX(drv_mutex);
|
|
static void (*func_stream_emmc) (void);
|
|
|
|
static struct kmsg_dumper ipanic_dumper;
|
|
|
|
static void emmc_panic_erase(unsigned char *buffer, Sector *sect)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
struct mmc_emergency_info *emmc = ctx->emmc;
|
|
unsigned char *read_buf_ptr = buffer;
|
|
Sector new_sect;
|
|
int rc;
|
|
|
|
if (!emmc) {
|
|
pr_err("%s:invalid emmc infomation\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (!read_buf_ptr || !sect) {
|
|
sect = &new_sect;
|
|
if (!emmc->bdev) {
|
|
pr_err("%s:invalid emmc block device\n",
|
|
__func__);
|
|
goto out;
|
|
}
|
|
/* make sure the block device is open rw */
|
|
rc = blkdev_get(emmc->bdev, FMODE_READ | FMODE_WRITE, emmc_panic_erase);
|
|
if (rc < 0) {
|
|
pr_err("%s: blk_dev_get failed!\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
/*read panic header */
|
|
read_buf_ptr =
|
|
read_dev_sector(emmc->bdev, emmc->start_block, sect);
|
|
if (!read_buf_ptr) {
|
|
pr_err("%s: read sector error(%llu)!\n",
|
|
__func__, (u64) emmc->start_block);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/*write all zero to panic header */
|
|
lock_page(sect->v);
|
|
memset(read_buf_ptr, 0, SECTOR_SIZE);
|
|
set_page_dirty(sect->v);
|
|
unlock_page(sect->v);
|
|
sync_blockdev(emmc->bdev);
|
|
|
|
if (!read_buf_ptr)
|
|
put_dev_sector(*sect);
|
|
out:
|
|
memset(&ctx->hdr, 0, SECTOR_SIZE);
|
|
return;
|
|
}
|
|
|
|
static int emmc_read(struct mmc_emergency_info *emmc, void *holder,
|
|
char *buffer, off_t offset, int count, bool to_user)
|
|
{
|
|
unsigned char *read_ptr;
|
|
unsigned int sector_no;
|
|
off_t sector_offset;
|
|
Sector sect;
|
|
int rc;
|
|
|
|
if (!emmc) {
|
|
pr_err("%s:invalid emmc infomation\n", __func__);
|
|
return 0;
|
|
}
|
|
if (!emmc->bdev) {
|
|
pr_err("%s:invalid emmc block device\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
sector_no = offset >> SECTOR_SIZE_SHIFT;
|
|
sector_offset = offset & (SECTOR_SIZE - 1);
|
|
if (sector_no >= emmc->block_count) {
|
|
pr_err("%s: reading an invalid address\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* make sure the block device is open rw */
|
|
rc = blkdev_get(emmc->bdev, FMODE_READ | FMODE_WRITE, holder);
|
|
if (rc < 0) {
|
|
pr_err("%s: blk_dev_get failed!\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
read_ptr = read_dev_sector(emmc->bdev, sector_no + emmc->start_block,
|
|
§);
|
|
if (!read_ptr) {
|
|
put_dev_sector(sect);
|
|
return -EINVAL;
|
|
}
|
|
/* count and read_ptr are updated to match flash page size */
|
|
if (count + sector_offset > SECTOR_SIZE)
|
|
count = SECTOR_SIZE - sector_offset;
|
|
|
|
if (sector_offset)
|
|
read_ptr += sector_offset;
|
|
|
|
if (to_user) {
|
|
if (copy_to_user(buffer, read_ptr, count)) {
|
|
pr_err( "%s: Failed to copy buffer to User\n",
|
|
__func__);
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
memcpy(buffer, read_ptr, count);
|
|
|
|
put_dev_sector(sect);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t emmc_ipanic_gbuffer_proc_read(struct file *file, char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
size_t log_len, log_head;
|
|
off_t log_off;
|
|
int rc;
|
|
|
|
if (!ctx) {
|
|
pr_err("%s:invalid panic handler\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (!count)
|
|
return 0;
|
|
|
|
mutex_lock(&drv_mutex);
|
|
|
|
log_off = ctx->curr.log_offset[IPANIC_LOG_GBUFFER];
|
|
log_len = ctx->curr.log_length[IPANIC_LOG_GBUFFER];
|
|
log_head = ctx->curr.log_head[IPANIC_LOG_GBUFFER];
|
|
|
|
if (*ppos >= log_len) {
|
|
mutex_unlock(&drv_mutex);
|
|
return 0;
|
|
}
|
|
|
|
if (*ppos < log_len - log_head) {
|
|
/* No overflow (log_head == 0)
|
|
* or
|
|
* overflow 2nd part buf (log_head = log_woff)
|
|
* |-------w--------|
|
|
* off^
|
|
* |--------|
|
|
*/
|
|
log_off += log_head;
|
|
log_len -= log_head;
|
|
} else {
|
|
/* 1st part buf
|
|
* |-------w--------|
|
|
* off^
|
|
* |-------|
|
|
*/
|
|
*ppos -= (log_len - log_head);
|
|
log_len = log_head;
|
|
}
|
|
|
|
if ((*ppos + count) > log_len)
|
|
count = log_len - *ppos;
|
|
|
|
rc = emmc_read(ctx->emmc, emmc_ipanic_gbuffer_proc_read,
|
|
buffer, log_off + *ppos, count, true);
|
|
if (rc <= 0) {
|
|
mutex_unlock(&drv_mutex);
|
|
pr_err("%s: emmc_read: invalid args: offset:0x%08llx, count:%zd",
|
|
__func__, (u64)(log_off + *ppos), count);
|
|
return rc;
|
|
}
|
|
|
|
*ppos += rc;
|
|
|
|
mutex_unlock(&drv_mutex);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t emmc_ipanic_proc_read_by_log(struct file *file, char __user *buffer,
|
|
size_t count, loff_t *ppos, int log)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
size_t file_length;
|
|
off_t file_offset;
|
|
int rc;
|
|
|
|
if (!ctx) {
|
|
pr_err("%s:invalid panic handler\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (!count)
|
|
return 0;
|
|
|
|
if (log < 0 || log > IPANIC_LOG_MAX) {
|
|
pr_err("%s: Bad log number (%d)\n", __func__, log);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&drv_mutex);
|
|
|
|
if (log == IPANIC_LOG_HEADER) {
|
|
file_length = ctx->hdr.log_size;
|
|
file_offset = offsetof(struct panic_header, panic);
|
|
}
|
|
else {
|
|
file_length = ctx->curr.log_length[log];
|
|
file_offset = ctx->curr.log_offset[log];
|
|
}
|
|
|
|
if (*ppos >= file_length) {
|
|
mutex_unlock(&drv_mutex);
|
|
return 0;
|
|
}
|
|
|
|
if ((*ppos + count) > file_length)
|
|
count = file_length - *ppos;
|
|
|
|
rc= emmc_read(ctx->emmc, emmc_ipanic_proc_read_by_log,
|
|
buffer, file_offset + *ppos, count, true);
|
|
if (rc <= 0) {
|
|
mutex_unlock(&drv_mutex);
|
|
pr_err("%s: emmc_read: invalid args: offset:0x%08llx, count:%zd",
|
|
__func__, (u64)(file_offset + *ppos), count);
|
|
return rc;
|
|
}
|
|
|
|
*ppos += rc;
|
|
|
|
mutex_unlock(&drv_mutex);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t emmc_ipanic_proc_read_hdr(struct file *file, char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
return emmc_ipanic_proc_read_by_log(file, buffer, count, ppos, IPANIC_LOG_HEADER);
|
|
}
|
|
|
|
static ssize_t emmc_ipanic_proc_read0(struct file *file, char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
return emmc_ipanic_proc_read_by_log(file, buffer, count, ppos, IPANIC_LOG_CONSOLE);
|
|
}
|
|
|
|
static ssize_t emmc_ipanic_proc_read1(struct file *file, char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
return emmc_ipanic_proc_read_by_log(file, buffer, count, ppos, IPANIC_LOG_THREADS);
|
|
}
|
|
|
|
static void emmc_ipanic_remove_proc_work(struct work_struct *work)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
int log;
|
|
|
|
mutex_lock(&drv_mutex);
|
|
emmc_panic_erase(NULL, NULL);
|
|
|
|
for (log = 0; log < PROC_MAX_ENTRIES; log++) {
|
|
if (ctx->ipanic_proc_entry[log]) {
|
|
remove_proc_entry(ctx->ipanic_proc_entry_name
|
|
[log], NULL);
|
|
ctx->ipanic_proc_entry[log] = NULL;
|
|
}
|
|
}
|
|
mutex_unlock(&drv_mutex);
|
|
}
|
|
|
|
static ssize_t emmc_ipanic_proc_write(struct file *file,
|
|
const char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
schedule_work(&proc_removal_work);
|
|
return count;
|
|
}
|
|
|
|
/* In section order inside panic partition : */
|
|
static const struct file_operations ipanic_emmc_read_header_fops = {
|
|
.read = emmc_ipanic_proc_read_hdr,
|
|
.write = emmc_ipanic_proc_write,
|
|
};
|
|
|
|
static const struct file_operations ipanic_emmc0_fops = {
|
|
.read = emmc_ipanic_proc_read0,
|
|
.write = emmc_ipanic_proc_write,
|
|
};
|
|
|
|
static const struct file_operations ipanic_emmc1_fops = {
|
|
.read = emmc_ipanic_proc_read1,
|
|
.write = emmc_ipanic_proc_write,
|
|
};
|
|
|
|
static const struct file_operations ipanic_emmc_gbuffer_fops = {
|
|
.read = emmc_ipanic_gbuffer_proc_read,
|
|
.write = emmc_ipanic_proc_write
|
|
};
|
|
|
|
static void emmc_panic_notify_add(void)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
struct mmc_emergency_info *emmc;
|
|
unsigned char *read_buf_ptr;
|
|
Sector sect;
|
|
int rc, idx_log, idx_proc;
|
|
int proc_entry_created = 0;
|
|
|
|
if (!ctx) {
|
|
pr_err("%s:invalid panic handler\n", __func__);
|
|
return;
|
|
}
|
|
|
|
emmc = ctx->emmc;
|
|
if (!emmc) {
|
|
pr_err("%s:invalid emmc infomation\n", __func__);
|
|
goto out_err;
|
|
}
|
|
|
|
if (!emmc->bdev) {
|
|
pr_err("%s:invalid emmc block device\n", __func__);
|
|
goto out_err;
|
|
}
|
|
|
|
/* make sure the block device is open rw */
|
|
rc = blkdev_get(emmc->bdev, FMODE_READ | FMODE_WRITE, emmc_panic_notify_add);
|
|
if (rc < 0) {
|
|
pr_err("%s: blk_dev_get failed!\n", __func__);
|
|
goto out_err;
|
|
}
|
|
|
|
/* read panic header */
|
|
read_buf_ptr = read_dev_sector(emmc->bdev, emmc->start_block, §);
|
|
if (!read_buf_ptr) {
|
|
pr_err("%s: read sector error(%llu)!\n", __func__,
|
|
(u64) emmc->start_block);
|
|
return;
|
|
}
|
|
|
|
memcpy(&ctx->hdr, read_buf_ptr, sizeof(struct panic_header));
|
|
|
|
if (ctx->hdr.magic != PANIC_MAGIC) {
|
|
pr_info("%s: bad magic %x, no data available\n",
|
|
__func__, ctx->hdr.magic);
|
|
emmc_panic_erase(read_buf_ptr, §);
|
|
goto put_sector;
|
|
}
|
|
|
|
pr_info("%s: Data available in panic partition\n", __func__);
|
|
|
|
if (ctx->hdr.version != PHDR_VERSION) {
|
|
pr_err("%s: Version mismatch (%d != %d)\n",
|
|
__func__, ctx->hdr.version, PHDR_VERSION);
|
|
emmc_panic_erase(read_buf_ptr, §);
|
|
goto put_sector;
|
|
}
|
|
|
|
/* Create proc entry for the panic header */
|
|
ctx->ipanic_proc_entry[PROC_HEADER_INDEX] =
|
|
proc_create(ctx->ipanic_proc_entry_name
|
|
[PROC_HEADER_INDEX], S_IFREG | S_IRUGO, NULL,
|
|
&ipanic_emmc_read_header_fops);
|
|
|
|
if (!ctx->ipanic_proc_entry[PROC_HEADER_INDEX])
|
|
pr_err("%s: failed creating proc file\n", __func__);
|
|
else {
|
|
proc_set_size(ctx->ipanic_proc_entry[PROC_HEADER_INDEX],
|
|
ctx->hdr.log_size);
|
|
proc_entry_created = 1;
|
|
pr_info("%s: proc entry created: %s\n", __func__,
|
|
ctx->ipanic_proc_entry_name[PROC_HEADER_INDEX]);
|
|
}
|
|
|
|
/* read log_info to retrieve block numbers and offsets */
|
|
read_buf_ptr = read_dev_sector(emmc->bdev, emmc->start_block+1, §);
|
|
if (!read_buf_ptr) {
|
|
pr_err("%s: read sector error(%llu)!\n", __func__,
|
|
(u64)emmc->start_block + 1);
|
|
return;
|
|
}
|
|
|
|
memcpy(&ctx->curr, read_buf_ptr, sizeof(struct log_info));
|
|
|
|
/* Log files other than header */
|
|
for (idx_log = 0; idx_log < IPANIC_LOG_MAX; idx_log++) {
|
|
|
|
pr_info("%s: log file %u(%u, %u)\n", __func__, idx_log,
|
|
ctx->curr.log_offset[idx_log],
|
|
ctx->curr.log_length[idx_log]);
|
|
|
|
/* Skip empty file. */
|
|
if (ctx->curr.log_length[idx_log] == 0) {
|
|
pr_info("%s: empty log file %u\n", __func__, idx_log);
|
|
continue;
|
|
}
|
|
|
|
/* Create proc entry for console, threads and gbuffer log. */
|
|
if (idx_log == IPANIC_LOG_CONSOLE) {
|
|
idx_proc = PROC_CONSOLE_INDEX;
|
|
ctx->ipanic_proc_entry[PROC_CONSOLE_INDEX] =
|
|
proc_create(ctx->ipanic_proc_entry_name
|
|
[PROC_CONSOLE_INDEX], S_IFREG | S_IRUGO,
|
|
NULL,
|
|
&ipanic_emmc0_fops);
|
|
} else if (idx_log == IPANIC_LOG_THREADS) {
|
|
idx_proc = PROC_THREADS_INDEX;
|
|
ctx->ipanic_proc_entry[PROC_THREADS_INDEX] =
|
|
proc_create(ctx->ipanic_proc_entry_name
|
|
[PROC_THREADS_INDEX], S_IFREG | S_IRUGO,
|
|
NULL,
|
|
&ipanic_emmc1_fops);
|
|
} else if (idx_log == IPANIC_LOG_GBUFFER) {
|
|
idx_proc = PROC_GBUFFER_INDEX;
|
|
ctx->ipanic_proc_entry[PROC_GBUFFER_INDEX] =
|
|
proc_create(ctx->ipanic_proc_entry_name
|
|
[PROC_GBUFFER_INDEX], S_IFREG | S_IRUGO,
|
|
NULL,
|
|
&ipanic_emmc_gbuffer_fops);
|
|
} else {
|
|
/* No proc entry for this index */
|
|
idx_proc = 0;
|
|
continue;
|
|
}
|
|
if (!ctx->ipanic_proc_entry[idx_proc])
|
|
pr_err("%s: failed creating proc file\n",
|
|
__func__);
|
|
else {
|
|
proc_set_size(ctx->ipanic_proc_entry[idx_proc],
|
|
ctx->curr.log_length[idx_log]);
|
|
proc_entry_created = 1;
|
|
pr_info("%s: proc entry created: %s\n",
|
|
__func__,
|
|
ctx->ipanic_proc_entry_name[idx_proc]);
|
|
}
|
|
}
|
|
|
|
if (!proc_entry_created)
|
|
emmc_panic_erase(read_buf_ptr, §);
|
|
|
|
put_sector:
|
|
put_dev_sector(sect);
|
|
return;
|
|
out_err:
|
|
ctx->emmc = NULL;
|
|
}
|
|
|
|
static void emmc_panic_notify_remove(void)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
|
|
if (ctx->emmc && ctx->emmc->part_dev) {
|
|
put_device(ctx->emmc->part_dev);
|
|
ctx->emmc->bdev = NULL;
|
|
}
|
|
|
|
ctx->emmc = NULL;
|
|
}
|
|
|
|
static int emmc_ipanic_writeflashpage(struct mmc_emergency_info *emmc,
|
|
loff_t to, const u_char *buf)
|
|
{
|
|
int rc;
|
|
size_t wlen = SECTOR_SIZE;
|
|
|
|
if (to >= emmc->start_block + emmc->block_count) {
|
|
pr_emerg("%s: panic partition is full.\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
rc = emmc->write((char *)buf, (unsigned int)to);
|
|
if (rc) {
|
|
pr_emerg("%s: Error writing data to flash (%d)\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
return wlen;
|
|
}
|
|
|
|
/*
|
|
* Writes the contents of the console to the specified offset in flash.
|
|
* Returns number of bytes written
|
|
*/
|
|
static int emmc_ipanic_write_console(struct mmc_emergency_info *emmc,
|
|
unsigned int off, int *actual_size)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
int saved_oip, rc, block_shift = 0, bounce_idx = 0;
|
|
size_t line_len = 0;
|
|
bool ret;
|
|
|
|
static unsigned char line[SECTOR_SIZE];
|
|
|
|
*actual_size = 0;
|
|
while (1) {
|
|
saved_oip = oops_in_progress;
|
|
oops_in_progress = 1;
|
|
bounce_idx = 0;
|
|
|
|
if (last_chunk_buf_len) {
|
|
memcpy(ctx->bounce, last_chunk_buf, last_chunk_buf_len);
|
|
bounce_idx += last_chunk_buf_len;
|
|
last_chunk_buf_len = 0;
|
|
}
|
|
|
|
do {
|
|
ret = kmsg_dump_get_line(&ipanic_dumper, false,
|
|
line, SECTOR_SIZE, &line_len);
|
|
|
|
if (ret) {
|
|
if (bounce_idx + line_len < SECTOR_SIZE) {
|
|
memcpy(ctx->bounce + bounce_idx,
|
|
line, line_len);
|
|
bounce_idx += line_len;
|
|
} else {
|
|
int len = SECTOR_SIZE - bounce_idx;
|
|
memcpy(ctx->bounce + bounce_idx,
|
|
line, len);
|
|
bounce_idx = SECTOR_SIZE;
|
|
memcpy(last_chunk_buf,
|
|
line + len, line_len - len);
|
|
last_chunk_buf_len = line_len - len;
|
|
}
|
|
}
|
|
} while (ret && (bounce_idx != SECTOR_SIZE));
|
|
|
|
oops_in_progress = saved_oip;
|
|
|
|
/* If it is the last chunk, just copy it to last chunk
|
|
* buffer and exit loop.
|
|
*/
|
|
if (!ret) {
|
|
/* Leave the last chunk for next writing */
|
|
memcpy(last_chunk_buf, ctx->bounce, bounce_idx);
|
|
last_chunk_buf_len = bounce_idx;
|
|
break;
|
|
}
|
|
|
|
rc = emmc_ipanic_writeflashpage(emmc, off + block_shift,
|
|
ctx->bounce);
|
|
if (rc <= 0) {
|
|
pr_emerg("%s: Flash write failed (%d)\n",
|
|
__func__, rc);
|
|
return block_shift;
|
|
}
|
|
|
|
block_shift++;
|
|
*actual_size += SECTOR_SIZE;
|
|
}
|
|
|
|
return block_shift;
|
|
}
|
|
|
|
static void emmc_ipanic_flush_lastchunk_emmc(loff_t to,
|
|
int *size_written,
|
|
int *sector_written)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
struct mmc_emergency_info *emmc = ctx->emmc;
|
|
int rc = 0;
|
|
|
|
if (last_chunk_buf_len) {
|
|
memset(last_chunk_buf + last_chunk_buf_len, 0,
|
|
SECTOR_SIZE - last_chunk_buf_len);
|
|
|
|
rc = emmc_ipanic_writeflashpage(emmc, to, last_chunk_buf);
|
|
if (rc <= 0) {
|
|
pr_emerg("emmc_ipanic: write last chunk failed (%d)\n",
|
|
rc);
|
|
return;
|
|
}
|
|
|
|
*size_written += last_chunk_buf_len;
|
|
(*sector_written)++;
|
|
last_chunk_buf_len = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void emmc_ipanic_write_thread_func(void)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
struct mmc_emergency_info *emmc = ctx->emmc;
|
|
int size_written;
|
|
int thread_sector_count;
|
|
|
|
thread_sector_count =
|
|
emmc_ipanic_write_console(emmc,
|
|
log_offset[IPANIC_LOG_THREADS] +
|
|
log_len[IPANIC_LOG_THREADS],
|
|
&size_written);
|
|
if (thread_sector_count < 0) {
|
|
pr_emerg("Error writing threads to panic log! (%d)\n",
|
|
log_len[IPANIC_LOG_THREADS]);
|
|
return;
|
|
}
|
|
log_size[IPANIC_LOG_THREADS] += size_written;
|
|
log_len[IPANIC_LOG_THREADS] += thread_sector_count;
|
|
|
|
/*reset the log buffer */
|
|
log_buf_clear();
|
|
kmsg_dump_rewind(&ipanic_dumper);
|
|
}
|
|
|
|
static void emmc_ipanic_write_logbuf(struct mmc_emergency_info *emmc, int log)
|
|
{
|
|
/*
|
|
* Write the log data from the third block :
|
|
* - the first block is reserved for panic header
|
|
* - the second one is reserved for offset information
|
|
*/
|
|
log_offset[log] = emmc->start_block + 2;
|
|
log_len[log] = emmc_ipanic_write_console(emmc, log_offset[log],
|
|
&log_size[log]);
|
|
if (log_size[log] < 0) {
|
|
pr_emerg("Error writing console to panic log! (%d)\n",
|
|
log_len[log]);
|
|
log_size[log] = 0;
|
|
log_len[log] = 0;
|
|
}
|
|
/* flush last chunk buffer for console */
|
|
emmc_ipanic_flush_lastchunk_emmc(log_offset[log] +
|
|
log_len[log],
|
|
&log_size[log], &log_len[log]);
|
|
}
|
|
|
|
static void emmc_ipanic_write_calltrace(struct mmc_emergency_info *emmc,
|
|
int log)
|
|
{
|
|
log_offset[log] = log_offset[log - 1] + log_len[log - 1];
|
|
/*
|
|
* config func_stream_emmc to emmc_ipanic_write_thread_func to
|
|
* stream thread call trace.
|
|
*/
|
|
log_buf_clear();
|
|
kmsg_dump_rewind(&ipanic_dumper);
|
|
func_stream_emmc = emmc_ipanic_write_thread_func;
|
|
show_state_filter(0);
|
|
|
|
/* flush last chunk buffer */
|
|
emmc_ipanic_flush_lastchunk_emmc(log_offset[log] +
|
|
log_len[log],
|
|
&log_size[log], &log_len[log]);
|
|
}
|
|
|
|
static int emmc_ipanic_write_gbuffer_data(struct mmc_emergency_info *emmc,
|
|
struct g_buffer_header *gbuffer,
|
|
unsigned int off,
|
|
int *actual_size)
|
|
{
|
|
int rc, block_shift = 0;
|
|
size_t log_off = 0;
|
|
size_t log_size;
|
|
unsigned char *buf = gbuffer->base;
|
|
|
|
if (gbuffer->head)
|
|
/* has overflow */
|
|
log_size = gbuffer->size;
|
|
else
|
|
/* no overflow */
|
|
log_size = gbuffer->woff;
|
|
|
|
while (log_off < log_size) {
|
|
size_t size_copy = log_size - log_off;
|
|
if (size_copy < SECTOR_SIZE) {
|
|
/*
|
|
* flash page not complete, flushed with
|
|
* emmc_ipanic_flush_lastchunk_emmc
|
|
*/
|
|
memcpy(last_chunk_buf, buf + log_off, size_copy);
|
|
last_chunk_buf_len = size_copy;
|
|
break;
|
|
}
|
|
rc = emmc_ipanic_writeflashpage(emmc, off + block_shift,
|
|
buf + log_off);
|
|
if (rc <= 0) {
|
|
pr_emerg("%s: Flash write failed (%d)\n",
|
|
__func__, rc);
|
|
return 0;
|
|
}
|
|
log_off += rc;
|
|
block_shift++;
|
|
}
|
|
*actual_size = log_off;
|
|
|
|
return block_shift;
|
|
}
|
|
|
|
static struct g_buffer_header gbuffer = {
|
|
.base = NULL,
|
|
};
|
|
|
|
static void emmc_ipanic_write_gbuffer(struct mmc_emergency_info *emmc,
|
|
int log)
|
|
{
|
|
struct g_buffer_header *m_gbuffer = &gbuffer;
|
|
|
|
log_offset[log] = log_offset[log - 1] + log_len[log - 1];
|
|
|
|
pr_info("write gbuffer data\n");
|
|
if (!m_gbuffer->base) {
|
|
pr_err("Ipanic error, no gbuffer data\n");
|
|
return;
|
|
}
|
|
|
|
log_len[log] = emmc_ipanic_write_gbuffer_data(emmc, m_gbuffer,
|
|
log_offset[log],
|
|
&log_size[log]);
|
|
if (log_len[log] < 0) {
|
|
pr_emerg("Error writing gbuffer to panic log! (%d)\n",
|
|
log_len[log]);
|
|
log_size[log] = 0;
|
|
log_len[log] = 0;
|
|
}
|
|
/* flush last chunk buffer */
|
|
emmc_ipanic_flush_lastchunk_emmc(log_offset[log] + log_len[log],
|
|
&log_size[log], &log_len[log]);
|
|
log_head[log] = m_gbuffer->head;
|
|
log_woff[log] = m_gbuffer->woff;
|
|
pr_info("write gbuffer data END\n");
|
|
}
|
|
|
|
/*
|
|
* Exported in <linux/panic_gbuffer.h>
|
|
*/
|
|
void panic_set_gbuffer(struct g_buffer_header *buf)
|
|
{
|
|
if (gbuffer.base) {
|
|
pr_err("%s: gbuffer already set to 0x%p, can not set again",
|
|
__func__, gbuffer.base);
|
|
return;
|
|
}
|
|
|
|
gbuffer.base = buf->base;
|
|
gbuffer.size = buf->size;
|
|
gbuffer.woff = buf->woff;
|
|
gbuffer.head = buf->head;
|
|
}
|
|
EXPORT_SYMBOL(panic_set_gbuffer);
|
|
|
|
static void emmc_ipanic_write_pageheader(struct mmc_emergency_info *emmc)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
struct panic_header *hdr = (struct panic_header *)ctx->bounce;
|
|
int wc;
|
|
size_t len, total, max;
|
|
|
|
memset(ctx->bounce, 0, SECTOR_SIZE);
|
|
hdr->magic = PANIC_MAGIC;
|
|
hdr->version = PHDR_VERSION;
|
|
|
|
total = snprintf(hdr->panic, SECTOR_SIZE,
|
|
"###Kernel panic###\n");
|
|
|
|
max = SECTOR_SIZE - offsetof(struct panic_header, panic) - total;
|
|
kmsg_dump_get_buffer(&ipanic_dumper, false, last_chunk_buf, max, &len);
|
|
kmsg_dump_rewind(&ipanic_dumper);
|
|
|
|
memcpy(hdr->panic + total, last_chunk_buf, len);
|
|
hdr->log_size = len + total;
|
|
|
|
/* Write header block */
|
|
wc = emmc_ipanic_writeflashpage(emmc, emmc->start_block, ctx->bounce);
|
|
if (wc <= 0) {
|
|
pr_emerg("emmc_ipanic: Info write failed (%d)\n", wc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void emmc_ipanic_clean_loginfo(struct mmc_emergency_info *emmc)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
int rc;
|
|
|
|
memset(log_offset, 0, IPANIC_LOG_MAX * sizeof(int));
|
|
memset(log_len, 0, IPANIC_LOG_MAX * sizeof(int));
|
|
memset(log_size, 0, IPANIC_LOG_MAX * sizeof(int));
|
|
|
|
memset(ctx->bounce, 0, SECTOR_SIZE);
|
|
|
|
rc = emmc_ipanic_writeflashpage(emmc, emmc->start_block+1, ctx->bounce);
|
|
if (rc <= 0) {
|
|
pr_emerg("emmc_ipanic: Header write failed (%d)\n",
|
|
rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void emmc_ipanic_write_loginfo(struct mmc_emergency_info *emmc, int newlog)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
struct log_info *info = (struct log_info *)ctx->bounce;
|
|
int log = IPANIC_LOG_CONSOLE;
|
|
int rc;
|
|
|
|
if ((newlog < 0) || (newlog >= IPANIC_LOG_MAX))
|
|
return;
|
|
|
|
if (log_size[newlog] == 0)
|
|
return;
|
|
|
|
memset(ctx->bounce, 0, SECTOR_SIZE);
|
|
/*Fill up log offset and size */
|
|
while (log < IPANIC_LOG_MAX) {
|
|
/*Configurate log offset and log size */
|
|
info->log_offset[log] = (log_offset[log] - emmc->start_block)
|
|
<< SECTOR_SIZE_SHIFT;
|
|
info->log_length[log] = log_size[log];
|
|
info->log_head[log] = log_head[log];
|
|
info->log_woff[log] = log_woff[log];
|
|
log++;
|
|
}
|
|
rc = emmc_ipanic_writeflashpage(emmc, emmc->start_block+1, ctx->bounce);
|
|
if (rc <= 0) {
|
|
pr_emerg("emmc_ipanic: Header write failed (%d)\n",
|
|
rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int emmc_ipanic(struct notifier_block *this, unsigned long event,
|
|
void *ptr)
|
|
{
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
struct mmc_emergency_info *emmc;
|
|
int rc, log;
|
|
|
|
pr_emerg("panic notified\n");
|
|
|
|
if (in_panic || disable_emmc_ipanic)
|
|
return NOTIFY_DONE;
|
|
|
|
in_panic = 1;
|
|
|
|
#ifdef CONFIG_PREEMPT
|
|
/* Ensure that cond_resched() won't try to preempt anybody */
|
|
add_preempt_count(PREEMPT_ACTIVE);
|
|
#endif
|
|
touch_nmi_watchdog();
|
|
|
|
if (!ctx)
|
|
goto out;
|
|
emmc = ctx->emmc;
|
|
if (!emmc)
|
|
goto out;
|
|
if (ctx->hdr.magic) {
|
|
pr_emerg("Crash partition in use!\n");
|
|
goto out;
|
|
}
|
|
|
|
rc = emmc->init();
|
|
if (rc) {
|
|
/* String too long to fit on 1 80-char line */
|
|
pr_emerg("%s %s, rc=%d\n",
|
|
"Emmc emergency driver is",
|
|
"not initialized successfully!",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
/* Prepare kmsg dumper */
|
|
ipanic_dumper.active = 1;
|
|
/* Rewind kmsg dumper */
|
|
kmsg_dump_rewind(&ipanic_dumper);
|
|
|
|
/* Write emmc ipanic partition header */
|
|
emmc_ipanic_write_pageheader(emmc);
|
|
/* Clean emmc ipanic sections offsets */
|
|
emmc_ipanic_clean_loginfo(emmc);
|
|
|
|
/*Write all buffer into emmc */
|
|
log = IPANIC_LOG_CONSOLE;
|
|
while (log < IPANIC_LOG_MAX) {
|
|
/* Clear temporary buffer */
|
|
memset(ctx->bounce, 0, SECTOR_SIZE);
|
|
/* Log every buffer into emmc */
|
|
switch (log) {
|
|
case IPANIC_LOG_CONSOLE:
|
|
emmc_ipanic_write_logbuf(emmc, log);
|
|
break;
|
|
case IPANIC_LOG_THREADS:
|
|
emmc_ipanic_write_calltrace(emmc, log);
|
|
break;
|
|
case IPANIC_LOG_GBUFFER:
|
|
emmc_ipanic_write_gbuffer(emmc, log);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* Update emmc ipanic sections offsets */
|
|
emmc_ipanic_write_loginfo(emmc, log);
|
|
log++;
|
|
}
|
|
/* Writeback invalidation to ensure that CPU flush cache into DRAM */
|
|
asm("cli;wbinvd;sti");
|
|
|
|
pr_info("Panic log data written done!\n");
|
|
|
|
ipanic_dumper.active = 0;
|
|
|
|
out:
|
|
#ifdef CONFIG_PREEMPT
|
|
sub_preempt_count(PREEMPT_ACTIVE);
|
|
#endif
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block panic_blk = {
|
|
.notifier_call = emmc_ipanic,
|
|
.priority = 100,
|
|
};
|
|
|
|
static int panic_dbg_set(void *data, u64 val)
|
|
{
|
|
BUG();
|
|
return -1;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(panic_dbg_fops, NULL, panic_dbg_set, "%llu\n");
|
|
|
|
static int match_dev_panic_part(struct device *dev, const void *data)
|
|
{
|
|
struct hd_struct *part;
|
|
const char *name = (char *)data;
|
|
|
|
if (!name || !dev || dev->class != &block_class)
|
|
return 0;
|
|
|
|
part = dev_to_part(dev);
|
|
|
|
return part->info && part->info->volname &&
|
|
!strcmp(name, part->info->volname);
|
|
}
|
|
|
|
static int emmc_panic_partition_notify(struct notifier_block *nb,
|
|
unsigned long action, void *data)
|
|
{
|
|
struct device *dev = data;
|
|
struct emmc_ipanic_data *ctx = &drv_ctx;
|
|
struct mmc_emergency_info *emmc;
|
|
struct gendisk *disk;
|
|
|
|
if (!ctx) {
|
|
pr_err("%s:invalid panic handler\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
emmc = ctx->emmc;
|
|
if (!emmc) {
|
|
pr_err("%s:invalid emmc information\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
switch (action) {
|
|
case BUS_NOTIFY_ADD_DEVICE:
|
|
case BUS_NOTIFY_BOUND_DRIVER:
|
|
/* if emmc already found, exit the function */
|
|
if (emmc->bdev)
|
|
return 0;
|
|
|
|
emmc->part_dev = class_find_device(&block_class, NULL,
|
|
emmc->part_label,
|
|
&match_dev_panic_part);
|
|
if (emmc->part_dev) {
|
|
emmc->part = dev_to_part(emmc->part_dev);
|
|
if (!emmc->part) {
|
|
pr_err("unable to get partition\n");
|
|
goto put_dev;
|
|
}
|
|
|
|
disk = part_to_disk(emmc->part);
|
|
if (!disk) {
|
|
pr_err("unable to get disk\n");
|
|
goto put_dev;
|
|
}
|
|
|
|
/* get whole disk */
|
|
emmc->bdev = bdget_disk(disk, 0);
|
|
if (!emmc->bdev) {
|
|
pr_err("unable to get emmc block device\n");
|
|
goto put_dev;
|
|
}
|
|
|
|
emmc->start_block = emmc->part->start_sect;
|
|
emmc->block_count = emmc->part->nr_sects;
|
|
|
|
pr_info("panic partition found, label:%s, device:%s\n",
|
|
emmc->part_label, dev_name(emmc->part_dev));
|
|
|
|
/* notify to add the panic device */
|
|
emmc_panic_notify_add();
|
|
|
|
atomic_notifier_chain_register(&panic_notifier_list, &panic_blk);
|
|
|
|
INIT_WORK(&proc_removal_work, emmc_ipanic_remove_proc_work);
|
|
}
|
|
break;
|
|
case BUS_NOTIFY_DEL_DEVICE:
|
|
case BUS_NOTIFY_UNBIND_DRIVER:
|
|
if (match_dev_panic_part(dev, emmc->part_label)) {
|
|
pr_info("bus notify removed device '%s', cleaning.\n", dev_name(dev));
|
|
flush_scheduled_work();
|
|
atomic_notifier_chain_unregister(&panic_notifier_list, &panic_blk);
|
|
emmc_panic_notify_remove();
|
|
}
|
|
break;
|
|
case BUS_NOTIFY_BIND_DRIVER:
|
|
case BUS_NOTIFY_UNBOUND_DRIVER:
|
|
/* Nothing to do here, but we don't want
|
|
* these actions to generate error messages,
|
|
* so we need to catch them
|
|
*/
|
|
break;
|
|
default:
|
|
pr_err("Unknown action (%lu) on %s\n",
|
|
action, dev_name(dev));
|
|
return 0;
|
|
}
|
|
return 1;
|
|
|
|
put_dev:
|
|
put_device(emmc->part_dev);
|
|
return 0;
|
|
}
|
|
|
|
static struct notifier_block panic_partition_notifier = {
|
|
.notifier_call = emmc_panic_partition_notify,
|
|
};
|
|
|
|
void emmc_ipanic_stream_emmc(void)
|
|
{
|
|
if (func_stream_emmc)
|
|
(*func_stream_emmc) ();
|
|
}
|
|
|
|
EXPORT_SYMBOL(emmc_ipanic_stream_emmc);
|
|
|
|
static struct dentry *emmc_ipanic_d;
|
|
static struct dentry *emmc_ipanic_disable_d;
|
|
|
|
static int __init emmc_ipanic_init(void)
|
|
{
|
|
/* initialization of drv_ctx */
|
|
memset(&drv_ctx, 0, sizeof(drv_ctx));
|
|
drv_ctx.emmc = &emmc_info;
|
|
|
|
if (*part_label)
|
|
strcpy(emmc_info.part_label, part_label);
|
|
|
|
drv_ctx.ipanic_proc_entry_name = ipanic_proc_entry_name;
|
|
drv_ctx.bounce = (void *)__get_free_page(GFP_KERNEL);
|
|
|
|
bus_register_notifier(&pci_bus_type, &panic_partition_notifier);
|
|
|
|
emmc_ipanic_d = debugfs_create_file("emmc_ipanic", 0644, NULL, NULL,
|
|
&panic_dbg_fops);
|
|
emmc_ipanic_disable_d = debugfs_create_u32("disable_emmc_ipanic", 0644,
|
|
NULL, &disable_emmc_ipanic);
|
|
|
|
pr_info("init success\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit emmc_ipanic_exit(void)
|
|
{
|
|
debugfs_remove(emmc_ipanic_d);
|
|
debugfs_remove(emmc_ipanic_disable_d);
|
|
bus_unregister_notifier(&pci_bus_type, &panic_partition_notifier);
|
|
flush_scheduled_work();
|
|
atomic_notifier_chain_unregister(&panic_notifier_list, &panic_blk);
|
|
emmc_panic_notify_remove();
|
|
}
|
|
|
|
module_init(emmc_ipanic_init);
|
|
module_exit(emmc_ipanic_exit);
|