--- zzzz-none-000/linux-4.4.271/drivers/watchdog/qcom-wdt.c 2021-06-03 06:22:09.000000000 +0000 +++ hawkeye-5590-750/linux-4.4.271/drivers/watchdog/qcom-wdt.c 2023-04-19 10:22:30.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (c) 2014, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014,2016 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -16,36 +16,1587 @@ #include #include #include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_QCA_MINIDUMP +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif +#include + +#define TCSR_WONCE_REG 0x193d010 + +static int in_panic; +bool mhi_wdt_panic_enable; +enum wdt_reg { + WDT_RST, + WDT_EN, + WDT_BARK_TIME, + WDT_BITE_TIME, +}; + +static const u32 reg_offset_data_apcs_tmr[] = { + [WDT_RST] = 0x38, + [WDT_EN] = 0x40, + [WDT_BARK_TIME] = 0x4C, + [WDT_BITE_TIME] = 0x5C, +}; -#define WDT_RST 0x38 -#define WDT_EN 0x40 -#define WDT_BITE_TIME 0x5C +static const u32 reg_offset_data_kpss[] = { + [WDT_RST] = 0x4, + [WDT_EN] = 0x8, + [WDT_BARK_TIME] = 0x10, + [WDT_BITE_TIME] = 0x14, +}; + +struct qcom_wdt_props { + const u32 *layout; + unsigned int tlv_msg_offset; + unsigned int crashdump_page_size; + bool secure_wdog; +}; struct qcom_wdt { struct watchdog_device wdd; struct clk *clk; unsigned long rate; + unsigned int bite; struct notifier_block restart_nb; void __iomem *base; + const struct qcom_wdt_props *dev_props; + struct resource *tlv_res; +}; + +#ifdef CONFIG_QCA_MINIDUMP +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; +qcom_wdt_scm_tlv_msg_t tlv_msg; +struct minidump_metadata_list metadata_list; +static const struct file_operations mini_dump_ops; +static struct class *dump_class; +int dump_major = 0; + +/* struct to store physical address and +* size of crashdump segments for live minidump +* capture +* +* @param: +* node - struct obj for kernel list +* addr - virtual address of dump segment +* size - size of dump segemnt +*/ +struct dump_segment { + struct list_head node; + unsigned long addr; + size_t size; +}; + +/* struct to store metadata info for +* preparing crashdump segemnts for +* live minidump capture. +* +* @param: +* total_size - total size of dumps +* num_seg - total number of dump segments +* flag - size of dump segment +* type - array to store 'type' of dump segments +* seg_size - array to store segment size of +* dump segments +* phy_addr - array to store physical address of +* dump segments +*/ +struct mini_hdr { + int total_size; + int num_seg; + int flag; + unsigned char *type; + int *seg_size; + unsigned long *phy_addr; +}; + +/* struct to store device file info for /dev/minidump +* +* @param: +* name - name of device node +* fops - struct obj for file operations +* fmode - file modes +* hdr - struct obj for metadata info +* dump_segments - struct obj for dump segment list +*/ +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 +static int qcom_wdt_scm_replace_tlv(struct qcom_wdt_scm_tlv_msg *scm_tlv_msg, + unsigned char type, unsigned int size, const char *data, unsigned char *offset); +static int qcom_wdt_scm_add_tlv(struct qcom_wdt_scm_tlv_msg *scm_tlv_msg, + unsigned char type, unsigned int size, const char *data); +static 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); +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; +extern int log_buf_len; +char *minidump_module_list[MINIDUMP_MODULE_COUNT] = {"qca_ol", "wifi_3_0", "umac", "qdf"}; +int minidump_dump_wlan_modules(void); +#endif +static void __iomem *wdt_addr(struct qcom_wdt *wdt, enum wdt_reg reg) +{ + return wdt->base + wdt->dev_props->layout[reg]; +}; + +#ifdef CONFIG_QCA_MINIDUMP + +/* +* 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 qcom_wdt_scm_tlv_msg *scm_tlv_msg; + struct dump_segment *segment = NULL; + int index = 0; + + if (!tlv_msg.msg_buffer) + return -ENOMEM; + scm_tlv_msg = &tlv_msg; + + 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(&scm_tlv_msg->minidump_tlv_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; + } + + switch(cur_node->type) { + + case QCA_WDT_LOG_DUMP_TYPE_DMESG: + segment->size = log_buf_len; + break; + + case QCA_WDT_LOG_DUMP_TYPE_WLAN_MMU_INFO: + + case QCA_WDT_LOG_DUMP_TYPE_WLAN_MOD_INFO: + segment->size = *(unsigned long *)(uintptr_t) + ((unsigned long)__va(cur_node->size)); + break; + + default: + 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(&scm_tlv_msg->minidump_tlv_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; +} + +/* file ops for /dev/minidump */ +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; + struct qcom_wdt_scm_tlv_msg *scm_tlv_msg; +#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 + scm_tlv_msg = &tlv_msg; + pr_err("\n Minidump: Size of Metadata file = %ld", mod_log_len); + pr_err("\n Minidump: Printing out contents of Metadata list"); + + spin_lock_irqsave(&scm_tlv_msg->minidump_tlv_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(&scm_tlv_msg->minidump_tlv_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"); + +} + +/* sysrq_key_op struct for registering operation +* for a particular key. +* +* @param: +* .hander - the key handler function +* .help_msg - help_msg string +* .action_msg - action_msg string +*/ +static struct sysrq_key_op sysrq_minidump_op = { + .handler = sysrq_minidump_handler, + .help_msg = "minidump(y)", + .action_msg = "MINIDUMP", +}; + +/* +* Function: qcom_wdt_scm_replace_tlv +* Description: Adds dump segment as a TLV into the global crashdump +* buffer at specified offset. +* +* @param: [out] scm_tlv_msg - pointer to global crashdump buffer +* [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 +*/ +static int qcom_wdt_scm_replace_tlv(struct qcom_wdt_scm_tlv_msg *scm_tlv_msg, + unsigned char type, unsigned int size, const char *data, unsigned char *offset) +{ + unsigned char *x; + unsigned char *y; + unsigned long flags; + + if (!scm_tlv_msg) { + return -ENOBUFS; + } + + spin_lock_irqsave(&scm_tlv_msg->minidump_tlv_spinlock, flags); + x = offset; + y = scm_tlv_msg->msg_buffer + scm_tlv_msg->len; + + if ((x + QCOM_WDT_SCM_TLV_TYPE_LEN_SIZE + size) >= y) { + spin_unlock_irqrestore(&scm_tlv_msg->minidump_tlv_spinlock, flags); + return -ENOBUFS; + } + + x[0] = type; + x[1] = size; + x[2] = size >> 8; + + memcpy(x + 3, data, size); + spin_unlock_irqrestore(&scm_tlv_msg->minidump_tlv_spinlock, flags); + + return 0; +} +/* +* Function: qcom_wdt_scm_add_tlv +* Description: Appends dump segment as a TLV entry to the end of the +* global crashdump buffer. +* +* @param: [out] scm_tlv_msg - pointer to global crashdump buffer +* [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 +*/ +static int qcom_wdt_scm_add_tlv(struct qcom_wdt_scm_tlv_msg *scm_tlv_msg, + unsigned char type, unsigned int size, const char *data) +{ + unsigned char *x; + unsigned char *y; + unsigned long flags; + + if (!scm_tlv_msg) { + return -ENOBUFS; + } + + spin_lock_irqsave(&scm_tlv_msg->minidump_tlv_spinlock, flags); + x = scm_tlv_msg->cur_msg_buffer_pos; + y = scm_tlv_msg->msg_buffer + scm_tlv_msg->len; + + if ((x + QCOM_WDT_SCM_TLV_TYPE_LEN_SIZE + size) >= y) { + spin_unlock_irqrestore(&scm_tlv_msg->minidump_tlv_spinlock, flags); + return -ENOBUFS; + } + + x[0] = type; + x[1] = size; + x[2] = size >> 8; + + memcpy(x + 3, data, size); + + scm_tlv_msg->cur_msg_buffer_pos += + (size + QCOM_WDT_SCM_TLV_TYPE_LEN_SIZE); + + spin_unlock_irqrestore(&scm_tlv_msg->minidump_tlv_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 +* QCA_WDT_LOG_DUMP_TYPE_EMPTY +* +* @param: [in] virt_addr - virtual address of the TLV to be invalidated +* +* Return: 0 +*/ +int minidump_remove_segments(const uint64_t virt_addr) +{ + struct minidump_metadata_list *cur_node; + struct list_head *pos; + unsigned long flags; + struct qcom_wdt_scm_tlv_msg *scm_tlv_msg; + + if (!tlv_msg.msg_buffer) + return -ENOMEM; + + scm_tlv_msg = &tlv_msg; + + if (!virt_addr) { + pr_err("\nMINIDUMP: Attempt to remove an invalid VA."); + return 0; + } + spin_lock_irqsave(&scm_tlv_msg->minidump_tlv_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 + * QCA_WDT_LOG_DUMP_TYPE_EMPTY + */ + *(cur_node->tlv_offset) = QCA_WDT_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(&scm_tlv_msg->minidump_tlv_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; + struct qcom_wdt_scm_tlv_msg *scm_tlv_msg; + 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; + + scm_tlv_msg = &tlv_msg; + spin_lock_irqsave(&scm_tlv_msg->minidump_tlv_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(&scm_tlv_msg->minidump_tlv_spinlock, + flags); +#ifdef CONFIG_QCA_MINIDUMP_DEBUG + pr_err("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, + * update metadata file pointer with the value at mod_offset. + */ + 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 = 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, + * update the MMU metadata file pointer with the value at mmu_offset. + */ + 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 = cur_mmuinfo_offset; + } + + spin_unlock_irqrestore(&scm_tlv_msg->minidump_tlv_spinlock, + flags); + /* return REPLACE to indicate TLV needs to be inserted to the crashdump buffer*/ + return REPLACE; + } + + spin_unlock_irqrestore(&scm_tlv_msg->minidump_tlv_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 ((scm_tlv_msg->cur_msg_buffer_pos + QCOM_WDT_SCM_TLV_TYPE_LEN_SIZE + + sizeof(struct minidump_tlv_info) >= + scm_tlv_msg->msg_buffer + scm_tlv_msg->len) || + (mod_log_len + METADATA_FILE_ENTRY_LEN >= METADATA_FILE_SZ)) { + return -ENOMEM; + } + + if ((scm_tlv_msg->cur_msg_buffer_pos + QCOM_WDT_SCM_TLV_TYPE_LEN_SIZE + + sizeof(struct minidump_tlv_info) >= + scm_tlv_msg->msg_buffer + scm_tlv_msg->len) || + (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 = 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 = scm_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 = cur_mmuinfo_offset; + } else { + cur_node->mmuinfo_offset = 0; + } + + minidump.hdr.num_seg++; + + spin_lock_irqsave(&scm_tlv_msg->minidump_tlv_spinlock, flags); + list_add_tail(&(cur_node->list), &(metadata_list.list)); + spin_unlock_irqrestore(&scm_tlv_msg->minidump_tlv_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; + struct qcom_wdt_scm_tlv_msg *scm_tlv_msg = &tlv_msg; + + int ret; + + minidump_tlv_info.start = start_addr; + minidump_tlv_info.size = size; + + if (replace && (*(tlv_offset) == QCA_WDT_LOG_DUMP_TYPE_EMPTY)) { + ret = qcom_wdt_scm_replace_tlv(&tlv_msg, type, + sizeof(minidump_tlv_info), + (unsigned char *)&minidump_tlv_info, tlv_offset); + } else { + ret = qcom_wdt_scm_add_tlv(&tlv_msg, 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 (scm_tlv_msg->cur_msg_buffer_pos >= + scm_tlv_msg->msg_buffer + scm_tlv_msg->len){ + pr_err("MINIDUMP buffer overflow %d\n", (int)type); + return -ENOBUFS; + } + *scm_tlv_msg->cur_msg_buffer_pos = + QCA_WDT_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; + struct qcom_wdt_scm_tlv_msg *scm_tlv_msg; + unsigned long flags; + + if (!tlv_msg.msg_buffer) { + return -ENOBUFS; + } + + scm_tlv_msg = &tlv_msg; + + /* Check for Metadata file overflow */ + if ((cur_mmuinfo_offset == (uintptr_t)mmu_log + mmu_log_len) && + (mmu_log_len + MMU_FILE_ENTRY_LEN >= MMU_FILE_SZ)) { + pr_err("\nMINIDUMP Metadata file overflow error"); + return 0; + } + + /* + * Check for valid cur_modinfo_offset value. Ensure + * that the offset is not NULL and is within bounds + * of the Metadata file. + */ + + if ((!(void *)(uintptr_t)cur_mmuinfo_offset) || + (cur_mmuinfo_offset < (uintptr_t)mmu_log) || + (cur_mmuinfo_offset + MMU_FILE_ENTRY_LEN >= + ((uintptr_t)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 (mmu_log_len + MMU_FILE_ENTRY_LEN >= MMU_FILE_SZ) { + return -ENOBUFS; + } + + spin_lock_irqsave(&scm_tlv_msg->minidump_tlv_spinlock, flags); + memset((void *)(uintptr_t)cur_mmuinfo_offset, '\0', MMU_FILE_ENTRY_LEN); + snprintf((char *)(uintptr_t)cur_mmuinfo_offset, MMU_FILE_ENTRY_LEN, "%s", substring); + + if (cur_mmuinfo_offset == (uintptr_t)mmu_log + mmu_log_len) { + mmu_log_len = mmu_log_len + MMU_FILE_ENTRY_LEN; + cur_mmuinfo_offset = (uintptr_t)mmu_log + mmu_log_len; + } else { + cur_mmuinfo_offset = (uintptr_t)mmu_log + mmu_log_len; + } + spin_unlock_irqrestore(&scm_tlv_msg->minidump_tlv_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; + struct qcom_wdt_scm_tlv_msg *scm_tlv_msg; + unsigned long flags; + + if (!tlv_msg.msg_buffer) { + return -ENOBUFS; + } + + scm_tlv_msg = &tlv_msg; + + /* Check for Metadata file overflow */ + if ((cur_modinfo_offset == (uintptr_t)mod_log + mod_log_len) && + (mod_log_len + METADATA_FILE_ENTRY_LEN >= METADATA_FILE_SZ)) { + pr_err("\nMINIDUMP Metadata file overflow error"); + return 0; + } + + /* + * Check for valid cur_modinfo_offset value. Ensure + * that the offset is not NULL and is within bounds + * of the Metadata file. + */ + if ((!(void *)(uintptr_t)cur_modinfo_offset) || + (cur_modinfo_offset < (uintptr_t)mod_log) || + (cur_modinfo_offset + METADATA_FILE_ENTRY_LEN >= + ((uintptr_t)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 == QCA_WDT_LOG_DUMP_TYPE_LEVEL1_PT || type == QCA_WDT_LOG_DUMP_TYPE_DMESG) { + ret_val = snprintf(substring, METADATA_FILE_ENTRY_LEN, + "\n%s pa=%lx", mod_name, (unsigned long)pa); + } else if (type == QCA_WDT_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 (mod_log_len + METADATA_FILE_ENTRY_LEN >= METADATA_FILE_SZ) { + kfree(mod_name); + return -ENOBUFS; + } + + spin_lock_irqsave(&scm_tlv_msg->minidump_tlv_spinlock, flags); + memset((void *)(uintptr_t)cur_modinfo_offset, '\0', METADATA_FILE_ENTRY_LEN); + snprintf((char *)(uintptr_t)cur_modinfo_offset, METADATA_FILE_ENTRY_LEN, "%s", substring); + + if (cur_modinfo_offset == (uintptr_t)mod_log + mod_log_len) { + mod_log_len = mod_log_len + METADATA_FILE_ENTRY_LEN; + cur_modinfo_offset = (uintptr_t)mod_log + mod_log_len; + } else { + cur_modinfo_offset = (uintptr_t)mod_log + mod_log_len; + } + spin_unlock_irqrestore(&scm_tlv_msg->minidump_tlv_spinlock, flags); + kfree(mod_name); + return 0; +} +/* +* Function: qcom_wdt_scm_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 +* +* @param: [in] scm_tlv_msg - pointer to crashdump buffer +* +* Return: 0 on success, -ENOBUFS on failure +*/ +static int qcom_wdt_scm_fill_log_dump_tlv( + struct qcom_wdt_scm_tlv_msg *scm_tlv_msg) +{ + struct new_utsname *uname; + int ret_val; + struct minidump_tlv_info pagetable_tlv_info; + struct minidump_tlv_info log_buf_info; + struct minidump_tlv_info linux_banner_info; + mod_log_len = 0; + minidump.hdr.num_seg = 0; + cur_modinfo_offset = (uintptr_t)mod_log; + mmu_log_len = 0; + cur_mmuinfo_offset = (uintptr_t)mmu_log; + + uname = utsname(); + + INIT_LIST_HEAD(&metadata_list.list); + + ret_val = qcom_wdt_scm_add_tlv(scm_tlv_msg, + QCA_WDT_LOG_DUMP_TYPE_UNAME, + sizeof(*uname), + (unsigned char *)uname); + if (ret_val) + return ret_val; + + 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, + QCA_WDT_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, QCA_WDT_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, + QCA_WDT_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)mod_log,(uint64_t)__pa(&mod_log_len), + QCA_WDT_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)mmu_log,(uint64_t)__pa(&mmu_log_len), + QCA_WDT_LOG_DUMP_TYPE_WLAN_MMU_INFO, NULL); + if (ret_val) { + pr_err("Minidump: Crashdump buffer is full %d \n", ret_val); + return ret_val; + } + + if (scm_tlv_msg->cur_msg_buffer_pos >= + scm_tlv_msg->msg_buffer + scm_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 +*/ +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, QCA_WDT_LOG_DUMP_TYPE_WLAN_MOD, NULL); + 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) { + 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, QCA_WDT_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, QCA_WDT_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].name))) { + module_tlv_info.start = (unsigned long) + mod->sect_attrs->attrs[i].address; + module_tlv_info.size = (unsigned long)mod->module_core + + (unsigned long) mod->core_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, QCA_WDT_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, QCA_WDT_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",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].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 inline struct qcom_wdt *to_qcom_wdt(struct watchdog_device *wdd) { return container_of(wdd, struct qcom_wdt, wdd); } -static int qcom_wdt_start(struct watchdog_device *wdd) +static int panic_prep_restart(struct notifier_block *this, + unsigned long event, void *ptr) +{ + in_panic = 1; + return NOTIFY_DONE; +} + +static struct notifier_block panic_blk = { + .notifier_call = panic_prep_restart, +}; + +static long qcom_wdt_configure_bark_dump(void *arg) +{ + void *scm_regsave; + void *tlv_ptr; + resource_size_t tlv_base; + resource_size_t tlv_size; + struct qcom_wdt *wdt = (struct qcom_wdt *) arg; + const struct qcom_wdt_props *device_props = wdt->dev_props; + long ret = -ENOMEM; + struct resource *res = wdt->tlv_res; + + scm_regsave = (void *) __get_free_pages(GFP_KERNEL, + get_order(device_props->crashdump_page_size)); + if (!scm_regsave) + return -ENOMEM; + + ret = qcom_scm_regsave(SCM_SVC_UTIL, SCM_CMD_SET_REGSAVE, + scm_regsave, device_props->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; + } + + /* Initialize the tlv and fill all the details */ +#ifdef CONFIG_QCA_MINIDUMP + spin_lock_init(&tlv_msg.minidump_tlv_spinlock); + tlv_msg.msg_buffer = scm_regsave + device_props->tlv_msg_offset; + tlv_msg.cur_msg_buffer_pos = tlv_msg.msg_buffer; + tlv_msg.len = device_props->crashdump_page_size - + device_props->tlv_msg_offset; + ret = qcom_wdt_scm_fill_log_dump_tlv(&tlv_msg); + + /* 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; + } +#endif + + if (res) { + tlv_base = res->start; + tlv_size = resource_size(res); + res = request_mem_region(tlv_base, tlv_size, "tlv_dump"); + + if (!res) { + pr_err("requesting memory region failed\n"); + return 0; + } + + tlv_ptr = ioremap(tlv_base, tlv_size); + + if (!tlv_ptr) { + pr_err("mapping physical mem failed\n"); + release_mem_region(tlv_base, tlv_size); + return 0; + } + +#ifdef CONFIG_QCA_MINIDUMP + memcpy_toio(tlv_ptr, tlv_msg.msg_buffer, tlv_msg.len); +#endif + iounmap(tlv_ptr); + release_mem_region(tlv_base, tlv_size); + } + + return 0; +} + +static int qcom_fiq_extwdt(unsigned int regaddr, unsigned int value) +{ + int ret; + + ret = qcom_scm_extwdt(SCM_SVC_EXTWDT, SCM_CMD_EXTWDT, regaddr, value); + if (ret) + pr_err("Setting value to TCSR_WONCE register failed\n"); + + return ret; +} + +static int qcom_wdt_start_secure(struct watchdog_device *wdd) { struct qcom_wdt *wdt = to_qcom_wdt(wdd); - writel(0, wdt->base + WDT_EN); - writel(1, wdt->base + WDT_RST); - writel(wdd->timeout * wdt->rate, wdt->base + WDT_BITE_TIME); - writel(1, wdt->base + WDT_EN); + writel(0, wdt_addr(wdt, WDT_EN)); + writel(1, wdt_addr(wdt, WDT_RST)); + + if (wdt->bite) { + writel((wdd->timeout - 1) * wdt->rate, + wdt_addr(wdt, WDT_BARK_TIME)); + writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME)); + } else { + writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME)); + writel((wdd->timeout * wdt->rate) * 2, wdt_addr(wdt, WDT_BITE_TIME)); + } + + writel(1, wdt_addr(wdt, WDT_EN)); + return 0; +} + +static int qcom_wdt_start_nonsecure(struct watchdog_device *wdd) +{ + struct qcom_wdt *wdt = to_qcom_wdt(wdd); + + writel(0, wdt_addr(wdt, WDT_EN)); + writel(1, wdt_addr(wdt, WDT_RST)); + writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME)); + writel(0x0FFFFFFF, wdt_addr(wdt, WDT_BITE_TIME)); + writel(1, wdt_addr(wdt, WDT_EN)); return 0; } @@ -53,7 +1604,7 @@ { struct qcom_wdt *wdt = to_qcom_wdt(wdd); - writel(0, wdt->base + WDT_EN); + writel(0, wdt_addr(wdt, WDT_EN)); return 0; } @@ -61,7 +1612,7 @@ { struct qcom_wdt *wdt = to_qcom_wdt(wdd); - writel(1, wdt->base + WDT_RST); + writel(1, wdt_addr(wdt, WDT_RST)); return 0; } @@ -69,11 +1620,19 @@ unsigned int timeout) { wdd->timeout = timeout; - return qcom_wdt_start(wdd); + return wdd->ops->start(wdd); } -static const struct watchdog_ops qcom_wdt_ops = { - .start = qcom_wdt_start, +static const struct watchdog_ops qcom_wdt_ops_secure = { + .start = qcom_wdt_start_secure, + .stop = qcom_wdt_stop, + .ping = qcom_wdt_ping, + .set_timeout = qcom_wdt_set_timeout, + .owner = THIS_MODULE, +}; + +static const struct watchdog_ops qcom_wdt_ops_nonsecure = { + .start = qcom_wdt_start_nonsecure, .stop = qcom_wdt_stop, .ping = qcom_wdt_ping, .set_timeout = qcom_wdt_set_timeout, @@ -87,6 +1646,197 @@ .identity = KBUILD_MODNAME, }; +const struct qcom_wdt_props qcom_wdt_props_ipq8064 = { + .layout = reg_offset_data_apcs_tmr, + .tlv_msg_offset = SZ_2K, + .crashdump_page_size = SZ_4K, + .secure_wdog = false, +}; + +const struct qcom_wdt_props qcom_wdt_props_ipq807x = { + .layout = reg_offset_data_kpss, + .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)), + .secure_wdog = true, +}; + +const struct qcom_wdt_props qcom_wdt_props_ipq40xx = { + .layout = reg_offset_data_kpss, + .tlv_msg_offset = SZ_2K, + .crashdump_page_size = SZ_4K, + .secure_wdog = true, +}; + +const struct qcom_wdt_props qcom_wdt_props_ipq6018 = { + .layout = reg_offset_data_kpss, + .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)), + .secure_wdog = true, +}; + +const struct qcom_wdt_props qcom_wdt_props_ipq5018 = { + .layout = reg_offset_data_kpss, + .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)), + .secure_wdog = true, +}; + +static const struct of_device_id qcom_wdt_of_table[] = { + { .compatible = "qcom,kpss-wdt-ipq8064", + .data = (void *) &qcom_wdt_props_ipq8064, + }, + { .compatible = "qcom,kpss-wdt-ipq807x", + .data = (void *) &qcom_wdt_props_ipq807x, + }, + { .compatible = "qcom,kpss-wdt-ipq40xx", + .data = (void *) &qcom_wdt_props_ipq40xx, + }, + { .compatible = "qcom,kpss-wdt-ipq6018", + .data = (void *) &qcom_wdt_props_ipq6018, + }, + { .compatible = "qcom,kpss-wdt-ipq5018", + .data = (void *) &qcom_wdt_props_ipq5018, + }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_wdt_of_table); static int qcom_wdt_restart(struct notifier_block *nb, unsigned long action, void *data) { @@ -99,33 +1849,90 @@ */ timeout = 128 * wdt->rate / 1000; - writel(0, wdt->base + WDT_EN); - writel(1, wdt->base + WDT_RST); - writel(timeout, wdt->base + WDT_BITE_TIME); - writel(1, wdt->base + WDT_EN); + writel(0, wdt_addr(wdt, WDT_EN)); + writel(1, wdt_addr(wdt, WDT_RST)); + if (in_panic) { + writel(timeout, wdt_addr(wdt, WDT_BARK_TIME)); + writel(2 * timeout, wdt_addr(wdt, WDT_BITE_TIME)); + } else { + writel(5 * timeout, wdt_addr(wdt, WDT_BARK_TIME)); + writel(timeout, wdt_addr(wdt, WDT_BITE_TIME)); + } + writel(1, wdt_addr(wdt, WDT_EN)); /* * Actually make sure the above sequence hits hardware before sleeping. */ wmb(); - msleep(150); + mdelay(150); return NOTIFY_DONE; } +static irqreturn_t wdt_bark_isr(int irq, void *wdd) +{ + struct qcom_wdt *wdt = to_qcom_wdt(wdd); + unsigned long nanosec_rem; + unsigned long long t = sched_clock(); + + printk(KERN_ERR, "Watch Dog Bark\n"); + + nanosec_rem = do_div(t, 1000000000); + pr_info("Watchdog bark! Now = %lu.%06lu\n", (unsigned long) t, + nanosec_rem / 1000); +#ifdef CONFIG_MHI_BUS + if (mhi_wdt_panic_enable) + mhi_wdt_panic_handler(); +#endif + + pr_info("Causing a watchdog bite!"); + writel(0, wdt_addr(wdt, WDT_EN)); + writel(1, wdt_addr(wdt, WDT_BITE_TIME)); + mb(); /* Avoid unpredictable behaviour in concurrent executions */ + pr_info("Configuring Watchdog Timer\n"); + writel(1, wdt_addr(wdt, WDT_RST)); + writel(1, wdt_addr(wdt, WDT_EN)); + mb(); /* Make sure the above sequence hits hardware before Reboot. */ + pr_info("Waiting for Reboot\n"); + + mdelay(1); + pr_err("Wdog - CTL: 0x%x, BARK TIME: 0x%x, BITE TIME: 0x%x", + readl(wdt_addr(wdt, WDT_EN)), + readl(wdt_addr(wdt, WDT_BARK_TIME)), + readl(wdt_addr(wdt, WDT_BITE_TIME))); + return IRQ_HANDLED; +} + +void register_wdt_bark_irq(int irq, struct qcom_wdt *wdt) +{ + int ret; + + ret = request_irq(irq, wdt_bark_isr, IRQF_TRIGGER_HIGH, + "watchdog bark", wdt); + if (ret) + pr_err("error request_irq(irq_num:%d ) ret:%d\n", irq, ret); +} + static int qcom_wdt_probe(struct platform_device *pdev) { + const struct of_device_id *id; struct qcom_wdt *wdt; struct resource *res; struct device_node *np = pdev->dev.of_node; u32 percpu_offset; - int ret; + int ret, irq; + uint32_t val; + unsigned int retn, extwdt_val = 0, regaddr; wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq_byname(pdev, "bark_irq"); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "kpss_wdt"); + if (!res) { + dev_err(&pdev->dev, "%s: no mem resource\n", __func__); + return -EINVAL; + } /* We use CPU0's DGT for the watchdog */ if (of_property_read_u32(np, "cpu-offset", &percpu_offset)) @@ -138,6 +1945,28 @@ if (IS_ERR(wdt->base)) return PTR_ERR(wdt->base); + wdt->tlv_res = platform_get_resource_byname + (pdev, IORESOURCE_MEM, "tlv"); + + id = of_match_device(qcom_wdt_of_table, &pdev->dev); + if (!id) + return -ENODEV; + + wdt->dev_props = (struct qcom_wdt_props *)id->data; + + mhi_wdt_panic_enable = of_property_read_bool(np, "mhi-wdt-panic-enable"); + + /* + * mhi-wdt-panic-enable if set, BARK and BITE time should have + * enough difference for the MDM to collect crash dump and + * reboot i.e BITE time should be set twice as BARK time. + */ + if (wdt->dev_props->secure_wdog && !mhi_wdt_panic_enable) + wdt->bite = 1; + + if (irq > 0) + register_wdt_bark_irq(irq, wdt); + wdt->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(wdt->clk)) { dev_err(&pdev->dev, "failed to get input clock\n"); @@ -166,11 +1995,19 @@ goto err_clk_unprepare; } + ret = work_on_cpu(0, qcom_wdt_configure_bark_dump, wdt); + if (ret) + wdt->wdd.ops = &qcom_wdt_ops_nonsecure; + else + wdt->wdd.ops = &qcom_wdt_ops_secure; + wdt->wdd.dev = &pdev->dev; wdt->wdd.info = &qcom_wdt_info; - wdt->wdd.ops = &qcom_wdt_ops; wdt->wdd.min_timeout = 1; - wdt->wdd.max_timeout = 0x10000000U / wdt->rate; + if (!of_property_read_u32(np, "wdt-max-timeout", &val)) + wdt->wdd.max_timeout = val; + else + wdt->wdd.max_timeout = 0x10000000U / wdt->rate; wdt->wdd.parent = &pdev->dev; /* @@ -191,11 +2028,36 @@ * WDT restart notifier has priority 0 (use as a last resort) */ wdt->restart_nb.notifier_call = qcom_wdt_restart; + atomic_notifier_chain_register(&panic_notifier_list, &panic_blk); ret = register_restart_handler(&wdt->restart_nb); if (ret) dev_err(&pdev->dev, "failed to setup restart handler\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*/ platform_set_drvdata(pdev, wdt); + + if (!of_property_read_u32(np, "extwdt-val", &val)) { + extwdt_val = val; + + if (of_property_read_u32(np, "tcsr-reg", ®addr)) + regaddr = TCSR_WONCE_REG; + + retn = qcom_fiq_extwdt(regaddr, extwdt_val); + if (retn) + dev_err(&pdev->dev, "FIQ scm_call failed\n"); + } + return 0; err_clk_unprepare: @@ -213,13 +2075,6 @@ return 0; } -static const struct of_device_id qcom_wdt_of_table[] = { - { .compatible = "qcom,kpss-timer" }, - { .compatible = "qcom,scss-timer" }, - { }, -}; -MODULE_DEVICE_TABLE(of, qcom_wdt_of_table); - static struct platform_driver qcom_watchdog_driver = { .probe = qcom_wdt_probe, .remove = qcom_wdt_remove,