This patch adds Platform dump retrieval interface. Flow: - We register to OPAL notification event. - OPAL sends new dump available notification. - We retrieve the dump and send it to debugfs. - User copies the dump data and end ACKs via debugfs. - We send ACK to OPAL.
debugfs files: We create below dump related files under "fsp" directory. - dump : Dump data - dump_available : New dump available notification to userspace - dump_control : ACK/initiate new dump - README : README Signed-off-by: Vasant Hegde <hegdevas...@linux.vnet.ibm.com> --- arch/powerpc/include/asm/opal.h | 12 + arch/powerpc/platforms/powernv/Makefile | 2 arch/powerpc/platforms/powernv/opal-dump.c | 420 ++++++++++++++++++++++++ arch/powerpc/platforms/powernv/opal-wrappers.S | 4 arch/powerpc/platforms/powernv/opal.c | 2 5 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 arch/powerpc/platforms/powernv/opal-dump.c diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h index d1af862..a1c5237 100644 --- a/arch/powerpc/include/asm/opal.h +++ b/arch/powerpc/include/asm/opal.h @@ -154,6 +154,10 @@ extern int opal_enter_rtas(struct rtas_args *args, #define OPAL_FLASH_VALIDATE 76 #define OPAL_FLASH_MANAGE 77 #define OPAL_FLASH_UPDATE 78 +#define OPAL_DUMP_INIT 81 +#define OPAL_DUMP_INFO 82 +#define OPAL_DUMP_READ 83 +#define OPAL_DUMP_ACK 84 #ifndef __ASSEMBLY__ @@ -233,7 +237,8 @@ enum OpalPendingState { OPAL_EVENT_ERROR_LOG = 0x40, OPAL_EVENT_EPOW = 0x80, OPAL_EVENT_LED_STATUS = 0x100, - OPAL_EVENT_PCI_ERROR = 0x200 + OPAL_EVENT_PCI_ERROR = 0x200, + OPAL_EVENT_DUMP_AVAIL = 0x400, }; /* Machine check related definitions */ @@ -752,6 +757,10 @@ int64_t opal_lpc_read(uint32_t chip_id, enum OpalLPCAddressType addr_type, int64_t opal_validate_flash(uint64_t buffer, uint32_t *size, uint32_t *result); int64_t opal_manage_flash(uint8_t op); int64_t opal_update_flash(uint64_t blk_list); +int64_t opal_dump_init(uint8_t dump_type); +int64_t opal_dump_info(uint32_t *dump_id, uint32_t *dump_size); +int64_t opal_dump_read(uint32_t dump_id, uint64_t buffer); +int64_t opal_dump_ack(uint32_t dump_id); /* Internal functions */ extern int early_init_dt_scan_opal(unsigned long node, const char *uname, int depth, void *data); @@ -781,6 +790,7 @@ extern void opal_get_rtc_time(struct rtc_time *tm); extern unsigned long opal_get_boot_time(void); extern void opal_nvram_init(void); extern void opal_flash_init(void); +extern void opal_platform_dump_init(void); extern int opal_machine_check(struct pt_regs *regs); diff --git a/arch/powerpc/platforms/powernv/Makefile b/arch/powerpc/platforms/powernv/Makefile index 873fa13..379b215 100644 --- a/arch/powerpc/platforms/powernv/Makefile +++ b/arch/powerpc/platforms/powernv/Makefile @@ -1,6 +1,6 @@ obj-y += setup.o opal-takeover.o opal-wrappers.o opal.o obj-y += opal-rtc.o opal-nvram.o opal-lpc.o opal-flash.o -obj-y += rng.o +obj-y += rng.o opal-dump.o obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_PCI) += pci.o pci-p5ioc2.o pci-ioda.o diff --git a/arch/powerpc/platforms/powernv/opal-dump.c b/arch/powerpc/platforms/powernv/opal-dump.c new file mode 100644 index 0000000..e102a80 --- /dev/null +++ b/arch/powerpc/platforms/powernv/opal-dump.c @@ -0,0 +1,420 @@ +/* + * PowerNV OPAL Dump Interface + * + * Copyright 2013 IBM Corp. + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/kobject.h> +#include <linux/debugfs.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/delay.h> + +#include <asm/opal.h> + +/* Dump type */ +#define DUMP_TYPE_FSP 0x01 + +/* Extract failed */ +#define DUMP_NACK_ID 0x00 + +/* Dump record */ +struct dump_record { + uint8_t type; + uint32_t id; + uint32_t size; + char *buffer; +}; +static struct dump_record dump_record; + +/* Dump available status */ +static u32 dump_avail; + +/* Binary blobs */ +static struct debugfs_blob_wrapper dump_blob; +static struct debugfs_blob_wrapper readme_blob; + +/* Ignore dump notification, if we fail to create debugfs files */ +static bool dump_disarmed = false; + + +static void free_dump_sg_list(struct opal_sg_list *list) +{ + struct opal_sg_list *sg1; + while (list) { + sg1 = list->next; + kfree(list); + list = sg1; + } + list = NULL; +} + +/* + * Build dump buffer scatter gather list + */ +static struct opal_sg_list *dump_data_to_sglist(void) +{ + struct opal_sg_list *sg1, *list = NULL; + void *addr; + int64_t size; + + addr = dump_record.buffer; + size = dump_record.size; + + sg1 = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!sg1) + goto nomem; + + list = sg1; + sg1->num_entries = 0; + while (size > 0) { + /* Translate virtual address to physical address */ + sg1->entry[sg1->num_entries].data = + (void *)(vmalloc_to_pfn(addr) << PAGE_SHIFT); + + if (size > PAGE_SIZE) + sg1->entry[sg1->num_entries].length = PAGE_SIZE; + else + sg1->entry[sg1->num_entries].length = size; + + sg1->num_entries++; + if (sg1->num_entries >= SG_ENTRIES_PER_NODE) { + sg1->next = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!sg1->next) + goto nomem; + + sg1 = sg1->next; + sg1->num_entries = 0; + } + addr += PAGE_SIZE; + size -= PAGE_SIZE; + } + return list; + +nomem: + pr_err("%s : Failed to allocate memory\n", __func__); + free_dump_sg_list(list); + return NULL; +} + +/* + * Translate sg list address to absolute + */ +static void sglist_to_phy_addr(struct opal_sg_list *list) +{ + struct opal_sg_list *sg, *next; + + for (sg = list; sg; sg = next) { + next = sg->next; + /* Don't translate NULL pointer for last entry */ + if (sg->next) + sg->next = (struct opal_sg_list *)__pa(sg->next); + else + sg->next = NULL; + + /* Convert num_entries to length */ + sg->num_entries = + sg->num_entries * sizeof(struct opal_sg_entry) + 16; + } +} + +static void free_dump_data_buf(void) +{ + vfree(dump_record.buffer); + dump_record.size = 0; +} + +/* + * Allocate dump data buffer. + */ +static int alloc_dump_data_buf(void) +{ + dump_record.buffer = vzalloc(PAGE_ALIGN(dump_record.size)); + if (!dump_record.buffer) { + pr_err("%s : Failed to allocate memory\n", __func__); + return -ENOMEM; + } + return 0; +} + +/* + * Initiate FipS dump + */ +static int64_t dump_fips_init(uint8_t type) +{ + int rc; + + rc = opal_dump_init(type); + if (rc) + pr_warn("%s: Failed to initiate FipS dump (%d)\n", + __func__, rc); + return rc; +} + +/* + * Get dump ID and size. + */ +static int64_t dump_read_info(void) +{ + int rc; + + rc = opal_dump_info(&dump_record.id, &dump_record.size); + if (rc) + pr_warn("%s: Failed to get dump info (%d)\n", + __func__, rc); + return rc; +} + +/* + * Send acknoledgement to OPAL + */ +static int64_t dump_send_ack(uint32_t dump_id) +{ + int rc; + + rc = opal_dump_ack(dump_id); + if (rc) + pr_warn("%s: Failed to send ack message to ID 0x%x (%d)\n", + __func__, dump_id, rc); + return rc; +} + +/* + * Retrieve dump data + */ +static int64_t dump_read_data(void) +{ + struct opal_sg_list *list; + uint64_t addr; + int64_t rc; + + /* Allocate memory */ + rc = alloc_dump_data_buf(); + if (rc) + goto out; + + /* Generate SG list */ + list = dump_data_to_sglist(); + if (!list) { + rc = -ENOMEM; + goto out; + } + + /* Translate sg list addr to real address */ + sglist_to_phy_addr(list); + + /* First entry address */ + addr = __pa(list); + + /* Fetch data */ + rc = OPAL_BUSY; + while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { + rc = opal_dump_read(dump_record.id, addr); + if (rc == OPAL_BUSY) { + opal_poll_events(NULL); + mdelay(10); + } + } + + if (rc != OPAL_SUCCESS && rc != OPAL_PARTIAL) + pr_warn("%s: Extract dump failed for ID 0x%x\n", + __func__, dump_record.id); + + /* Free SG list */ + free_dump_sg_list(list); + +out: + return rc; +} + +static int extract_dump(void) +{ + int rc; + + /* Get dump ID, size */ + rc = dump_read_info(); + if (rc != OPAL_SUCCESS) + return rc; + + /* Read dump data */ + rc = dump_read_data(); + if (rc != OPAL_SUCCESS && rc != OPAL_PARTIAL) { + /* + * Failed to allocate memory to retrieve dump. Lets send + * negative ack so that we get notification again. + */ + dump_send_ack(DUMP_NACK_ID); + + /* Free dump buffer */ + free_dump_data_buf(); + + return rc; + } + if (rc == OPAL_PARTIAL) + pr_info("%s: Partially read dump ID 0x%x\n", + __func__, dump_record.id); + + pr_info("%s: New platform dump available. ID = 0x%x\n", + __func__, dump_record.id); + + /* Update dump blob */ + dump_blob.data = (void *)dump_record.buffer; + dump_blob.size = dump_record.size; + + /* Update dump available status */ + dump_avail = 1; + + return rc; +} + +static void dump_extract_fn(struct work_struct *work) +{ + extract_dump(); +} + +static DECLARE_WORK(dump_work, dump_extract_fn); + +/* Workqueue to extract dump */ +static void schedule_extract_dump(void) +{ + schedule_work(&dump_work); +} + +/* + * New dump available notification + * + * Once we get notification, we extract dump via OPAL call + * and then write dump to file. + */ +static int dump_event(struct notifier_block *nb, + unsigned long events, void *change) +{ + /* + * Don't retrieve dump, if we don't have debugfs + * interface to pass data to userspace. + */ + if (dump_disarmed) + return 0; + + /* Check for dump available notification */ + if (events & OPAL_EVENT_DUMP_AVAIL) + schedule_extract_dump(); + + return 0; +} + +static struct notifier_block dump_nb = { + .notifier_call = dump_event, + .next = NULL, + .priority = 0 +}; + + +/* FIXME: debugfs README message */ +static const char readme_msg[] = + "This file will be populated shortly.."; + +/* debugfs dump_control file operations */ +static ssize_t dump_control_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[4]; + size_t buf_size; + + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + switch (buf[0]) { + case '1': /* Dump send ack */ + if (dump_avail) { + dump_avail = 0; + free_dump_data_buf(); + dump_send_ack(dump_record.id); + } + break; + case '2': /* Initiate FipS dump */ + dump_fips_init(DUMP_TYPE_FSP); + break; + default: + break; + } + return count; +} + +static const struct file_operations dump_control_fops = { + .open = simple_open, + .write = dump_control_write, + .llseek = default_llseek, +}; + +/* + * Create dump debugfs file + */ +static int debugfs_dump_init(void) +{ + struct dentry *dir, *file; + + /* FSP dump directory */ + dir = debugfs_create_dir("fsp", NULL); + if (!dir) + goto out; + + /* README */ + readme_blob.data = (void *)readme_msg; + readme_blob.size = strlen(readme_msg); + file = debugfs_create_blob("README", 0400, dir, &readme_blob); + if (!file) + goto remove_dir; + + /* Dump available notification */ + file = debugfs_create_u32("dump_avail", 0400, dir, &dump_avail); + if (!file) + goto remove_dir; + + /* data file */ + dump_blob.data = (void *)dump_record.buffer; + dump_blob.size = dump_record.size; + file = debugfs_create_blob("dump", 0400, dir, &dump_blob); + if (!file) + goto remove_dir; + + /* Control file */ + file = debugfs_create_file("dump_control", 0200, dir, + NULL, &dump_control_fops); + if (!file) + goto remove_dir; + + return 0; + +remove_dir: + debugfs_remove_recursive(dir); + +out: + dump_disarmed = true; + return -1; +} + +void __init opal_platform_dump_init(void) +{ + int ret; + + /* Register for opal notifier */ + ret = opal_notifier_register(&dump_nb); + if (ret) { + pr_warn("%s: Can't register OPAL event notifier (%d)\n", + __func__, ret); + return; + } + + /* debugfs interface */ + ret = debugfs_dump_init(); +} diff --git a/arch/powerpc/platforms/powernv/opal-wrappers.S b/arch/powerpc/platforms/powernv/opal-wrappers.S index e780650..1485a09 100644 --- a/arch/powerpc/platforms/powernv/opal-wrappers.S +++ b/arch/powerpc/platforms/powernv/opal-wrappers.S @@ -126,3 +126,7 @@ OPAL_CALL(opal_return_cpu, OPAL_RETURN_CPU); OPAL_CALL(opal_validate_flash, OPAL_FLASH_VALIDATE); OPAL_CALL(opal_manage_flash, OPAL_FLASH_MANAGE); OPAL_CALL(opal_update_flash, OPAL_FLASH_UPDATE); +OPAL_CALL(opal_dump_init, OPAL_DUMP_INIT); +OPAL_CALL(opal_dump_info, OPAL_DUMP_INFO); +OPAL_CALL(opal_dump_read, OPAL_DUMP_READ); +OPAL_CALL(opal_dump_ack, OPAL_DUMP_ACK); diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c index 1c798cd..7c7524c 100644 --- a/arch/powerpc/platforms/powernv/opal.c +++ b/arch/powerpc/platforms/powernv/opal.c @@ -442,6 +442,8 @@ static int __init opal_init(void) if (rc == 0) { /* Setup code update interface */ opal_flash_init(); + /* Setup platform dump extract interface */ + opal_platform_dump_init(); } return 0; _______________________________________________ Linuxppc-dev mailing list Linuxppc-dev@lists.ozlabs.org https://lists.ozlabs.org/listinfo/linuxppc-dev