| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2020, The Linux Foundation. All rights reserved. |
| */ |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/platform_device.h> |
| #include <linux/qcom_scm.h> |
| #include <linux/utsname.h> |
| #include <linux/sizes.h> |
| #include <soc/qcom/ctx-save.h> |
| #include <linux/spinlock.h> |
| #include <linux/pfn.h> |
| #include <linux/list.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/mhi.h> |
| #include <linux/sysrq.h> |
| #include <linux/fs.h> |
| #include <linux/device.h> |
| #include <linux/types.h> |
| #include <linux/uaccess.h> |
| #include <linux/init.h> |
| #include <uapi/linux/major.h> |
| #include <linux/highmem.h> |
| #include <linux/ioctl.h> |
| |
| typedef struct ctx_save_tlv_msg { |
| unsigned char *msg_buffer; |
| unsigned char *cur_msg_buffer_pos; |
| unsigned int len; |
| spinlock_t spinlock; |
| bool is_panic; |
| } ctx_save_tlv_msg_t; |
| |
| #ifdef CONFIG_QCA_MINIDUMP |
| struct minidump_tlv_info { |
| uint64_t start; |
| uint64_t size; |
| }; |
| |
| /* Metadata List for bookkeeping and managing entries and invalidation of |
| * TLVs into the global crashdump buffer and the Metadata text file |
| */ |
| struct minidump_metadata_list { |
| struct list_head list; /*kernel's list structure*/ |
| unsigned long va; /* Virtual address of TLV. Set to 0 if invalid*/ |
| unsigned long pa; /*Physical address of TLV segment*/ |
| unsigned long modinfo_offset; /* Offset associated with the entry for |
| * module information in Metadata text file |
| */ |
| unsigned long size; /*size associated with TLV entry */ |
| unsigned char *tlv_offset; /* Offset associated with the TLV entry in |
| * the crashdump buffer |
| */ |
| unsigned long mmuinfo_offset; /* Offset associated with the entry for |
| * mmu information in MMU Metadata text file |
| */ |
| unsigned char type; |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| char *name; /* Name associated with the TLV */ |
| #endif |
| }; |
| #endif /* CONFIG_QCA_MINIDUMP */ |
| |
| struct ctx_save_props { |
| unsigned int tlv_msg_offset; |
| unsigned int crashdump_page_size; |
| }; |
| |
| ctx_save_tlv_msg_t tlv_msg; |
| |
| #ifdef CONFIG_QCA_MINIDUMP |
| struct minidump_metadata { |
| char mod_log[METADATA_FILE_SZ]; |
| unsigned long mod_log_len; |
| unsigned long cur_modinfo_offset; |
| char mmu_log[MMU_FILE_SZ]; |
| unsigned long mmu_log_len; |
| unsigned long cur_mmuinfo_offset; |
| }; |
| |
| struct minidump_metadata_list metadata_list; |
| struct minidump_metadata minidump_meta_info; |
| |
| static const struct file_operations mini_dump_ops; |
| static struct class *dump_class; |
| int dump_major = 0; |
| |
| struct dump_segment { |
| struct list_head node; |
| unsigned long addr; |
| size_t size; |
| }; |
| |
| struct mini_hdr { |
| int total_size; |
| int num_seg; |
| int flag; |
| unsigned char *type; |
| int *seg_size; |
| unsigned long *phy_addr; |
| }; |
| |
| struct dumpdev { |
| const char *name; |
| const struct file_operations *fops; |
| fmode_t fmode; |
| struct mini_hdr hdr; |
| struct list_head dump_segments; |
| } minidump = {"minidump", &mini_dump_ops, FMODE_UNSIGNED_OFFSET | FMODE_EXCL}; |
| |
| #define MINIDUMP_IOCTL_MAGIC 'm' |
| #define MINIDUMP_IOCTL_PREPARE_HDR _IOR(MINIDUMP_IOCTL_MAGIC, 0, int) |
| #define MINIDUMP_IOCTL_PREPARE_SEG _IOR(MINIDUMP_IOCTL_MAGIC, 1, int) |
| #define MINIDUMP_IOCTL_PREPARE_TYP _IOR(MINIDUMP_IOCTL_MAGIC, 2, int) |
| #define MINIDUMP_IOCTL_PREPARE_PHY _IOR(MINIDUMP_IOCTL_MAGIC, 3, int) |
| |
| #define REPLACE 1 |
| #define APPEND 0 |
| extern void minidump_get_pgd_info(uint64_t *pt_start, uint64_t *pt_len); |
| extern void minidump_get_linux_buf_info(uint64_t *plinux_buf, uint64_t *plinux_buf_len); |
| extern void minidump_get_log_buf_info(uint64_t *plog_buf, uint64_t *plog_buf_len); |
| extern struct list_head *minidump_modules; |
| char *minidump_module_list[MINIDUMP_MODULE_COUNT] = {"qca_ol", "wifi_3_0", "umac", "qdf"}; |
| int minidump_dump_wlan_modules(void); |
| extern int log_buf_len; |
| |
| /* |
| * Function: mini_dump_open |
| * |
| * Description: Traverse metadata list and store valid address |
| * size pairs in dump segment list for minidump device node. Also |
| * save useful metadata information of segment size, physical address |
| * and dump type per dump segment. |
| * |
| * Return: 0 |
| */ |
| static int mini_dump_open(struct inode *inode, struct file *file) { |
| struct minidump_metadata_list *cur_node; |
| struct list_head *pos; |
| unsigned long flags; |
| struct dump_segment *segment = NULL; |
| int index = 0; |
| |
| if (!tlv_msg.msg_buffer) { |
| return -ENOMEM; |
| } |
| |
| minidump.hdr.seg_size = (unsigned int *) |
| kmalloc((sizeof(int) * minidump.hdr.num_seg), GFP_KERNEL); |
| minidump.hdr.phy_addr =(unsigned long *) |
| kmalloc((sizeof(unsigned long) * minidump.hdr.num_seg), GFP_KERNEL); |
| minidump.hdr.type = (unsigned char *) |
| kmalloc((sizeof(unsigned char) * minidump.hdr.num_seg), GFP_KERNEL); |
| |
| if (!minidump.hdr.seg_size || !minidump.hdr.phy_addr || !minidump.hdr.type) { |
| return -ENOMEM; |
| } |
| |
| INIT_LIST_HEAD(&minidump.dump_segments); |
| spin_lock_irqsave(&tlv_msg.spinlock, flags); |
| |
| /* Traverse Metadata list and store valid address-size pairs in |
| * dump segment list for /dev/minidump |
| */ |
| list_for_each(pos, &metadata_list.list) { |
| cur_node = list_entry(pos, struct minidump_metadata_list, list); |
| |
| if (cur_node->va != INVALID) { |
| segment = (struct dump_segment *) kmalloc(sizeof(struct dump_segment), |
| GFP_KERNEL); |
| if (!segment) { |
| pr_err("\nMinidump: Unable to allocate memory for dump segment"); |
| return -ENOMEM; |
| } |
| if ( (cur_node->type == CTX_SAVE_LOG_DUMP_TYPE_WLAN_MOD_INFO) || |
| (cur_node->type == CTX_SAVE_LOG_DUMP_TYPE_WLAN_MMU_INFO) || |
| (cur_node->type == CTX_SAVE_LOG_DUMP_TYPE_DMESG) ) { |
| segment->size = *(unsigned long *)(uintptr_t) |
| ((unsigned long)__va(cur_node->size)); |
| } else { |
| segment->size = cur_node->size; |
| } |
| |
| segment->addr = cur_node->va; |
| list_add_tail(&(segment->node), &(minidump.dump_segments)); |
| minidump.hdr.total_size += segment->size; |
| minidump.hdr.seg_size[index] = segment->size; |
| minidump.hdr.phy_addr[index] = cur_node->pa; |
| minidump.hdr.type[index] = cur_node->type; |
| index ++; |
| } |
| } |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| |
| file->f_mode |= minidump.fmode; |
| file->private_data = (void *)&minidump; |
| |
| return 0; |
| } |
| |
| /* |
| * Function: mini_dump_release |
| * |
| * Description: Free resources for minidump device node |
| * |
| * Return: 0 |
| */ |
| static int mini_dump_release(struct inode *inode, struct file *file) { |
| int dump_minor_dev = iminor(inode); |
| int dump_major_dev = imajor(inode); |
| |
| struct dump_segment *segment, *tmp; |
| |
| struct dumpdev *dfp = (struct dumpdev *) file->private_data; |
| |
| list_for_each_entry_safe(segment, tmp, &dfp->dump_segments, node) { |
| list_del(&segment->node); |
| kfree(segment); |
| } |
| |
| kfree(minidump.hdr.seg_size); |
| kfree(minidump.hdr.phy_addr); |
| kfree(minidump.hdr.type); |
| |
| device_destroy(dump_class, MKDEV(dump_major_dev, dump_minor_dev)); |
| class_destroy(dump_class); |
| |
| dump_major = 0; |
| dump_class = NULL; |
| |
| return 0; |
| } |
| |
| /* |
| * Function: mini_dump_read |
| * |
| * Description: Traverse dump segment list and copy dump segment |
| * content into user space buffer |
| * |
| * Return: 0 |
| */ |
| static ssize_t mini_dump_read(struct file *file, char __user *buf, |
| size_t count, loff_t *ppos) { |
| |
| int ret = 0; |
| int seg_num = 0; |
| struct dumpdev *dfp = (struct dumpdev *) file->private_data; |
| struct dump_segment *segment, *tmp; |
| int copied = 0; |
| |
| list_for_each_entry_safe(segment, tmp, &dfp->dump_segments, node) { |
| size_t pending = 0; |
| seg_num ++; |
| pending = segment->size; |
| |
| ret = copy_to_user(buf, (const void *)(uintptr_t)segment->addr, pending); |
| if (ret) { |
| pr_info("\n Minidump: copy_to_user error"); |
| return 0; |
| } |
| |
| buf = buf + (pending - ret); |
| copied = copied + (pending-ret); |
| |
| list_del(&segment->node); |
| kfree(segment); |
| } |
| |
| return copied; |
| } |
| |
| /* |
| * Function: mini_dump_ioctl |
| * |
| * Description: Based on ioctl code, copy relevant metadata |
| * information to userspace buffer. |
| * |
| * Return: 0 |
| */ |
| static long mini_dump_ioctl(struct file *file, unsigned int ioctl_num, |
| unsigned long arg) { |
| |
| int ret = 0; |
| struct dumpdev *dfp = (struct dumpdev *) file->private_data; |
| |
| switch(ioctl_num) { |
| case MINIDUMP_IOCTL_PREPARE_HDR: |
| ret = copy_to_user((void __user *)arg, |
| (const void *)(uintptr_t)(&(dfp->hdr)), sizeof(dfp->hdr)); |
| break; |
| case MINIDUMP_IOCTL_PREPARE_SEG: |
| ret = copy_to_user((void __user *)arg, |
| (const void *)(uintptr_t)((dfp->hdr.seg_size)), |
| (sizeof(int) * minidump.hdr.num_seg)); |
| break; |
| case MINIDUMP_IOCTL_PREPARE_TYP: |
| ret = copy_to_user((void __user *)arg, |
| (const void *)(uintptr_t)((dfp->hdr.type)), |
| (sizeof(unsigned char) * minidump.hdr.num_seg)); |
| break; |
| case MINIDUMP_IOCTL_PREPARE_PHY: |
| ret = copy_to_user((void __user *)arg, |
| (const void *)(uintptr_t)((dfp->hdr.phy_addr)), |
| (sizeof(unsigned long) * minidump.hdr.num_seg)); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| return ret; |
| } |
| |
| static const struct file_operations mini_dump_ops = { |
| .open = mini_dump_open, |
| .read = mini_dump_read, |
| .unlocked_ioctl = mini_dump_ioctl, |
| .release = mini_dump_release, |
| }; |
| /* |
| * Function: do_minidump |
| * |
| * Description: Create and register minidump device node /dev/minidump |
| * |
| * @param: none |
| * |
| * Return: 0 |
| */ |
| int do_minidump(void) { |
| |
| int ret = 0; |
| struct device *dump_dev = NULL; |
| |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| int count =0; |
| struct minidump_metadata_list *cur_node; |
| struct list_head *pos; |
| unsigned long flags; |
| #endif |
| |
| minidump.hdr.total_size = 0; |
| if (!tlv_msg.msg_buffer) { |
| pr_err("\n Minidump: Crashdump buffer is empty"); |
| return NOTIFY_OK; |
| } |
| |
| /* Add subset of kernel module list to minidump metadata list */ |
| ret = minidump_dump_wlan_modules(); |
| if (ret) |
| pr_err("Minidump: Error dumping modules: %d", ret); |
| |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| pr_err("\n Minidump: Size of Metadata file = %ld", minidump_meta_info.mod_log_len); |
| pr_err("\n Minidump: Printing out contents of Metadata list"); |
| |
| spin_lock_irqsave(&tlv_msg.spinlock, flags); |
| list_for_each(pos, &metadata_list.list) { |
| count ++; |
| cur_node = list_entry(pos, struct minidump_metadata_list, list); |
| if (cur_node->va != 0) { |
| if (cur_node->name != NULL) |
| pr_info(" %s [%lx] ---> ",cur_node->name,cur_node->va); |
| else |
| pr_info(" un-named [%lx] ---> ",cur_node->va); |
| } |
| } |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| pr_err("\n Minidump: # nodes in the Metadata list = %d",count); |
| pr_err("\n Minidump: Size of node in Metadata list = %ld\n", |
| (unsigned long)sizeof(struct minidump_metadata_list)); |
| #endif |
| |
| if(dump_class || dump_major) { |
| device_destroy(dump_class, MKDEV(dump_major, 0)); |
| class_destroy(dump_class); |
| } |
| |
| dump_major = register_chrdev(UNNAMED_MAJOR, "minidump", &mini_dump_ops); |
| if (dump_major < 0) { |
| ret = dump_major; |
| pr_err("Unable to allocate a major number err = %d \n", ret); |
| goto reg_failed; |
| } |
| |
| dump_class = class_create(THIS_MODULE, "minidump"); |
| if (IS_ERR(dump_class)) { |
| ret = PTR_ERR(dump_class); |
| pr_err("Unable to create dump class = %d\n", ret); |
| goto class_failed; |
| } |
| |
| dump_dev = device_create(dump_class, NULL, MKDEV(dump_major, 0), NULL,minidump.name); |
| if (IS_ERR(dump_dev)) { |
| ret = PTR_ERR(dump_dev); |
| pr_err("Unable to create a device err = %d\n", ret); |
| goto device_failed; |
| } |
| |
| return ret; |
| device_failed: |
| class_destroy(dump_class); |
| class_failed: |
| unregister_chrdev(dump_major, "minidump"); |
| reg_failed: |
| return ret; |
| |
| } |
| EXPORT_SYMBOL(do_minidump); |
| |
| /* |
| * Function: sysrq_minidump_handler |
| * |
| * Description: Handler function for sysrq key event which |
| * is invoked on command line trigger 'echo y > /proc/sysrq-trigger' |
| * |
| * @param: key registered for sysrq event |
| * |
| * Return: 0 |
| */ |
| static void sysrq_minidump_handler(int key) { |
| int ret =0; |
| ret = do_minidump(); |
| if (ret) |
| pr_info("\n Minidump: unable to init minidump dev node"); |
| |
| } |
| |
| static struct sysrq_key_op sysrq_minidump_op = { |
| .handler = sysrq_minidump_handler, |
| .help_msg = "minidump(y)", |
| .action_msg = "MINIDUMP", |
| }; |
| #endif /* CONFIG_QCA_MINIDUMP */ |
| |
| /* |
| * Function: ctx_save_replace_tlv |
| * Description: Adds dump segment as a TLV into the global crashdump |
| * buffer at specified offset. |
| * |
| * @param: [in] type - Type associated with Dump segment |
| * [in] size - Size associted with Dump segment |
| * [in] data - Physical address of the Dump segment |
| * [in] offset - offset at which TLV entry is added to the crashdump |
| * buffer |
| * |
| * Return: 0 on success, -ENOBUFS on failure |
| */ |
| int ctx_save_replace_tlv(unsigned char type, unsigned int size, const char *data, unsigned char *offset) |
| { |
| unsigned char *x; |
| unsigned char *y; |
| unsigned long flags; |
| |
| if (!tlv_msg.msg_buffer) { |
| return -ENOMEM; |
| } |
| |
| spin_lock_irqsave(&tlv_msg.spinlock, flags); |
| x = offset; |
| y = tlv_msg.msg_buffer + tlv_msg.len; |
| |
| if ((x + CTX_SAVE_SCM_TLV_TYPE_LEN_SIZE + size) >= y) { |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| return -ENOBUFS; |
| } |
| |
| x[0] = type; |
| x[1] = size; |
| x[2] = size >> 8; |
| |
| memcpy(x + 3, data, size); |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| |
| return 0; |
| } |
| /* |
| * Function: ctx_save_add_tlv |
| * Description: Appends dump segment as a TLV entry to the end of the |
| * global crashdump buffer. |
| * |
| * @param: [in] type - Type associated with Dump segment |
| * [in] size - Size associated with Dump segment |
| * [in] data - Physical address of the Dump segment |
| * |
| * Return: 0 on success, -ENOBUFS on failure |
| */ |
| int ctx_save_add_tlv(unsigned char type, unsigned int size, const char *data) |
| { |
| unsigned char *x; |
| unsigned char *y; |
| unsigned long flags; |
| |
| if (!tlv_msg.msg_buffer) { |
| return -ENOMEM; |
| } |
| |
| spin_lock_irqsave(&tlv_msg.spinlock, flags); |
| x = tlv_msg.cur_msg_buffer_pos; |
| y = tlv_msg.msg_buffer + tlv_msg.len; |
| |
| if ((x + CTX_SAVE_SCM_TLV_TYPE_LEN_SIZE + size) >= y) { |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| return -ENOBUFS; |
| } |
| |
| x[0] = type; |
| x[1] = size; |
| x[2] = size >> 8; |
| |
| memcpy(x + 3, data, size); |
| |
| tlv_msg.cur_msg_buffer_pos += |
| (size + CTX_SAVE_SCM_TLV_TYPE_LEN_SIZE); |
| |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| return 0; |
| } |
| |
| /* |
| * Function: minidump_remove_segments |
| * Description: Traverse metadata list and search for the TLV |
| * entry corresponding to the input virtual address. If found, |
| * set va of the Metadata list node to 0 and invalidate the TLV |
| * entry in the crashdump buffer by setting type to |
| * CTX_SAVE_LOG_DUMP_TYPE_EMPTY |
| * |
| * @param: [in] virt_addr - virtual address of the TLV to be invalidated |
| * |
| * Return: 0 |
| */ |
| #ifdef CONFIG_QCA_MINIDUMP |
| int minidump_remove_segments(const uint64_t virt_addr) |
| { |
| struct minidump_metadata_list *cur_node; |
| struct list_head *pos; |
| unsigned long flags; |
| |
| if (!tlv_msg.msg_buffer) { |
| return -ENOMEM; |
| } |
| if (!virt_addr) { |
| pr_info("\nMINIDUMP: Attempt to remove an invalid VA."); |
| return 0; |
| } |
| spin_lock_irqsave(&tlv_msg.spinlock, flags); |
| /* Traverse Metadata list*/ |
| list_for_each(pos, &metadata_list.list) { |
| cur_node = list_entry(pos, struct minidump_metadata_list, list); |
| if (cur_node->va == virt_addr) { |
| /* If entry with a matching va is found, invalidate |
| * this entry by setting va to 0 |
| */ |
| cur_node->va = INVALID; |
| /* Invalidate TLV entry in the crashdump buffer by setting type |
| * ( value pointed to by cur_node->tlv_offset ) to |
| * CTX_SAVE_LOG_DUMP_TYPE_EMPTY |
| */ |
| *(cur_node->tlv_offset) = CTX_SAVE_LOG_DUMP_TYPE_EMPTY; |
| |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| if(cur_node->name!=NULL) { |
| kfree(cur_node->name); |
| cur_node->name = NULL; |
| } |
| #endif |
| |
| /* If the metadata list node has an entry in the Metadata file, |
| * invalidate that entry. |
| */ |
| if (cur_node->modinfo_offset != 0) |
| memset((void *)(uintptr_t)cur_node->modinfo_offset, '\0', |
| METADATA_FILE_ENTRY_LEN); |
| |
| /* If the metadata list node has an entry in the MMU Metadata file, |
| * invalidate that entry. |
| */ |
| if (cur_node->mmuinfo_offset != 0) |
| memset((void *)(uintptr_t)cur_node->mmuinfo_offset, '\0', |
| MMU_FILE_ENTRY_LEN); |
| |
| minidump.hdr.num_seg--; |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| return 0; |
| } |
| EXPORT_SYMBOL(minidump_remove_segments); |
| |
| /* |
| * Function: minidump_traverse_metadata_list |
| * |
| * Description: Maintain a Metadata list to keep track |
| * of TLVs in the crashdump buffer and entries in the Meta |
| * data file and MMU Metadata file. |
| * |
| * Each node in the Metadata list stores the name and virtual |
| * address associated with the dump segments and three offsets, |
| * tlv_offset, mod_offset and mmu_offset that stores the offset |
| * corresponding to the TLV in the crashdump buffer and the |
| * entries in the Metadata file and MMU Metadata file. |
| * |
| * Metadata file (12 K) MMU Metadata file(12 K) |
| * |-------------| |------------| |
| * | Entry 1 |<--| | Entry 1 | |
| * |-------------| | |------------| |
| * | Entry 2 | | |----> | Entry 2 | |
| * |-------------| | | |------------| |
| * |--->| Entry 3 | | | | Entry 3 | |
| * | |-------------| | | |------------| |
| * | | Entry 4 | | | |-->| Entry 4 | |
| * | |-------------| | | | |------------| |
| * | | Entry n | | | | | Entry n | |
| * | |-------------| | | | |------------| |
| * | | | | |
| * | |--------------------|-|---| |
| * | | Metadata List | | |
| * -------------------------------------------------------- |
| * | Node | Node | Node | Node | Node | Node | Node | Node | |
| * | 1 | 2 | 3 | 4 | 5 | 6 | 7 | n | |
| * -------------------------------------------------------- |
| * | | |
| * | | |
| * |-------------------| | |
| * --------------| |
| * | | |
| * \/ \/ |
| * -------------------------------------------------------------- |
| * | | | | | | | | |
| * | TLV | TLV | TLV | TLV | TLV | TLV | TLV | |
| * | | | | | | | | |
| * -------------------------------------------------------------- |
| * Crashdump Buffer (12 K) |
| * |
| * When a dump segment needs to be added, the Metadata list is travered |
| * to check if any invalid entries (entries with va = 0) exist. If an invalid |
| * enrty exists, name and va of the node is updated with info from new dump segment |
| * and the dump segment is added as a TLV in the crashdump buffer at tlv_offset. If |
| * the dumpsegment has a valid name, entry is added to the Metadata file at mod_offset. |
| * |
| * |
| * @param: name - name associated with TLV |
| * [in] virt_addr - virtual address of the Dump segment to be added |
| * [in] phy_addr - physical address of the Dump segment to be added |
| * [out] tlv_offset - offset at which corresponding TLV entry will be |
| * added to the crashdump buffer |
| * |
| * Return: 'REPLACE' if TLV needs to be inserted into the crashdump buffer at |
| * offset position. 'APPEND' if TLV needs to be appended to the crashdump buffer. |
| * Also tlv_offset is updated to offset at which corresponding TLV entry will be |
| * added to the crashdump buffer. Return -ENOMEM if new list node was not created |
| * due to an alloc failure or NULL address. Return -EINVAL if there is an attempt to |
| * add a duplicate entry |
| */ |
| int minidump_traverse_metadata_list(const char *name, const unsigned long virt_addr, const unsigned long phy_addr, |
| unsigned char **tlv_offset, unsigned long size, unsigned char type) |
| { |
| |
| unsigned long flags; |
| struct minidump_metadata_list *cur_node; |
| struct minidump_metadata_list *list_node; |
| struct list_head *pos; |
| int invalid_flag = 0; |
| cur_node = NULL; |
| |
| /* If tlv_msg has not been initialized with non NULL value , return error*/ |
| if (!tlv_msg.msg_buffer) { |
| return -ENOMEM; |
| } |
| |
| spin_lock_irqsave(&tlv_msg.spinlock, flags); |
| list_for_each(pos, &metadata_list.list) { |
| /* Traverse Metadata list to check if dump sgment to be added |
| already has a duplicate entry in the crashdump buffer. Also store address |
| of first invalid entry , if it exists. Return EINVAL*/ |
| list_node = list_entry(pos, struct minidump_metadata_list, list); |
| if (list_node->va == virt_addr && list_node->size == size) { |
| spin_unlock_irqrestore(&tlv_msg.spinlock, |
| flags); |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| pr_debug("Minidump: TLV entry with this VA is already present %s %lx\n",name,virt_addr); |
| #endif |
| return -EINVAL; |
| } |
| |
| if (!invalid_flag) { |
| if (list_node->va == INVALID) { |
| cur_node = list_node; |
| invalid_flag = 1; |
| } |
| } |
| } |
| |
| if (invalid_flag && cur_node) { |
| /* If an invalid entry exits, update node entries and use |
| * offset values to write TLVs to the crashdump buffer and |
| * an entry in the Metadata file if applicable. |
| */ |
| *tlv_offset = cur_node->tlv_offset; |
| cur_node->va = virt_addr; |
| cur_node->pa = phy_addr; |
| cur_node->size = size; |
| cur_node->type = type; |
| minidump.hdr.num_seg++; |
| |
| if (cur_node->modinfo_offset != 0) { |
| /* If the metadata list node has an entry in the Metadata file, |
| * invalidate that entry and update metadata file pointer with the |
| * value at mod_offset. |
| */ |
| minidump_meta_info.cur_modinfo_offset = cur_node->modinfo_offset; |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| if (name != NULL) { |
| cur_node->name = kstrndup(name, strlen(name), GFP_KERNEL); |
| } |
| #endif |
| } else { |
| if (name != NULL) { |
| /* If the metadta list node does not have an entry in the |
| * Metdata file, update metadata file pointer to point |
| * to the end of the metadata file. |
| */ |
| cur_node->modinfo_offset = minidump_meta_info.cur_modinfo_offset; |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| cur_node->name = kstrndup(name, strlen(name), GFP_KERNEL); |
| #endif |
| } |
| } |
| |
| if (cur_node->mmuinfo_offset != 0) { |
| /* If the metadata list node has an entry in the MMU Metadata file, |
| * invalidate that entry and update the MMU metadata file pointer with the |
| * value at mmu_offset. |
| */ |
| minidump_meta_info.cur_mmuinfo_offset = cur_node->mmuinfo_offset; |
| } else { |
| if ( IS_ENABLED(CONFIG_ARM64) || ( (unsigned long)virt_addr < PAGE_OFFSET |
| || (unsigned long)virt_addr >= (unsigned long)high_memory) ) |
| cur_node->mmuinfo_offset = minidump_meta_info.cur_mmuinfo_offset; |
| } |
| |
| spin_unlock_irqrestore(&tlv_msg.spinlock, |
| flags); |
| /* return REPLACE to indicate TLV needs to be inserted to the crashdump buffer*/ |
| return REPLACE; |
| } |
| |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| /* |
| * If no invalid entry was found, create new node provided the |
| * crashdump buffer, metadata file and mmu metadata file are not full. |
| */ |
| if ((tlv_msg.cur_msg_buffer_pos + CTX_SAVE_SCM_TLV_TYPE_LEN_SIZE + |
| sizeof(struct minidump_tlv_info) >= |
| tlv_msg.msg_buffer + tlv_msg.len) || |
| (minidump_meta_info.mod_log_len + METADATA_FILE_ENTRY_LEN >= METADATA_FILE_SZ)) { |
| return -ENOMEM; |
| } |
| |
| if ((tlv_msg.cur_msg_buffer_pos + CTX_SAVE_SCM_TLV_TYPE_LEN_SIZE + |
| sizeof(struct minidump_tlv_info) >= |
| tlv_msg.msg_buffer + tlv_msg.len) || |
| (minidump_meta_info.mmu_log_len + MMU_FILE_ENTRY_LEN >= MMU_FILE_SZ)) { |
| return -ENOMEM; |
| } |
| |
| cur_node = (struct minidump_metadata_list *) |
| kmalloc(sizeof(struct minidump_metadata_list), GFP_KERNEL); |
| |
| if (!cur_node) { |
| return -ENOMEM; |
| } |
| |
| if (name != NULL) { |
| /* If dump segment has a valid name, update name and offset with |
| * pointer to the Metadata file |
| */ |
| cur_node->modinfo_offset = minidump_meta_info.cur_modinfo_offset; |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| cur_node->name = kstrndup(name, strlen(name), GFP_KERNEL); |
| #endif |
| } else { |
| /* If dump segment does not have a valid name, set name to null and |
| * mod_offset to 0 |
| */ |
| cur_node->modinfo_offset = 0; |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| cur_node->name = NULL; |
| #endif |
| } |
| /* Update va and offsets to crashdump buffer and MMU Metadata file*/ |
| cur_node->va = virt_addr; |
| cur_node->size = size; |
| cur_node->pa = phy_addr; |
| cur_node->type = type; |
| cur_node->tlv_offset = tlv_msg.cur_msg_buffer_pos; |
| if ( IS_ENABLED(CONFIG_ARM64) || ( (unsigned long)virt_addr < PAGE_OFFSET |
| || (unsigned long)virt_addr >= (unsigned long)high_memory) ) { |
| cur_node->mmuinfo_offset = minidump_meta_info.cur_mmuinfo_offset; |
| } else { |
| cur_node->mmuinfo_offset = 0; |
| } |
| minidump.hdr.num_seg++; |
| spin_lock_irqsave(&tlv_msg.spinlock, flags); |
| list_add_tail(&(cur_node->list), &(metadata_list.list)); |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| /* return APPEND to indicate TLV needs to be appended to the crashdump buffer*/ |
| return APPEND; |
| } |
| |
| /* |
| * Function: minidump_fill_tlv_crashdump_buffer |
| * |
| * Description: Add TLV entries into the global crashdump |
| * buffer at specified offset. |
| * |
| * @param: [in] start_address - Physical address of Dump segment |
| * [in] type - Type associated with the Dump segment |
| * [in] size - Size associated with the Dump segment |
| * [in] replace - Flag used to determine if TLV entry needs to be |
| * inserted at a specified offset or appended to the end of |
| * the crashdump buffer |
| * [in] tlv_offset - offset at which TLV entry is added to the |
| * crashdump buffer |
| * |
| * Return: 0 on success, -ENOBUFS on failure |
| */ |
| int minidump_fill_tlv_crashdump_buffer(const uint64_t start_addr, uint64_t size, |
| minidump_tlv_type_t type, unsigned int replace, unsigned char *tlv_offset) |
| { |
| struct minidump_tlv_info minidump_tlv_info; |
| |
| int ret; |
| |
| minidump_tlv_info.start = start_addr; |
| minidump_tlv_info.size = size; |
| |
| if (replace && (*(tlv_offset) == CTX_SAVE_LOG_DUMP_TYPE_EMPTY)) { |
| ret = ctx_save_replace_tlv(type, |
| sizeof(minidump_tlv_info), |
| (unsigned char *)&minidump_tlv_info, tlv_offset); |
| } else { |
| ret = ctx_save_add_tlv(type, |
| sizeof(minidump_tlv_info), |
| (unsigned char *)&minidump_tlv_info); |
| } |
| |
| if (ret) { |
| pr_err("Minidump: Crashdump buffer is full %d\n", ret); |
| return ret; |
| } |
| |
| if (tlv_msg.cur_msg_buffer_pos >= |
| tlv_msg.msg_buffer + tlv_msg.len){ |
| pr_err("MINIDUMP buffer overflow %d\n", (int)type); |
| return -ENOBUFS; |
| } |
| *tlv_msg.cur_msg_buffer_pos = |
| CTX_SAVE_LOG_DUMP_TYPE_INVALID; |
| |
| return 0; |
| } |
| |
| /* |
| * Function: minidump_fill_segments |
| * |
| * Description: Add a dump segment as a TLV entry in the Metadata list |
| * and global crashdump buffer. Store relevant VA and PA information in |
| * MMU Metadata file. Also writes module information to Metadata text |
| * file, which is useful for post processing of collected dumps. |
| * |
| * @param: [in] start_address - Virtual address of Dump segment |
| * [in] type - Type associated with the Dump segment |
| * [in] size - Size associated with the Dump segment |
| * [in] name - name associated with the Dump segment. Can be set to NULL. |
| * |
| * Return: 0 on success, -ENOMEM on failure |
| */ |
| int minidump_fill_segments(const uint64_t start_addr, uint64_t size, minidump_tlv_type_t type, const char *name) |
| { |
| |
| int ret = 0; |
| unsigned int replace = 0; |
| int highmem = 0; |
| struct page *minidump_tlv_page; |
| uint64_t phys_addr; |
| unsigned char *tlv_offset = NULL; |
| |
| /* |
| * Calculate PA of Dump segment using relevant APIs for lowmem and highmem |
| * virtual address. |
| */ |
| if ((unsigned long)start_addr >= PAGE_OFFSET && (unsigned long) start_addr |
| < (unsigned long)high_memory) { |
| phys_addr = (uint64_t)__pa(start_addr); |
| } else { |
| minidump_tlv_page = vmalloc_to_page((const void *)(uintptr_t) |
| (start_addr & (~(PAGE_SIZE - 1)))); |
| phys_addr = page_to_phys(minidump_tlv_page) + offset_in_page(start_addr); |
| highmem = 1; |
| } |
| |
| replace = minidump_traverse_metadata_list(name, start_addr,(const unsigned long)phys_addr, &tlv_offset, size, (unsigned char)type); |
| /* return value of -ENOMEM indicates new list node was not created |
| * due to an alloc failure. return value of -EINVAL indicates an attempt to |
| * add a duplicate entry |
| */ |
| if (replace == -EINVAL) |
| return 0; |
| |
| if (replace == -ENOMEM) |
| return replace; |
| |
| ret = minidump_fill_tlv_crashdump_buffer((const uint64_t)phys_addr, size, type, replace, tlv_offset); |
| if (ret) |
| return ret; |
| |
| if (IS_ENABLED(CONFIG_ARM64) || highmem ) |
| minidump_store_mmu_info(start_addr,(const unsigned long)phys_addr); |
| |
| if (name) |
| minidump_store_module_info(name, start_addr,(const unsigned long)phys_addr, type); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(minidump_fill_segments); |
| /* |
| * Function: minidump_store_mmu_info |
| * Description: Add virtual address and physical address |
| * information to a metadata file 'MMU_INFO.txt' at the |
| * specified offset. Useful for post processing with the |
| * collected dumps and offline pagetable constuction |
| * |
| * @param: [in] va - Virtual address of Dump segment |
| * [in] pa - Physical address of the Dump segment |
| * |
| * Return: 0 on success, -ENOBUFS on failure |
| */ |
| int minidump_store_mmu_info(const unsigned long va, const unsigned long pa) |
| { |
| |
| char substring[MMU_FILE_ENTRY_LEN]; |
| int ret_val =0; |
| unsigned long flags; |
| |
| if (!tlv_msg.msg_buffer) { |
| return -ENOBUFS; |
| } |
| |
| /* Check for Metadata file overflow */ |
| if ((minidump_meta_info.cur_mmuinfo_offset == (uintptr_t)minidump_meta_info.mmu_log + minidump_meta_info.mmu_log_len) && |
| (minidump_meta_info.mmu_log_len + MMU_FILE_ENTRY_LEN >= MMU_FILE_SZ)) { |
| pr_err("\nMINIDUMP Metadata file overflow error"); |
| return 0; |
| } |
| |
| /* |
| * Check for valid minidump_meta_info.cur_modinfo_offset value. Ensure |
| * that the offset is not NULL and is within bounds |
| * of the Metadata file. |
| */ |
| |
| if ((!(void *)(uintptr_t)minidump_meta_info.cur_mmuinfo_offset) || |
| (minidump_meta_info.cur_mmuinfo_offset < (uintptr_t)minidump_meta_info.mmu_log) || |
| (minidump_meta_info.cur_mmuinfo_offset + MMU_FILE_ENTRY_LEN >= |
| ((uintptr_t)minidump_meta_info.mmu_log + MMU_FILE_SZ))) { |
| pr_err("\nMINIDUMP Metadata file offset error"); |
| return -ENOBUFS; |
| } |
| |
| ret_val = snprintf(substring, MMU_FILE_ENTRY_LEN, |
| "\nva=%lx pa=%lx", va,pa); |
| |
| /* Check for Metadatafile overflow */ |
| if (minidump_meta_info.mmu_log_len + MMU_FILE_ENTRY_LEN >= MMU_FILE_SZ) { |
| return -ENOBUFS; |
| } |
| |
| spin_lock_irqsave(&tlv_msg.spinlock, flags); |
| memset((void *)(uintptr_t)minidump_meta_info.cur_mmuinfo_offset, '\0', MMU_FILE_ENTRY_LEN); |
| snprintf((char *)(uintptr_t)minidump_meta_info.cur_mmuinfo_offset, MMU_FILE_ENTRY_LEN, "%s", substring); |
| |
| if (minidump_meta_info.cur_mmuinfo_offset == (uintptr_t)minidump_meta_info.mmu_log + minidump_meta_info.mmu_log_len) { |
| minidump_meta_info.mmu_log_len = minidump_meta_info.mmu_log_len + MMU_FILE_ENTRY_LEN; |
| minidump_meta_info.cur_mmuinfo_offset = (uintptr_t)minidump_meta_info.mmu_log + minidump_meta_info.mmu_log_len; |
| } else { |
| minidump_meta_info.cur_mmuinfo_offset = (uintptr_t)minidump_meta_info.mmu_log + minidump_meta_info.mmu_log_len; |
| } |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| return 0; |
| } |
| /* |
| * Function: store_module_info |
| * Description: Add module name and virtual address information |
| * to a metadata file 'MODULE_INFO.txt' at the specified offset. |
| * Useful for post processing with the collected dumps. |
| * |
| * @param: [in] address - Virtual address of Dump segment |
| * [in] type - Type associated with the Dump segment |
| * [in] name - name associated with the Dump segment. |
| * If set to NULL,enrty is not written to the file |
| * |
| * Return: 0 on success, -ENOBUFS on failure |
| */ |
| int minidump_store_module_info(const char *name ,const unsigned long va, |
| const unsigned long pa, minidump_tlv_type_t type) |
| { |
| |
| char substring[METADATA_FILE_ENTRY_LEN]; |
| char *mod_name; |
| int ret_val =0; |
| unsigned long flags; |
| |
| if (!tlv_msg.msg_buffer) { |
| return -ENOBUFS; |
| } |
| |
| /* Check for Metadata file overflow */ |
| if ((minidump_meta_info.cur_modinfo_offset == (uintptr_t)minidump_meta_info.mod_log + minidump_meta_info.mod_log_len) && |
| (minidump_meta_info.mod_log_len + METADATA_FILE_ENTRY_LEN >= METADATA_FILE_SZ)) { |
| pr_err("\nMINIDUMP Metadata file overflow error"); |
| return 0; |
| } |
| |
| /* |
| * Check for valid minidump_meta_info.cur_modinfo_offset value. Ensure |
| * that the offset is not NULL and is within bounds |
| * of the Metadata file. |
| */ |
| if ((!(void *)(uintptr_t)minidump_meta_info.cur_modinfo_offset) || |
| (minidump_meta_info.cur_modinfo_offset < (uintptr_t)minidump_meta_info.mod_log) || |
| (minidump_meta_info.cur_modinfo_offset + METADATA_FILE_ENTRY_LEN >= |
| ((uintptr_t)minidump_meta_info.mod_log + METADATA_FILE_SZ))) { |
| pr_err("\nMINIDUMP Metadata file offset error"); |
| return -ENOBUFS; |
| } |
| |
| /* Check for valid name */ |
| if (!name) |
| return 0; |
| |
| mod_name = kstrndup(name, strlen(name), GFP_KERNEL); |
| if (!mod_name) |
| return 0; |
| |
| /* Truncate name if name is greater than 28 char */ |
| if (strlen(mod_name) > NAME_LEN) { |
| mod_name[NAME_LEN] = '\0'; |
| } |
| |
| if (type == CTX_SAVE_LOG_DUMP_TYPE_LEVEL1_PT || type == CTX_SAVE_LOG_DUMP_TYPE_DMESG) { |
| ret_val = snprintf(substring, METADATA_FILE_ENTRY_LEN, |
| "\n%s pa=%lx", mod_name, (unsigned long)pa); |
| } else if (type == CTX_SAVE_LOG_DUMP_TYPE_WLAN_MOD_DEBUGFS) { |
| ret_val = snprintf(substring, METADATA_FILE_ENTRY_LEN, |
| "\nDFS %s pa=%lx", mod_name, (unsigned long)pa); |
| } else { |
| ret_val = snprintf(substring, METADATA_FILE_ENTRY_LEN, |
| "\n%s va=%lx", mod_name, va); |
| } |
| |
| /* Check for Metadatafile overflow */ |
| if (minidump_meta_info.mod_log_len + METADATA_FILE_ENTRY_LEN >= METADATA_FILE_SZ) { |
| kfree(mod_name); |
| return -ENOBUFS; |
| } |
| |
| spin_lock_irqsave(&tlv_msg.spinlock, flags); |
| memset((void *)(uintptr_t)minidump_meta_info.cur_modinfo_offset, '\0', METADATA_FILE_ENTRY_LEN); |
| snprintf((char *)(uintptr_t)minidump_meta_info.cur_modinfo_offset, METADATA_FILE_ENTRY_LEN, "%s", substring); |
| |
| if (minidump_meta_info.cur_modinfo_offset == (uintptr_t)minidump_meta_info.mod_log + minidump_meta_info.mod_log_len) { |
| minidump_meta_info.mod_log_len = minidump_meta_info.mod_log_len + METADATA_FILE_ENTRY_LEN; |
| minidump_meta_info.cur_modinfo_offset = (uintptr_t)minidump_meta_info.mod_log + minidump_meta_info.mod_log_len; |
| } else { |
| minidump_meta_info.cur_modinfo_offset = (uintptr_t)minidump_meta_info.mod_log + minidump_meta_info.mod_log_len; |
| } |
| spin_unlock_irqrestore(&tlv_msg.spinlock, flags); |
| kfree(mod_name); |
| return 0; |
| } |
| #endif /* CONFIG_QCA_MINIDUMP */ |
| |
| /* |
| * Function: ctx_save_fill_log_dump_tlv |
| * Description: Add 'static' dump segments - uname, demsg, |
| * page global directory, linux buffer and metadata text |
| * file to the global crashdump buffer |
| * |
| * |
| * Return: 0 on success, -ENOBUFS on failure |
| */ |
| static int ctx_save_fill_log_dump_tlv(void) |
| { |
| struct new_utsname *uname; |
| int ret_val; |
| |
| #ifdef CONFIG_QCA_MINIDUMP |
| struct minidump_tlv_info pagetable_tlv_info; |
| struct minidump_tlv_info log_buf_info; |
| struct minidump_tlv_info linux_banner_info; |
| minidump_meta_info.mod_log_len = 0; |
| minidump.hdr.num_seg = 0; |
| minidump_meta_info.cur_modinfo_offset = (uintptr_t)minidump_meta_info.mod_log; |
| minidump_meta_info.mmu_log_len = 0; |
| minidump_meta_info.cur_mmuinfo_offset = (uintptr_t)minidump_meta_info.mmu_log; |
| INIT_LIST_HEAD(&metadata_list.list); |
| #endif /* CONFIG_QCA_MINIDUMP */ |
| uname = utsname(); |
| |
| ret_val = ctx_save_add_tlv(CTX_SAVE_LOG_DUMP_TYPE_UNAME, |
| sizeof(*uname), |
| (unsigned char *)uname); |
| if (ret_val) |
| return ret_val; |
| |
| #ifdef CONFIG_QCA_MINIDUMP |
| minidump_get_log_buf_info(&log_buf_info.start, &log_buf_info.size); |
| ret_val = minidump_fill_segments(log_buf_info.start, log_buf_info.size, |
| CTX_SAVE_LOG_DUMP_TYPE_DMESG, "DMESG"); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d \n", ret_val); |
| return ret_val; |
| } |
| |
| minidump_get_pgd_info(&pagetable_tlv_info.start, &pagetable_tlv_info.size); |
| ret_val = minidump_fill_segments(pagetable_tlv_info.start, |
| pagetable_tlv_info.size, CTX_SAVE_LOG_DUMP_TYPE_LEVEL1_PT, "PGD"); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d \n", ret_val); |
| return ret_val; |
| } |
| |
| minidump_get_linux_buf_info(&linux_banner_info.start, &linux_banner_info.size); |
| ret_val = minidump_fill_segments(linux_banner_info.start, linux_banner_info.size, |
| CTX_SAVE_LOG_DUMP_TYPE_WLAN_MOD, NULL); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d \n", ret_val); |
| return ret_val; |
| } |
| |
| ret_val = minidump_fill_segments((uint64_t)(uintptr_t)minidump_meta_info.mod_log,(uint64_t)__pa(&minidump_meta_info.mod_log_len), |
| CTX_SAVE_LOG_DUMP_TYPE_WLAN_MOD_INFO, NULL); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d \n", ret_val); |
| return ret_val; |
| } |
| |
| ret_val = minidump_fill_segments((uint64_t)(uintptr_t)minidump_meta_info.mmu_log,(uint64_t)__pa(&minidump_meta_info.mmu_log_len), |
| CTX_SAVE_LOG_DUMP_TYPE_WLAN_MMU_INFO, NULL); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d \n", ret_val); |
| return ret_val; |
| } |
| #endif /* CONFIG_QCA_MINIDUMP */ |
| if (tlv_msg.cur_msg_buffer_pos >= |
| tlv_msg.msg_buffer + tlv_msg.len) |
| return -ENOBUFS; |
| |
| return 0; |
| } |
| |
| /* |
| * Function: minidump_dump_wlan_modules |
| * Description: Add module structure, section attributes and bss sections |
| * of specified modules to the Metadata list. Also include a subset of |
| * kernel module list in the Metadata list due to T32 limitaion |
| * |
| * T32 Limitation: T32 scripts expect to parse the module list from |
| * the list head , and allows loading of specific modules only if it |
| * is included in this list. Instead of dumping each node of the complete |
| * kernel module list ( which is very large and will take up a lot of TLVs ), |
| * dump only a subset of the list that is required to load the specified modules. |
| * |
| * @param: none |
| * |
| * Return: NOTIFY_DONE on success, -ENOMEM on failure |
| */ |
| #ifdef CONFIG_QCA_MINIDUMP |
| int minidump_dump_wlan_modules(void){ |
| |
| struct module *mod; |
| struct minidump_tlv_info module_tlv_info; |
| int ret_val; |
| unsigned int i; |
| int wlan_count = 0; |
| int minidump_module_list_index; |
| |
| /*Dump list head*/ |
| module_tlv_info.start = (uintptr_t)minidump_modules; |
| module_tlv_info.size = sizeof(struct module); |
| ret_val = minidump_fill_segments(module_tlv_info.start, |
| module_tlv_info.size, CTX_SAVE_LOG_DUMP_TYPE_WLAN_MOD, "mod_list_head"); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d\n", ret_val); |
| return ret_val; |
| } |
| |
| list_for_each_entry_rcu(mod, minidump_modules, list) { |
| |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| pr_info("\n Dumping %s \n",mod->name); |
| #endif |
| |
| if (mod->state != MODULE_STATE_LIVE) |
| continue; |
| |
| minidump_module_list_index = 0; |
| while (minidump_module_list_index < MINIDUMP_MODULE_COUNT) { |
| if (!strcmp(minidump_module_list[minidump_module_list_index], mod->name)) |
| break; |
| minidump_module_list_index ++; |
| } |
| |
| /* For specified modules in minidump modules list, |
| dump module struct, sections and bss */ |
| |
| if (minidump_module_list_index < MINIDUMP_MODULE_COUNT ) { |
| wlan_count++ ; |
| module_tlv_info.start = (uintptr_t)mod; |
| module_tlv_info.size = sizeof(struct module); |
| ret_val = minidump_fill_segments(module_tlv_info.start, |
| module_tlv_info.size, CTX_SAVE_LOG_DUMP_TYPE_WLAN_MOD, NULL); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d\n", ret_val); |
| return ret_val; |
| } |
| |
| module_tlv_info.start = (unsigned long)mod->sect_attrs; |
| module_tlv_info.size = (unsigned long)(sizeof(struct module_sect_attrs) + ((sizeof(struct module_sect_attr))*(mod->sect_attrs->nsections))); |
| ret_val = minidump_fill_segments(module_tlv_info.start, |
| module_tlv_info.size, CTX_SAVE_LOG_DUMP_TYPE_WLAN_MOD, NULL); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d\n", ret_val); |
| return ret_val; |
| } |
| |
| for (i = 0; i < mod->sect_attrs->nsections; i++) { |
| if ((!strcmp(".bss", mod->sect_attrs->attrs[i].battr.attr.name))) { |
| module_tlv_info.start = (unsigned long) |
| mod->sect_attrs->attrs[i].address; |
| module_tlv_info.size = (unsigned long)mod->core_layout.base |
| + (unsigned long) mod->core_layout.size - |
| (unsigned long)mod->sect_attrs->attrs[i].address; |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| pr_err("\n MINIDUMP VA .bss start=%lx module=%s", |
| (unsigned long)mod->sect_attrs->attrs[i].address, |
| mod->name); |
| #endif |
| /* Log .bss VA of module in buffer */ |
| ret_val = minidump_fill_segments(module_tlv_info.start, |
| module_tlv_info.size, CTX_SAVE_LOG_DUMP_TYPE_WLAN_MOD, |
| mod->name); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d", ret_val); |
| return ret_val; |
| } |
| } |
| } |
| } else { |
| |
| /* For all other modules dump module meta data*/ |
| module_tlv_info.start = (unsigned long)mod; |
| module_tlv_info.size = sizeof(mod->list) + sizeof(mod->state) + sizeof(mod->name); |
| ret_val = minidump_fill_segments(module_tlv_info.start, |
| module_tlv_info.size, CTX_SAVE_LOG_DUMP_TYPE_WLAN_MOD, NULL); |
| if (ret_val) { |
| pr_err("Minidump: Crashdump buffer is full %d\n", ret_val); |
| return ret_val; |
| } |
| } |
| |
| if ( wlan_count == MINIDUMP_MODULE_COUNT) |
| return NOTIFY_DONE; |
| } |
| return NOTIFY_DONE; |
| } |
| |
| static int wlan_modinfo_panic_handler(struct notifier_block *this, |
| unsigned long event, void *ptr) |
| { |
| int ret; |
| |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| int count =0; |
| struct minidump_metadata_list *cur_node; |
| struct list_head *pos; |
| #endif |
| |
| if (!tlv_msg.msg_buffer) { |
| pr_err("\n Minidump: Crashdump buffer is empty"); |
| return NOTIFY_OK; |
| } |
| |
| ret = minidump_dump_wlan_modules(); |
| if (ret) |
| pr_err("Minidump: Error dumping modules: %d", ret); |
| |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| pr_err("\n Minidump: Size of Metadata file = %ld",minidump_meta_info.mod_log_len); |
| pr_err("\n Minidump: Printing out contents of Metadata list"); |
| |
| list_for_each(pos, &metadata_list.list) { |
| count ++; |
| cur_node = list_entry(pos, struct minidump_metadata_list, list); |
| if( cur_node->va!=0 ) { |
| if (cur_node->name != NULL) |
| pr_info(" %s [%lx] ---> ",cur_node->name,cur_node->va); |
| else |
| pr_info(" un-named [%lx] ---> ",cur_node->va); |
| } |
| } |
| pr_err("\n Minidump: # nodes in the Metadata list = %d",count); |
| pr_err("\n Minidump: Size of node in Metadata list = %ld\n", |
| (unsigned long)sizeof(struct minidump_metadata_list)); |
| |
| #endif |
| return NOTIFY_DONE; |
| } |
| |
| /* |
| * Function: wlan_module_notify_exit |
| * |
| * Description: Remove module information from metadata list |
| * when module is unloaded. This ensures the Module/MMU metadata |
| * files are updated when TLVs are invlaidated. |
| * |
| * Return: 0 |
| */ |
| static int wlan_module_notify_exit(struct notifier_block *self, unsigned long val, void *data) { |
| struct module *mod = data; |
| int i=0; |
| int minidump_module_list_index = 0; |
| |
| if (val == MODULE_STATE_GOING) { |
| minidump_module_list_index = 0; |
| /* Remove module info TLV from metadata list and invalidate entires in Metadata files*/ |
| minidump_remove_segments((const uint64_t)(uintptr_t)mod); |
| |
| while (minidump_module_list_index < MINIDUMP_MODULE_COUNT) { |
| if (!strcmp(minidump_module_list[minidump_module_list_index], mod->name)) { |
| /* For specific modules, additionally remove bss and sect attribute TLVs*/ |
| minidump_remove_segments((const uint64_t)(uintptr_t)mod->sect_attrs); |
| for (i = 0; i < mod->sect_attrs->nsections; i++) { |
| if ((!strcmp(".bss", mod->sect_attrs->attrs[i].battr.attr.name))) { |
| minidump_remove_segments((const uint64_t) |
| (uintptr_t)mod->sect_attrs->attrs[i].address); |
| #ifdef CONFIG_QCA_MINIDUMP_DEBUG |
| pr_err("\n Minidump: mod=%s sect=%lx bss=%lx has been removed", |
| mod->name, (unsigned long)(uintptr_t)mod->sect_attrs, |
| (unsigned long)(uintptr_t)mod->sect_attrs->attrs[i].address); |
| #endif |
| break; |
| } |
| } |
| break; |
| } |
| minidump_module_list_index ++; |
| } |
| } |
| return 0; |
| } |
| |
| struct notifier_block wlan_module_exit_nb = { |
| .notifier_call = wlan_module_notify_exit, |
| }; |
| |
| static struct notifier_block wlan_panic_nb = { |
| .notifier_call = wlan_modinfo_panic_handler, |
| }; |
| #endif /* CONFIG_QCA_MINIDUMP */ |
| |
| static int ctx_save_panic_handler(struct notifier_block *nb, |
| unsigned long event, void *ptr) |
| { |
| tlv_msg.is_panic = true; |
| return NOTIFY_DONE; |
| } |
| |
| static struct notifier_block panic_nb = { |
| .notifier_call = ctx_save_panic_handler, |
| }; |
| |
| static int ctx_save_probe(struct platform_device *pdev) |
| { |
| void *scm_regsave; |
| const struct ctx_save_props *prop = device_get_match_data(&pdev->dev); |
| int ret; |
| |
| if (!prop) |
| return -ENODEV; |
| |
| scm_regsave = (void *) __get_free_pages(GFP_KERNEL, |
| get_order(prop->crashdump_page_size)); |
| |
| if (!scm_regsave) |
| return -ENOMEM; |
| |
| ret = qti_scm_regsave(SCM_SVC_UTIL, SCM_CMD_SET_REGSAVE, |
| scm_regsave, prop->crashdump_page_size); |
| |
| if (ret) { |
| pr_err("Setting register save address failed.\n" |
| "Registers won't be dumped on a dog bite\n"); |
| return ret; |
| } |
| |
| spin_lock_init(&tlv_msg.spinlock); |
| tlv_msg.msg_buffer = scm_regsave + prop->tlv_msg_offset; |
| tlv_msg.cur_msg_buffer_pos = tlv_msg.msg_buffer; |
| tlv_msg.len = prop->crashdump_page_size - |
| prop->tlv_msg_offset; |
| ret = ctx_save_fill_log_dump_tlv(); |
| |
| /* if failed, we still return 0 because it should not |
| * affect the boot flow. The return value 0 does not |
| * necessarily indicate success in this function. |
| */ |
| if (ret) { |
| pr_err("log dump initialization failed\n"); |
| return 0; |
| } |
| |
| ret = atomic_notifier_chain_register(&panic_notifier_list, &panic_nb); |
| |
| if (ret) |
| dev_err(&pdev->dev, |
| "Failed to register panic notifier\n"); |
| |
| #ifdef CONFIG_QCA_MINIDUMP |
| ret = register_module_notifier(&wlan_module_exit_nb); |
| if (ret) |
| dev_err(&pdev->dev, "Failed to register WLAN module exit notifier\n"); |
| |
| ret = atomic_notifier_chain_register(&panic_notifier_list, |
| &wlan_panic_nb); |
| if (ret) |
| dev_err(&pdev->dev, |
| "Failed to register panic notifier for WLAN module info\n"); |
| register_sysrq_key('y', &sysrq_minidump_op); |
| #endif /* CONFIG_QCA_MINIDUMP */ |
| return ret; |
| } |
| |
| const struct ctx_save_props ctx_save_props_ipq5018 = { |
| .tlv_msg_offset = (1012 * SZ_1K), |
| /* As SBL overwrites the NSS IMEM, TZ has to copy it to some memory |
| * on crash before it restarts the system. Hence, reserving of 384K |
| * is required to copy the NSS IMEM before restart is done. |
| * So that TZ can dump NSS dump data after the first 8K. |
| * |
| * get_order function returns the next higher order as output, |
| * so when we pass 392K(8K for regsave + 384K for NSS IMEM) as argument |
| * 512K will be allocated. |
| * |
| * 3K is required for DCC regsave memory. |
| * 82K is unused currently and can be used based on future needs. |
| * 12K is used for crashdump TLV buffer for Minidump feature. |
| */ |
| |
| /* |
| * The memory is allocated using alloc_pages, hence it will be in |
| * power of 2. The unused memory is the result of using alloc_pages. |
| * As we need contigous memory for > 256K we have to use alloc_pages. |
| * |
| * --------------- |
| * | 8K | |
| * | regsave | |
| * --------------- |
| * | | |
| * | 192K | |
| * | NSS IMEM | |
| * | | |
| * | | |
| * --------------- |
| * | 352 K | |
| * | BTSS RAM | |
| * --------------- |
| * | 3K - DCC | |
| * --------------- |
| * | | |
| * | 457K | |
| * | Unused | |
| * | | |
| * --------------- |
| * | 12 K | |
| * | TLV Buffer | |
| * --------------- |
| * |
| */ |
| .crashdump_page_size = (SZ_8K + (192 * SZ_1K) + (352 * SZ_1K) + |
| (3 * SZ_1K) + (457 * SZ_1K) + (12 * SZ_1K)), |
| }; |
| |
| const struct ctx_save_props ctx_save_props_ipq6018 = { |
| .tlv_msg_offset = (244 * SZ_1K), |
| /* As XBL overwrites the NSS UTCM, TZ has to copy it to some memory |
| * on crash before it restarts the system. Hence, reserving of 192K |
| * is required to copy the NSS UTCM before restart is done. |
| * So that TZ can dump NSS dump data after the first 8K. |
| * |
| * 3K for DCC Memory |
| * |
| * get_order function returns the next higher order as output, |
| * so when we pass 203K as argument 256K will be allocated. |
| * 41K is unused currently and can be used based on future needs. |
| * |
| * 12K is used for crashdump TLV buffer for Minidump feature. |
| * For minidump feature, last 16K of crashdump page size is used for |
| * TLV buffer in the case of ipq807x. Same offset (last 16 K from end |
| * of crashdump page) is used for ipq60xx as well, to keep design |
| * consistent. |
| * |
| * |
| * The memory is allocated using alloc_pages, hence it will be in |
| * power of 2. The unused memory is the result of using alloc_pages. |
| * As we need contigous memory for > 256K we have to use alloc_pages. |
| * |
| * --------------- |
| * | 8K | |
| * | regsave | |
| * --------------- |
| * | | |
| * | 192K | |
| * | NSS UTCM | |
| * | | |
| * | | |
| * --------------- |
| * | 3K - DCC | |
| * --------------- |
| * | | |
| * | 41K | |
| * | Unused | |
| * | | |
| * --------------- |
| * | 12 K | |
| * | TLV Buffer | |
| * --------------- |
| * |
| */ |
| .crashdump_page_size = (SZ_8K + (192 * SZ_1K) + (3 * SZ_1K) + |
| (41 * SZ_1K) + (12 * SZ_1K)), |
| }; |
| |
| const struct ctx_save_props ctx_save_props_ipq807x = { |
| .tlv_msg_offset = (500 * SZ_1K), |
| /* As SBL overwrites the NSS IMEM, TZ has to copy it to some memory |
| * on crash before it restarts the system. Hence, reserving of 384K |
| * is required to copy the NSS IMEM before restart is done. |
| * So that TZ can dump NSS dump data after the first 8K. |
| * Additionally 8K memory is allocated which can be used by TZ |
| * to dump PMIC memory. |
| * get_order function returns the next higher order as output, |
| * so when we pass 400K as argument 512K will be allocated. |
| * 3K is required for DCC regsave memory. |
| * 15K is required for CPR. |
| * 82K is unused currently and can be used based on future needs. |
| * 12K is used for crashdump TLV buffer for Minidump feature. |
| * |
| * The memory is allocated using alloc_pages, hence it will be in |
| * power of 2. The unused memory is the result of using alloc_pages. |
| * As we need contigous memory for > 256K we have to use alloc_pages. |
| * |
| * *---------------* |
| * | 8K | |
| * | regsave | |
| * *---------------* |
| * | | |
| * | 384K | |
| * | NSS IMEM | |
| * | | |
| * | | |
| * *---------------* |
| * | 8K | |
| * | PMIC mem | |
| * *---------------* |
| * | 3K - DCC | |
| * | | |
| * *---------------* |
| * | 15K | |
| * | CPR Reg | |
| * * --------------* |
| * | | |
| * | 82K | |
| * | Unused | |
| * | | |
| * * --------------* |
| * | 12 K | |
| * | TLV Buffer | |
| * *---------------* |
| * |
| */ |
| .crashdump_page_size = (SZ_8K + (384 * SZ_1K) + (SZ_8K) + (3 * SZ_1K) + |
| (15 * SZ_1K) + (82 * SZ_1K) + (12 * SZ_1K)), |
| }; |
| |
| static const struct of_device_id ctx_save_of_table[] = { |
| { |
| .compatible = "qti,ctxt-save-ipq5018", |
| .data = (void *)&ctx_save_props_ipq5018, |
| }, |
| { |
| .compatible = "qti,ctxt-save-ipq6018", |
| .data = (void *)&ctx_save_props_ipq6018, |
| }, |
| { |
| .compatible = "qti,ctxt-save-ipq8074", |
| .data = (void *)&ctx_save_props_ipq807x, |
| }, |
| {} |
| }; |
| |
| static struct platform_driver ctx_save_driver = { |
| .probe = ctx_save_probe, |
| .driver = { |
| .name = "qti_ctx_save_driver", |
| .of_match_table = ctx_save_of_table, |
| }, |
| }; |
| module_platform_driver(ctx_save_driver); |
| |
| MODULE_DESCRIPTION("QTI context save driver for storing cpu regs, etc"); |
| MODULE_LICENSE("GPL v2"); |