From 88707fb091da9d0872bcdd62b46e035a0e2a68c1 Mon Sep 17 00:00:00 2001 From: Avaneesh Kumar Dwivedi Date: Thu, 6 Jul 2017 21:18:03 +0530 Subject: [PATCH] soc: qcom: Add SSR minidump provision for subsystem ramdump Minidump is concise and bare minimum dump to enable technology teams to debug most of subsystem issues. This change adds required driver code changes to provide support of subsystem minidump. Increase PIL timeout to give MBA more time for encryption and decryption for modem segments. Change-Id: I1d04a9306ce507bc610777bc476197f26c1e18ac Signed-off-by: Avaneesh Kumar Dwivedi --- .../devicetree/bindings/arm/msm/imem.txt | 10 ++ .../devicetree/bindings/pil/pil-q6v5-mss.txt | 1 + drivers/soc/qcom/peripheral-loader.c | 108 +++++++++++++- drivers/soc/qcom/peripheral-loader.h | 28 ++++ drivers/soc/qcom/pil-msa.c | 10 +- drivers/soc/qcom/ramdump.c | 133 ++++++++++++++++++ include/soc/qcom/ramdump.h | 5 +- 7 files changed, 291 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/arm/msm/imem.txt b/Documentation/devicetree/bindings/arm/msm/imem.txt index 2989fbfe7972..440628d02630 100644 --- a/Documentation/devicetree/bindings/arm/msm/imem.txt +++ b/Documentation/devicetree/bindings/arm/msm/imem.txt @@ -73,6 +73,11 @@ USB Diag Cookies: Memory region used to store USB PID and serial numbers to be used by bootloader in download mode. +SSR Minidump Offset +------------------- +-Compatible: "qcom,msm-imem-minidump" +-reg: start address and size of ssr imem region + Required properties: -compatible: "qcom,msm-imem-diag-dload" -reg: start address and size of USB Diag download mode region in imem @@ -121,4 +126,9 @@ Example: compatible = "qcom,msm-imem-emergency_download_mode"; reg = <0xfe0 12>; }; + + ss_mdump@b88 { + compatible = "qcom,msm-imem-minidump"; + reg = <0xb88 28>; + }; }; diff --git a/Documentation/devicetree/bindings/pil/pil-q6v5-mss.txt b/Documentation/devicetree/bindings/pil/pil-q6v5-mss.txt index 47a6fdd300ca..406920b7246e 100644 --- a/Documentation/devicetree/bindings/pil/pil-q6v5-mss.txt +++ b/Documentation/devicetree/bindings/pil/pil-q6v5-mss.txt @@ -88,6 +88,7 @@ Optional properties: - qcom,override-acc-1: Override the default ACC settings with this value if present. - qcom,cx-ipeak-vote: Boolean- Present if we need to set bit 5 of cxip_lm_vote_clear during modem shutdown +- qcom,minidump-id: Unique id for each subsystem One child node to represent the MBA image may be specified, when the MBA image needs to be loaded in a specifically carved out memory region. diff --git a/drivers/soc/qcom/peripheral-loader.c b/drivers/soc/qcom/peripheral-loader.c index ace2bc4f30b6..d77a12626330 100644 --- a/drivers/soc/qcom/peripheral-loader.c +++ b/drivers/soc/qcom/peripheral-loader.c @@ -56,7 +56,9 @@ #endif #define PIL_NUM_DESC 10 +#define NUM_OF_ENCRYPTED_KEY 3 static void __iomem *pil_info_base; +static void __iomem *pil_minidump_base; /** * proxy_timeout - Override for proxy vote timeouts @@ -79,6 +81,18 @@ struct pil_mdt { struct elf32_phdr phdr[]; }; +/** + * struct boot_minidump_smem_region - Representation of SMEM TOC + * @region_name: Name of modem segment to be dumped + * @region_base_address: Where segment start from + * @region_size: Size of segment to be dumped + */ +struct boot_minidump_smem_region { + char region_name[16]; + u64 region_base_address; + u64 region_size; +}; + /** * struct pil_seg - memory map representing one segment * @next: points to next seg mentor NULL if last segment @@ -133,11 +147,67 @@ struct pil_priv { phys_addr_t region_end; void *region; struct pil_image_info __iomem *info; + struct md_ssr_ss_info __iomem *minidump; + int minidump_id; int id; int unvoted_flag; size_t region_size; }; +static int pil_do_minidump(struct pil_desc *desc, void *ramdump_dev) +{ + struct boot_minidump_smem_region __iomem *region_info; + struct ramdump_segment *ramdump_segs, *s; + struct pil_priv *priv = desc->priv; + void __iomem *subsys_smem_base; + void __iomem *offset; + int ss_mdump_seg_cnt; + int ret, i; + + memcpy(&offset, &priv->minidump, sizeof(priv->minidump)); + offset = offset + sizeof(priv->minidump->md_ss_smem_regions_baseptr); + /* There are 3 encryption keys which also need to be dumped */ + ss_mdump_seg_cnt = readb_relaxed(offset) + + NUM_OF_ENCRYPTED_KEY; + + subsys_smem_base = ioremap(__raw_readl(priv->minidump), + ss_mdump_seg_cnt * sizeof(*region_info)); + region_info = + (struct boot_minidump_smem_region __iomem *)subsys_smem_base; + ramdump_segs = kcalloc(ss_mdump_seg_cnt, + sizeof(*ramdump_segs), GFP_KERNEL); + if (!ramdump_segs) + return -ENOMEM; + + if (desc->subsys_vmid > 0) + ret = pil_assign_mem_to_linux(desc, priv->region_start, + (priv->region_end - priv->region_start)); + + s = ramdump_segs; + for (i = 0; i < ss_mdump_seg_cnt; i++) { + memcpy(&offset, ®ion_info, sizeof(region_info)); + memcpy(&s->name, ®ion_info, sizeof(region_info)); + offset = offset + sizeof(region_info->region_name); + s->address = __raw_readl(offset); + offset = offset + sizeof(region_info->region_base_address); + s->size = __raw_readl(offset); + s++; + region_info++; + } + ret = do_minidump(ramdump_dev, ramdump_segs, ss_mdump_seg_cnt); + kfree(ramdump_segs); + if (ret) + pil_err(desc, "%s: Ramdump collection failed for subsys %s rc:%d\n", + __func__, desc->name, ret); + writel_relaxed(0, &priv->minidump->md_ss_smem_regions_baseptr); + writeb_relaxed(1, &priv->minidump->md_ss_ssr_cause); + + if (desc->subsys_vmid > 0) + ret = pil_assign_mem_to_subsys(desc, priv->region_start, + (priv->region_end - priv->region_start)); + return ret; +} + /** * pil_do_ramdump() - Ramdump an image * @desc: descriptor from pil_desc_init() @@ -153,6 +223,9 @@ int pil_do_ramdump(struct pil_desc *desc, void *ramdump_dev) int count = 0, ret; struct ramdump_segment *ramdump_segs, *s; + if (priv->minidump && (__raw_readl(priv->minidump) > 0)) + return pil_do_minidump(desc, ramdump_dev); + list_for_each_entry(seg, &priv->segs, list) count++; @@ -1014,9 +1087,10 @@ bool is_timeout_disabled(void) int pil_desc_init(struct pil_desc *desc) { struct pil_priv *priv; - int ret; void __iomem *addr; + int ret, ss_imem_offset_mdump; char buf[sizeof(priv->info->name)]; + struct device_node *ofnode = desc->dev->of_node; if (WARN(desc->ops->proxy_unvote && !desc->ops->proxy_vote, "Invalid proxy voting. Ignoring\n")) @@ -1039,6 +1113,22 @@ int pil_desc_init(struct pil_desc *desc) strncpy(buf, desc->name, sizeof(buf)); __iowrite32_copy(priv->info->name, buf, sizeof(buf) / 4); } + if (of_property_read_u32(ofnode, "qcom,minidump-id", + &priv->minidump_id)) + pr_debug("minidump-id not found for %s\n", desc->name); + else { + ss_imem_offset_mdump = + sizeof(struct md_ssr_ss_info) * priv->minidump_id; + if (pil_minidump_base) { + /* Add 0x4 to get start of struct md_ssr_ss_info base + * from struct md_ssr_toc for any subsystem, + * struct md_ssr_ss_info is actually the pointer + * of ToC in smem for any subsystem. + */ + addr = pil_minidump_base + ss_imem_offset_mdump + 0x4; + priv->minidump = (struct md_ssr_ss_info __iomem *)addr; + } + } ret = pil_parse_devicetree(desc); if (ret) @@ -1148,6 +1238,20 @@ static int __init msm_pil_init(void) for (i = 0; i < resource_size(&res)/sizeof(u32); i++) writel_relaxed(0, pil_info_base + (i * sizeof(u32))); + np = of_find_compatible_node(NULL, NULL, "qcom,msm-imem-minidump"); + if (!np) { + pr_warn("pil: failed to find qcom,msm-imem-minidump node\n"); + goto out; + } else { + pil_minidump_base = of_iomap(np, 0); + if (!pil_minidump_base) { + pr_err("unable to map pil minidump imem offset\n"); + goto out; + } + } + for (i = 0; i < sizeof(struct md_ssr_toc)/sizeof(u32); i++) + writel_relaxed(0, pil_minidump_base + (i * sizeof(u32))); + writel_relaxed(1, pil_minidump_base); out: return register_pm_notifier(&pil_pm_notifier); } @@ -1158,6 +1262,8 @@ static void __exit msm_pil_exit(void) unregister_pm_notifier(&pil_pm_notifier); if (pil_info_base) iounmap(pil_info_base); + if (pil_minidump_base) + iounmap(pil_minidump_base); } module_exit(msm_pil_exit); diff --git a/drivers/soc/qcom/peripheral-loader.h b/drivers/soc/qcom/peripheral-loader.h index 0cd2aeae1edd..908ab78124f7 100644 --- a/drivers/soc/qcom/peripheral-loader.h +++ b/drivers/soc/qcom/peripheral-loader.h @@ -74,6 +74,34 @@ struct pil_image_info { __le32 size; } __attribute__((__packed__)); +#define MAX_NUM_OF_SS 3 + +/** + * struct md_ssr_ss_info - Info in imem about smem ToC + * @md_ss_smem_regions_baseptr: Start physical address of SMEM TOC + * @md_ss_num_of_regions: number of segments that need to be dumped + * @md_ss_encryption_status: status of encryption of segments + * @md_ss_ssr_cause: ssr cause enum + */ +struct md_ssr_ss_info { + u32 md_ss_smem_regions_baseptr; + u8 md_ss_num_of_regions; + u8 md_ss_encryption_status; + u8 md_ss_ssr_cause; + u8 reserved; +}; + +/** + * struct md_ssr_toc - Wrapper of struct md_ssr_ss_info + * @md_ssr_toc_init: flag to indicate to MSS SW about imem init done + * @md_ssr_ss: Instance of struct md_ssr_ss_info for a subsystem + */ +struct md_ssr_toc /* Shared IMEM ToC struct */ +{ + u32 md_ssr_toc_init; + struct md_ssr_ss_info md_ssr_ss[MAX_NUM_OF_SS]; +}; + /** * struct pil_reset_ops - PIL operations * @init_image: prepare an image for authentication diff --git a/drivers/soc/qcom/pil-msa.c b/drivers/soc/qcom/pil-msa.c index 5fcb0f95733c..4bea034f0bdd 100644 --- a/drivers/soc/qcom/pil-msa.c +++ b/drivers/soc/qcom/pil-msa.c @@ -78,7 +78,8 @@ #define MSS_MAGIC 0XAABADEAD /* CX_IPEAK Parameters */ #define CX_IPEAK_MSS BIT(5) - +/* Timeout value for MBA boot when minidump is enabled */ +#define MBA_ENCRYPTION_TIMEOUT 3000 enum scm_cmd { PAS_MEM_SETUP_CMD = 2, }; @@ -244,7 +245,12 @@ static int pil_msa_wait_for_mba_ready(struct q6v5_data *drv) struct device *dev = drv->desc.dev; int ret; u32 status; - u64 val = is_timeout_disabled() ? 0 : pbl_mba_boot_timeout_ms * 1000; + u64 val; + + if (of_property_read_bool(dev->of_node, "qcom,minidump-id")) + pbl_mba_boot_timeout_ms = MBA_ENCRYPTION_TIMEOUT; + + val = is_timeout_disabled() ? 0 : pbl_mba_boot_timeout_ms * 1000; /* Wait for PBL completion. */ ret = readl_poll_timeout(drv->rmb_base + RMB_PBL_STATUS, status, diff --git a/drivers/soc/qcom/ramdump.c b/drivers/soc/qcom/ramdump.c index c712ed392b0b..c8353dc8a43a 100644 --- a/drivers/soc/qcom/ramdump.c +++ b/drivers/soc/qcom/ramdump.c @@ -29,6 +29,8 @@ #include #define RAMDUMP_WAIT_MSECS 120000 +#define MAX_STRTBL_SIZE 512 +#define MAX_NAME_LENGTH 16 struct ramdump_device { char name[256]; @@ -391,12 +393,143 @@ static int _do_ramdump(void *handle, struct ramdump_segment *segments, } +static inline struct elf_shdr *elf_sheader(struct elfhdr *hdr) +{ + return (struct elf_shdr *)((size_t)hdr + (size_t)hdr->e_shoff); +} + +static inline struct elf_shdr *elf_section(struct elfhdr *hdr, int idx) +{ + return &elf_sheader(hdr)[idx]; +} + +static inline char *elf_str_table(struct elfhdr *hdr) +{ + if (hdr->e_shstrndx == SHN_UNDEF) + return NULL; + return (char *)hdr + elf_section(hdr, hdr->e_shstrndx)->sh_offset; +} + +static inline unsigned int set_section_name(const char *name, + struct elfhdr *ehdr) +{ + char *strtab = elf_str_table(ehdr); + static int strtable_idx = 1; + int idx, ret = 0; + + idx = strtable_idx; + if ((strtab == NULL) || (name == NULL)) + return 0; + + ret = idx; + idx += strlcpy((strtab + idx), name, MAX_NAME_LENGTH); + strtable_idx = idx + 1; + + return ret; +} + +static int _do_minidump(void *handle, struct ramdump_segment *segments, + int nsegments) +{ + int ret, i; + struct ramdump_device *rd_dev = (struct ramdump_device *)handle; + struct elfhdr *ehdr; + struct elf_shdr *shdr; + unsigned long offset, strtbl_off; + + if (!rd_dev->consumer_present) { + pr_err("Ramdump(%s): No consumers. Aborting..\n", rd_dev->name); + return -EPIPE; + } + + rd_dev->segments = segments; + rd_dev->nsegments = nsegments; + + rd_dev->elfcore_size = sizeof(*ehdr) + + (sizeof(*shdr) * (nsegments + 2)) + MAX_STRTBL_SIZE; + ehdr = kzalloc(rd_dev->elfcore_size, GFP_KERNEL); + rd_dev->elfcore_buf = (char *)ehdr; + if (!rd_dev->elfcore_buf) + return -ENOMEM; + + memcpy(ehdr->e_ident, ELFMAG, SELFMAG); + ehdr->e_ident[EI_CLASS] = ELF_CLASS; + ehdr->e_ident[EI_DATA] = ELF_DATA; + ehdr->e_ident[EI_VERSION] = EV_CURRENT; + ehdr->e_ident[EI_OSABI] = ELF_OSABI; + ehdr->e_type = ET_CORE; + ehdr->e_machine = ELF_ARCH; + ehdr->e_version = EV_CURRENT; + ehdr->e_ehsize = sizeof(*ehdr); + ehdr->e_shoff = sizeof(*ehdr); + ehdr->e_shentsize = sizeof(*shdr); + ehdr->e_shstrndx = 1; + + + offset = rd_dev->elfcore_size; + shdr = (struct elf_shdr *)(ehdr + 1); + strtbl_off = sizeof(*ehdr) + sizeof(*shdr) * (nsegments + 2); + shdr++; + shdr->sh_type = SHT_STRTAB; + shdr->sh_offset = (elf_addr_t)strtbl_off; + shdr->sh_size = MAX_STRTBL_SIZE; + shdr->sh_entsize = 0; + shdr->sh_flags = 0; + shdr->sh_name = set_section_name("STR_TBL", ehdr); + shdr++; + + for (i = 0; i < nsegments; i++, shdr++) { + /* Update elf header */ + shdr->sh_type = SHT_PROGBITS; + shdr->sh_name = set_section_name(segments[i].name, ehdr); + shdr->sh_addr = (elf_addr_t)segments[i].address; + shdr->sh_size = segments[i].size; + shdr->sh_flags = SHF_WRITE; + shdr->sh_offset = offset; + shdr->sh_entsize = 0; + offset += shdr->sh_size; + } + ehdr->e_shnum = nsegments + 2; + + rd_dev->data_ready = 1; + rd_dev->ramdump_status = -1; + + reinit_completion(&rd_dev->ramdump_complete); + + /* Tell userspace that the data is ready */ + wake_up(&rd_dev->dump_wait_q); + + /* Wait (with a timeout) to let the ramdump complete */ + ret = wait_for_completion_timeout(&rd_dev->ramdump_complete, + msecs_to_jiffies(RAMDUMP_WAIT_MSECS)); + + if (!ret) { + pr_err("Ramdump(%s): Timed out waiting for userspace.\n", + rd_dev->name); + ret = -EPIPE; + } else { + ret = (rd_dev->ramdump_status == 0) ? 0 : -EPIPE; + } + + rd_dev->data_ready = 0; + rd_dev->elfcore_size = 0; + kfree(rd_dev->elfcore_buf); + rd_dev->elfcore_buf = NULL; + return ret; +} + int do_ramdump(void *handle, struct ramdump_segment *segments, int nsegments) { return _do_ramdump(handle, segments, nsegments, false); } EXPORT_SYMBOL(do_ramdump); +int do_minidump(void *handle, struct ramdump_segment *segments, int nsegments) +{ + return _do_minidump(handle, segments, nsegments); +} +EXPORT_SYMBOL(do_minidump); + int do_elf_ramdump(void *handle, struct ramdump_segment *segments, int nsegments) { diff --git a/include/soc/qcom/ramdump.h b/include/soc/qcom/ramdump.h index 50a17c8ad605..4e23ccf269a7 100644 --- a/include/soc/qcom/ramdump.h +++ b/include/soc/qcom/ramdump.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. +/* Copyright (c) 2011-2014, 2017 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,6 +16,7 @@ struct device; struct ramdump_segment { + char *name; unsigned long address; void *v_address; unsigned long size; @@ -28,6 +29,8 @@ extern int do_ramdump(void *handle, struct ramdump_segment *segments, int nsegments); extern int do_elf_ramdump(void *handle, struct ramdump_segment *segments, int nsegments); +extern int do_minidump(void *handle, struct ramdump_segment *segments, + int nsegments); #else static inline void *create_ramdump_device(const char *dev_name,