diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 669bc96ad601..cc497ebd393e 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -104,6 +104,102 @@ config QCOM_PM modes. It interface with various system drivers to put the cores in low power modes. +config MSM_PIL + tristate "Peripheral image loader" + select FW_LOADER + help + Some peripherals' firmware need to be loaded into memory before + they can be brought out of reset. The Peripheral Image Loader (PIL) + framework is used to achive this. The driver relays the lower-level + functionalities such as, authenticating the images, performing the + reset of the peripheral, and so on to the drivers that are registered + with it. + + Say 'y' or 'm' to support these devices. + +config MSM_SUBSYSTEM_RESTART + tristate "MSM Subsystem Restart" + depends on MSM_PIL + select QCOM_QMI_HELPERS + help + This option enables the MSM subsystem restart framework. + + The MSM subsystem restart framework provides support to boot, + shutdown, and restart subsystems with a reference counted API. + It also extends the APIs to add support for MSM System Monitor + using the QMI layer. These APIs may be used for sending events + or passing commands. + Moreover, the framework also notifies userspace of transitions + between these states via sysfs. + +config SETUP_SSR_NOTIF_TIMEOUTS + bool "Set timeouts on SSR sysmon notifications and notifier callbacks" + help + Setup timers prior to initiating communication between + subsystems through sysmon, and prior to sending notifications + to drivers in the kernel that have registered callbacks with the + subsystem notifier framework for a particular subsystem. This + is a debugging feature. + +config SSR_SYSMON_NOTIF_TIMEOUT + depends on SETUP_SSR_NOTIF_TIMEOUTS + int "SSR Sysmon notifications timeout in ms" + default 10000 + help + The amount of time, in milliseconds, that should elapse between + the start and end of sysmon SSR notifications, before a warning + is emitted. + +config SSR_SUBSYS_NOTIF_TIMEOUT + depends on SETUP_SSR_NOTIF_TIMEOUTS + int "SSR Subsystem notifier timeout in ms" + default 10000 + help + The amount of time, in milliseconds, that should elapse between + the start and end of SSR notifications through the subsystem + notifier, before a warning is emitted. + +config PANIC_ON_SSR_NOTIF_TIMEOUT + bool "Trigger kernel panic when notification timeout expires" + depends on SETUP_SSR_NOTIF_TIMEOUTS + help + This is a debug feature where a kernel panic is triggered when + communication between subsystems through sysmon is taking too + long. This scneario can happen if the peripheral has died and + is no longer responsive. + Also trigger a kernel panic if invoking the callbacks registered + with a particular subsystem's notifications by the subsystem + notifier framework is taking too long. + +config MSM_PIL_SSR_GENERIC + tristate "MSM Subsystem Boot Support" + depends on MSM_PIL && MSM_SUBSYSTEM_RESTART + help + Support for booting and shutting down MSM Subsystem processors. + This driver also monitors the SMSM status bits and the watchdog + interrupt for the subsystem and restarts it on a watchdog bite + or a fatal error. Subsystems include LPASS, Venus, VPU, WCNSS and + BCSS. + +config MSM_SERVICE_LOCATOR + tristate "Service Locator" + select QCOM_QMI_HELPERS + help + The Service Locator provides a library to retrieve location + information given a service identifier. Location here translates + to what process domain exports the service, and which subsystem + that process domain will execute in. + +config MSM_SERVICE_NOTIFIER + tristate "Service Notifier" + depends on MSM_SERVICE_LOCATOR && MSM_SUBSYSTEM_RESTART + help + The Service Notifier provides a library for a kernel client to + register for state change notifications regarding a remote service. + A remote service here refers to a process providing certain services + like audio, the identifier for which is provided by the service + locator. + config QCOM_QMI_HELPERS tristate depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 826950744ef6..7142bff8dc36 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -7,6 +7,12 @@ obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o obj-$(CONFIG_QCOM_PM) += spm.o +obj-$(CONFIG_MSM_PIL) += peripheral-loader.o +obj-$(CONFIG_MSM_SUBSYSTEM_RESTART) += ssr.o +ssr-y := subsystem_restart.o subsystem_notif.o ramdump.o sysmon-qmi.o +obj-$(CONFIG_MSM_PIL_SSR_GENERIC) += subsys-pil-tz.o +obj-$(CONFIG_MSM_SERVICE_NOTIFIER) += service-notifier.o +obj-$(CONFIG_MSM_SERVICE_LOCATOR) += service-locator.o obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o qmi_helpers-y += qmi_encdec.o qmi_interface.o obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o diff --git a/drivers/soc/qcom/minidump_private.h b/drivers/soc/qcom/minidump_private.h new file mode 100644 index 000000000000..808cf0673fed --- /dev/null +++ b/drivers/soc/qcom/minidump_private.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __MINIDUMP_PRIVATE_H +#define __MINIDUMP_PRIVATE_H + +#define MD_REVISION 1 +#define SBL_MINIDUMP_SMEM_ID 602 +#define MAX_NUM_OF_SS 10 +#define MD_SS_HLOS_ID 0 +#define SMEM_ENTRY_SIZE 40 + +/* Bootloader has 16 byte support, 4 bytes reserved for itself */ +#define MAX_REGION_NAME_LENGTH 16 + +#define MD_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0) +#define MD_REGION_INVALID ('I' << 24 | 'N' << 16 | 'V' << 8 | 'A' << 0) +#define MD_REGION_INIT ('I' << 24 | 'N' << 16 | 'I' << 8 | 'T' << 0) +#define MD_REGION_NOINIT 0 + +#define MD_SS_ENCR_REQ (0 << 24 | 'Y' << 16 | 'E' << 8 | 'S' << 0) +#define MD_SS_ENCR_NOTREQ (0 << 24 | 0 << 16 | 'N' << 8 | 'R' << 0) +#define MD_SS_ENCR_NONE ('N' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0) +#define MD_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0) +#define MD_SS_ENCR_START ('S' << 24 | 'T' << 16 | 'R' << 8 | 'T' << 0) +#define MD_SS_ENABLED ('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0) +#define MD_SS_DISABLED ('D' << 24 | 'S' << 16 | 'B' << 8 | 'L' << 0) + +/** + * md_ss_region - Minidump region + * @name : Name of the region to be dumped + * @seq_num: : Use to differentiate regions with same name. + * @md_valid : This entry to be dumped (if set to 1) + * @region_base_address : Physical address of region to be dumped + * @region_size : Size of the region + */ +struct md_ss_region { + char name[MAX_REGION_NAME_LENGTH]; + u32 seq_num; + u32 md_valid; + u64 region_base_address; + u64 region_size; +}; + +/** + * md_ss_toc: Sub system SMEM Table of content + * @md_ss_toc_init : SS toc init status + * @md_ss_enable_status : if set to 1, Bootloader would dump this SS regions + * @encryption_status: Encryption status for this subsystem + * @encryption_required : Decides to encrypt the SS regions or not + * @ss_region_count : Number of regions added in this SS toc + * @md_ss_smem_regions_baseptr : regions base pointer of the Subsystem + */ +struct md_ss_toc { + u32 md_ss_toc_init; + u32 md_ss_enable_status; + u32 encryption_status; + u32 encryption_required; + u32 ss_region_count; + u64 md_ss_smem_regions_baseptr; +}; + +/** + * md_global_toc: Global Table of Content + * @md_toc_init : Global Minidump init status + * @md_revision : Minidump revision + * @md_enable_status : Minidump enable status + * @md_ss_toc : Array of subsystems toc + */ +struct md_global_toc { + u32 md_toc_init; + u32 md_revision; + u32 md_enable_status; + struct md_ss_toc md_ss_toc[MAX_NUM_OF_SS]; +}; + +#endif diff --git a/drivers/soc/qcom/peripheral-loader.c b/drivers/soc/qcom/peripheral-loader.c new file mode 100644 index 000000000000..7f033aa34526 --- /dev/null +++ b/drivers/soc/qcom/peripheral-loader.c @@ -0,0 +1,1678 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2010-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#define CREATE_TRACE_POINTS +#include + +#include "peripheral-loader.h" + +#define pil_err(desc, fmt, ...) \ + dev_err(desc->dev, "%s: " fmt, desc->name, ##__VA_ARGS__) +#define pil_info(desc, fmt, ...) \ + dev_info(desc->dev, "%s: " fmt, desc->name, ##__VA_ARGS__) + +#if defined(CONFIG_ARM) +#define pil_memset_io(d, c, count) memset(d, c, count) +#else +#define pil_memset_io(d, c, count) memset_io(d, c, count) +#endif + +#define PIL_NUM_DESC 16 +#define MAX_LEN 96 +#define NUM_OF_ENCRYPTED_KEY 3 + +static void __iomem *pil_info_base; +static struct md_global_toc *g_md_toc; + +/** + * proxy_timeout - Override for proxy vote timeouts + * -1: Use driver-specified timeout + * 0: Hold proxy votes until shutdown + * >0: Specify a custom timeout in ms + */ +static int proxy_timeout_ms = -1; +module_param(proxy_timeout_ms, int, 0644); + +static bool disable_timeouts; + +static struct workqueue_struct *pil_wq; + +/** + * struct pil_mdt - Representation of .mdt file in memory + * @hdr: ELF32 header + * @phdr: ELF32 program headers + */ +struct pil_mdt { + struct elf32_hdr hdr; + struct elf32_phdr phdr[]; +}; + +/** + * struct pil_seg - memory map representing one segment + * @next: points to next seg mentor NULL if last segment + * @paddr: physical start address of segment + * @sz: size of segment + * @filesz: size of segment on disk + * @num: segment number + * @relocated: true if segment is relocated, false otherwise + * + * Loosely based on an elf program header. Contains all necessary information + * to load and initialize a segment of the image in memory. + */ +struct pil_seg { + phys_addr_t paddr; + unsigned long sz; + size_t filesz; + int num; + struct list_head list; + bool relocated; +}; + +/** + * struct pil_priv - Private state for a pil_desc + * @proxy: work item used to run the proxy unvoting routine + * @ws: wakeup source to prevent suspend during pil_boot + * @wname: name of @ws + * @desc: pointer to pil_desc this is private data for + * @seg: list of segments sorted by physical address + * @entry_addr: physical address where processor starts booting at + * @base_addr: smallest start address among all segments that are relocatable + * @region_start: address where relocatable region starts or lowest address + * for non-relocatable images + * @region_end: address where relocatable region ends or highest address for + * non-relocatable images + * @region: region allocated for relocatable images + * @unvoted_flag: flag to keep track if we have unvoted or not. + * + * This struct contains data for a pil_desc that should not be exposed outside + * of this file. This structure points to the descriptor and the descriptor + * points to this structure so that PIL drivers can't access the private + * data of a descriptor but this file can access both. + */ +struct pil_priv { + struct delayed_work proxy; + struct wakeup_source ws; + char wname[32]; + struct pil_desc *desc; + int num_segs; + struct list_head segs; + phys_addr_t entry_addr; + phys_addr_t base_addr; + phys_addr_t region_start; + phys_addr_t region_end; + bool is_region_allocated; + struct pil_image_info __iomem *info; + int id; + int unvoted_flag; + size_t region_size; +}; + +/** + * struct aux_minidumpinfo - State maintained for each aux minidump entry dumped + * during SSR + * @region_info_aux: region that contains an array of descriptors, where + * each one describes the base address and size of a segment that should be + * dumped during SSR minidump + * @seg_cnt: the number of such regions + */ +struct aux_minidump_info { + struct md_ss_region __iomem *region_info_aux; + unsigned int seg_cnt; +}; + +/** + * map_minidump_regions() - map the region containing the segment descriptors + * for an entry in the global minidump table + * @toc: the subsystem table of contents + * @num_segs: the number of segment descriptors in the region defined by the + * subsystem table of contents + * + * Maps the region containing num_segs segment descriptor into the kernel's + * address space. + */ +static struct md_ss_region __iomem *map_minidump_regions(struct md_ss_toc *toc, + int num_segs) +{ + u64 region_ptr = (u64)toc->md_ss_smem_regions_baseptr; + void __iomem *segtable_base = ioremap((unsigned long)region_ptr, + num_segs * + sizeof(struct md_ss_region)); + struct md_ss_region __iomem *region_info = (struct md_ss_region __iomem + *)segtable_base; + if (!region_info) + return NULL; + + pr_debug("Minidump : Segments in minidump 0x%x\n", num_segs); + + return region_info; +} + +/** + * map_aux_minidump_regions() - map the region containing the segment + * descriptors for a set of entries in the global minidump table + * @desc: descriptor from pil_desc_init() + * @aux_mdump_data: contains an array of pointers to segment descriptor regions + * per auxiliary minidump ID + * @total_aux_segs: value that is incremented to capture the total number of + * segments that are needed to dump all auxiliary regions + * + * Maps the regions of segment descriptors for a set of auxiliary minidump IDs. + */ +static int map_aux_minidump_regions(struct pil_desc *desc, + struct aux_minidump_info *aux_mdump_data, + int *total_aux_segs) +{ + unsigned int i; + struct md_ss_toc *toc; + + for (i = 0; i < desc->num_aux_minidump_ids; i++) { + toc = desc->aux_minidump[i]; + if (toc && (toc->md_ss_toc_init == true) && + (toc->md_ss_enable_status == MD_SS_ENABLED) && + (toc->md_ss_smem_regions_baseptr) && + (toc->encryption_status == MD_SS_ENCR_DONE)) { + aux_mdump_data[i].seg_cnt = toc->ss_region_count; + aux_mdump_data[i].region_info_aux = + map_minidump_regions(desc->aux_minidump[i], + aux_mdump_data[i].seg_cnt); + if (!aux_mdump_data[i].region_info_aux) + return -ENOMEM; + *total_aux_segs += aux_mdump_data[i].seg_cnt; + } + } + + return 0; +} + +/** + * unmap_aux_minidump_regions() - unmap the regions containing the segment + * descriptors for a set of entries in the global minidump table + * @aux_mdump: contains an array of pointers to segment descriptor regions + * per auxiliary minidump ID + * @num_aux_md_ids: the number of auxiliary minidump IDs + * + * Unmaps the regions of segment descriptors for a set of auxiliary minidump + * IDs. + */ +static void unmap_aux_minidump_regions(struct aux_minidump_info *aux_mdump, + int num_aux_md_ids) +{ + unsigned int i = 0; + + while (i < num_aux_md_ids && aux_mdump[i].region_info_aux) { + iounmap(aux_mdump[i].region_info_aux); + i++; + } +} + +/** + * prepare_minidump_segments() - Fills in the necessary information for the + * ramdump driver to dump a region of memory, described by a segment. + * @rd_segs: segments that will be filled in for ramdump collection + * @region_info: the start of the region that contains the segment descriptors + * @num_segs: the number of segment descriptors in region_info + * @ss_valid_seg_cnt: the number of valid segments for this ramdump. Will be + * decremented if a segment is found to be invalid. + * + * Fills in the necessary information in the ramdump_segment structures to + * describe regions that should be dumped by the ramdump driver. + */ +static unsigned int prepare_minidump_segments(struct ramdump_segment *rd_segs, + struct md_ss_region __iomem *region_info, + int num_segs, int *ss_valid_seg_cnt) +{ + void __iomem *offset; + unsigned int i; + unsigned int val_segs = 0; + + + for (i = 0; i < num_segs; i++) { + memcpy(&offset, ®ion_info, sizeof(region_info)); + offset = offset + sizeof(region_info->name) + + sizeof(region_info->seq_num); + if (__raw_readl(offset) == MD_REGION_VALID) { + memcpy(&rd_segs->name, ®ion_info, + sizeof(region_info)); + offset = offset + + sizeof(region_info->md_valid); + rd_segs->address = __raw_readl(offset); + offset = offset + + sizeof(region_info->region_base_address); + rd_segs->size = __raw_readl(offset); + pr_debug("Minidump : Dumping segment %s with address 0x%lx and size 0x%x\n", + rd_segs->name, rd_segs->address, + (unsigned int)rd_segs->size); + rd_segs++; + val_segs++; + } else { + (*ss_valid_seg_cnt)--; + } + + region_info++; + } + + return val_segs; +} + +/** + * prepare_aux_minidump_segments() - Fills in the necessary information for the + * ramdump driver to dump a region of memory, described by a segment. This is + * done for a set of auxiliary minidump IDs. + * @rd_segs: segments that will be filled in for ramdump collection + * @aux_mdump: contains an array of pointers to segment descriptor regions per + * auxiliary minidump ID + * @ss_valid_seg_cnt: the number of valid segments for this ramdump. Will be + * decremented if a segment is found to be invalid. + * @num_aux_md_ids: the number of auxiliary minidump IDs + * + * Fills in the necessary information in the ramdump_segment structures to + * describe the regions that should be dumped by the ramdump driver for a set + * of auxiliary minidump IDs. + */ +static void prepare_aux_minidump_segments(struct ramdump_segment *rd_segs, + struct aux_minidump_info *aux_mdump, + int *ss_valid_seg_cnt, + int num_aux_md_ids) +{ + unsigned int i; + struct ramdump_segment *s = rd_segs; + unsigned int next_offset = 0; + + for (i = 0; i < num_aux_md_ids; i++) { + s = &rd_segs[next_offset]; + next_offset = prepare_minidump_segments(s, + aux_mdump[i].region_info_aux, + aux_mdump[i].seg_cnt, + ss_valid_seg_cnt); + } +} + +static int pil_do_minidump(struct pil_desc *desc, void *ramdump_dev) +{ + struct md_ss_region __iomem *region_info_ss; + struct ramdump_segment *ramdump_segs; + struct pil_priv *priv = desc->priv; + int ss_mdump_seg_cnt_ss = 0, total_segs; + int total_aux_segs = 0; + int ss_valid_seg_cnt; + int ret; + struct aux_minidump_info *aux_minidump_data = NULL; + unsigned int next_offset; + + if (!ramdump_dev) + return -ENODEV; + + ss_mdump_seg_cnt_ss = desc->minidump_ss->ss_region_count; + region_info_ss = map_minidump_regions(desc->minidump_ss, + ss_mdump_seg_cnt_ss); + if (!region_info_ss) + return -EINVAL; + + if (desc->num_aux_minidump_ids > 0) { + aux_minidump_data = kcalloc(desc->num_aux_minidump_ids, + sizeof(*aux_minidump_data), + GFP_KERNEL); + if (!aux_minidump_data) { + ret = -ENOMEM; + goto setup_fail; + } + + if (map_aux_minidump_regions(desc, aux_minidump_data, + &total_aux_segs) < 0) { + ret = -ENOMEM; + goto mapping_fail; + } + } + + total_segs = ss_mdump_seg_cnt_ss + total_aux_segs; + ramdump_segs = kcalloc(total_segs, + sizeof(*ramdump_segs), GFP_KERNEL); + if (!ramdump_segs) { + ret = -ENOMEM; + goto mapping_fail; + } + + if (desc->subsys_vmid > 0) + ret = pil_assign_mem_to_linux(desc, priv->region_start, + (priv->region_end - priv->region_start)); + + ss_valid_seg_cnt = total_segs; + next_offset = prepare_minidump_segments(ramdump_segs, region_info_ss, + ss_mdump_seg_cnt_ss, + &ss_valid_seg_cnt); + + if (desc->num_aux_minidump_ids > 0) + prepare_aux_minidump_segments(&ramdump_segs[next_offset], + aux_minidump_data, + &ss_valid_seg_cnt, + desc->num_aux_minidump_ids); + + ret = do_minidump(ramdump_dev, ramdump_segs, ss_valid_seg_cnt); + if (ret) + pil_err(desc, "%s: Minidump collection failed for subsys %s rc:%d\n", + __func__, desc->name, ret); + + if (desc->subsys_vmid > 0) + ret = pil_assign_mem_to_subsys(desc, priv->region_start, + (priv->region_end - priv->region_start)); + + kfree(ramdump_segs); +mapping_fail: + unmap_aux_minidump_regions(aux_minidump_data, + desc->num_aux_minidump_ids); + kfree(aux_minidump_data); +setup_fail: + iounmap(region_info_ss); + return ret; +} + +/** + * pil_do_ramdump() - Ramdump an image + * @desc: descriptor from pil_desc_init() + * @ramdump_dev: ramdump device returned from create_ramdump_device() + * + * Calls the ramdump API with a list of segments generated from the addresses + * that the descriptor corresponds to. + */ +int pil_do_ramdump(struct pil_desc *desc, + void *ramdump_dev, void *minidump_dev) +{ + struct ramdump_segment *ramdump_segs, *s; + struct pil_priv *priv = desc->priv; + struct pil_seg *seg; + int count = 0, map_cnt = 0, ret; + + if (desc->minidump_ss) { + pr_debug("Minidump : md_ss_toc->md_ss_toc_init is 0x%x\n", + (unsigned int)desc->minidump_ss->md_ss_toc_init); + pr_debug("Minidump : md_ss_toc->md_ss_enable_status is 0x%x\n", + (unsigned int)desc->minidump_ss->md_ss_enable_status); + pr_debug("Minidump : md_ss_toc->encryption_status is 0x%x\n", + (unsigned int)desc->minidump_ss->encryption_status); + pr_debug("Minidump : md_ss_toc->ss_region_count is 0x%x\n", + (unsigned int)desc->minidump_ss->ss_region_count); + pr_debug("Minidump : md_ss_toc->md_ss_smem_regions_baseptr is 0x%x\n", + (unsigned int) + desc->minidump_ss->md_ss_smem_regions_baseptr); + /** + * Collect minidump if SS ToC is valid and segment table + * is initialized in memory and encryption status is set. + */ + if ((desc->minidump_ss->md_ss_smem_regions_baseptr != 0) && + (desc->minidump_ss->md_ss_toc_init == true) && + (desc->minidump_ss->md_ss_enable_status == + MD_SS_ENABLED)) { + if (desc->minidump_ss->encryption_status == + MD_SS_ENCR_DONE) { + pr_debug("Dumping Minidump for %s\n", + desc->name); + return pil_do_minidump(desc, minidump_dev); + } + pr_debug("Minidump aborted for %s\n", desc->name); + return -EINVAL; + } + } + pr_debug("Continuing with full SSR dump for %s\n", desc->name); + list_for_each_entry(seg, &priv->segs, list) + count++; + + ramdump_segs = kcalloc(count, 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; + list_for_each_entry(seg, &priv->segs, list) { + s->v_address = ioremap_wc(seg->paddr, seg->sz); + if (!s->v_address) + goto ioremap_err; + + s->size = seg->sz; + s++; + map_cnt++; + } + + ret = do_elf_ramdump(ramdump_dev, ramdump_segs, count); + + s = ramdump_segs; + list_for_each_entry(seg, &priv->segs, list) { + iounmap(s->v_address); + s++; + } + + kfree(ramdump_segs); + + if (ret) + pil_err(desc, "%s: Ramdump collection failed for subsys %s rc:%d\n", + __func__, desc->name, ret); + + if (desc->subsys_vmid > 0) + ret = pil_assign_mem_to_subsys(desc, priv->region_start, + (priv->region_end - priv->region_start)); + + return ret; + +ioremap_err: + /* Undo all the previous mappings */ + s = ramdump_segs; + while (map_cnt--) { + iounmap(s->v_address); + s++; + } + + kfree(ramdump_segs); + return -ENOMEM; +} +EXPORT_SYMBOL(pil_do_ramdump); + +int pil_assign_mem_to_subsys(struct pil_desc *desc, phys_addr_t addr, + size_t size) +{ + int ret; + int srcVM[1] = {VMID_HLOS}; + int destVM[1] = {desc->subsys_vmid}; + int destVMperm[1] = {PERM_READ | PERM_WRITE}; + + ret = hyp_assign_phys(addr, size, srcVM, 1, destVM, destVMperm, 1); + if (ret) + pil_err(desc, "%s: failed for %pa address of size %zx - subsys VMid %d rc:%d\n", + __func__, &addr, size, desc->subsys_vmid, ret); + return ret; +} +EXPORT_SYMBOL(pil_assign_mem_to_subsys); + +int pil_assign_mem_to_linux(struct pil_desc *desc, phys_addr_t addr, + size_t size) +{ + int ret; + int srcVM[1] = {desc->subsys_vmid}; + int destVM[1] = {VMID_HLOS}; + int destVMperm[1] = {PERM_READ | PERM_WRITE | PERM_EXEC}; + + ret = hyp_assign_phys(addr, size, srcVM, 1, destVM, destVMperm, 1); + if (ret) + panic("%s: failed for %pa address of size %zx - subsys VMid %d rc:%d\n", + __func__, &addr, size, desc->subsys_vmid, ret); + + return ret; +} +EXPORT_SYMBOL(pil_assign_mem_to_linux); + +int pil_assign_mem_to_subsys_and_linux(struct pil_desc *desc, + phys_addr_t addr, size_t size) +{ + int ret; + int srcVM[1] = {VMID_HLOS}; + int destVM[2] = {VMID_HLOS, desc->subsys_vmid}; + int destVMperm[2] = {PERM_READ | PERM_WRITE, PERM_READ | PERM_WRITE}; + + ret = hyp_assign_phys(addr, size, srcVM, 1, destVM, destVMperm, 2); + if (ret) + pil_err(desc, "%s: failed for %pa address of size %zx - subsys VMid %d rc:%d\n", + __func__, &addr, size, desc->subsys_vmid, ret); + + return ret; +} +EXPORT_SYMBOL(pil_assign_mem_to_subsys_and_linux); + +int pil_reclaim_mem(struct pil_desc *desc, phys_addr_t addr, size_t size, + int VMid) +{ + int ret; + int srcVM[2] = {VMID_HLOS, desc->subsys_vmid}; + int destVM[1] = {VMid}; + int destVMperm[1] = {PERM_READ | PERM_WRITE}; + + if (VMid == VMID_HLOS) + destVMperm[0] = PERM_READ | PERM_WRITE | PERM_EXEC; + + ret = hyp_assign_phys(addr, size, srcVM, 2, destVM, destVMperm, 1); + if (ret) + panic("%s: failed for %pa address of size %zx - subsys VMid %d. Fatal error.\n", + __func__, &addr, size, desc->subsys_vmid); + + return ret; +} +EXPORT_SYMBOL(pil_reclaim_mem); + +/** + * pil_get_entry_addr() - Retrieve the entry address of a peripheral image + * @desc: descriptor from pil_desc_init() + * + * Returns the physical address where the image boots at or 0 if unknown. + */ +phys_addr_t pil_get_entry_addr(struct pil_desc *desc) +{ + return desc->priv ? desc->priv->entry_addr : 0; +} +EXPORT_SYMBOL(pil_get_entry_addr); + +static void __pil_proxy_unvote(struct pil_priv *priv) +{ + struct pil_desc *desc = priv->desc; + + desc->ops->proxy_unvote(desc); + notify_proxy_unvote(desc->dev); + __pm_relax(&priv->ws); + module_put(desc->owner); + +} + +static void pil_proxy_unvote_work(struct work_struct *work) +{ + struct delayed_work *delayed = to_delayed_work(work); + struct pil_priv *priv = container_of(delayed, struct pil_priv, proxy); + + __pil_proxy_unvote(priv); +} + +static int pil_proxy_vote(struct pil_desc *desc) +{ + int ret = 0; + struct pil_priv *priv = desc->priv; + + if (desc->ops->proxy_vote) { + __pm_stay_awake(&priv->ws); + ret = desc->ops->proxy_vote(desc); + if (ret) + __pm_relax(&priv->ws); + } + + if (desc->proxy_unvote_irq) + enable_irq(desc->proxy_unvote_irq); + notify_proxy_vote(desc->dev); + + return ret; +} + +static void pil_proxy_unvote(struct pil_desc *desc, int immediate) +{ + struct pil_priv *priv = desc->priv; + unsigned long timeout; + + if (proxy_timeout_ms == 0 && !immediate) + return; + else if (proxy_timeout_ms > 0) + timeout = proxy_timeout_ms; + else + timeout = desc->proxy_timeout; + + if (desc->ops->proxy_unvote) { + if (WARN_ON(!try_module_get(desc->owner))) + return; + + if (immediate) + timeout = 0; + + if (!desc->proxy_unvote_irq || immediate) + schedule_delayed_work(&priv->proxy, + msecs_to_jiffies(timeout)); + } +} + +static irqreturn_t proxy_unvote_intr_handler(int irq, void *dev_id) +{ + struct pil_desc *desc = dev_id; + struct pil_priv *priv = desc->priv; + + pil_info(desc, "Power/Clock ready interrupt received\n"); + if (!desc->priv->unvoted_flag) { + desc->priv->unvoted_flag = 1; + __pil_proxy_unvote(priv); + } + + return IRQ_HANDLED; +} + +static bool segment_is_relocatable(const struct elf32_phdr *p) +{ + return !!(p->p_flags & BIT(27)); +} + +static phys_addr_t pil_reloc(const struct pil_priv *priv, phys_addr_t addr) +{ + return addr - priv->base_addr + priv->region_start; +} + +static struct pil_seg *pil_init_seg(const struct pil_desc *desc, + const struct elf32_phdr *phdr, int num) +{ + bool reloc = segment_is_relocatable(phdr); + const struct pil_priv *priv = desc->priv; + struct pil_seg *seg; + + if (phdr->p_filesz > phdr->p_memsz) { + pil_err(desc, "Segment %d: file size (%u) is greater than mem size (%u).\n", + num, phdr->p_filesz, phdr->p_memsz); + return ERR_PTR(-EINVAL); + } + + seg = kmalloc(sizeof(*seg), GFP_KERNEL); + if (!seg) + return ERR_PTR(-ENOMEM); + seg->num = num; + seg->paddr = reloc ? pil_reloc(priv, phdr->p_paddr) : phdr->p_paddr; + seg->filesz = phdr->p_filesz; + seg->sz = phdr->p_memsz; + seg->relocated = reloc; + INIT_LIST_HEAD(&seg->list); + + return seg; +} + +#define segment_is_hash(flag) (((flag) & (0x7 << 24)) == (0x2 << 24)) + +static int segment_is_loadable(const struct elf32_phdr *p) +{ + return (p->p_type == PT_LOAD) && !segment_is_hash(p->p_flags) && + p->p_memsz; +} + +static void pil_dump_segs(const struct pil_priv *priv) +{ + struct pil_seg *seg; + phys_addr_t seg_h_paddr; + + list_for_each_entry(seg, &priv->segs, list) { + seg_h_paddr = seg->paddr + seg->sz; + pil_info(priv->desc, "%d: %pa %pa\n", seg->num, + &seg->paddr, &seg_h_paddr); + } +} + +/* + * Ensure the entry address lies within the image limits and if the image is + * relocatable ensure it lies within a relocatable segment. + */ +static int pil_init_entry_addr(struct pil_priv *priv, const struct pil_mdt *mdt) +{ + struct pil_seg *seg; + phys_addr_t entry = mdt->hdr.e_entry; + bool image_relocated = priv->is_region_allocated; + + if (image_relocated) + entry = pil_reloc(priv, entry); + priv->entry_addr = entry; + + if (priv->desc->flags & PIL_SKIP_ENTRY_CHECK) + return 0; + + list_for_each_entry(seg, &priv->segs, list) { + if (entry >= seg->paddr && entry < seg->paddr + seg->sz) { + if (!image_relocated) + return 0; + else if (seg->relocated) + return 0; + } + } + pil_err(priv->desc, "entry address %pa not within range\n", &entry); + pil_dump_segs(priv); + return -EADDRNOTAVAIL; +} + +static int pil_alloc_region(struct pil_priv *priv, phys_addr_t min_addr, + phys_addr_t max_addr, size_t align) +{ + size_t size = max_addr - min_addr; + size_t aligned_size; + struct device_node *ofnode = priv->desc->dev->of_node; + struct device_node *mem_node; + int ret; + struct resource res; + + /* Don't reallocate due to fragmentation concerns, just sanity check */ + if (priv->is_region_allocated) { + if (WARN(priv->region_end - priv->region_start < size, + "Can't reuse PIL memory, too small\n")) + return -ENOMEM; + return 0; + } + + if (align >= SZ_4M) + aligned_size = ALIGN(size, SZ_4M); + else if (align >= SZ_1M) + aligned_size = ALIGN(size, SZ_1M); + else + aligned_size = ALIGN(size, SZ_4K); + + /* read memory region here */ + mem_node = of_parse_phandle(ofnode, "memory-region", 0); + if (!mem_node) { + pil_err(priv->desc, "No memory-region associated\n"); + ret = -ENOMEM; + goto err; + } + + ret = of_address_to_resource(mem_node, 0, &res); + if (ret < 0) { + pil_err(priv->desc, "Failed to get the resource\n"); + goto err; + } + + priv->is_region_allocated = true; + priv->region_start = res.start; + priv->region_end = priv->region_start + size; + priv->base_addr = min_addr; + priv->region_size = aligned_size; + + return 0; + +err: + priv->region_start = 0; + priv->region_end = 0; + + return ret; +} + +static int pil_setup_region(struct pil_priv *priv, const struct pil_mdt *mdt) +{ + const struct elf32_phdr *phdr; + phys_addr_t min_addr_r, min_addr_n, max_addr_r, max_addr_n, start, end; + size_t align = 0; + int i, ret = 0; + bool relocatable = false; + + min_addr_n = min_addr_r = (phys_addr_t)ULLONG_MAX; + max_addr_n = max_addr_r = 0; + + /* Find the image limits */ + for (i = 0; i < mdt->hdr.e_phnum; i++) { + phdr = &mdt->phdr[i]; + if (!segment_is_loadable(phdr)) + continue; + + start = phdr->p_paddr; + end = start + phdr->p_memsz; + + if (segment_is_relocatable(phdr)) { + min_addr_r = min(min_addr_r, start); + max_addr_r = max(max_addr_r, end); + /* + * Lowest relocatable segment dictates alignment of + * relocatable region + */ + if (min_addr_r == start) + align = phdr->p_align; + relocatable = true; + } else { + min_addr_n = min(min_addr_n, start); + max_addr_n = max(max_addr_n, end); + } + + } + + /* + * Align the max address to the next 4K boundary to satisfy iommus and + * XPUs that operate on 4K chunks. + */ + max_addr_n = ALIGN(max_addr_n, SZ_4K); + max_addr_r = ALIGN(max_addr_r, SZ_4K); + + if (relocatable) { + ret = pil_alloc_region(priv, min_addr_r, max_addr_r, align); + } else { + priv->region_start = min_addr_n; + priv->region_end = max_addr_n; + priv->base_addr = min_addr_n; + } + + if (priv->info) { + __iowrite32_copy(&priv->info->start, &priv->region_start, + sizeof(priv->region_start) / 4); + writel_relaxed(priv->region_end - priv->region_start, + &priv->info->size); + } + + return ret; +} + +static int pil_cmp_seg(void *priv, struct list_head *a, struct list_head *b) +{ + int ret = 0; + struct pil_seg *seg_a = list_entry(a, struct pil_seg, list); + struct pil_seg *seg_b = list_entry(b, struct pil_seg, list); + + if (seg_a->paddr < seg_b->paddr) + ret = -1; + else if (seg_a->paddr > seg_b->paddr) + ret = 1; + + return ret; +} + +static int pil_init_mmap(struct pil_desc *desc, const struct pil_mdt *mdt) +{ + struct pil_priv *priv = desc->priv; + const struct elf32_phdr *phdr; + struct pil_seg *seg; + int i, ret; + + ret = pil_setup_region(priv, mdt); + if (ret) + return ret; + + + pil_info(desc, "loading from %pa to %pa\n", &priv->region_start, + &priv->region_end); + + priv->num_segs = 0; + for (i = 0; i < mdt->hdr.e_phnum; i++) { + phdr = &mdt->phdr[i]; + if (!segment_is_loadable(phdr)) + continue; + + seg = pil_init_seg(desc, phdr, i); + if (IS_ERR(seg)) + return PTR_ERR(seg); + + list_add_tail(&seg->list, &priv->segs); + priv->num_segs++; + } + list_sort(NULL, &priv->segs, pil_cmp_seg); + + return pil_init_entry_addr(priv, mdt); +} + +struct pil_map_fw_info { + void *region; + unsigned long attrs; + phys_addr_t base_addr; + struct device *dev; +}; + +static void pil_release_mmap(struct pil_desc *desc) +{ + struct pil_priv *priv = desc->priv; + struct pil_seg *p, *tmp; + u64 zero = 0ULL; + + if (priv->info) { + __iowrite32_copy(&priv->info->start, &zero, + sizeof(zero) / 4); + writel_relaxed(0, &priv->info->size); + } + + list_for_each_entry_safe(p, tmp, &priv->segs, list) { + list_del(&p->list); + kfree(p); + } +} + +static void pil_clear_segment(struct pil_desc *desc) +{ + struct pil_priv *priv = desc->priv; + void __iomem *buf; + + struct pil_map_fw_info map_fw_info = { + .attrs = desc->attrs, + .base_addr = priv->region_start, + .dev = desc->dev, + }; + + void *map_data = desc->map_data ? desc->map_data : &map_fw_info; + + /* Clear memory so that unauthorized ELF code is not left behind */ + buf = desc->map_fw_mem(priv->region_start, (priv->region_end - + priv->region_start), map_data); + + if (!buf) { + pil_err(desc, "Failed to map memory\n"); + return; + } + + pil_memset_io(buf, 0, (priv->region_end - priv->region_start)); + desc->unmap_fw_mem(buf, (priv->region_end - priv->region_start), + map_data); + +} + +#define IOMAP_SIZE SZ_1M + +static void __iomem *map_fw_mem(phys_addr_t paddr, size_t size, void *data) +{ + return ioremap_wc(paddr, size); +} + +static void unmap_fw_mem(void __iomem *vaddr, size_t size, void *data) +{ + iounmap(vaddr); +} + +static int pil_load_seg(struct pil_desc *desc, struct pil_seg *seg) +{ + int ret = 0, count; + phys_addr_t paddr; + char fw_name[30]; + int num = seg->num; + const struct firmware *fw = NULL; + void __iomem *firmware_buf; + struct pil_map_fw_info map_fw_info = { + .attrs = desc->attrs, + .base_addr = desc->priv->region_start, + .dev = desc->dev, + }; + void *map_data = desc->map_data ? desc->map_data : &map_fw_info; + + if (seg->filesz) { + snprintf(fw_name, ARRAY_SIZE(fw_name), "%s.b%02d", + desc->fw_name, num); + firmware_buf = desc->map_fw_mem(seg->paddr, seg->filesz, + map_data); + if (!firmware_buf) { + pil_err(desc, "Failed to map memory for firmware buffer\n"); + return -ENOMEM; + } + + ret = request_firmware_into_buf(&fw, fw_name, desc->dev, + firmware_buf, seg->filesz); + desc->unmap_fw_mem(firmware_buf, seg->filesz, map_data); + + if (ret) { + pil_err(desc, "Failed to locate blob %s or blob is too big(rc:%d)\n", + fw_name, ret); + return ret; + } + + if (fw->size != seg->filesz) { + pil_err(desc, "Blob size %u doesn't match %lu\n", + ret, seg->filesz); + release_firmware(fw); + return -EPERM; + } + + release_firmware(fw); + } + + /* Zero out trailing memory */ + paddr = seg->paddr + seg->filesz; + count = seg->sz - seg->filesz; + while (count > 0) { + size_t size; + void __iomem *buf; + + size = min_t(size_t, IOMAP_SIZE, count); + buf = desc->map_fw_mem(paddr, size, map_data); + if (!buf) { + pil_err(desc, "Failed to map memory\n"); + return -ENOMEM; + } + pil_memset_io(buf, 0, size); + + desc->unmap_fw_mem(buf, size, map_data); + + count -= size; + paddr += size; + } + + if (desc->ops->verify_blob) { + ret = desc->ops->verify_blob(desc, seg->paddr, seg->sz); + if (ret) + pil_err(desc, "Blob%u failed verification(rc:%d)\n", + num, ret); + } + + return ret; +} + +static int pil_parse_devicetree(struct pil_desc *desc) +{ + struct device_node *ofnode = desc->dev->of_node; + int clk_ready = 0; + + if (!ofnode) + return -EINVAL; + + if (of_property_read_u32(ofnode, "qcom,mem-protect-id", + &desc->subsys_vmid)) + pr_debug("Unable to read the addr-protect-id for %s\n", + desc->name); + + if (desc->ops->proxy_unvote && + of_property_match_string(ofnode, "interrupt-names", + "qcom,proxy-unvote") >= 0) { + clk_ready = of_irq_get_byname(ofnode, + "qcom,proxy-unvote"); + + if (clk_ready < 0) { + dev_dbg(desc->dev, + "[%s]: Error getting proxy unvoting irq\n", + desc->name); + return clk_ready; + } + + } + desc->proxy_unvote_irq = clk_ready; + + + return 0; +} + +static int pil_notify_aop(struct pil_desc *desc, char *status) +{ + struct qmp_pkt pkt; + char mbox_msg[MAX_LEN]; + + if (!desc->signal_aop) + return 0; + + snprintf(mbox_msg, MAX_LEN, + "{class: image, res: load_state, name: %s, val: %s}", + desc->name, status); + pkt.size = MAX_LEN; + pkt.data = mbox_msg; + + return mbox_send_message(desc->mbox, &pkt); +} + +/* Synchronize request_firmware() with suspend */ +static DECLARE_RWSEM(pil_pm_rwsem); + +struct pil_seg_data { + struct pil_desc *desc; + struct pil_seg *seg; + struct work_struct load_seg_work; + int retval; +}; + +static void pil_load_seg_work_fn(struct work_struct *work) +{ + struct pil_seg_data *pil_seg_data = container_of(work, + struct pil_seg_data, + load_seg_work); + struct pil_desc *desc = pil_seg_data->desc; + struct pil_seg *seg = pil_seg_data->seg; + + pil_seg_data->retval = pil_load_seg(desc, seg); +} + +static int pil_load_segs(struct pil_desc *desc) +{ + int ret = 0; + int seg_id = 0; + struct pil_priv *priv = desc->priv; + struct pil_seg_data *pil_seg_data; + struct pil_seg *seg; + unsigned long *err_map; + + err_map = kcalloc(BITS_TO_LONGS(priv->num_segs), sizeof(*err_map), + GFP_KERNEL); + if (!err_map) + return -ENOMEM; + + pil_seg_data = kcalloc(priv->num_segs, sizeof(*pil_seg_data), + GFP_KERNEL); + if (!pil_seg_data) { + ret = -ENOMEM; + goto out; + } + + /* Initialize and spawn a thread for each segment */ + list_for_each_entry(seg, &desc->priv->segs, list) { + pil_seg_data[seg_id].desc = desc; + pil_seg_data[seg_id].seg = seg; + + INIT_WORK(&pil_seg_data[seg_id].load_seg_work, + pil_load_seg_work_fn); + queue_work(pil_wq, &pil_seg_data[seg_id].load_seg_work); + + seg_id++; + } + + bitmap_zero(err_map, priv->num_segs); + + /* Wait for the parallel loads to finish */ + seg_id = 0; + list_for_each_entry(seg, &desc->priv->segs, list) { + flush_work(&pil_seg_data[seg_id].load_seg_work); + + /* Don't exit if one of the thread fails. Wait for others to + * complete. Bitmap the return codes we get from the threads. + */ + if (pil_seg_data[seg_id].retval) { + pil_err(desc, + "Failed to load the segment[%d]. ret = %d\n", + seg_id, pil_seg_data[seg_id].retval); + __set_bit(seg_id, err_map); + } + + seg_id++; + } + + kfree(pil_seg_data); + + /* Each segment can fail due to different reason. Send a generic err */ + if (!bitmap_empty(err_map, priv->num_segs)) + ret = -EFAULT; + +out: + kfree(err_map); + return ret; +} + +/** + * pil_boot() - Load a peripheral image into memory and boot it + * @desc: descriptor from pil_desc_init() + * + * Returns 0 on success or -ERROR on failure. + */ +int pil_boot(struct pil_desc *desc) +{ + int ret; + char fw_name[30]; + struct pil_seg *seg; + const struct pil_mdt *mdt; + const struct elf32_hdr *ehdr; + const struct firmware *fw; + struct pil_priv *priv = desc->priv; + bool mem_protect = false; + bool hyp_assign = false; + + ret = pil_notify_aop(desc, "on"); + if (ret < 0) { + pil_err(desc, "Failed to send ON message to AOP rc:%d\n", ret); + return ret; + } + + if (desc->shutdown_fail) + pil_err(desc, "Subsystem shutdown failed previously!\n"); + + /* Reinitialize for new image */ + pil_release_mmap(desc); + + down_read(&pil_pm_rwsem); + snprintf(fw_name, sizeof(fw_name), "%s.mdt", desc->fw_name); + ret = request_firmware(&fw, fw_name, desc->dev); + if (ret) { + pil_err(desc, "Failed to locate %s(rc:%d)\n", fw_name, ret); + goto out; + } + + if (fw->size < sizeof(*ehdr)) { + pil_err(desc, "Not big enough to be an elf header\n"); + ret = -EIO; + goto release_fw; + } + + mdt = (const struct pil_mdt *)fw->data; + ehdr = &mdt->hdr; + + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) { + pil_err(desc, "Not an elf header\n"); + ret = -EIO; + goto release_fw; + } + + if (ehdr->e_phnum == 0) { + pil_err(desc, "No loadable segments\n"); + ret = -EIO; + goto release_fw; + } + if (sizeof(struct elf32_phdr) * ehdr->e_phnum + + sizeof(struct elf32_hdr) > fw->size) { + pil_err(desc, "Program headers not within mdt\n"); + ret = -EIO; + goto release_fw; + } + + ret = pil_init_mmap(desc, mdt); + if (ret) + goto release_fw; + + desc->priv->unvoted_flag = 0; + ret = pil_proxy_vote(desc); + if (ret) { + pil_err(desc, "Failed to proxy vote(rc:%d)\n", ret); + goto release_fw; + } + + trace_pil_event("before_init_image", desc); + if (desc->ops->init_image) + ret = desc->ops->init_image(desc, fw->data, fw->size); + if (ret) { + pil_err(desc, "Initializing image failed(rc:%d)\n", ret); + goto err_boot; + } + + trace_pil_event("before_mem_setup", desc); + if (desc->ops->mem_setup) + ret = desc->ops->mem_setup(desc, priv->region_start, + priv->region_end - priv->region_start); + if (ret) { + pil_err(desc, "Memory setup error(rc:%d)\n", ret); + goto err_deinit_image; + } + + if (desc->subsys_vmid > 0) { + /** + * In case of modem ssr, we need to assign memory back to linux. + * This is not true after cold boot since linux already owns it. + * Also for secure boot devices, modem memory has to be released + * after MBA is booted + */ + trace_pil_event("before_assign_mem", desc); + if (desc->modem_ssr) { + ret = pil_assign_mem_to_linux(desc, priv->region_start, + (priv->region_end - priv->region_start)); + if (ret) + pil_err(desc, "Failed to assign to linux, ret- %d\n", + ret); + } + ret = pil_assign_mem_to_subsys_and_linux(desc, + priv->region_start, + (priv->region_end - priv->region_start)); + if (ret) { + pil_err(desc, "Failed to assign memory, ret - %d\n", + ret); + goto err_deinit_image; + } + hyp_assign = true; + } + + trace_pil_event("before_load_seg", desc); + + /** + * Fallback to serial loading of blobs if the + * workqueue creatation failed during module init. + */ + if (pil_wq) { + ret = pil_load_segs(desc); + if (ret) + goto err_deinit_image; + } else { + list_for_each_entry(seg, &desc->priv->segs, list) { + ret = pil_load_seg(desc, seg); + if (ret) + goto err_deinit_image; + } + } + + if (desc->subsys_vmid > 0) { + trace_pil_event("before_reclaim_mem", desc); + ret = pil_reclaim_mem(desc, priv->region_start, + (priv->region_end - priv->region_start), + desc->subsys_vmid); + if (ret) { + pil_err(desc, "Failed to assign %s memory, ret - %d\n", + desc->name, ret); + goto err_deinit_image; + } + hyp_assign = false; + } + + trace_pil_event("before_auth_reset", desc); + notify_before_auth_and_reset(desc->dev); + ret = desc->ops->auth_and_reset(desc); + if (ret) { + pil_err(desc, "Failed to bring out of reset(rc:%d)\n", ret); + goto err_auth_and_reset; + } + trace_pil_event("reset_done", desc); + pil_info(desc, "Brought out of reset\n"); + desc->modem_ssr = false; +err_auth_and_reset: + if (ret && desc->subsys_vmid > 0) { + pil_assign_mem_to_linux(desc, priv->region_start, + (priv->region_end - priv->region_start)); + mem_protect = true; + } +err_deinit_image: + if (ret && desc->ops->deinit_image) + desc->ops->deinit_image(desc); +err_boot: + if (ret && desc->proxy_unvote_irq) + disable_irq(desc->proxy_unvote_irq); + pil_proxy_unvote(desc, ret); +release_fw: + release_firmware(fw); +out: + up_read(&pil_pm_rwsem); + if (ret) { + if (priv->is_region_allocated) { + if (desc->subsys_vmid > 0 && !mem_protect && + hyp_assign) { + pil_reclaim_mem(desc, priv->region_start, + (priv->region_end - + priv->region_start), + VMID_HLOS); + } + if (desc->clear_fw_region && priv->region_start) + pil_clear_segment(desc); + priv->is_region_allocated = false; + } + pil_release_mmap(desc); + pil_notify_aop(desc, "off"); + } + return ret; +} +EXPORT_SYMBOL(pil_boot); + +/** + * pil_shutdown() - Shutdown a peripheral + * @desc: descriptor from pil_desc_init() + */ +void pil_shutdown(struct pil_desc *desc) +{ + int ret; + struct pil_priv *priv = desc->priv; + + if (desc->ops->shutdown) { + if (desc->ops->shutdown(desc)) + desc->shutdown_fail = true; + else + desc->shutdown_fail = false; + } + + if (desc->proxy_unvote_irq) { + disable_irq(desc->proxy_unvote_irq); + if (!desc->priv->unvoted_flag) + pil_proxy_unvote(desc, 1); + } else if (!proxy_timeout_ms) + pil_proxy_unvote(desc, 1); + else + flush_delayed_work(&priv->proxy); + ret = pil_notify_aop(desc, "off"); + if (ret < 0) + pr_warn("pil: failed to send OFF message to AOP rc:%d\n", ret); + desc->modem_ssr = true; +} +EXPORT_SYMBOL(pil_shutdown); + +/** + * pil_free_memory() - Free memory resources associated with a peripheral + * @desc: descriptor from pil_desc_init() + */ +void pil_free_memory(struct pil_desc *desc) +{ + struct pil_priv *priv = desc->priv; + + if (priv->is_region_allocated) { + if (desc->subsys_vmid > 0) + pil_assign_mem_to_linux(desc, priv->region_start, + (priv->region_end - priv->region_start)); + priv->is_region_allocated = false; + } +} +EXPORT_SYMBOL(pil_free_memory); + +static DEFINE_IDA(pil_ida); + +bool pil_is_timeout_disabled(void) +{ + return disable_timeouts; +} +EXPORT_SYMBOL(pil_is_timeout_disabled); + +static int collect_aux_minidump_ids(struct pil_desc *desc) +{ + u32 id; + const __be32 *p; + struct property *prop; + unsigned int i = 0; + void *aux_toc_addr; + struct device_node *node = desc->dev->of_node; + int num_ids = of_property_count_u32_elems(node, + "qcom,aux-minidump-ids"); + + if (num_ids > 0) { + desc->num_aux_minidump_ids = num_ids; + desc->aux_minidump_ids = kmalloc_array(num_ids, + sizeof(*desc->aux_minidump_ids), + GFP_KERNEL); + if (!desc->aux_minidump_ids) + return -ENOMEM; + + desc->aux_minidump = kmalloc_array(num_ids, + sizeof(*desc->aux_minidump), + GFP_KERNEL); + if (!desc->aux_minidump) { + kfree(desc->aux_minidump_ids); + desc->aux_minidump_ids = NULL; + return -ENOMEM; + } + + of_property_for_each_u32(node, "qcom,aux-minidump-ids", prop, + p, id) { + desc->aux_minidump_ids[i] = id; + aux_toc_addr = &g_md_toc->md_ss_toc[id]; + pr_debug("Minidump: aux_toc_addr is %pa and id: %d\n", + &aux_toc_addr, id); + memcpy(&desc->aux_minidump[i], &aux_toc_addr, + sizeof(aux_toc_addr)); + i++; + } + } + + return 0; +} + +/** + * pil_desc_init() - Initialize a pil descriptor + * @desc: descriptor to initialize + * + * Initialize a pil descriptor for use by other pil functions. This function + * must be called before calling pil_boot() or pil_shutdown(). + * + * Returns 0 for success and -ERROR on failure. + */ +int pil_desc_init(struct pil_desc *desc) +{ + struct pil_priv *priv; + void __iomem *addr; + void *ss_toc_addr; + int ret; + 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")) + ((struct pil_reset_ops *)desc->ops)->proxy_unvote = NULL; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + desc->priv = priv; + priv->desc = desc; + + priv->id = ret = ida_simple_get(&pil_ida, 0, PIL_NUM_DESC, GFP_KERNEL); + if (priv->id < 0) + goto err; + + if (pil_info_base) { + addr = pil_info_base + sizeof(struct pil_image_info) * priv->id; + priv->info = (struct pil_image_info __iomem *)addr; + + strlcpy(buf, desc->name, sizeof(buf)); + __iowrite32_copy(priv->info->name, buf, sizeof(buf) / 4); + } + if (of_property_read_u32(ofnode, "qcom,minidump-id", + &desc->minidump_id)) + pr_err("minidump-id not found for %s\n", desc->name); + else { + if (g_md_toc && g_md_toc->md_toc_init == true) { + ss_toc_addr = &g_md_toc->md_ss_toc[desc->minidump_id]; + pr_debug("Minidump : ss_toc_addr for ss is %pa and desc->minidump_id is %d\n", + &ss_toc_addr, desc->minidump_id); + memcpy(&desc->minidump_ss, &ss_toc_addr, + sizeof(ss_toc_addr)); + + if (collect_aux_minidump_ids(desc) < 0) + pr_err("Failed to get aux %s minidump ids\n", + desc->name); + } + } + + ret = pil_parse_devicetree(desc); + if (ret) + goto err_parse_dt; + + /* Ignore users who don't make any sense */ + WARN(desc->ops->proxy_unvote && desc->proxy_unvote_irq == 0 + && !desc->proxy_timeout, + "Invalid proxy unvote callback or a proxy timeout of 0 was specified or no proxy unvote IRQ was specified.\n"); + + if (desc->proxy_unvote_irq) { + ret = request_threaded_irq(desc->proxy_unvote_irq, + NULL, + proxy_unvote_intr_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + desc->name, desc); + if (ret < 0) { + dev_err(desc->dev, + "Unable to request proxy unvote IRQ: %d\n", + ret); + goto err; + } + disable_irq(desc->proxy_unvote_irq); + } + + snprintf(priv->wname, sizeof(priv->wname), "pil-%s", desc->name); + wakeup_source_init(&priv->ws, priv->wname); + INIT_DELAYED_WORK(&priv->proxy, pil_proxy_unvote_work); + INIT_LIST_HEAD(&priv->segs); + + /* Make sure mapping functions are set. */ + if (!desc->map_fw_mem) + desc->map_fw_mem = map_fw_mem; + + if (!desc->unmap_fw_mem) + desc->unmap_fw_mem = unmap_fw_mem; + + return 0; +err_parse_dt: + ida_simple_remove(&pil_ida, priv->id); +err: + kfree(desc->aux_minidump); + kfree(desc->aux_minidump_ids); + kfree(priv); + return ret; +} +EXPORT_SYMBOL(pil_desc_init); + +/** + * pil_desc_release() - Release a pil descriptor + * @desc: descriptor to free + */ +void pil_desc_release(struct pil_desc *desc) +{ + struct pil_priv *priv = desc->priv; + + if (priv) { + ida_simple_remove(&pil_ida, priv->id); + flush_delayed_work(&priv->proxy); + wakeup_source_trash(&priv->ws); + } + desc->priv = NULL; + kfree(priv); +} +EXPORT_SYMBOL(pil_desc_release); + +static int pil_pm_notify(struct notifier_block *b, unsigned long event, void *p) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + down_write(&pil_pm_rwsem); + break; + case PM_POST_SUSPEND: + up_write(&pil_pm_rwsem); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block pil_pm_notifier = { + .notifier_call = pil_pm_notify, +}; + +static int __init msm_pil_init(void) +{ + struct device_node *np; + struct resource res; + int i; + size_t size; + + np = of_find_compatible_node(NULL, NULL, "qcom,msm-imem-pil"); + if (!np) { + pr_warn("pil: failed to find qcom,msm-imem-pil node\n"); + goto out; + } + if (of_address_to_resource(np, 0, &res)) { + pr_warn("pil: address to resource on imem region failed\n"); + goto out; + } + pil_info_base = ioremap(res.start, resource_size(&res)); + if (!pil_info_base) { + pr_warn("pil: could not map imem region\n"); + goto out; + } + if (__raw_readl(pil_info_base) == 0x53444247) { + pr_info("pil: pil-imem set to disable pil timeouts\n"); + disable_timeouts = true; + } + for (i = 0; i < resource_size(&res)/sizeof(u32); i++) + writel_relaxed(0, pil_info_base + (i * sizeof(u32))); + + /* Get Global minidump ToC*/ + g_md_toc = qcom_smem_get(QCOM_SMEM_HOST_ANY, SBL_MINIDUMP_SMEM_ID, + &size); + pr_debug("Minidump: g_md_toc is %pa\n", &g_md_toc); + if (PTR_ERR(g_md_toc) == -EPROBE_DEFER) { + pr_err("SMEM is not initialized.\n"); + return -EPROBE_DEFER; + } + + pil_wq = alloc_workqueue("pil_workqueue", WQ_HIGHPRI | WQ_UNBOUND, 0); + if (!pil_wq) + pr_warn("pil: Defaulting to sequential firmware loading.\n"); + +out: + return register_pm_notifier(&pil_pm_notifier); +} +subsys_initcall(msm_pil_init); + +static void __exit msm_pil_exit(void) +{ + if (pil_wq) + destroy_workqueue(pil_wq); + unregister_pm_notifier(&pil_pm_notifier); + if (pil_info_base) + iounmap(pil_info_base); +} +module_exit(msm_pil_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Load peripheral images and bring peripherals out of reset"); diff --git a/drivers/soc/qcom/peripheral-loader.h b/drivers/soc/qcom/peripheral-loader.h new file mode 100644 index 000000000000..0965b21ea0e9 --- /dev/null +++ b/drivers/soc/qcom/peripheral-loader.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2010-2019, The Linux Foundation. All rights reserved. + */ +#ifndef __MSM_PERIPHERAL_LOADER_H +#define __MSM_PERIPHERAL_LOADER_H + +#include +#include +#include "minidump_private.h" + +struct device; +struct module; +struct pil_priv; + +/** + * struct pil_desc - PIL descriptor + * @name: string used for pil_get() + * @fw_name: firmware name + * @dev: parent device + * @ops: callback functions + * @owner: module the descriptor belongs to + * @proxy_timeout: delay in ms until proxy vote is removed + * @flags: bitfield for image flags + * @priv: DON'T USE - internal only + * @attrs: DMA attributes to be used during dma allocation. + * @proxy_unvote_irq: IRQ to trigger a proxy unvote. proxy_timeout + * is ignored if this is set. + * @map_fw_mem: Custom function used to map physical address space to virtual. + * This defaults to ioremap if not specified. + * @unmap_fw_mem: Custom function used to undo mapping by map_fw_mem. + * This defaults to iounmap if not specified. + * @shutdown_fail: Set if PIL op for shutting down subsystem fails. + * @modem_ssr: true if modem is restarting, false if booting for first time. + * @clear_fw_region: Clear fw region on failure in loading. + * @subsys_vmid: memprot id for the subsystem. + */ +struct pil_desc { + const char *name; + const char *fw_name; + struct device *dev; + const struct pil_reset_ops *ops; + struct module *owner; + unsigned long proxy_timeout; + unsigned long flags; +#define PIL_SKIP_ENTRY_CHECK BIT(0) + struct pil_priv *priv; + unsigned long attrs; + unsigned int proxy_unvote_irq; + void __iomem * (*map_fw_mem)(phys_addr_t phys, size_t size, void *data); + void (*unmap_fw_mem)(void __iomem *virt, size_t size, void *data); + void *map_data; + bool shutdown_fail; + bool modem_ssr; + bool clear_fw_region; + u32 subsys_vmid; + bool signal_aop; + struct mbox_client cl; + struct mbox_chan *mbox; + struct md_ss_toc *minidump_ss; + struct md_ss_toc **aux_minidump; + int minidump_id; + int *aux_minidump_ids; + int num_aux_minidump_ids; +}; + +/** + * struct pil_image_info - info in IMEM about image and where it is loaded + * @name: name of image (may or may not be NULL terminated) + * @start: indicates physical address where image starts (little endian) + * @size: size of image (little endian) + */ +struct pil_image_info { + char name[8]; + __le64 start; + __le32 size; +} __attribute__((__packed__)); + +/** + * struct pil_reset_ops - PIL operations + * @init_image: prepare an image for authentication + * @mem_setup: prepare the image memory region + * @verify_blob: authenticate a program segment, called once for each loadable + * program segment (optional) + * @proxy_vote: make proxy votes before auth_and_reset (optional) + * @auth_and_reset: boot the processor + * @proxy_unvote: remove any proxy votes (optional) + * @deinit_image: restore actions performed in init_image if necessary + * @shutdown: shutdown the processor + */ +struct pil_reset_ops { + int (*init_image)(struct pil_desc *pil, const u8 *metadata, + size_t size); + int (*mem_setup)(struct pil_desc *pil, phys_addr_t addr, size_t size); + int (*verify_blob)(struct pil_desc *pil, phys_addr_t phy_addr, + size_t size); + int (*proxy_vote)(struct pil_desc *pil); + int (*auth_and_reset)(struct pil_desc *pil); + void (*proxy_unvote)(struct pil_desc *pil); + int (*deinit_image)(struct pil_desc *pil); + int (*shutdown)(struct pil_desc *pil); +}; + +#if IS_ENABLED(CONFIG_MSM_PIL) +extern int pil_desc_init(struct pil_desc *desc); +extern int pil_boot(struct pil_desc *desc); +extern void pil_shutdown(struct pil_desc *desc); +extern void pil_free_memory(struct pil_desc *desc); +extern void pil_desc_release(struct pil_desc *desc); +extern phys_addr_t pil_get_entry_addr(struct pil_desc *desc); +extern int pil_do_ramdump(struct pil_desc *desc, void *ramdump_dev, + void *minidump_dev); +extern int pil_assign_mem_to_subsys(struct pil_desc *desc, phys_addr_t addr, + size_t size); +extern int pil_assign_mem_to_linux(struct pil_desc *desc, phys_addr_t addr, + size_t size); +extern int pil_assign_mem_to_subsys_and_linux(struct pil_desc *desc, + phys_addr_t addr, size_t size); +extern int pil_reclaim_mem(struct pil_desc *desc, phys_addr_t addr, size_t size, + int VMid); +extern bool pil_is_timeout_disabled(void); +#else +static inline int pil_desc_init(struct pil_desc *desc) { return 0; } +static inline int pil_boot(struct pil_desc *desc) { return 0; } +static inline void pil_shutdown(struct pil_desc *desc) { } +static inline void pil_free_memory(struct pil_desc *desc) { } +static inline void pil_desc_release(struct pil_desc *desc) { } +static inline phys_addr_t pil_get_entry_addr(struct pil_desc *desc) +{ + return 0; +} +static inline int pil_do_ramdump(struct pil_desc *desc, + void *ramdump_dev, void *minidump_dev) +{ + return 0; +} +static inline int pil_assign_mem_to_subsys(struct pil_desc *desc, + phys_addr_t addr, size_t size) +{ + return 0; +} +static inline int pil_assign_mem_to_linux(struct pil_desc *desc, + phys_addr_t addr, size_t size) +{ + return 0; +} +static inline int pil_assign_mem_to_subsys_and_linux(struct pil_desc *desc, + phys_addr_t addr, size_t size) +{ + return 0; +} +static inline int pil_reclaim_mem(struct pil_desc *desc, phys_addr_t addr, + size_t size, int VMid) +{ + return 0; +} +static inline bool pil_is_timeout_disabled(void) { return false; } +#endif + +#endif diff --git a/drivers/soc/qcom/ramdump.c b/drivers/soc/qcom/ramdump.c new file mode 100644 index 000000000000..c86a59e3ec80 --- /dev/null +++ b/drivers/soc/qcom/ramdump.c @@ -0,0 +1,646 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define RAMDUMP_NUM_DEVICES 256 +#define RAMDUMP_NAME "ramdump" + +static struct class *ramdump_class; +static dev_t ramdump_dev; +static DEFINE_MUTEX(rd_minor_mutex); +static DEFINE_IDA(rd_minor_id); +static bool ramdump_devnode_inited; +#define RAMDUMP_WAIT_MSECS 120000 +#define MAX_STRTBL_SIZE 512 +#define MAX_NAME_LENGTH 16 + +struct consumer_entry { + bool data_ready; + struct ramdump_device *rd_dev; + struct list_head list; +}; + +struct ramdump_device { + char name[256]; + + unsigned int consumers; + atomic_t readers_left; + int ramdump_status; + + struct completion ramdump_complete; + struct mutex consumer_lock; + struct list_head consumer_list; + struct cdev cdev; + struct device *dev; + + wait_queue_head_t dump_wait_q; + int nsegments; + struct ramdump_segment *segments; + size_t elfcore_size; + char *elfcore_buf; + bool complete_ramdump; +}; + +static int ramdump_open(struct inode *inode, struct file *filep) +{ + struct ramdump_device *rd_dev = container_of(inode->i_cdev, + struct ramdump_device, cdev); + struct consumer_entry *entry = kzalloc(sizeof(*entry), GFP_KERNEL); + + if (!entry) + return -ENOMEM; + + INIT_LIST_HEAD(&entry->list); + entry->rd_dev = rd_dev; + mutex_lock(&rd_dev->consumer_lock); + rd_dev->consumers++; + rd_dev->ramdump_status = 0; + list_add_tail(&entry->list, &rd_dev->consumer_list); + mutex_unlock(&rd_dev->consumer_lock); + filep->private_data = entry; + return 0; +} + +static void reset_ramdump_entry(struct consumer_entry *entry) +{ + struct ramdump_device *rd_dev = entry->rd_dev; + + entry->data_ready = false; + if (atomic_dec_return(&rd_dev->readers_left) == 0) + complete(&rd_dev->ramdump_complete); +} + +static int ramdump_release(struct inode *inode, struct file *filep) +{ + + struct ramdump_device *rd_dev = container_of(inode->i_cdev, + struct ramdump_device, cdev); + struct consumer_entry *entry = filep->private_data; + + mutex_lock(&rd_dev->consumer_lock); + /* + * Avoid double decrementing in cases where we finish reading the dump + * and then close the file, but there are other readers that have not + * yet finished. + */ + if (entry->data_ready) + reset_ramdump_entry(entry); + rd_dev->consumers--; + list_del(&entry->list); + mutex_unlock(&rd_dev->consumer_lock); + entry->rd_dev = NULL; + kfree(entry); + return 0; +} + +static unsigned long offset_translate(loff_t user_offset, + struct ramdump_device *rd_dev, unsigned long *data_left, + void **vaddr) +{ + int i = 0; + *vaddr = NULL; + + for (i = 0; i < rd_dev->nsegments; i++) + if (user_offset >= rd_dev->segments[i].size) + user_offset -= rd_dev->segments[i].size; + else + break; + + if (i == rd_dev->nsegments) { + pr_debug("Ramdump(%s): %s returning zero\n", rd_dev->name, + __func__); + *data_left = 0; + return 0; + } + + *data_left = rd_dev->segments[i].size - user_offset; + + pr_debug("Ramdump(%s): Returning address: %llx, data_left = %ld\n", + rd_dev->name, rd_dev->segments[i].address + user_offset, + *data_left); + + if (rd_dev->segments[i].v_address) + *vaddr = rd_dev->segments[i].v_address + user_offset; + + return rd_dev->segments[i].address + user_offset; +} + +#define MAX_IOREMAP_SIZE SZ_1M + +static ssize_t ramdump_read(struct file *filep, char __user *buf, size_t count, + loff_t *pos) +{ + struct consumer_entry *entry = filep->private_data; + struct ramdump_device *rd_dev = entry->rd_dev; + void __iomem *device_mem = NULL, *origdevice_mem = NULL, *vaddr = NULL; + unsigned long data_left = 0, bytes_before, bytes_after; + unsigned long addr = 0; + size_t copy_size = 0, alignsize; + unsigned char *alignbuf = NULL, *finalbuf = NULL; + int ret = 0; + loff_t orig_pos = *pos; + + if ((filep->f_flags & O_NONBLOCK) && !entry->data_ready) + return -EAGAIN; + + ret = wait_event_interruptible(rd_dev->dump_wait_q, entry->data_ready); + if (ret) + return ret; + + if (*pos < rd_dev->elfcore_size) { + copy_size = rd_dev->elfcore_size - *pos; + copy_size = min(copy_size, count); + + if (copy_to_user(buf, rd_dev->elfcore_buf + *pos, copy_size)) { + ret = -EFAULT; + goto ramdump_done; + } + *pos += copy_size; + count -= copy_size; + buf += copy_size; + if (count == 0) + return copy_size; + } + + addr = offset_translate(*pos - rd_dev->elfcore_size, rd_dev, + &data_left, &vaddr); + + /* EOF check */ + if (data_left == 0) { + pr_debug("Ramdump(%s): Ramdump complete. %lld bytes read.\n", + rd_dev->name, *pos); + rd_dev->ramdump_status = 0; + ret = 0; + goto ramdump_done; + } + + copy_size = min_t(size_t, count, (size_t)MAX_IOREMAP_SIZE); + copy_size = min_t(unsigned long, (unsigned long)copy_size, data_left); + + device_mem = (void __iomem *) vaddr; + origdevice_mem = device_mem; + + if (device_mem == NULL) { + pr_err("Ramdump(%s): Virtual addr is NULL:addr %lx, size %zd\n", + rd_dev->name, addr, copy_size); + rd_dev->ramdump_status = -1; + ret = -ENOMEM; + goto ramdump_done; + } + + alignbuf = kzalloc(copy_size, GFP_KERNEL); + if (!alignbuf) { + rd_dev->ramdump_status = -1; + ret = -ENOMEM; + goto ramdump_done; + } + + finalbuf = alignbuf; + alignsize = copy_size; + + if ((unsigned long)device_mem & 0x7) { + bytes_before = 8 - ((unsigned long)device_mem & 0x7); + memcpy_fromio(alignbuf, device_mem, bytes_before); + device_mem += bytes_before; + alignbuf += bytes_before; + alignsize -= bytes_before; + } + + if (alignsize & 0x7) { + bytes_after = alignsize & 0x7; + memcpy_fromio(alignbuf, device_mem, alignsize - bytes_after); + device_mem += alignsize - bytes_after; + alignbuf += (alignsize - bytes_after); + alignsize = bytes_after; + memcpy_fromio(alignbuf, device_mem, alignsize); + } else { + memcpy_fromio(alignbuf, device_mem, alignsize); + } + + if (copy_to_user(buf, finalbuf, copy_size)) { + pr_err("Ramdump(%s): Couldn't copy all data to user.\n", + rd_dev->name); + rd_dev->ramdump_status = -1; + ret = -EFAULT; + goto ramdump_done; + } + + kfree(finalbuf); + + *pos += copy_size; + + pr_debug("Ramdump(%s): Read %zd bytes from address %lx.\n", + rd_dev->name, copy_size, addr); + + return *pos - orig_pos; + +ramdump_done: + kfree(finalbuf); + *pos = 0; + reset_ramdump_entry(entry); + return ret; +} + +static __poll_t ramdump_poll(struct file *filep, + struct poll_table_struct *wait) +{ + struct consumer_entry *entry = filep->private_data; + struct ramdump_device *rd_dev = entry->rd_dev; + __poll_t mask = 0; + + if (entry->data_ready) + mask |= (EPOLLIN | EPOLLRDNORM); + + poll_wait(filep, &rd_dev->dump_wait_q, wait); + return mask; +} + +static const struct file_operations ramdump_file_ops = { + .open = ramdump_open, + .release = ramdump_release, + .read = ramdump_read, + .poll = ramdump_poll +}; + +static int ramdump_devnode_init(void) +{ + int ret; + + ramdump_class = class_create(THIS_MODULE, RAMDUMP_NAME); + ret = alloc_chrdev_region(&ramdump_dev, 0, RAMDUMP_NUM_DEVICES, + RAMDUMP_NAME); + if (ret < 0) { + pr_warn("%s: unable to allocate major\n", __func__); + return ret; + } + + ramdump_devnode_inited = true; + + return 0; +} + +void *create_ramdump_device(const char *dev_name, struct device *parent) +{ + int ret, minor; + struct ramdump_device *rd_dev; + + if (!dev_name) { + pr_err("%s: Invalid device name.\n", __func__); + return NULL; + } + + mutex_lock(&rd_minor_mutex); + if (!ramdump_devnode_inited) { + ret = ramdump_devnode_init(); + if (ret) { + mutex_unlock(&rd_minor_mutex); + return ERR_PTR(ret); + } + } + mutex_unlock(&rd_minor_mutex); + + rd_dev = kzalloc(sizeof(struct ramdump_device), GFP_KERNEL); + + if (!rd_dev) + return NULL; + + /* get a minor number */ + minor = ida_simple_get(&rd_minor_id, 0, RAMDUMP_NUM_DEVICES, + GFP_KERNEL); + if (minor < 0) { + pr_err("%s: No more minor numbers left! rc:%d\n", __func__, + minor); + ret = -ENODEV; + goto fail_out_of_minors; + } + + snprintf(rd_dev->name, ARRAY_SIZE(rd_dev->name), "ramdump_%s", + dev_name); + + init_completion(&rd_dev->ramdump_complete); + if (parent) { + rd_dev->complete_ramdump = of_property_read_bool( + parent->of_node, "qcom,complete-ramdump"); + if (!rd_dev->complete_ramdump) + dev_info(parent, + "for %s segments only will be dumped.\n", dev_name); + } + + INIT_LIST_HEAD(&rd_dev->consumer_list); + init_waitqueue_head(&rd_dev->dump_wait_q); + + rd_dev->dev = device_create(ramdump_class, parent, + MKDEV(MAJOR(ramdump_dev), minor), + rd_dev, rd_dev->name); + if (IS_ERR(rd_dev->dev)) { + ret = PTR_ERR(rd_dev->dev); + pr_err("%s: device_create failed for %s (%d)\n", __func__, + dev_name, ret); + goto fail_return_minor; + } + + mutex_init(&rd_dev->consumer_lock); + atomic_set(&rd_dev->readers_left, 0); + cdev_init(&rd_dev->cdev, &ramdump_file_ops); + + ret = cdev_add(&rd_dev->cdev, MKDEV(MAJOR(ramdump_dev), minor), 1); + if (ret < 0) { + pr_err("%s: cdev_add failed for %s (%d)\n", __func__, + dev_name, ret); + goto fail_cdev_add; + } + + return (void *)rd_dev; + +fail_cdev_add: + mutex_destroy(&rd_dev->consumer_lock); + device_unregister(rd_dev->dev); +fail_return_minor: + ida_simple_remove(&rd_minor_id, minor); +fail_out_of_minors: + kfree(rd_dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(create_ramdump_device); + +void destroy_ramdump_device(void *dev) +{ + struct ramdump_device *rd_dev = dev; + int minor = MINOR(rd_dev->cdev.dev); + + if (IS_ERR_OR_NULL(rd_dev)) + return; + + cdev_del(&rd_dev->cdev); + device_unregister(rd_dev->dev); + ida_simple_remove(&rd_minor_id, minor); + kfree(rd_dev); +} +EXPORT_SYMBOL(destroy_ramdump_device); + +static int _do_ramdump(void *handle, struct ramdump_segment *segments, + int nsegments, bool use_elf) +{ + int ret, i; + struct ramdump_device *rd_dev = (struct ramdump_device *)handle; + struct consumer_entry *entry; + Elf32_Phdr *phdr; + Elf32_Ehdr *ehdr; + unsigned long offset; + + /* + * Acquire the consumer lock here, and hold the lock until we are done + * preparing the data structures required for the ramdump session, and + * have woken all readers. This essentially freezes the current readers + * when the lock is taken here, such that the readers at that time are + * the only ones that will participate in the ramdump session. After + * the current list of readers has been awoken, new readers that add + * themselves to the reader list will not participate in the current + * ramdump session. This allows for the lock to be free while the + * ramdump is occurring, which prevents stalling readers who want to + * close the ramdump node or new readers that want to open it. + */ + mutex_lock(&rd_dev->consumer_lock); + if (!rd_dev->consumers) { + pr_err("Ramdump(%s): No consumers. Aborting..\n", rd_dev->name); + mutex_unlock(&rd_dev->consumer_lock); + return -EPIPE; + } + + if (rd_dev->complete_ramdump) { + for (i = 0; i < nsegments-1; i++) + segments[i].size = + segments[i + 1].address - segments[i].address; + } + + rd_dev->segments = segments; + rd_dev->nsegments = nsegments; + + if (use_elf) { + rd_dev->elfcore_size = sizeof(*ehdr) + + sizeof(*phdr) * nsegments; + ehdr = kzalloc(rd_dev->elfcore_size, GFP_KERNEL); + rd_dev->elfcore_buf = (char *)ehdr; + if (!rd_dev->elfcore_buf) { + mutex_unlock(&rd_dev->consumer_lock); + return -ENOMEM; + } + + memcpy(ehdr->e_ident, ELFMAG, SELFMAG); + ehdr->e_ident[EI_CLASS] = ELFCLASS32; + ehdr->e_ident[EI_DATA] = ELFDATA2LSB; + ehdr->e_ident[EI_VERSION] = EV_CURRENT; + ehdr->e_ident[EI_OSABI] = ELFOSABI_NONE; + ehdr->e_type = ET_CORE; + ehdr->e_version = EV_CURRENT; + ehdr->e_phoff = sizeof(*ehdr); + ehdr->e_ehsize = sizeof(*ehdr); + ehdr->e_phentsize = sizeof(*phdr); + ehdr->e_phnum = nsegments; + + offset = rd_dev->elfcore_size; + phdr = (Elf32_Phdr *)(ehdr + 1); + for (i = 0; i < nsegments; i++, phdr++) { + phdr->p_type = PT_LOAD; + phdr->p_offset = offset; + phdr->p_vaddr = phdr->p_paddr = segments[i].address; + phdr->p_filesz = phdr->p_memsz = segments[i].size; + phdr->p_flags = PF_R | PF_W | PF_X; + offset += phdr->p_filesz; + } + } + + list_for_each_entry(entry, &rd_dev->consumer_list, list) + entry->data_ready = true; + rd_dev->ramdump_status = -1; + + reinit_completion(&rd_dev->ramdump_complete); + atomic_set(&rd_dev->readers_left, rd_dev->consumers); + + /* Tell userspace that the data is ready */ + wake_up(&rd_dev->dump_wait_q); + mutex_unlock(&rd_dev->consumer_lock); + + /* 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->elfcore_size = 0; + kfree(rd_dev->elfcore_buf); + rd_dev->elfcore_buf = NULL; + return ret; + +} + +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 consumer_entry *entry; + struct elfhdr *ehdr; + struct elf_shdr *shdr; + unsigned long offset, strtbl_off; + + /* + * Acquire the consumer lock here, and hold the lock until we are done + * preparing the data structures required for the ramdump session, and + * have woken all readers. This essentially freezes the current readers + * when the lock is taken here, such that the readers at that time are + * the only ones that will participate in the ramdump session. After + * the current list of readers has been awoken, new readers that add + * themselves to the reader list will not participate in the current + * ramdump session. This allows for the lock to be free while the + * ramdump is occurring, which prevents stalling readers who want to + * close the ramdump node or new readers that want to open it. + */ + mutex_lock(&rd_dev->consumer_lock); + if (!rd_dev->consumers) { + pr_err("Ramdump(%s): No consumers. Aborting..\n", rd_dev->name); + mutex_unlock(&rd_dev->consumer_lock); + 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) { + mutex_unlock(&rd_dev->consumer_lock); + 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; + + list_for_each_entry(entry, &rd_dev->consumer_list, list) + entry->data_ready = true; + rd_dev->ramdump_status = -1; + + reinit_completion(&rd_dev->ramdump_complete); + atomic_set(&rd_dev->readers_left, rd_dev->consumers); + + /* Tell userspace that the data is ready */ + wake_up(&rd_dev->dump_wait_q); + mutex_unlock(&rd_dev->consumer_lock); + + /* 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->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) +{ + return _do_ramdump(handle, segments, nsegments, true); +} +EXPORT_SYMBOL(do_elf_ramdump); diff --git a/drivers/soc/qcom/service-locator-private.h b/drivers/soc/qcom/service-locator-private.h new file mode 100644 index 000000000000..357a24ce7c50 --- /dev/null +++ b/drivers/soc/qcom/service-locator-private.h @@ -0,0 +1,365 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015,2017-2019, The Linux Foundation. All rights reserved. + */ +#ifndef SERVICE_LOCATOR_V01_H +#define SERVICE_LOCATOR_V01_H + +#include +#include + +#define SERVREG_LOC_SERVICE_ID_V01 0x40 +#define SERVREG_LOC_SERVICE_VERS_V01 0x01 + +#define QMI_SERVREG_LOC_INDICATION_REGISTER_RESP_V01 0x0020 +#define QMI_SERVREG_LOC_REGISTER_SERVICE_LIST_REQ_V01 0x0022 +#define QMI_SERVREG_LOC_GET_DOMAIN_LIST_REQ_V01 0x0021 +#define QMI_SERVREG_LOC_GET_DOMAIN_LIST_RESP_V01 0x0021 +#define QMI_SERVREG_LOC_DATABASE_UPDATED_IND_V01 0x0023 +#define QMI_SERVREG_LOC_INDICATION_REGISTER_REQ_V01 0x0020 +#define QMI_SERVREG_LOC_REGISTER_SERVICE_LIST_RESP_V01 0x0022 + +#define QMI_SERVREG_LOC_NAME_LENGTH_V01 64 +#define QMI_SERVREG_LOC_LIST_LENGTH_V01 32 + +enum qmi_servreg_loc_service_instance_enum_type_v01 { + QMI_SERVREG_LOC_SERVICE_INSTANCE_ENUM_TYPE_MIN_VAL_V01 = INT_MIN, + QMI_SERVREG_LOC_SERVICE_INSTANCE_APSS_V01 = 0x1, + QMI_SERVREG_LOC_SERVICE_INSTANCE_ENUM_TYPE_MAX_VAL_V01 = INT_MAX, +}; + +struct qmi_servreg_loc_indication_register_req_msg_v01 { + uint8_t enable_database_updated_indication_valid; + uint8_t enable_database_updated_indication; +}; +#define QMI_SERVREG_LOC_INDICATION_REGISTER_REQ_MSG_V01_MAX_MSG_LEN 4 +static +struct qmi_elem_info qmi_servreg_loc_indication_register_req_msg_v01_ei[]; + +struct qmi_servreg_loc_indication_register_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; +#define QMI_SERVREG_LOC_INDICATION_REGISTER_RESP_MSG_V01_MAX_MSG_LEN 7 +static +struct qmi_elem_info qmi_servreg_loc_indication_register_resp_msg_v01_ei[]; + +struct qmi_servreg_loc_get_domain_list_req_msg_v01 { + char service_name[QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1]; + uint8_t domain_offset_valid; + uint32_t domain_offset; +}; +#define QMI_SERVREG_LOC_GET_DOMAIN_LIST_REQ_MSG_V01_MAX_MSG_LEN 74 +static +struct qmi_elem_info qmi_servreg_loc_get_domain_list_req_msg_v01_ei[]; + +struct qmi_servreg_loc_get_domain_list_resp_msg_v01 { + struct qmi_response_type_v01 resp; + uint8_t total_domains_valid; + uint16_t total_domains; + uint8_t db_rev_count_valid; + uint16_t db_rev_count; + uint8_t domain_list_valid; + uint32_t domain_list_len; + struct servreg_loc_entry_v01 + domain_list[QMI_SERVREG_LOC_LIST_LENGTH_V01]; +}; +#define QMI_SERVREG_LOC_GET_DOMAIN_LIST_RESP_MSG_V01_MAX_MSG_LEN 2389 +static +struct qmi_elem_info qmi_servreg_loc_get_domain_list_resp_msg_v01_ei[]; + +struct qmi_servreg_loc_register_service_list_req_msg_v01 { + char domain_name[QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1]; + uint32_t service_list_len; + struct servreg_loc_entry_v01 + service_list[QMI_SERVREG_LOC_LIST_LENGTH_V01]; +}; +#define QMI_SERVREG_LOC_REGISTER_SERVICE_LIST_REQ_MSG_V01_MAX_MSG_LEN 2439 +static +struct qmi_elem_info qmi_servreg_loc_register_service_list_req_msg_v01_ei[]; + +struct qmi_servreg_loc_register_service_list_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; +#define QMI_SERVREG_LOC_REGISTER_SERVICE_LIST_RESP_MSG_V01_MAX_MSG_LEN 7 +static +struct qmi_elem_info qmi_servreg_loc_register_service_list_resp_msg_v01_ei[]; + +struct qmi_servreg_loc_database_updated_ind_msg_v01 { + char placeholder; +}; +#define QMI_SERVREG_LOC_DATABASE_UPDATED_IND_MSG_V01_MAX_MSG_LEN 0 +static +struct qmi_elem_info qmi_servreg_loc_database_updated_ind_msg_v01_ei[]; + +#define QMI_EOTI_DATA_TYPE \ +{ \ + .data_type = QMI_EOTI, \ + .elem_len = 0, \ + .elem_size = 0, \ + .array_type = NO_ARRAY, \ + .tlv_type = 0x00, \ + .offset = 0, \ + .ei_array = NULL, \ +}, + +static struct qmi_elem_info servreg_loc_entry_v01_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct servreg_loc_entry_v01, + name), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct servreg_loc_entry_v01, + instance_id), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct servreg_loc_entry_v01, + service_data_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct servreg_loc_entry_v01, + service_data), + }, + QMI_EOTI_DATA_TYPE +}; + +static +struct qmi_elem_info qmi_servreg_loc_indication_register_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_loc_indication_register_req_msg_v01, + enable_database_updated_indication_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_loc_indication_register_req_msg_v01, + enable_database_updated_indication), + }, + QMI_EOTI_DATA_TYPE +}; + +static +struct qmi_elem_info qmi_servreg_loc_indication_register_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_loc_indication_register_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + QMI_EOTI_DATA_TYPE +}; + +static +struct qmi_elem_info qmi_servreg_loc_get_domain_list_req_msg_v01_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_req_msg_v01, + service_name), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_req_msg_v01, + domain_offset_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_req_msg_v01, + domain_offset), + }, + QMI_EOTI_DATA_TYPE +}; + +static +struct qmi_elem_info qmi_servreg_loc_get_domain_list_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_resp_msg_v01, + total_domains_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_resp_msg_v01, + total_domains), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_resp_msg_v01, + db_rev_count_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_resp_msg_v01, + db_rev_count), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_resp_msg_v01, + domain_list_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_resp_msg_v01, + domain_list_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_SERVREG_LOC_LIST_LENGTH_V01, + .elem_size = sizeof(struct servreg_loc_entry_v01), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct + qmi_servreg_loc_get_domain_list_resp_msg_v01, + domain_list), + .ei_array = servreg_loc_entry_v01_ei, + }, + QMI_EOTI_DATA_TYPE +}; + +static +struct qmi_elem_info qmi_servreg_loc_register_service_list_req_msg_v01_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + qmi_servreg_loc_register_service_list_req_msg_v01, + domain_name), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_loc_register_service_list_req_msg_v01, + service_list_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_SERVREG_LOC_LIST_LENGTH_V01, + .elem_size = sizeof(struct servreg_loc_entry_v01), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_loc_register_service_list_req_msg_v01, + service_list), + .ei_array = servreg_loc_entry_v01_ei, + }, + QMI_EOTI_DATA_TYPE +}; + +static +struct qmi_elem_info qmi_servreg_loc_register_service_list_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_loc_register_service_list_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + QMI_EOTI_DATA_TYPE +}; + +static +struct qmi_elem_info qmi_servreg_loc_database_updated_ind_msg_v01_ei[] = { + QMI_EOTI_DATA_TYPE +}; + +#endif diff --git a/drivers/soc/qcom/service-locator.c b/drivers/soc/qcom/service-locator.c new file mode 100644 index 000000000000..a522e38d7d36 --- /dev/null +++ b/drivers/soc/qcom/service-locator.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "servloc: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "service-locator-private.h" + +#define SERVREG_LOC_SERVICE_INSTANCE_ID 1 + +#define QMI_SERVREG_LOC_SERVER_INITIAL_TIMEOUT 2000 +#define QMI_SERVREG_LOC_SERVER_TIMEOUT 2000 +#define INITIAL_TIMEOUT 100000 +#define LOCATOR_SERVICE_TIMEOUT 300000 + +#define LOCATOR_NOT_PRESENT 0 +#define LOCATOR_PRESENT 1 + +static u32 locator_status = LOCATOR_NOT_PRESENT; +static bool service_inited; + +module_param_named(enable, locator_status, uint, 0644); + +static void pd_locator_work(struct work_struct *work); + +struct pd_qmi_data { + struct notifier_block notifier; + struct completion service_available; + struct qmi_handle clnt_handle; + bool connected; + struct sockaddr_qrtr s_addr; +}; + +struct pd_qmi_work { + struct work_struct pd_loc_work; + struct pd_qmi_client_data *pdc; + struct notifier_block *notifier; +}; + +static DEFINE_MUTEX(service_init_mutex); +static struct pd_qmi_data service_locator; + +/* Please refer soc/qcom/service-locator.h for use about APIs defined here */ + +static int service_locator_new_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + + /* Create a Local client port for QMI communication */ + service_locator.s_addr.sq_family = AF_QIPCRTR; + service_locator.s_addr.sq_node = svc->node; + service_locator.s_addr.sq_port = svc->port; + service_locator.connected = true; + if (!service_inited) + complete_all(&service_locator.service_available); + pr_info("Connection established with the Service locator\n"); + return 0; +} + +static void service_locator_del_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + service_locator.connected = false; + complete_all(&service_locator.service_available); + pr_info("Connection with service locator lost\n"); +} + +static struct qmi_ops server_ops = { + .new_server = service_locator_new_server, + .del_server = service_locator_del_server, +}; + +static void store_get_domain_list_response(struct pd_qmi_client_data *pd, + struct qmi_servreg_loc_get_domain_list_resp_msg_v01 *resp, + int offset) +{ + int i; + + for (i = offset; i < resp->domain_list_len; i++) { + pd->domain_list[i].instance_id = + resp->domain_list[i].instance_id; + strlcpy(pd->domain_list[i].name, resp->domain_list[i].name, + QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1); + pd->domain_list[i].service_data_valid = + resp->domain_list[i].service_data_valid; + pd->domain_list[i].service_data = + resp->domain_list[i].service_data; + } +} + +static int servreg_loc_send_msg( + struct qmi_servreg_loc_get_domain_list_req_msg_v01 *req, + struct qmi_servreg_loc_get_domain_list_resp_msg_v01 *resp, + struct pd_qmi_client_data *pd) +{ + int rc; + struct qmi_txn txn; + /* + * Send msg and get response. There is a chance that the service went + * away since the time we last checked for it to be available and + * actually made this call. In that case the call just fails. + */ + rc = qmi_txn_init(&service_locator.clnt_handle, &txn, + qmi_servreg_loc_get_domain_list_resp_msg_v01_ei, resp); + if (rc < 0) { + pr_err("QMI tx init failed for client %s, ret - %d\n", + pd->client_name, rc); + return rc; + } + + rc = qmi_send_request(&service_locator.clnt_handle, + &service_locator.s_addr, + &txn, QMI_SERVREG_LOC_GET_DOMAIN_LIST_REQ_V01, + QMI_SERVREG_LOC_GET_DOMAIN_LIST_REQ_MSG_V01_MAX_MSG_LEN, + qmi_servreg_loc_get_domain_list_req_msg_v01_ei, + req); + if (rc < 0) { + pr_err("QMI send req failed for client %s, ret - %d\n", + pd->client_name, rc); + qmi_txn_cancel(&txn); + return rc; + } + + rc = qmi_txn_wait(&txn, + msecs_to_jiffies(QMI_SERVREG_LOC_SERVER_TIMEOUT)); + if (rc < 0) { + pr_err("QMI qmi txn wait failed for client %s, ret - %d\n", + pd->client_name, rc); + return rc; + } + + /* Check the response */ + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + pr_err("QMI request for client %s failed 0x%x\n", + pd->client_name, resp->resp.error); + return -EREMOTEIO; + } + return rc; +} + +static int service_locator_send_msg(struct pd_qmi_client_data *pd) +{ + struct qmi_servreg_loc_get_domain_list_resp_msg_v01 *resp = NULL; + struct qmi_servreg_loc_get_domain_list_req_msg_v01 *req = NULL; + int rc; + int db_rev_count = 0, domains_read = 0; + + if (!service_locator.connected) { + pr_err("Service locator not available!\n"); + return -EAGAIN; + } + + req = kzalloc(sizeof( + struct qmi_servreg_loc_get_domain_list_req_msg_v01), + GFP_KERNEL); + if (!req) { + pr_err("Unable to allocate memory for req message\n"); + rc = -ENOMEM; + goto out; + } + resp = kzalloc(sizeof( + struct qmi_servreg_loc_get_domain_list_resp_msg_v01), + GFP_KERNEL); + if (!resp) { + pr_err("Unable to allocate memory for resp message\n"); + rc = -ENOMEM; + goto out; + } + + /* Prepare req and response message */ + strlcpy(req->service_name, pd->service_name, + QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1); + req->domain_offset_valid = true; + req->domain_offset = 0; + + do { + req->domain_offset += domains_read; + rc = servreg_loc_send_msg(req, resp, pd); + if (rc < 0) { + pr_err("send msg failed rc:%d\n", rc); + goto out; + } + if (!domains_read) { + db_rev_count = pd->db_rev_count = resp->db_rev_count; + pd->total_domains = resp->total_domains; + if (!resp->total_domains) { + pr_err("No matching domains found\n"); + goto out; + } + + pd->domain_list = kmalloc( + sizeof(struct servreg_loc_entry_v01) * + resp->total_domains, GFP_KERNEL); + if (!pd->domain_list) { + pr_err("Cannot allocate domain list\n"); + rc = -ENOMEM; + goto out; + } + } + if (db_rev_count != resp->db_rev_count) { + pr_err("Service Locator DB updated for client %s\n", + pd->client_name); + kfree(pd->domain_list); + pd->domain_list = NULL; + rc = -EAGAIN; + goto out; + } + if (resp->domain_list_len > resp->total_domains) { + /* Always read total_domains from the response msg */ + resp->domain_list_len = resp->total_domains; + } + /* Copy the response*/ + store_get_domain_list_response(pd, resp, domains_read); + domains_read += resp->domain_list_len; + } while (domains_read < resp->total_domains); + rc = 0; +out: + kfree(req); + kfree(resp); + return rc; +} + +static int init_service_locator(void) +{ + int rc = 0; + static bool service_timedout; + + rc = mutex_lock_interruptible(&service_init_mutex); + if (rc) + return rc; + if (locator_status == LOCATOR_NOT_PRESENT) { + pr_err("Service Locator not enabled\n"); + rc = -ENODEV; + goto inited; + } + if (service_timedout) { + rc = -ETIME; + goto inited; + } + if (service_inited) + goto inited; + + init_completion(&service_locator.service_available); + + service_locator.connected = false; + + rc = qmi_handle_init(&service_locator.clnt_handle, + QMI_SERVREG_LOC_GET_DOMAIN_LIST_RESP_MSG_V01_MAX_MSG_LEN, + &server_ops, NULL); + if (rc < 0) { + pr_err("Service locator QMI handle init failed rc:%d\n", rc); + goto inited; + } + + qmi_add_lookup(&service_locator.clnt_handle, + SERVREG_LOC_SERVICE_ID_V01, + SERVREG_LOC_SERVICE_VERS_V01, + SERVREG_LOC_SERVICE_INSTANCE_ID); + + rc = wait_for_completion_interruptible_timeout( + &service_locator.service_available, + msecs_to_jiffies(LOCATOR_SERVICE_TIMEOUT)); + if (rc < 0) { + pr_err("Wait for locator service interrupted by signal\n"); + goto inited; + } + if (!rc) { + pr_err("%s: wait for locator service timed out\n", __func__); + service_timedout = true; + rc = -ETIME; + goto inited; + } + + service_inited = true; + mutex_unlock(&service_init_mutex); + pr_info("Service locator initialized\n"); + return 0; + +inited: + mutex_unlock(&service_init_mutex); + return rc; +} + +int get_service_location(char *client_name, char *service_name, + struct notifier_block *locator_nb) +{ + struct pd_qmi_client_data *pqcd; + struct pd_qmi_work *pqw; + int rc = 0; + + if (!locator_nb || !client_name || !service_name) { + rc = -EINVAL; + pr_err("Invalid input!\n"); + goto err; + } + + pqcd = kzalloc(sizeof(struct pd_qmi_client_data), GFP_KERNEL); + if (!pqcd) { + rc = -ENOMEM; + pr_err("Allocation failed\n"); + goto err; + } + strlcpy(pqcd->client_name, client_name, ARRAY_SIZE(pqcd->client_name)); + strlcpy(pqcd->service_name, service_name, + ARRAY_SIZE(pqcd->service_name)); + + pqw = kmalloc(sizeof(struct pd_qmi_work), GFP_KERNEL); + if (!pqw) { + rc = -ENOMEM; + pr_err("Allocation failed\n"); + kfree(pqcd); + goto err; + } + pqw->notifier = locator_nb; + pqw->pdc = pqcd; + + INIT_WORK(&pqw->pd_loc_work, pd_locator_work); + schedule_work(&pqw->pd_loc_work); + +err: + return rc; +} +EXPORT_SYMBOL(get_service_location); + +static void pd_locator_work(struct work_struct *work) +{ + int rc = 0; + struct pd_qmi_client_data *data; + struct pd_qmi_work *pdqw = container_of(work, struct pd_qmi_work, + pd_loc_work); + + data = pdqw->pdc; + rc = init_service_locator(); + if (rc) { + pr_err("Unable to connect to service locator!, rc = %d\n", rc); + pdqw->notifier->notifier_call(pdqw->notifier, + LOCATOR_DOWN, NULL); + goto err_init_servloc; + } + rc = service_locator_send_msg(data); + if (rc) { + pr_err("Failed to get process domains for %s for client %s rc:%d\n", + data->service_name, data->client_name, rc); + pdqw->notifier->notifier_call(pdqw->notifier, + LOCATOR_DOWN, NULL); + goto err_servloc_send_msg; + } + pdqw->notifier->notifier_call(pdqw->notifier, LOCATOR_UP, data); + +err_servloc_send_msg: + kfree(data->domain_list); +err_init_servloc: + kfree(data); + kfree(pdqw); +} + +int find_subsys(const char *pd_path, char *subsys) +{ + char *start, *end; + + if (!subsys || !pd_path) + return -EINVAL; + + start = strnstr(pd_path, "/", QMI_SERVREG_LOC_NAME_LENGTH_V01); + if (!start) + return -EINVAL; + start++; + end = strnstr(start, "/", QMI_SERVREG_LOC_NAME_LENGTH_V01); + if (!end || start == end) + return -EINVAL; + + strlcpy(subsys, start, end - start + 1); + return 0; +} +EXPORT_SYMBOL(find_subsys); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Service Locator driver"); diff --git a/drivers/soc/qcom/service-notifier-private.h b/drivers/soc/qcom/service-notifier-private.h new file mode 100644 index 000000000000..272b0eb8fda3 --- /dev/null +++ b/drivers/soc/qcom/service-notifier-private.h @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. + */ +#ifndef SERVICE_REGISTRY_NOTIFIER_H +#define SERVICE_REGISTRY_NOTIFIER_H + +#include + +#define SERVREG_NOTIF_SERVICE_ID_V01 0x42 +#define SERVREG_NOTIF_SERVICE_VERS_V01 0x01 + +#define QMI_SERVREG_NOTIF_REGISTER_LISTENER_REQ_V01 0x0020 +#define QMI_SERVREG_NOTIF_REGISTER_LISTENER_RESP_V01 0x0020 +#define QMI_SERVREG_NOTIF_QUERY_STATE_REQ_V01 0x0021 +#define QMI_SERVREG_NOTIF_QUERY_STATE_RESP_V01 0x0021 +#define QMI_SERVREG_NOTIF_STATE_UPDATED_IND_V01 0x0022 +#define QMI_SERVREG_NOTIF_STATE_UPDATED_IND_ACK_REQ_V01 0x0023 +#define QMI_SERVREG_NOTIF_STATE_UPDATED_IND_ACK_RESP_V01 0x0023 +#define QMI_SERVREG_NOTIF_RESTART_PD_REQ_V01 0x0024 +#define QMI_SERVREG_NOTIF_RESTART_PD_RESP_V01 0x0024 + +#define QMI_SERVREG_NOTIF_NAME_LENGTH_V01 64 + +struct qmi_servreg_notif_register_listener_req_msg_v01 { + uint8_t enable; + char service_name[QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1]; +}; +#define QMI_SERVREG_NOTIF_REGISTER_LISTENER_REQ_MSG_V01_MAX_MSG_LEN 71 +static +struct qmi_elem_info qmi_servreg_notif_register_listener_req_msg_v01_ei[]; + +struct qmi_servreg_notif_register_listener_resp_msg_v01 { + struct qmi_response_type_v01 resp; + uint8_t curr_state_valid; + enum qmi_servreg_notif_service_state_enum_type_v01 curr_state; +}; +#define QMI_SERVREG_NOTIF_REGISTER_LISTENER_RESP_MSG_V01_MAX_MSG_LEN 14 +static +struct qmi_elem_info qmi_servreg_notif_register_listener_resp_msg_v01_ei[]; + +struct qmi_servreg_notif_query_state_req_msg_v01 { + char service_name[QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1]; +}; +#define QMI_SERVREG_NOTIF_QUERY_STATE_REQ_MSG_V01_MAX_MSG_LEN 67 +static +struct qmi_elem_info qmi_servreg_notif_query_state_req_msg_v01_ei[]; + +struct qmi_servreg_notif_query_state_resp_msg_v01 { + struct qmi_response_type_v01 resp; + uint8_t curr_state_valid; + enum qmi_servreg_notif_service_state_enum_type_v01 curr_state; +}; +#define QMI_SERVREG_NOTIF_QUERY_STATE_RESP_MSG_V01_MAX_MSG_LEN 14 +static +struct qmi_elem_info qmi_servreg_notif_query_state_resp_msg_v01_ei[]; + +struct qmi_servreg_notif_state_updated_ind_msg_v01 { + enum qmi_servreg_notif_service_state_enum_type_v01 curr_state; + char service_name[QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1]; + uint16_t transaction_id; +}; +#define QMI_SERVREG_NOTIF_STATE_UPDATED_IND_MSG_V01_MAX_MSG_LEN 79 +static +struct qmi_elem_info qmi_servreg_notif_state_updated_ind_msg_v01_ei[]; + +struct qmi_servreg_notif_set_ack_req_msg_v01 { + char service_name[QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1]; + uint16_t transaction_id; +}; +#define QMI_SERVREG_NOTIF_SET_ACK_REQ_MSG_V01_MAX_MSG_LEN 72 +static +struct qmi_elem_info qmi_servreg_notif_set_ack_req_msg_v01_ei[]; + +struct qmi_servreg_notif_set_ack_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; +#define QMI_SERVREG_NOTIF_SET_ACK_RESP_MSG_V01_MAX_MSG_LEN 7 +static +struct qmi_elem_info qmi_servreg_notif_set_ack_resp_msg_v01_ei[]; + +struct qmi_servreg_notif_restart_pd_req_msg_v01 { + char service_name[QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1]; +}; +#define QMI_SERVREG_NOTIF_RESTART_PD_REQ_MSG_V01_MAX_MSG_LEN 67 +extern struct qmi_elem_info qmi_servreg_notif_restart_pd_req_msg_v01_ei[]; + +struct qmi_servreg_notif_restart_pd_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; +#define QMI_SERVREG_NOTIF_RESTART_PD_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct qmi_elem_info qmi_servreg_notif_restart_pd_resp_msg_v01_ei[]; + +static +struct qmi_elem_info qmi_servreg_notif_register_listener_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + qmi_servreg_notif_register_listener_req_msg_v01, + enable), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_notif_register_listener_req_msg_v01, + service_name), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static +struct qmi_elem_info qmi_servreg_notif_register_listener_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_notif_register_listener_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_notif_register_listener_resp_msg_v01, + curr_state_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof( + enum qmi_servreg_notif_service_state_enum_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_notif_register_listener_resp_msg_v01, + curr_state), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static +struct qmi_elem_info qmi_servreg_notif_query_state_req_msg_v01_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + qmi_servreg_notif_query_state_req_msg_v01, + service_name), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static +struct qmi_elem_info qmi_servreg_notif_query_state_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_notif_query_state_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_notif_query_state_resp_msg_v01, + curr_state_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum + qmi_servreg_notif_service_state_enum_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + qmi_servreg_notif_query_state_resp_msg_v01, + curr_state), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static +struct qmi_elem_info qmi_servreg_notif_state_updated_ind_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum + qmi_servreg_notif_service_state_enum_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + qmi_servreg_notif_state_updated_ind_msg_v01, + curr_state), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_notif_state_updated_ind_msg_v01, + service_name), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .array_type = NO_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct + qmi_servreg_notif_state_updated_ind_msg_v01, + transaction_id), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static +struct qmi_elem_info qmi_servreg_notif_set_ack_req_msg_v01_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + qmi_servreg_notif_set_ack_req_msg_v01, + service_name), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_notif_set_ack_req_msg_v01, + transaction_id), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static +struct qmi_elem_info qmi_servreg_notif_set_ack_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_notif_set_ack_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info qmi_servreg_notif_restart_pd_req_msg_v01_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = QMI_SERVREG_NOTIF_NAME_LENGTH_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + qmi_servreg_notif_restart_pd_req_msg_v01, + service_name), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info qmi_servreg_notif_restart_pd_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + qmi_servreg_notif_restart_pd_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; +#endif diff --git a/drivers/soc/qcom/service-notifier.c b/drivers/soc/qcom/service-notifier.c new file mode 100644 index 000000000000..77ba27ea9be9 --- /dev/null +++ b/drivers/soc/qcom/service-notifier.c @@ -0,0 +1,763 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "service-notifier: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "service-notifier-private.h" + +#define SERVREG_NOTIF_NAME_LENGTH QMI_SERVREG_NOTIF_NAME_LENGTH_V01 +#define SERVREG_NOTIF_SERVICE_ID SERVREG_NOTIF_SERVICE_ID_V01 +#define SERVREG_NOTIF_SERVICE_VERS SERVREG_NOTIF_SERVICE_VERS_V01 + +#define SERVREG_NOTIF_SET_ACK_REQ \ + QMI_SERVREG_NOTIF_STATE_UPDATED_IND_ACK_REQ_V01 +#define SERVREG_NOTIF_SET_ACK_REQ_MSG_LEN \ + QMI_SERVREG_NOTIF_SET_ACK_REQ_MSG_V01_MAX_MSG_LEN +#define SERVREG_NOTIF_SET_ACK_RESP \ + QMI_SERVREG_NOTIF_STATE_UPDATED_IND_ACK_RESP_V01 +#define SERVREG_NOTIF_SET_ACK_RESP_MSG_LEN \ + QMI_SERVREG_NOTIF_SET_ACK_RESP_MSG_V01_MAX_MSG_LEN +#define SERVREG_NOTIF_STATE_UPDATED_IND_MSG \ + QMI_SERVREG_NOTIF_STATE_UPDATED_IND_V01 +#define SERVREG_NOTIF_STATE_UPDATED_IND_MSG_LEN \ + QMI_SERVREG_NOTIF_STATE_UPDATED_IND_MSG_V01_MAX_MSG_LEN + +#define SERVREG_NOTIF_REGISTER_LISTENER_REQ \ + QMI_SERVREG_NOTIF_REGISTER_LISTENER_REQ_V01 +#define SERVREG_NOTIF_REGISTER_LISTENER_REQ_MSG_LEN \ + QMI_SERVREG_NOTIF_REGISTER_LISTENER_REQ_MSG_V01_MAX_MSG_LEN +#define SERVREG_NOTIF_REGISTER_LISTENER_RESP \ + QMI_SERVREG_NOTIF_REGISTER_LISTENER_RESP_V01 +#define SERVREG_NOTIF_REGISTER_LISTENER_RESP_MSG_LEN \ + QMI_SERVREG_NOTIF_REGISTER_LISTENER_RESP_MSG_V01_MAX_MSG_LEN + +#define QMI_STATE_MIN_VAL QMI_SERVREG_NOTIF_SERVICE_STATE_ENUM_TYPE_MIN_VAL_V01 +#define QMI_STATE_MAX_VAL QMI_SERVREG_NOTIF_SERVICE_STATE_ENUM_TYPE_MAX_VAL_V01 + +#define SERVER_TIMEOUT 500 +#define MAX_STRING_LEN 100 + +/* + * Per user service data structure + * struct service_notif_info - notifier struct for each unique service path + * service_path - service provider path/location + * instance_id - service instance id specific to a subsystem + * service_notif_rcvr_list - list of clients interested in this service + * providers notifications + * curr_state: Current state of the service + */ +struct service_notif_info { + char service_path[SERVREG_NOTIF_NAME_LENGTH]; + int instance_id; + struct srcu_notifier_head service_notif_rcvr_list; + struct list_head list; + int curr_state; +}; +static LIST_HEAD(service_list); +static DEFINE_MUTEX(service_list_lock); + +struct ind_req_resp { + int transaction_id; + int curr_state; + struct work_struct ind_ack; + struct qmi_client_info *client_data; +}; + +/* + * Per Root Process Domain (Root service) data structure + * struct qmi_client_info - QMI client info for each subsystem/instance id + * instance_id - service instance id specific to a subsystem (Root PD) + * clnt_handle - unique QMI client handle + * service_connected - indicates if QMI service is up on the subsystem + * ssr_handle - The SSR handle provided by the SSR driver for the subsystem + * on which the remote root PD runs. + */ +struct qmi_client_info { + int instance_id; + char service_path[SERVREG_NOTIF_NAME_LENGTH]; + enum pd_subsys_state subsys_state; + struct work_struct new_server; + struct work_struct del_server; + struct workqueue_struct *svc_event_wq; + struct workqueue_struct *ind_ack_wq; + struct qmi_handle clnt_handle; + struct notifier_block notifier; + void *ssr_handle; + struct notifier_block ssr_notifier; + bool service_connected; + struct list_head list; + struct sockaddr_qrtr s_addr; +}; +static LIST_HEAD(qmi_client_list); +static DEFINE_MUTEX(qmi_list_lock); +static DEFINE_MUTEX(notif_add_lock); + +static struct service_notif_info *_find_service_info(const char *service_path) +{ + struct service_notif_info *service_notif; + + mutex_lock(&service_list_lock); + list_for_each_entry(service_notif, &service_list, list) + if (!strcmp(service_notif->service_path, service_path)) { + mutex_unlock(&service_list_lock); + return service_notif; + } + mutex_unlock(&service_list_lock); + return NULL; +} + +static int service_notif_queue_notification(struct service_notif_info + *service_notif, + enum qmi_servreg_notif_service_state_enum_type_v01 notif_type, + void *info) +{ + int ret; + + if (service_notif->curr_state == notif_type) + return 0; + + ret = srcu_notifier_call_chain(&service_notif->service_notif_rcvr_list, + notif_type, info); + return ret; +} + +static void send_ind_ack(struct work_struct *work) +{ + struct ind_req_resp *ind_info = container_of(work, struct ind_req_resp, + ind_ack); + struct qmi_client_info *data = ind_info->client_data; + struct qmi_servreg_notif_set_ack_req_msg_v01 req; + struct qmi_servreg_notif_set_ack_resp_msg_v01 resp = { { 0, 0 } }; + struct qmi_txn txn; + struct service_notif_info *service_notif; + enum pd_subsys_state state = USER_PD_STATE_CHANGE; + int rc; + + service_notif = _find_service_info(data->service_path); + if (!service_notif) + goto out; + if ((int)ind_info->curr_state < QMI_STATE_MIN_VAL || + (int)ind_info->curr_state > QMI_STATE_MAX_VAL) + pr_err("Unexpected indication notification state %d\n", + ind_info->curr_state); + else { + mutex_lock(¬if_add_lock); + mutex_lock(&service_list_lock); + rc = service_notif_queue_notification(service_notif, + ind_info->curr_state, &state); + if (rc & NOTIFY_STOP_MASK) + pr_err("Notifier callback aborted for %s with error %d\n", + data->service_path, rc); + service_notif->curr_state = ind_info->curr_state; + mutex_unlock(&service_list_lock); + mutex_unlock(¬if_add_lock); + } + + req.transaction_id = ind_info->transaction_id; + snprintf(req.service_name, ARRAY_SIZE(req.service_name), "%s", + data->service_path); + + rc = qmi_txn_init(&data->clnt_handle, &txn, + qmi_servreg_notif_set_ack_resp_msg_v01_ei, + &resp); + + if (rc < 0) { + pr_err("%s QMI tx init failed , ret - %d\n", + data->service_path, rc); + goto out; + } + + rc = qmi_send_request(&data->clnt_handle, &data->s_addr, + &txn, SERVREG_NOTIF_SET_ACK_REQ, + SERVREG_NOTIF_SET_ACK_REQ_MSG_LEN, + qmi_servreg_notif_set_ack_req_msg_v01_ei, + &req); + if (rc < 0) { + pr_err("%s: QMI send ACK failed, ret - %d\n", + data->service_path, rc); + qmi_txn_cancel(&txn); + goto out; + } + + rc = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT)); + if (rc < 0) { + pr_err("%s: QMI qmi txn wait failed, ret - %d\n", + data->service_path, rc); + goto out; + } + + /* Check the response */ + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) + pr_err("QMI request failed 0x%x\n", resp.resp.error); + pr_info("Indication ACKed for transid %d, service %s, instance %d!\n", + ind_info->transaction_id, data->service_path, + data->instance_id); +out: + kfree(ind_info); +} + +static void root_service_service_ind_cb(struct qmi_handle *qmi, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct qmi_client_info *qmi_data = container_of(qmi, + struct qmi_client_info, clnt_handle); + struct qmi_servreg_notif_state_updated_ind_msg_v01 ind_msg = + *((struct qmi_servreg_notif_state_updated_ind_msg_v01 *)data); + struct ind_req_resp *ind_info = kmalloc(sizeof(*ind_info), GFP_KERNEL); + + if (!ind_info) + return; + + pr_info("Indication received from %s, state: 0x%x, trans-id: %d\n", + ind_msg.service_name, ind_msg.curr_state, + ind_msg.transaction_id); + + ind_info->transaction_id = ind_msg.transaction_id; + ind_info->curr_state = ind_msg.curr_state; + ind_info->client_data = qmi_data; + INIT_WORK(&ind_info->ind_ack, send_ind_ack); + queue_work(qmi_data->ind_ack_wq, &ind_info->ind_ack); +} + +static int send_notif_listener_msg_req(struct service_notif_info *service_notif, + struct qmi_client_info *data, + bool register_notif, int *curr_state) +{ + struct qmi_servreg_notif_register_listener_req_msg_v01 req; + struct qmi_servreg_notif_register_listener_resp_msg_v01 + resp = { { 0, 0 } }; + struct qmi_txn txn; + int rc; + + snprintf(req.service_name, ARRAY_SIZE(req.service_name), "%s", + service_notif->service_path); + req.enable = register_notif; + + rc = qmi_txn_init(&data->clnt_handle, &txn, + qmi_servreg_notif_register_listener_resp_msg_v01_ei, + &resp); + + if (rc < 0) { + pr_err("%s QMI tx init failed , ret - %d\n", + service_notif->service_path, rc); + return rc; + } + + rc = qmi_send_request(&data->clnt_handle, &data->s_addr, + &txn, SERVREG_NOTIF_REGISTER_LISTENER_REQ, + SERVREG_NOTIF_REGISTER_LISTENER_REQ_MSG_LEN, + qmi_servreg_notif_register_listener_req_msg_v01_ei, + &req); + if (rc < 0) { + pr_err("%s: QMI send req failed, ret - %d\n", + service_notif->service_path, rc); + qmi_txn_cancel(&txn); + return rc; + } + + rc = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT)); + if (rc < 0) { + pr_err("%s: QMI qmi txn wait failed, ret - %d\n", + service_notif->service_path, rc); + return rc; + } + + /* Check the response */ + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + pr_err("QMI request failed 0x%x\n", resp.resp.error); + return -EREMOTEIO; + } + + if ((int) resp.curr_state < QMI_STATE_MIN_VAL || + (int) resp.curr_state > QMI_STATE_MAX_VAL) { + pr_err("Invalid indication notification state %d\n", + resp.curr_state); + rc = -EINVAL; + } + *curr_state = resp.curr_state; + return rc; +} + +static struct qmi_msg_handler qmi_indication_handler[] = { + { + .type = QMI_INDICATION, + .msg_id = SERVREG_NOTIF_STATE_UPDATED_IND_MSG, + .ei = qmi_servreg_notif_state_updated_ind_msg_v01_ei, + .decoded_size = + sizeof(struct qmi_servreg_notif_state_updated_ind_msg_v01), + .fn = root_service_service_ind_cb, + }, + {} +}; + +static int register_notif_listener(struct service_notif_info *service_notif, + struct qmi_client_info *data, + int *curr_state) +{ + return send_notif_listener_msg_req(service_notif, data, true, + curr_state); +} + +static int service_notifier_new_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + struct qmi_client_info *data = container_of(qmi, + struct qmi_client_info, clnt_handle); + + data->s_addr.sq_family = AF_QIPCRTR; + data->s_addr.sq_node = svc->node; + data->s_addr.sq_port = svc->port; + data->service_connected = true; + pr_info("Connection established between QMI handle and %d service\n", + data->instance_id); + queue_work(data->svc_event_wq, &data->new_server); + return 0; +} + +static void new_server_work(struct work_struct *work) +{ + struct service_notif_info *service_notif = NULL; + struct qmi_client_info *data = container_of(work, + struct qmi_client_info, new_server); + int rc = 0, curr_state = 0; + + mutex_lock(¬if_add_lock); + mutex_lock(&service_list_lock); + list_for_each_entry(service_notif, &service_list, list) { + if (service_notif->instance_id == data->instance_id && !strcmp + (service_notif->service_path, data->service_path)) { + enum pd_subsys_state state = ROOT_PD_UP; + + rc = register_notif_listener(service_notif, data, + &curr_state); + if (rc < 0) { + pr_err("Notifier registration failed for %s rc:%d\n", + service_notif->service_path, rc); + } else { + rc = service_notif_queue_notification( + service_notif, curr_state, &state); + if (rc & NOTIFY_STOP_MASK) + pr_err("Notifier callback aborted for %s error:%d\n", + service_notif->service_path, rc); + service_notif->curr_state = curr_state; + } + } + } + mutex_unlock(&service_list_lock); + mutex_unlock(¬if_add_lock); +} + +static void root_service_service_exit(struct qmi_client_info *data, + enum pd_subsys_state state) +{ + struct service_notif_info *service_notif = NULL; + int rc; + + /* + * Send service down notifications to all clients + * of registered for notifications for that service. + */ + mutex_lock(¬if_add_lock); + mutex_lock(&service_list_lock); + list_for_each_entry(service_notif, &service_list, list) { + if (service_notif->instance_id == data->instance_id && !strcmp + (data->service_path, service_notif->service_path)) { + rc = service_notif_queue_notification(service_notif, + SERVREG_NOTIF_SERVICE_STATE_DOWN_V01, + &state); + if (rc & NOTIFY_STOP_MASK) + pr_err("Notifier callback aborted for %s with error %d\n", + service_notif->service_path, rc); + service_notif->curr_state = + SERVREG_NOTIF_SERVICE_STATE_DOWN_V01; + } + } + mutex_unlock(&service_list_lock); + mutex_unlock(¬if_add_lock); +} + +static int ssr_event_notify(struct notifier_block *this, + unsigned long code, + void *data) +{ + struct qmi_client_info *info = container_of(this, + struct qmi_client_info, ssr_notifier); + struct notif_data *notif = data; + + switch (code) { + case SUBSYS_BEFORE_SHUTDOWN: + pr_debug("Root PD DOWN(SSR notification), state:%d\n", + notif->crashed); + switch (notif->crashed) { + case CRASH_STATUS_ERR_FATAL: + info->subsys_state = ROOT_PD_ERR_FATAL; + break; + case CRASH_STATUS_WDOG_BITE: + info->subsys_state = ROOT_PD_WDOG_BITE; + break; + default: + info->subsys_state = ROOT_PD_SHUTDOWN; + break; + } + root_service_service_exit(info, info->subsys_state); + break; + default: + break; + } + return NOTIFY_DONE; +} + +static void del_server_work(struct work_struct *work) +{ + struct qmi_client_info *data = container_of(work, + struct qmi_client_info, del_server); + + data->subsys_state = ROOT_PD_DOWN; + root_service_service_exit(data, data->subsys_state); +} + +static void service_notifier_del_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + struct qmi_client_info *data = container_of(qmi, + struct qmi_client_info, clnt_handle); + data->service_connected = false; + pr_info("Connection lost between QMI handle and %d service\n", + data->instance_id); + queue_work(data->svc_event_wq, &data->del_server); +} + +static struct qmi_ops server_ops = { + .new_server = service_notifier_new_server, + .del_server = service_notifier_del_server, +}; + +static void *add_service_notif(const char *service_path, int instance_id, + int *curr_state) +{ + struct service_notif_info *service_notif; + struct qmi_client_info *tmp, *qmi_data; + long rc; + char subsys[SERVREG_NOTIF_NAME_LENGTH]; + + rc = find_subsys(service_path, subsys); + if (rc < 0) { + pr_err("Could not find subsys for %s\n", service_path); + return ERR_PTR(rc); + } + + service_notif = kzalloc(sizeof(struct service_notif_info), GFP_KERNEL); + if (!service_notif) + return ERR_PTR(-ENOMEM); + + strlcpy(service_notif->service_path, service_path, + ARRAY_SIZE(service_notif->service_path)); + service_notif->instance_id = instance_id; + + /* If we already have a connection to the root PD on which the remote + * service we are interested in notifications about runs, then use + * the existing QMI connection. + */ + mutex_lock(&qmi_list_lock); + list_for_each_entry(tmp, &qmi_client_list, list) { + if (tmp->instance_id == instance_id && !strcmp + (tmp->service_path, service_path)) { + if (tmp->service_connected) { + rc = register_notif_listener(service_notif, tmp, + curr_state); + if (rc < 0) { + mutex_unlock(&qmi_list_lock); + pr_err("Register notifier failed: %s\n", + service_path); + kfree(service_notif); + return ERR_PTR(rc); + } + service_notif->curr_state = *curr_state; + } + mutex_unlock(&qmi_list_lock); + goto add_service_list; + } + } + mutex_unlock(&qmi_list_lock); + + qmi_data = kzalloc(sizeof(struct qmi_client_info), GFP_KERNEL); + if (!qmi_data) { + kfree(service_notif); + return ERR_PTR(-ENOMEM); + } + + qmi_data->instance_id = instance_id; + strlcpy(qmi_data->service_path, service_path, + ARRAY_SIZE(service_notif->service_path)); + qmi_data->svc_event_wq = create_singlethread_workqueue(subsys); + if (!qmi_data->svc_event_wq) { + rc = -ENOMEM; + goto exit; + } + qmi_data->ind_ack_wq = alloc_ordered_workqueue("%s_pdr_wq", WQ_HIGHPRI, + subsys); + if (!qmi_data->ind_ack_wq) { + rc = -ENOMEM; + goto exit; + } + + INIT_WORK(&qmi_data->new_server, new_server_work); + INIT_WORK(&qmi_data->del_server, del_server_work); + + *curr_state = service_notif->curr_state = + SERVREG_NOTIF_SERVICE_STATE_UNINIT_V01; + + rc = qmi_handle_init(&qmi_data->clnt_handle, + SERVREG_NOTIF_STATE_UPDATED_IND_MSG_LEN, + &server_ops, + qmi_indication_handler); + if (rc < 0) { + pr_err("Service Notifier qmi handle init failed rc:%ld\n", rc); + goto exit; + } + + qmi_add_lookup(&qmi_data->clnt_handle, + SERVREG_NOTIF_SERVICE_ID, + SERVREG_NOTIF_SERVICE_VERS_V01, + instance_id); + + qmi_data->ssr_notifier.notifier_call = ssr_event_notify; + qmi_data->ssr_handle = subsys_notif_register_notifier(subsys, + &qmi_data->ssr_notifier); + if (IS_ERR(qmi_data->ssr_handle)) { + pr_err("SSR notif register for %s failed(instance-id: %d)\n", + subsys, qmi_data->instance_id); + rc = PTR_ERR(qmi_data->ssr_handle); + qmi_handle_release(&qmi_data->clnt_handle); + goto exit; + } + + mutex_lock(&qmi_list_lock); + INIT_LIST_HEAD(&qmi_data->list); + list_add_tail(&qmi_data->list, &qmi_client_list); + mutex_unlock(&qmi_list_lock); + +add_service_list: + srcu_init_notifier_head(&service_notif->service_notif_rcvr_list); + + mutex_lock(&service_list_lock); + INIT_LIST_HEAD(&service_notif->list); + list_add_tail(&service_notif->list, &service_list); + mutex_unlock(&service_list_lock); + + return service_notif; +exit: + if (qmi_data->ind_ack_wq) + destroy_workqueue(qmi_data->ind_ack_wq); + if (qmi_data->svc_event_wq) + destroy_workqueue(qmi_data->svc_event_wq); + kfree(qmi_data); + kfree(service_notif); + return ERR_PTR(rc); +} + +static int send_pd_restart_req(const char *service_path, + struct qmi_client_info *data) +{ + struct qmi_servreg_notif_restart_pd_req_msg_v01 req; + struct qmi_servreg_notif_register_listener_resp_msg_v01 + resp = { { 0, 0 } }; + struct qmi_txn txn; + int rc; + + snprintf(req.service_name, ARRAY_SIZE(req.service_name), "%s", + service_path); + + rc = qmi_txn_init(&data->clnt_handle, &txn, + qmi_servreg_notif_restart_pd_resp_msg_v01_ei, + &resp); + + if (rc < 0) { + pr_err("%s QMI tx init failed , ret - %d\n", service_path, rc); + return rc; + } + + rc = qmi_send_request(&data->clnt_handle, &data->s_addr, + &txn, QMI_SERVREG_NOTIF_RESTART_PD_REQ_V01, + QMI_SERVREG_NOTIF_RESTART_PD_REQ_MSG_V01_MAX_MSG_LEN, + qmi_servreg_notif_restart_pd_req_msg_v01_ei, + &req); + if (rc < 0) { + pr_err("%s: QMI send req failed, ret - %d\n", + service_path, rc); + qmi_txn_cancel(&txn); + return rc; + } + + rc = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT)); + if (rc < 0) { + pr_err("%s: QMI qmi txn wait failed for client, ret - %d\n", + service_path, rc); + return rc; + } + + /* Check response if PDR is disabled */ + if (resp.resp.result == QMI_RESULT_FAILURE_V01 && + resp.resp.error == QMI_ERR_DISABLED_V01) { + pr_err("PD restart is disabled 0x%x\n", resp.resp.error); + return -EOPNOTSUPP; + } + /* Check the response for other error case*/ + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + pr_err("QMI request for PD restart failed 0x%x\n", + resp.resp.error); + return -EREMOTEIO; + } + + return rc; +} + +/* service_notif_pd_restart() - Request PD restart + * @service_path: Individual service identifier path for which restart is + * being requested. + * @instance_id: Instance id specific to a subsystem. + * + * @return: >=0 on success, standard Linux error codes on failure. + */ +int service_notif_pd_restart(const char *service_path, int instance_id) +{ + struct qmi_client_info *tmp; + int rc = 0; + + list_for_each_entry(tmp, &qmi_client_list, list) { + if (tmp->instance_id == instance_id && !strcmp + (tmp->service_path, service_path)) { + if (tmp->service_connected) { + pr_info("Restarting service %s, instance-id %d\n", + service_path, instance_id); + rc = send_pd_restart_req(service_path, tmp); + } else + pr_info("Service %s is not connected\n", + service_path); + } + } + return rc; +} +EXPORT_SYMBOL(service_notif_pd_restart); + +/* service_notif_register_notifier() - Register a notifier for a service + * On success, it returns back a handle. It takes the following arguments: + * service_path: Individual service identifier path for which a client + * registers for notifications. + * instance_id: Instance id specific to a subsystem. + * current_state: Current state of service returned by the registration + * process. + * notifier block: notifier callback for service events. + */ +void *service_notif_register_notifier(const char *service_path, int instance_id, + struct notifier_block *nb, int *curr_state) +{ + struct service_notif_info *service_notif; + int ret = 0; + + if (!service_path || !instance_id || !nb) + return ERR_PTR(-EINVAL); + + mutex_lock(¬if_add_lock); + service_notif = _find_service_info(service_path); + if (!service_notif) { + service_notif = (struct service_notif_info *)add_service_notif( + service_path, + instance_id, + curr_state); + if (IS_ERR(service_notif)) + goto exit; + } + + ret = srcu_notifier_chain_register( + &service_notif->service_notif_rcvr_list, nb); + *curr_state = service_notif->curr_state; + if (ret < 0) + service_notif = ERR_PTR(ret); +exit: + mutex_unlock(¬if_add_lock); + return service_notif; +} +EXPORT_SYMBOL(service_notif_register_notifier); + +static void remove_service_notif(struct service_notif_info *service_notif) +{ + mutex_lock(&service_list_lock); + list_del(&service_notif->list); + mutex_unlock(&service_list_lock); + + kfree(service_notif); +} + +/* service_notif_unregister_notifier() - Unregister a notifier for a service. + * service_notif_handle - The notifier handler that was provided by the + * service_notif_register_notifier function when the + * client registered for notifications. + * nb - The notifier block that was previously used during the registration. + */ +int service_notif_unregister_notifier(void *service_notif_handle, + struct notifier_block *nb) +{ + int ret; + struct service_notif_info *service_notif; + + if (!service_notif_handle || !nb) + return -EINVAL; + + service_notif = (struct service_notif_info *)service_notif_handle; + if (!service_notif) + return -EINVAL; + + ret = srcu_notifier_chain_unregister( + &service_notif->service_notif_rcvr_list, nb); + + if (!ret) + remove_service_notif(service_notif); + + return ret; +} +EXPORT_SYMBOL(service_notif_unregister_notifier); + +static void __exit service_notifier_exit(void) +{ + struct qmi_client_info *tmp, *qmi_data; + + mutex_lock(&qmi_list_lock); + list_for_each_entry_safe(qmi_data, tmp, &qmi_client_list, list) { + list_del(&qmi_data->list); + + qmi_handle_release(&qmi_data->clnt_handle); + + flush_work(&qmi_data->new_server); + flush_work(&qmi_data->del_server); + + if (qmi_data->ind_ack_wq) + destroy_workqueue(qmi_data->ind_ack_wq); + if (qmi_data->svc_event_wq) + destroy_workqueue(qmi_data->svc_event_wq); + + kfree(qmi_data); + } + mutex_unlock(&qmi_list_lock); +} +module_exit(service_notifier_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Service Notifier driver"); diff --git a/drivers/soc/qcom/subsys-pil-tz.c b/drivers/soc/qcom/subsys-pil-tz.c new file mode 100644 index 000000000000..cbd3d3d57b16 --- /dev/null +++ b/drivers/soc/qcom/subsys-pil-tz.c @@ -0,0 +1,1297 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "peripheral-loader.h" + +#define PIL_TZ_AVG_BW 0 +#define PIL_TZ_PEAK_BW UINT_MAX + +#define XO_FREQ 19200000 +#define PROXY_TIMEOUT_MS 10000 +#define MAX_SSR_REASON_LEN 256U +#define STOP_ACK_TIMEOUT_MS 1000 +#define CRASH_STOP_ACK_TO_MS 200 + +#define ERR_READY 0 +#define PBL_DONE 1 + +#define desc_to_data(d) container_of(d, struct pil_tz_data, desc) +#define subsys_to_data(d) container_of(d, struct pil_tz_data, subsys_desc) + +/** + * struct reg_info - regulator info + * @reg: regulator handle + * @uV: voltage in uV + * @uA: current in uA + */ +struct reg_info { + struct regulator *reg; + int uV; + int uA; +}; + +/** + * struct pil_tz_data + * @regs: regulators that should be always on when the subsystem is + * brought out of reset + * @proxy_regs: regulators that should be on during pil proxy voting + * @clks: clocks that should be always on when the subsystem is + * brought out of reset + * @proxy_clks: clocks that should be on during pil proxy voting + * @reg_count: the number of always on regulators + * @proxy_reg_count: the number of proxy voting regulators + * @clk_count: the number of always on clocks + * @proxy_clk_count: the number of proxy voting clocks + * @smem_id: the smem id used for read the subsystem crash reason + * @ramdump_dev: ramdump device pointer + * @pas_id: the PAS id for tz + * @bus_client: bus client + * @enable_bus_scaling: set to true if PIL needs to vote for + * bus bandwidth + * @keep_proxy_regs_on: If set, during proxy unvoting, PIL removes the + * voltage/current vote for proxy regulators but leaves + * them enabled. + * @stop_ack: state of completion of stop ack + * @desc: PIL descriptor + * @subsys: subsystem device pointer + * @subsys_desc: subsystem descriptor + * @u32 bits_arr[2]: array of bit positions in SCSR registers + */ +struct pil_tz_data { + struct reg_info *regs; + struct reg_info *proxy_regs; + struct clk **clks; + struct clk **proxy_clks; + int reg_count; + int proxy_reg_count; + int clk_count; + int proxy_clk_count; + int smem_id; + void *ramdump_dev; + void *minidump_dev; + u32 pas_id; + struct icc_path *bus_client; + bool enable_bus_scaling; + bool keep_proxy_regs_on; + struct completion stop_ack; + struct completion shutdown_ack; + struct pil_desc desc; + struct subsys_device *subsys; + struct subsys_desc subsys_desc; + void __iomem *irq_status; + void __iomem *irq_clear; + void __iomem *irq_mask; + void __iomem *err_status; + void __iomem *err_status_spare; + u32 bits_arr[2]; +}; + +enum scm_cmd { + PAS_INIT_IMAGE_CMD = 1, + PAS_MEM_SETUP_CMD, + PAS_AUTH_AND_RESET_CMD = 5, + PAS_SHUTDOWN_CMD, +}; + +enum pas_id { + PAS_MODEM, + PAS_Q6, + PAS_DSPS, + PAS_TZAPPS, + PAS_MODEM_SW, + PAS_MODEM_FW, + PAS_WCNSS, + PAS_SECAPP, + PAS_GSS, + PAS_VIDC, + PAS_VPU, + PAS_BCSS, +}; + +static struct icc_path *scm_perf_client; +static int scm_pas_bw_count; +static DEFINE_MUTEX(scm_pas_bw_mutex); + +static int scm_pas_enable_bw(void) +{ + int ret = 0; + + if (!scm_perf_client) + return -EINVAL; + + mutex_lock(&scm_pas_bw_mutex); + if (!scm_pas_bw_count) { + ret = icc_set_bw(scm_perf_client, PIL_TZ_AVG_BW, + PIL_TZ_PEAK_BW); + if (ret) + goto err_bus; + scm_pas_bw_count++; + } + + mutex_unlock(&scm_pas_bw_mutex); + return ret; + +err_bus: + pr_err("scm-pas; Bandwidth request failed (%d)\n", ret); + icc_set_bw(scm_perf_client, 0, 0); + + mutex_unlock(&scm_pas_bw_mutex); + return ret; +} + +static void scm_pas_disable_bw(void) +{ + mutex_lock(&scm_pas_bw_mutex); + if (scm_pas_bw_count-- == 1) + icc_set_bw(scm_perf_client, 0, 0); + mutex_unlock(&scm_pas_bw_mutex); +} + +static int of_read_clocks(struct device *dev, struct clk ***clks_ref, + const char *propname) +{ + long clk_count; + int i, len; + struct clk **clks; + + if (!of_find_property(dev->of_node, propname, &len)) + return 0; + + clk_count = of_property_count_strings(dev->of_node, propname); + if (IS_ERR_VALUE(clk_count)) { + dev_err(dev, "Failed to get clock names\n"); + return -EINVAL; + } + + clks = devm_kzalloc(dev, sizeof(struct clk *) * clk_count, + GFP_KERNEL); + if (!clks) + return -ENOMEM; + + for (i = 0; i < clk_count; i++) { + const char *clock_name; + char clock_freq_name[50]; + u32 clock_rate = XO_FREQ; + + of_property_read_string_index(dev->of_node, + propname, i, + &clock_name); + snprintf(clock_freq_name, ARRAY_SIZE(clock_freq_name), + "qcom,%s-freq", clock_name); + if (of_find_property(dev->of_node, clock_freq_name, &len)) + if (of_property_read_u32(dev->of_node, clock_freq_name, + &clock_rate)) { + dev_err(dev, "Failed to read %s clock's freq\n", + clock_freq_name); + return -EINVAL; + } + + clks[i] = devm_clk_get(dev, clock_name); + if (IS_ERR(clks[i])) { + int rc = PTR_ERR(clks[i]); + + if (rc != -EPROBE_DEFER) + dev_err(dev, "Failed to get %s clock\n", + clock_name); + return rc; + } + + /* Make sure rate-settable clocks' rates are set */ + if (clk_get_rate(clks[i]) == 0) + clk_set_rate(clks[i], clk_round_rate(clks[i], + clock_rate)); + } + + *clks_ref = clks; + return clk_count; +} + +static int of_read_regs(struct device *dev, struct reg_info **regs_ref, + const char *propname) +{ + long reg_count; + int i, len, rc; + struct reg_info *regs; + + if (!of_find_property(dev->of_node, propname, &len)) + return 0; + + reg_count = of_property_count_strings(dev->of_node, propname); + if (IS_ERR_VALUE(reg_count)) { + dev_err(dev, "Failed to get regulator names\n"); + return -EINVAL; + } + + regs = devm_kzalloc(dev, sizeof(struct reg_info) * reg_count, + GFP_KERNEL); + if (!regs) + return -ENOMEM; + + for (i = 0; i < reg_count; i++) { + const char *reg_name; + char reg_uV_uA_name[50]; + u32 vdd_uV_uA[2]; + + of_property_read_string_index(dev->of_node, + propname, i, + ®_name); + + regs[i].reg = devm_regulator_get(dev, reg_name); + if (IS_ERR(regs[i].reg)) { + int rc = PTR_ERR(regs[i].reg); + + if (rc != -EPROBE_DEFER) + dev_err(dev, "Failed to get %s\n regulator\n", + reg_name); + return rc; + } + + /* + * Read the voltage and current values for the corresponding + * regulator. The device tree property name is "qcom," + + * "regulator_name" + "-uV-uA". + */ + rc = snprintf(reg_uV_uA_name, ARRAY_SIZE(reg_uV_uA_name), + "qcom,%s-uV-uA", reg_name); + if (rc < strlen(reg_name) + 6) { + dev_err(dev, "Failed to hold reg_uV_uA_name\n"); + return -EINVAL; + } + + if (!of_find_property(dev->of_node, reg_uV_uA_name, &len)) + continue; + + len /= sizeof(vdd_uV_uA[0]); + + /* There should be two entries: one for uV and one for uA */ + if (len != 2) { + dev_err(dev, "Missing uV/uA value\n"); + return -EINVAL; + } + + rc = of_property_read_u32_array(dev->of_node, reg_uV_uA_name, + vdd_uV_uA, len); + if (rc) { + dev_err(dev, "Failed to read uV/uA values(rc:%d)\n", + rc); + return rc; + } + + regs[i].uV = vdd_uV_uA[0]; + regs[i].uA = vdd_uV_uA[1]; + } + + *regs_ref = regs; + return reg_count; +} + +#if IS_ENABLED(CONFIG_INTERCONNECT_QCOM) +static int of_read_bus_client(struct platform_device *pdev, + struct pil_tz_data *d) +{ + d->bus_client = of_icc_get(&pdev->dev, NULL); + if (!d->bus_client) + pr_warn("%s: Unable to register bus client\n", __func__); + + return 0; +} +static int do_bus_scaling_request(struct pil_desc *pil, int enable) +{ + int rc; + struct pil_tz_data *d = desc_to_data(pil); + u32 avg_bw = enable ? PIL_TZ_AVG_BW : 0; + u32 peak_bw = enable ? PIL_TZ_PEAK_BW : 0; + + if (d->bus_client) { + rc = icc_set_bw(d->bus_client, avg_bw, peak_bw); + if (rc) { + dev_err(pil->dev, "bandwidth request failed(rc:%d)\n", + rc); + return rc; + } + } else + WARN(d->enable_bus_scaling, "Bus scaling not set up for %s!\n", + d->subsys_desc.name); + return 0; +} +#else +static int of_read_bus_client(struct platform_device *pdev, + struct pil_tz_data *d) +{ + return 0; +} +static int do_bus_scaling_request(struct pil_desc *pil, int enable) +{ + return 0; +} +#endif + +static int piltz_resc_init(struct platform_device *pdev, struct pil_tz_data *d) +{ + int len, count, rc; + struct device *dev = &pdev->dev; + + count = of_read_clocks(dev, &d->clks, "qcom,active-clock-names"); + if (count < 0) { + dev_err(dev, "Failed to setup clocks(rc:%d).\n", count); + return count; + } + d->clk_count = count; + + count = of_read_clocks(dev, &d->proxy_clks, "qcom,proxy-clock-names"); + if (count < 0) { + dev_err(dev, "Failed to setup proxy clocks(rc:%d).\n", count); + return count; + } + d->proxy_clk_count = count; + + count = of_read_regs(dev, &d->regs, "qcom,active-reg-names"); + if (count < 0) { + dev_err(dev, "Failed to setup regulators(rc:%d).\n", count); + return count; + } + d->reg_count = count; + + count = of_read_regs(dev, &d->proxy_regs, "qcom,proxy-reg-names"); + if (count < 0) { + dev_err(dev, "Failed to setup proxy regulators(rc:%d).\n", + count); + return count; + } + d->proxy_reg_count = count; + + if (of_find_property(dev->of_node, "interconnects", &len)) { + d->enable_bus_scaling = true; + rc = of_read_bus_client(pdev, d); + if (rc) { + dev_err(dev, "Failed to setup bus scaling client(rc:%d).\n", + rc); + return rc; + } + } + + return 0; +} + +static int enable_regulators(struct pil_tz_data *d, struct device *dev, + struct reg_info *regs, int reg_count, + bool reg_no_enable) +{ + int i, rc = 0; + + for (i = 0; i < reg_count; i++) { + if (regs[i].uV > 0) { + rc = regulator_set_voltage(regs[i].reg, + regs[i].uV, INT_MAX); + if (rc) { + dev_err(dev, "Failed to request voltage(rc:%d)\n", + rc); + goto err_voltage; + } + } + + if (regs[i].uA > 0) { + rc = regulator_set_load(regs[i].reg, + regs[i].uA); + if (rc < 0) { + dev_err(dev, "Failed to set regulator mode(rc:%d)\n", + rc); + goto err_mode; + } + } + + if (d->keep_proxy_regs_on && reg_no_enable) + continue; + + rc = regulator_enable(regs[i].reg); + if (rc) { + dev_err(dev, "Regulator enable failed(rc:%d)\n", rc); + goto err_enable; + } + } + + return 0; +err_enable: + if (regs[i].uA > 0) { + regulator_set_voltage(regs[i].reg, 0, INT_MAX); + regulator_set_load(regs[i].reg, 0); + } +err_mode: + if (regs[i].uV > 0) + regulator_set_voltage(regs[i].reg, 0, INT_MAX); +err_voltage: + for (i--; i >= 0; i--) { + if (regs[i].uV > 0) + regulator_set_voltage(regs[i].reg, 0, INT_MAX); + + if (regs[i].uA > 0) + regulator_set_load(regs[i].reg, 0); + + if (d->keep_proxy_regs_on && reg_no_enable) + continue; + regulator_disable(regs[i].reg); + } + + return rc; +} + +static void disable_regulators(struct pil_tz_data *d, struct reg_info *regs, + int reg_count, bool reg_no_disable) +{ + int i; + + for (i = 0; i < reg_count; i++) { + if (regs[i].uV > 0) + regulator_set_voltage(regs[i].reg, 0, INT_MAX); + + if (regs[i].uA > 0) + regulator_set_load(regs[i].reg, 0); + + if (d->keep_proxy_regs_on && reg_no_disable) + continue; + regulator_disable(regs[i].reg); + } +} + +static int prepare_enable_clocks(struct device *dev, struct clk **clks, + int clk_count) +{ + int rc = 0; + int i; + + for (i = 0; i < clk_count; i++) { + rc = clk_prepare_enable(clks[i]); + if (rc) { + dev_err(dev, "Clock enable failed(rc:%d)\n", rc); + goto err; + } + } + + return 0; +err: + for (i--; i >= 0; i--) + clk_disable_unprepare(clks[i]); + + return rc; +} + +static void disable_unprepare_clocks(struct clk **clks, int clk_count) +{ + int i; + + for (i = --clk_count; i >= 0; i--) + clk_disable_unprepare(clks[i]); +} + +static int pil_make_proxy_vote(struct pil_desc *pil) +{ + struct pil_tz_data *d = desc_to_data(pil); + int rc; + + if (d->subsys_desc.no_auth) + return 0; + + rc = do_bus_scaling_request(pil, 1); + if (rc) + return rc; + + rc = enable_regulators(d, pil->dev, d->proxy_regs, + d->proxy_reg_count, false); + if (rc) + return rc; + + rc = prepare_enable_clocks(pil->dev, d->proxy_clks, + d->proxy_clk_count); + if (rc) + goto err_clks; + + return 0; + +err_clks: + disable_regulators(d, d->proxy_regs, d->proxy_reg_count, false); + + return rc; +} + +static void pil_remove_proxy_vote(struct pil_desc *pil) +{ + struct pil_tz_data *d = desc_to_data(pil); + + if (d->subsys_desc.no_auth) + return; + + disable_unprepare_clocks(d->proxy_clks, d->proxy_clk_count); + + disable_regulators(d, d->proxy_regs, d->proxy_reg_count, true); + + do_bus_scaling_request(pil, 0); +} + +static int pil_init_image_trusted(struct pil_desc *pil, + const u8 *metadata, size_t size) +{ + struct pil_tz_data *d = desc_to_data(pil); + u32 scm_ret = 0; + void *mdata_buf; + dma_addr_t mdata_phys; + int ret; + struct scm_desc desc = {0}; + + if (d->subsys_desc.no_auth) + return 0; + + ret = scm_pas_enable_bw(); + if (ret) + return ret; + + dma_set_mask(pil->dev, DMA_BIT_MASK(sizeof(dma_addr_t) * 8)); + mdata_buf = dma_alloc_coherent(pil->dev, size, &mdata_phys, GFP_KERNEL); + if (!mdata_buf) { + pr_err("scm-pas: Allocation for metadata failed.\n"); + scm_pas_disable_bw(); + return -ENOMEM; + } + + memcpy(mdata_buf, metadata, size); + + desc.args[0] = d->pas_id; + desc.args[1] = mdata_phys; + desc.arginfo = SCM_ARGS(2, SCM_VAL, SCM_RW); + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_PIL, PAS_INIT_IMAGE_CMD), + &desc); + scm_ret = desc.ret[0]; + + dma_free_coherent(pil->dev, size, mdata_buf, mdata_phys); + + scm_pas_disable_bw(); + if (ret) + return ret; + return scm_ret; +} + +static int pil_mem_setup_trusted(struct pil_desc *pil, phys_addr_t addr, + size_t size) +{ + struct pil_tz_data *d = desc_to_data(pil); + u32 scm_ret = 0; + int ret; + struct scm_desc desc = {0}; + + if (d->subsys_desc.no_auth) + return 0; + + desc.args[0] = d->pas_id; + desc.args[1] = addr; + desc.args[2] = size; + desc.arginfo = SCM_ARGS(3); + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_PIL, PAS_MEM_SETUP_CMD), + &desc); + scm_ret = desc.ret[0]; + + if (ret) + return ret; + return scm_ret; +} + +static int pil_auth_and_reset(struct pil_desc *pil) +{ + struct pil_tz_data *d = desc_to_data(pil); + int rc; + u32 proc, scm_ret = 0; + struct scm_desc desc = {0}; + + if (d->subsys_desc.no_auth) + return 0; + + desc.args[0] = proc = d->pas_id; + desc.arginfo = SCM_ARGS(1); + + rc = scm_pas_enable_bw(); + if (rc) + return rc; + + rc = enable_regulators(d, pil->dev, d->regs, d->reg_count, false); + if (rc) + return rc; + + rc = prepare_enable_clocks(pil->dev, d->clks, d->clk_count); + if (rc) + goto err_clks; + + rc = scm_call2(SCM_SIP_FNID(SCM_SVC_PIL, + PAS_AUTH_AND_RESET_CMD), &desc); + scm_ret = desc.ret[0]; + + scm_pas_disable_bw(); + if (rc) + goto err_reset; + + return scm_ret; +err_reset: + disable_unprepare_clocks(d->clks, d->clk_count); +err_clks: + disable_regulators(d, d->regs, d->reg_count, false); + + return rc; +} + +static int pil_shutdown_trusted(struct pil_desc *pil) +{ + struct pil_tz_data *d = desc_to_data(pil); + u32 proc, scm_ret = 0; + int rc; + struct scm_desc desc = {0}; + + if (d->subsys_desc.no_auth) + return 0; + + desc.args[0] = proc = d->pas_id; + desc.arginfo = SCM_ARGS(1); + + rc = do_bus_scaling_request(pil, 1); + if (rc) + return rc; + + rc = enable_regulators(d, pil->dev, d->proxy_regs, + d->proxy_reg_count, true); + if (rc) + goto err_regulators; + + rc = prepare_enable_clocks(pil->dev, d->proxy_clks, + d->proxy_clk_count); + if (rc) + goto err_clks; + + rc = scm_call2(SCM_SIP_FNID(SCM_SVC_PIL, PAS_SHUTDOWN_CMD), + &desc); + scm_ret = desc.ret[0]; + + disable_unprepare_clocks(d->proxy_clks, d->proxy_clk_count); + disable_regulators(d, d->proxy_regs, d->proxy_reg_count, false); + + do_bus_scaling_request(pil, 0); + + if (rc) + return rc; + + disable_unprepare_clocks(d->clks, d->clk_count); + disable_regulators(d, d->regs, d->reg_count, false); + + return scm_ret; + +err_clks: + disable_regulators(d, d->proxy_regs, d->proxy_reg_count, false); +err_regulators: + do_bus_scaling_request(pil, 0); + + return rc; +} + +static int pil_deinit_image_trusted(struct pil_desc *pil) +{ + struct pil_tz_data *d = desc_to_data(pil); + u32 proc, scm_ret = 0; + int rc; + struct scm_desc desc = {0}; + + if (d->subsys_desc.no_auth) + return 0; + + desc.args[0] = proc = d->pas_id; + desc.arginfo = SCM_ARGS(1); + + rc = scm_call2(SCM_SIP_FNID(SCM_SVC_PIL, PAS_SHUTDOWN_CMD), + &desc); + scm_ret = desc.ret[0]; + + if (rc) + return rc; + return scm_ret; +} + +static struct pil_reset_ops pil_ops_trusted = { + .init_image = pil_init_image_trusted, + .mem_setup = pil_mem_setup_trusted, + .auth_and_reset = pil_auth_and_reset, + .shutdown = pil_shutdown_trusted, + .proxy_vote = pil_make_proxy_vote, + .proxy_unvote = pil_remove_proxy_vote, + .deinit_image = pil_deinit_image_trusted, +}; + +static void log_failure_reason(const struct pil_tz_data *d) +{ + size_t size; + char *smem_reason, reason[MAX_SSR_REASON_LEN]; + const char *name = d->subsys_desc.name; + + if (d->smem_id == -1) + return; + + smem_reason = qcom_smem_get(QCOM_SMEM_HOST_ANY, d->smem_id, &size); + if (IS_ERR(smem_reason) || !size) { + pr_err("%s SFR: (unknown, qcom_smem_get failed).\n", + name); + return; + } + if (!smem_reason[0]) { + pr_err("%s SFR: (unknown, empty string found).\n", name); + return; + } + + strlcpy(reason, smem_reason, min(size, (size_t)MAX_SSR_REASON_LEN)); + pr_err("%s subsystem failure reason: %s.\n", name, reason); +} + +static int subsys_shutdown(const struct subsys_desc *subsys, bool force_stop) +{ + struct pil_tz_data *d = subsys_to_data(subsys); + int ret; + + if (!subsys_get_crash_status(d->subsys) && force_stop && + subsys->state) { + qcom_smem_state_update_bits(subsys->state, + BIT(subsys->force_stop_bit), + BIT(subsys->force_stop_bit)); + ret = wait_for_completion_timeout(&d->stop_ack, + msecs_to_jiffies(STOP_ACK_TIMEOUT_MS)); + if (!ret) + pr_warn("Timed out on stop ack from %s.\n", + subsys->name); + qcom_smem_state_update_bits(subsys->state, + BIT(subsys->force_stop_bit), 0); + } + + pil_shutdown(&d->desc); + return 0; +} + +static int subsys_powerup(const struct subsys_desc *subsys) +{ + struct pil_tz_data *d = subsys_to_data(subsys); + int ret = 0; + + if (subsys->stop_ack_irq) + reinit_completion(&d->stop_ack); + + d->desc.fw_name = subsys->fw_name; + ret = pil_boot(&d->desc); + + return ret; +} + +static int subsys_ramdump(int enable, const struct subsys_desc *subsys) +{ + struct pil_tz_data *d = subsys_to_data(subsys); + + if (!enable) + return 0; + + return pil_do_ramdump(&d->desc, d->ramdump_dev, d->minidump_dev); +} + +static void subsys_free_memory(const struct subsys_desc *subsys) +{ + struct pil_tz_data *d = subsys_to_data(subsys); + + pil_free_memory(&d->desc); +} + +static void subsys_crash_shutdown(const struct subsys_desc *subsys) +{ + struct pil_tz_data *d = subsys_to_data(subsys); + + if (subsys->state && !subsys_get_crash_status(d->subsys)) { + qcom_smem_state_update_bits(subsys->state, + BIT(subsys->force_stop_bit), + BIT(subsys->force_stop_bit)); + mdelay(CRASH_STOP_ACK_TO_MS); + } +} + +static irqreturn_t subsys_err_fatal_intr_handler (int irq, void *dev_id) +{ + struct pil_tz_data *d = subsys_to_data(dev_id); + + pr_err("Fatal error on %s!\n", d->subsys_desc.name); + if (subsys_get_crash_status(d->subsys)) { + pr_err("%s: Ignoring error fatal, restart in progress\n", + d->subsys_desc.name); + return IRQ_HANDLED; + } + subsys_set_crash_status(d->subsys, CRASH_STATUS_ERR_FATAL); + log_failure_reason(d); + subsystem_restart_dev(d->subsys); + + return IRQ_HANDLED; +} + +static irqreturn_t subsys_wdog_bite_irq_handler(int irq, void *dev_id) +{ + struct pil_tz_data *d = subsys_to_data(dev_id); + + if (subsys_get_crash_status(d->subsys)) + return IRQ_HANDLED; + pr_err("Watchdog bite received from %s!\n", d->subsys_desc.name); + + if (d->subsys_desc.system_debug) + panic("%s: System ramdump requested. Triggering device restart!\n", + __func__); + subsys_set_crash_status(d->subsys, CRASH_STATUS_WDOG_BITE); + log_failure_reason(d); + subsystem_restart_dev(d->subsys); + + return IRQ_HANDLED; +} + +static irqreturn_t subsys_stop_ack_intr_handler(int irq, void *dev_id) +{ + struct pil_tz_data *d = subsys_to_data(dev_id); + + pr_info("Received stop ack interrupt from %s\n", d->subsys_desc.name); + complete(&d->stop_ack); + return IRQ_HANDLED; +} + +static irqreturn_t subsys_shutdown_ack_intr_handler(int irq, void *dev_id) +{ + struct pil_tz_data *d = subsys_to_data(dev_id); + + pr_info("Received stop shutdown interrupt from %s\n", + d->subsys_desc.name); + complete_shutdown_ack(&d->subsys_desc); + return IRQ_HANDLED; +} + +static irqreturn_t subsys_ramdump_disable_intr_handler(int irq, void *dev_id) +{ + struct pil_tz_data *d = subsys_to_data(dev_id); + + pr_info("Received ramdump disable interrupt from %s\n", + d->subsys_desc.name); + d->subsys_desc.ramdump_disable = 1; + return IRQ_HANDLED; +} + +static void clear_pbl_done(struct pil_tz_data *d) +{ + uint32_t err_value; + + err_value = __raw_readl(d->err_status); + + if (err_value) { + uint32_t rmb_err_spare0; + uint32_t rmb_err_spare1; + uint32_t rmb_err_spare2; + + pr_debug("PBL_DONE received from %s!\n", d->subsys_desc.name); + + rmb_err_spare2 = __raw_readl(d->err_status_spare); + rmb_err_spare1 = __raw_readl(d->err_status_spare-4); + rmb_err_spare0 = __raw_readl(d->err_status_spare-8); + + pr_err("PBL error status register: 0x%08x\n", err_value); + + pr_err("PBL error status spare0 register: 0x%08x\n", + rmb_err_spare0); + pr_err("PBL error status spare1 register: 0x%08x\n", + rmb_err_spare1); + pr_err("PBL error status spare2 register: 0x%08x\n", + rmb_err_spare2); + } else { + pr_info("PBL_DONE - 1st phase loading [%s] completed ok\n", + d->subsys_desc.name); + } + __raw_writel(BIT(d->bits_arr[PBL_DONE]), d->irq_clear); +} + +static void clear_err_ready(struct pil_tz_data *d) +{ + pr_debug("Subsystem error services up received from %s\n", + d->subsys_desc.name); + + pr_info("SW_INIT_DONE - 2nd phase loading [%s] completed ok\n", + d->subsys_desc.name); + + __raw_writel(BIT(d->bits_arr[ERR_READY]), d->irq_clear); + complete_err_ready(d->subsys); +} + +static void clear_sw_init_done_error(struct pil_tz_data *d, int err) +{ + uint32_t rmb_err_spare0; + uint32_t rmb_err_spare1; + uint32_t rmb_err_spare2; + + pr_info("SW_INIT_DONE - ERROR [%s] [0x%x].\n", + d->subsys_desc.name, err); + + rmb_err_spare2 = __raw_readl(d->err_status_spare); + rmb_err_spare1 = __raw_readl(d->err_status_spare-4); + rmb_err_spare0 = __raw_readl(d->err_status_spare-8); + + pr_err("spare0 register: 0x%08x\n", rmb_err_spare0); + pr_err("spare1 register: 0x%08x\n", rmb_err_spare1); + pr_err("spare2 register: 0x%08x\n", rmb_err_spare2); + + /* Clear the interrupt source */ + __raw_writel(BIT(d->bits_arr[ERR_READY]), d->irq_clear); +} + + + +static void clear_wdog(struct pil_tz_data *d) +{ + /* Check crash status to know if device is restarting*/ + if (!subsys_get_crash_status(d->subsys)) { + pr_err("wdog bite received from %s!\n", d->subsys_desc.name); + __raw_writel(BIT(d->bits_arr[ERR_READY]), d->irq_clear); + subsys_set_crash_status(d->subsys, CRASH_STATUS_WDOG_BITE); + log_failure_reason(d); + subsystem_restart_dev(d->subsys); + } +} + +static irqreturn_t subsys_generic_handler(int irq, void *dev_id) +{ + struct pil_tz_data *d = subsys_to_data(dev_id); + uint32_t status_val, err_value; + + err_value = __raw_readl(d->err_status_spare); + status_val = __raw_readl(d->irq_status); + + if (status_val & BIT(d->bits_arr[ERR_READY])) { + if (!err_value) + clear_err_ready(d); + else if (err_value == 0x44554d50) + clear_wdog(d); + else + clear_sw_init_done_error(d, err_value); + } + + if (status_val & BIT(d->bits_arr[PBL_DONE])) + clear_pbl_done(d); + + return IRQ_HANDLED; +} + +static void mask_scsr_irqs(struct pil_tz_data *d) +{ + uint32_t mask_val; + /* Masking all interrupts not handled by HLOS */ + mask_val = ~0; + __raw_writel(mask_val & ~BIT(d->bits_arr[ERR_READY]) & + ~BIT(d->bits_arr[PBL_DONE]), d->irq_mask); +} + +static int pil_tz_generic_probe(struct platform_device *pdev) +{ + struct pil_tz_data *d; + struct resource *res; + u32 proxy_timeout; + int len, rc; + char md_node[20]; + + /* Do not probe the generic PIL driver yet if the SCM BW driver + * is not yet registered. Return error if that driver returns with + * any error other than EPROBE_DEFER. + */ + if (!scm_perf_client) + return -EPROBE_DEFER; + if (IS_ERR(scm_perf_client)) + return PTR_ERR(scm_perf_client); + + d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + platform_set_drvdata(pdev, d); + + if (of_property_read_bool(pdev->dev.of_node, "qcom,pil-no-auth")) + d->subsys_desc.no_auth = true; + + d->keep_proxy_regs_on = of_property_read_bool(pdev->dev.of_node, + "qcom,keep-proxy-regs-on"); + + rc = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name", + &d->desc.name); + if (rc) + return rc; + + /* Defaulting smem_id to be not present */ + d->smem_id = -1; + + if (of_find_property(pdev->dev.of_node, "qcom,smem-id", &len)) { + rc = of_property_read_u32(pdev->dev.of_node, "qcom,smem-id", + &d->smem_id); + if (rc) { + dev_err(&pdev->dev, "Failed to get the smem_id(rc:%d)\n", + rc); + return rc; + } + } + + d->desc.dev = &pdev->dev; + d->desc.owner = THIS_MODULE; + d->desc.ops = &pil_ops_trusted; + + d->desc.proxy_timeout = PROXY_TIMEOUT_MS; + d->desc.clear_fw_region = true; + + rc = of_property_read_u32(pdev->dev.of_node, "qcom,proxy-timeout-ms", + &proxy_timeout); + if (!rc) + d->desc.proxy_timeout = proxy_timeout; + + if (!d->subsys_desc.no_auth) { + rc = piltz_resc_init(pdev, d); + if (rc) + return rc; + + rc = of_property_read_u32(pdev->dev.of_node, "qcom,pas-id", + &d->pas_id); + if (rc) { + dev_err(&pdev->dev, "Failed to find the pas_id(rc:%d)\n", + rc); + goto err_deregister_bus; + } + } + + rc = pil_desc_init(&d->desc); + if (rc) + goto err_deregister_bus; + + init_completion(&d->stop_ack); + d->subsys_desc.name = d->desc.name; + d->subsys_desc.owner = THIS_MODULE; + d->subsys_desc.dev = &pdev->dev; + d->subsys_desc.shutdown = subsys_shutdown; + d->subsys_desc.powerup = subsys_powerup; + d->subsys_desc.ramdump = subsys_ramdump; + d->subsys_desc.free_memory = subsys_free_memory; + d->subsys_desc.crash_shutdown = subsys_crash_shutdown; + if (of_property_read_bool(pdev->dev.of_node, + "qcom,pil-generic-irq-handler")) { + d->subsys_desc.generic_handler = subsys_generic_handler; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "sp2soc_irq_status"); + d->irq_status = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(d->irq_status)) { + dev_err(&pdev->dev, "Invalid resource for sp2soc_irq_status\n"); + rc = PTR_ERR(d->irq_status); + goto err_ramdump; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "sp2soc_irq_clr"); + d->irq_clear = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(d->irq_clear)) { + dev_err(&pdev->dev, "Invalid resource for sp2soc_irq_clr\n"); + rc = PTR_ERR(d->irq_clear); + goto err_ramdump; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "sp2soc_irq_mask"); + d->irq_mask = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(d->irq_mask)) { + dev_err(&pdev->dev, "Invalid resource for sp2soc_irq_mask\n"); + rc = PTR_ERR(d->irq_mask); + goto err_ramdump; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "rmb_err"); + d->err_status = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(d->err_status)) { + dev_err(&pdev->dev, "Invalid resource for rmb_err\n"); + rc = PTR_ERR(d->err_status); + goto err_ramdump; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "rmb_err_spare2"); + d->err_status_spare = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(d->err_status_spare)) { + dev_err(&pdev->dev, "Invalid resource for rmb_err_spare2\n"); + rc = PTR_ERR(d->err_status_spare); + goto err_ramdump; + } + + rc = of_property_read_u32_array(pdev->dev.of_node, + "qcom,spss-scsr-bits", d->bits_arr, + ARRAY_SIZE(d->bits_arr)); + if (rc) { + dev_err(&pdev->dev, + "Failed to read qcom,spss-scsr-bits(rc:%d)\n", + rc); + goto err_ramdump; + } + mask_scsr_irqs(d); + + } else { + d->subsys_desc.err_fatal_handler = + subsys_err_fatal_intr_handler; + d->subsys_desc.wdog_bite_handler = subsys_wdog_bite_irq_handler; + d->subsys_desc.stop_ack_handler = subsys_stop_ack_intr_handler; + d->subsys_desc.shutdown_ack_handler = + subsys_shutdown_ack_intr_handler; + d->subsys_desc.ramdump_disable_handler = + subsys_ramdump_disable_intr_handler; + } + d->desc.signal_aop = of_property_read_bool(pdev->dev.of_node, + "qcom,signal-aop"); + if (d->desc.signal_aop) { + d->desc.cl.dev = &pdev->dev; + d->desc.cl.tx_block = true; + d->desc.cl.tx_tout = 1000; + d->desc.cl.knows_txdone = false; + d->desc.mbox = mbox_request_channel(&d->desc.cl, 0); + if (IS_ERR(d->desc.mbox)) { + rc = PTR_ERR(d->desc.mbox); + dev_err(&pdev->dev, "Failed to get mailbox channel %pK %d\n", + d->desc.mbox, rc); + goto err_ramdump; + } + } + + d->ramdump_dev = create_ramdump_device(d->subsys_desc.name, + &pdev->dev); + if (!d->ramdump_dev) { + rc = -ENOMEM; + goto err_ramdump; + } + + scnprintf(md_node, sizeof(md_node), "md_%s", d->subsys_desc.name); + + d->minidump_dev = create_ramdump_device(md_node, &pdev->dev); + if (!d->minidump_dev) { + pr_err("%s: Unable to create a %s minidump device.\n", + __func__, d->subsys_desc.name); + rc = -ENOMEM; + goto err_minidump; + } + + d->subsys = subsys_register(&d->subsys_desc); + if (IS_ERR(d->subsys)) { + rc = PTR_ERR(d->subsys); + goto err_subsys; + } + + return 0; +err_subsys: + destroy_ramdump_device(d->minidump_dev); +err_minidump: + destroy_ramdump_device(d->ramdump_dev); +err_ramdump: + pil_desc_release(&d->desc); + platform_set_drvdata(pdev, NULL); +err_deregister_bus: + if (d->bus_client) + icc_put(d->bus_client); + + return rc; +} + +static int pil_tz_scm_pas_probe(struct platform_device *pdev) +{ + int ret = 0; + + scm_perf_client = of_icc_get(&pdev->dev, NULL); + if (IS_ERR(scm_perf_client)) { + ret = PTR_ERR(scm_perf_client); + pr_err("scm-pas: Unable to register bus client: %d\n", ret); + } + + return ret; +} + +static const struct of_device_id pil_tz_match_table[] = { + {.compatible = "qcom,pil-tz-generic", .data = pil_tz_generic_probe}, + {.compatible = "qcom,pil-tz-scm-pas", .data = pil_tz_scm_pas_probe}, + {} +}; + +static int pil_tz_driver_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + int (*pil_tz_probe)(struct platform_device *pdev); + + match = of_match_node(pil_tz_match_table, pdev->dev.of_node); + if (!match) + return -ENODEV; + + pil_tz_probe = match->data; + return pil_tz_probe(pdev); +} + +static int pil_tz_driver_exit(struct platform_device *pdev) +{ + struct pil_tz_data *d = platform_get_drvdata(pdev); + const struct of_device_id *match; + + match = of_match_node(pil_tz_match_table, pdev->dev.of_node); + if (!match) + return -ENODEV; + + if (match->data == pil_tz_scm_pas_probe) { + icc_put(scm_perf_client); + } else { + subsys_unregister(d->subsys); + destroy_ramdump_device(d->ramdump_dev); + destroy_ramdump_device(d->minidump_dev); + pil_desc_release(&d->desc); + if (d->bus_client) + icc_put(d->bus_client); + } + + return 0; +} + +static struct platform_driver pil_tz_driver = { + .probe = pil_tz_driver_probe, + .remove = pil_tz_driver_exit, + .driver = { + .name = "subsys-pil-tz", + .of_match_table = pil_tz_match_table, + }, +}; + +static int __init pil_tz_init(void) +{ + return platform_driver_register(&pil_tz_driver); +} +module_init(pil_tz_init); + +static void __exit pil_tz_exit(void) +{ + platform_driver_unregister(&pil_tz_driver); +} +module_exit(pil_tz_exit); + +MODULE_DESCRIPTION("Support for booting subsystems"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/subsystem_notif.c b/drivers/soc/qcom/subsystem_notif.c new file mode 100644 index 000000000000..8a44a64e4a36 --- /dev/null +++ b/drivers/soc/qcom/subsystem_notif.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011, 2013, 2016-2019, The Linux Foundation. All rights reserved. + */ +/* + * Subsystem Notifier -- Provides notifications + * of subsys events. + * + * Use subsys_notif_register_notifier to register for notifications + * and subsys_notif_queue_notification to send notifications. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * The callbacks that are registered in this data structure as early + * notification callbacks will be called as soon as the SSR framework is + * informed that the subsystem has crashed. This means that these functions will + * be invoked as part of an IRQ handler, and thus, will be called in an atomic + * context. Therefore, functions that are registered as early notification + * callback must obey to the same constraints as interrupt handlers + * (i.e. these functions must not sleep or block, etc). + */ +struct subsys_early_notif_info { + spinlock_t cb_lock; + void (*early_notif_cb[NUM_EARLY_NOTIFS])(void *); + void *data[NUM_EARLY_NOTIFS]; +}; + +struct subsys_notif_info { + char name[50]; + struct srcu_notifier_head subsys_notif_rcvr_list; + struct subsys_early_notif_info early_notif_info; + struct list_head list; +}; + +static LIST_HEAD(subsystem_list); +static DEFINE_MUTEX(notif_lock); +static DEFINE_MUTEX(notif_add_lock); + +#if defined(SUBSYS_RESTART_DEBUG) +static void subsys_notif_reg_test_notifier(const char *); +#endif + +static struct subsys_notif_info *_notif_find_subsys(const char *subsys_name) +{ + struct subsys_notif_info *subsys; + + mutex_lock(¬if_lock); + list_for_each_entry(subsys, &subsystem_list, list) + if (!strcmp(subsys->name, subsys_name)) { + mutex_unlock(¬if_lock); + return subsys; + } + mutex_unlock(¬if_lock); + + return NULL; +} + +void *subsys_notif_register_notifier( + const char *subsys_name, struct notifier_block *nb) +{ + int ret; + struct subsys_notif_info *subsys = _notif_find_subsys(subsys_name); + + if (!subsys) { + + /* Possible first time reference to this subsystem. Add it. */ + subsys = (struct subsys_notif_info *) + subsys_notif_add_subsys(subsys_name); + + if (!subsys) + return ERR_PTR(-EINVAL); + } + + ret = srcu_notifier_chain_register( + &subsys->subsys_notif_rcvr_list, nb); + + if (ret < 0) + return ERR_PTR(ret); + + return subsys; +} +EXPORT_SYMBOL(subsys_notif_register_notifier); + +int subsys_notif_unregister_notifier(void *subsys_handle, + struct notifier_block *nb) +{ + int ret; + struct subsys_notif_info *subsys = + (struct subsys_notif_info *)subsys_handle; + + if (!subsys) + return -EINVAL; + + ret = srcu_notifier_chain_unregister( + &subsys->subsys_notif_rcvr_list, nb); + + return ret; +} +EXPORT_SYMBOL(subsys_notif_unregister_notifier); + +void subsys_send_early_notifications(void *early_notif_handle) +{ + struct subsys_early_notif_info *early_info = early_notif_handle; + unsigned long flags; + unsigned int i; + void (*notif_cb)(void *data); + + if (!early_notif_handle) + return; + + spin_lock_irqsave(&early_info->cb_lock, flags); + for (i = 0; i < NUM_EARLY_NOTIFS; i++) { + notif_cb = early_info->early_notif_cb[i]; + if (notif_cb) + notif_cb(early_info->data[i]); + } + spin_unlock_irqrestore(&early_info->cb_lock, flags); +} +EXPORT_SYMBOL(subsys_send_early_notifications); + +static bool valid_early_notif(enum early_subsys_notif_type notif_type) +{ + return notif_type >= 0 && notif_type < NUM_EARLY_NOTIFS; +} + +/** + * The early_notif_cb parameter must point to a function that conforms to the + * same constraints placed upon interrupt handlers, as the function will be + * called in an atomic context (i.e. these functions must not sleep or block). + */ +int subsys_register_early_notifier(const char *subsys_name, + enum early_subsys_notif_type notif_type, + void (*early_notif_cb)(void *), void *data) +{ + struct subsys_notif_info *subsys; + struct subsys_early_notif_info *early_notif_info; + unsigned long flags; + int rc = 0; + + if (!subsys_name || !early_notif_cb || !valid_early_notif(notif_type)) + return -EINVAL; + + subsys = _notif_find_subsys(subsys_name); + if (!subsys) + return -EINVAL; + + early_notif_info = &subsys->early_notif_info; + spin_lock_irqsave(&early_notif_info->cb_lock, flags); + if (early_notif_info->early_notif_cb[notif_type]) { + rc = -EEXIST; + goto out; + } + early_notif_info->early_notif_cb[notif_type] = early_notif_cb; + early_notif_info->data[notif_type] = data; +out: + spin_unlock_irqrestore(&early_notif_info->cb_lock, flags); + return rc; +} +EXPORT_SYMBOL(subsys_register_early_notifier); + +int subsys_unregister_early_notifier(const char *subsys_name, enum + early_subsys_notif_type notif_type) +{ + struct subsys_notif_info *subsys; + struct subsys_early_notif_info *early_notif_info; + unsigned long flags; + + if (!subsys_name || !valid_early_notif(notif_type)) + return -EINVAL; + + subsys = _notif_find_subsys(subsys_name); + if (!subsys) + return -EINVAL; + + early_notif_info = &subsys->early_notif_info; + spin_lock_irqsave(&early_notif_info->cb_lock, flags); + early_notif_info->early_notif_cb[notif_type] = NULL; + early_notif_info->data[notif_type] = NULL; + spin_unlock_irqrestore(&early_notif_info->cb_lock, flags); + return 0; +} +EXPORT_SYMBOL(subsys_unregister_early_notifier); + +void *subsys_get_early_notif_info(const char *subsys_name) +{ + struct subsys_notif_info *subsys; + + if (!subsys_name) + return ERR_PTR(-EINVAL); + + subsys = _notif_find_subsys(subsys_name); + + if (!subsys) + return ERR_PTR(-EINVAL); + + return &subsys->early_notif_info; +} +EXPORT_SYMBOL(subsys_get_early_notif_info); + +void *subsys_notif_add_subsys(const char *subsys_name) +{ + struct subsys_notif_info *subsys = NULL; + + if (!subsys_name) + goto done; + + mutex_lock(¬if_add_lock); + + subsys = _notif_find_subsys(subsys_name); + + if (subsys) { + mutex_unlock(¬if_add_lock); + goto done; + } + + subsys = kmalloc(sizeof(struct subsys_notif_info), GFP_KERNEL); + + if (!subsys) { + mutex_unlock(¬if_add_lock); + return ERR_PTR(-EINVAL); + } + + strlcpy(subsys->name, subsys_name, ARRAY_SIZE(subsys->name)); + + srcu_init_notifier_head(&subsys->subsys_notif_rcvr_list); + + memset(&subsys->early_notif_info, 0, sizeof(struct + subsys_early_notif_info)); + spin_lock_init(&subsys->early_notif_info.cb_lock); + INIT_LIST_HEAD(&subsys->list); + + mutex_lock(¬if_lock); + list_add_tail(&subsys->list, &subsystem_list); + mutex_unlock(¬if_lock); + + #if defined(SUBSYS_RESTART_DEBUG) + subsys_notif_reg_test_notifier(subsys->name); + #endif + + mutex_unlock(¬if_add_lock); + +done: + return subsys; +} +EXPORT_SYMBOL(subsys_notif_add_subsys); + +int subsys_notif_queue_notification(void *subsys_handle, + enum subsys_notif_type notif_type, + void *data) +{ + struct subsys_notif_info *subsys = subsys_handle; + + if (!subsys) + return -EINVAL; + + if (notif_type < 0 || notif_type >= SUBSYS_NOTIF_TYPE_COUNT) + return -EINVAL; + + return srcu_notifier_call_chain(&subsys->subsys_notif_rcvr_list, + notif_type, data); +} +EXPORT_SYMBOL(subsys_notif_queue_notification); + +#if defined(SUBSYS_RESTART_DEBUG) +static const char *notif_to_string(enum subsys_notif_type notif_type) +{ + switch (notif_type) { + + case SUBSYS_BEFORE_SHUTDOWN: + return __stringify(SUBSYS_BEFORE_SHUTDOWN); + + case SUBSYS_AFTER_SHUTDOWN: + return __stringify(SUBSYS_AFTER_SHUTDOWN); + + case SUBSYS_BEFORE_POWERUP: + return __stringify(SUBSYS_BEFORE_POWERUP); + + case SUBSYS_AFTER_POWERUP: + return __stringify(SUBSYS_AFTER_POWERUP); + + default: + return "unknown"; + } +} + +static int subsys_notifier_test_call(struct notifier_block *this, + unsigned long code, + void *data) +{ + switch (code) { + + default: + pr_warn("%s: Notification %s from subsystem %pK\n", + __func__, notif_to_string(code), data); + break; + + } + + return NOTIFY_DONE; +} + +static struct notifier_block nb = { + .notifier_call = subsys_notifier_test_call, +}; + +static void subsys_notif_reg_test_notifier(const char *subsys_name) +{ + void *handle = subsys_notif_register_notifier(subsys_name, &nb); + + pr_warn("%s: Registered test notifier, handle=%pK\n", + __func__, handle); +} +#endif + +MODULE_DESCRIPTION("Subsystem Restart Notifier"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/subsystem_restart.c b/drivers/soc/qcom/subsystem_restart.c new file mode 100644 index 000000000000..961501c1d8f9 --- /dev/null +++ b/drivers/soc/qcom/subsystem_restart.c @@ -0,0 +1,1957 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2019, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "subsys-restart: %s(): " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CREATE_TRACE_POINTS +#include + +#include "peripheral-loader.h" + +#define DISABLE_SSR 0x9889deed +/* If set to 0x9889deed, call to subsystem_restart_dev() returns immediately */ +static uint disable_restart_work; +module_param(disable_restart_work, uint, 0644); + +static int enable_debug; +module_param(enable_debug, int, 0644); + +/* The maximum shutdown timeout is the product of MAX_LOOPS and DELAY_MS. */ +#define SHUTDOWN_ACK_MAX_LOOPS 100 +#define SHUTDOWN_ACK_DELAY_MS 100 + +#ifdef CONFIG_SETUP_SSR_NOTIF_TIMEOUTS +/* Timeout used for detection of notification hangs. In seconds.*/ +#define SYSMON_COMM_TIMEOUT CONFIG_SSR_SYSMON_NOTIF_TIMEOUT +#define SUBSYS_NOTIF_TIMEOUT CONFIG_SSR_SUBSYS_NOTIF_TIMEOUT + +#define setup_timeout(dest_ss, source_ss, comm_type) \ + _setup_timeout(dest_ss, source_ss, comm_type) +#define cancel_timeout(subsys) del_timer_sync(&subsys->timeout_data.timer) +#define init_subsys_timer(subsys) _init_subsys_timer(subsys) + +/* Timeout values */ +static unsigned long timeout_vals[NUM_SSR_COMMS] = { + [SUBSYS_TO_SUBSYS_SYSMON] = SYSMON_COMM_TIMEOUT, + [SUBSYS_TO_HLOS] = SUBSYS_NOTIF_TIMEOUT, + [HLOS_TO_SUBSYS_SYSMON_SHUTDOWN] = SYSMON_COMM_TIMEOUT, +}; + +#ifdef CONFIG_PANIC_ON_SSR_NOTIF_TIMEOUT +#define SSR_NOTIF_TIMEOUT_WARN(fmt...) panic(fmt) +#else /* CONFIG_PANIC_ON_SSR_NOTIF_TIMEOUT */ +#define SSR_NOTIF_TIMEOUT_WARN(fmt...) WARN(1, fmt) +#endif /* CONFIG_PANIC_ON_SSR_NOTIF_TIMEOUT */ + +#else /* CONFIG_SETUP_SSR_NOTIF_TIMEOUTS */ +#define setup_timeout(dest_ss, source_ss, sysmon_comm) +#define cancel_timeout(subsys) +#define init_subsys_timer(subsys) +#endif /* CONFIG_SETUP_SSR_NOTIF_TIMEOUTS */ + +/** + * enum p_subsys_state - state of a subsystem (private) + * @SUBSYS_NORMAL: subsystem is operating normally + * @SUBSYS_CRASHED: subsystem has crashed and hasn't been shutdown + * @SUBSYS_RESTARTING: subsystem has been shutdown and is now restarting + * + * The 'private' side of the subsytem state used to determine where in the + * restart process the subsystem is. + */ +enum p_subsys_state { + SUBSYS_NORMAL, + SUBSYS_CRASHED, + SUBSYS_RESTARTING, +}; + +/** + * enum subsys_state - state of a subsystem (public) + * @SUBSYS_OFFLINING: subsystem is offlining + * @SUBSYS_OFFLINE: subsystem is offline + * @SUBSYS_ONLINE: subsystem is online + * + * The 'public' side of the subsytem state, exposed to userspace. + */ +enum subsys_state { + SUBSYS_OFFLINING, + SUBSYS_OFFLINE, + SUBSYS_ONLINE, +}; + +static const char * const subsys_states[] = { + [SUBSYS_OFFLINING] = "OFFLINING", + [SUBSYS_OFFLINE] = "OFFLINE", + [SUBSYS_ONLINE] = "ONLINE", +}; + +static const char * const restart_levels[] = { + [RESET_SOC] = "SYSTEM", + [RESET_SUBSYS_COUPLED] = "RELATED", +}; + +/** + * struct subsys_tracking - track state of a subsystem or restart order + * @p_state: private state of subsystem/order + * @state: public state of subsystem/order + * @s_lock: protects p_state + * @lock: protects subsystem/order callbacks and state + * + * Tracks the state of a subsystem or a set of subsystems (restart order). + * Doing this avoids the need to grab each subsystem's lock and update + * each subsystems state when restarting an order. + */ +struct subsys_tracking { + enum p_subsys_state p_state; + spinlock_t s_lock; + enum subsys_state state; + struct mutex lock; +}; + +/** + * struct subsys_soc_restart_order - subsystem restart order + * @subsystem_list: names of subsystems in this restart order + * @count: number of subsystems in order + * @track: state tracking and locking + * @subsys_ptrs: pointers to subsystems in this restart order + */ +struct subsys_soc_restart_order { + struct device_node **device_ptrs; + int count; + + struct subsys_tracking track; + struct subsys_device **subsys_ptrs; + struct list_head list; +}; + +struct restart_log { + struct timespec64 time; + struct subsys_device *dev; + struct list_head list; +}; + +/** + * struct subsys_device - subsystem device + * @desc: subsystem descriptor + * @work: context for subsystem_restart_wq_func() for this device + * @ssr_wlock: prevents suspend during subsystem_restart() + * @wlname: name of wakeup source + * @device_restart_work: work struct for device restart + * @track: state tracking and locking + * @notify: subsys notify handle + * @dev: device + * @owner: module that provides @desc + * @count: reference count of subsystem_get()/subsystem_put() + * @id: ida + * @restart_level: restart level (0 - panic, 1 - related, 2 - independent, etc.) + * @restart_order: order of other devices this devices restarts with + * @crash_count: number of times the device has crashed + * @do_ramdump_on_put: ramdump on subsystem_put() if true + * @err_ready: completion variable to record error ready from subsystem + * @crashed: indicates if subsystem has crashed + * @notif_state: current state of subsystem in terms of subsys notifications + */ +struct subsys_device { + struct subsys_desc *desc; + struct work_struct work; + struct wakeup_source ssr_wlock; + char wlname[64]; + struct work_struct device_restart_work; + struct subsys_tracking track; + + void *notify; + void *early_notify; + struct device dev; + struct module *owner; + int count; + int id; + int restart_level; + int crash_count; + struct subsys_soc_restart_order *restart_order; + bool do_ramdump_on_put; + struct cdev char_dev; + dev_t dev_no; + struct completion err_ready; + enum crash_status crashed; + int notif_state; + struct list_head list; +}; + +static struct subsys_device *to_subsys(struct device *d) +{ + return container_of(d, struct subsys_device, dev); +} + +void complete_err_ready(struct subsys_device *subsys) +{ + complete(&subsys->err_ready); +} +EXPORT_SYMBOL(complete_err_ready); + +static struct subsys_tracking *subsys_get_track(struct subsys_device *subsys) +{ + struct subsys_soc_restart_order *order = subsys->restart_order; + + if (order) + return &order->track; + else + return &subsys->track; +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", to_subsys(dev)->desc->name); +} +static DEVICE_ATTR_RO(name); + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + enum subsys_state state = to_subsys(dev)->track.state; + + return snprintf(buf, PAGE_SIZE, "%s\n", subsys_states[state]); +} +static DEVICE_ATTR_RO(state); + +static ssize_t crash_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", to_subsys(dev)->crash_count); +} +static DEVICE_ATTR_RO(crash_count); + +static ssize_t +restart_level_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + int level = to_subsys(dev)->restart_level; + + return snprintf(buf, PAGE_SIZE, "%s\n", restart_levels[level]); +} + +static ssize_t restart_level_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct subsys_device *subsys = to_subsys(dev); + const char *p; + int i, orig_count = count; + + p = memchr(buf, '\n', count); + if (p) + count = p - buf; + + for (i = 0; i < ARRAY_SIZE(restart_levels); i++) + if (!strncasecmp(buf, restart_levels[i], count)) { + subsys->restart_level = i; + return orig_count; + } + return -EPERM; +} +static DEVICE_ATTR_RW(restart_level); + +static ssize_t firmware_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", to_subsys(dev)->desc->fw_name); +} + +static ssize_t firmware_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct subsys_device *subsys = to_subsys(dev); + struct subsys_tracking *track = subsys_get_track(subsys); + const char *p; + int orig_count = count; + + p = memchr(buf, '\n', count); + if (p) + count = p - buf; + + pr_info("Changing subsys fw_name to %s\n", buf); + mutex_lock(&track->lock); + strlcpy(subsys->desc->fw_name, buf, + min(count + 1, sizeof(subsys->desc->fw_name))); + mutex_unlock(&track->lock); + return orig_count; +} +static DEVICE_ATTR_RW(firmware_name); + +static ssize_t system_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct subsys_device *subsys = to_subsys(dev); + char p[6] = "set"; + + if (!subsys->desc->system_debug) + strlcpy(p, "reset", sizeof(p)); + + return snprintf(buf, PAGE_SIZE, "%s\n", p); +} + +static ssize_t system_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct subsys_device *subsys = to_subsys(dev); + const char *p; + int orig_count = count; + + p = memchr(buf, '\n', count); + if (p) + count = p - buf; + + if (!strncasecmp(buf, "set", count)) + subsys->desc->system_debug = true; + else if (!strncasecmp(buf, "reset", count)) + subsys->desc->system_debug = false; + else + return -EPERM; + return orig_count; +} +static DEVICE_ATTR_RW(system_debug); + +static void subsys_set_state(struct subsys_device *subsys, + enum subsys_state state) +{ + unsigned long flags; + + spin_lock_irqsave(&subsys->track.s_lock, flags); + if (subsys->track.state != state) { + subsys->track.state = state; + spin_unlock_irqrestore(&subsys->track.s_lock, flags); + sysfs_notify(&subsys->dev.kobj, NULL, "state"); + return; + } + spin_unlock_irqrestore(&subsys->track.s_lock, flags); +} + +static struct attribute *subsys_attrs[] = { + &dev_attr_name.attr, + &dev_attr_state.attr, + &dev_attr_crash_count.attr, + &dev_attr_restart_level.attr, + &dev_attr_firmware_name.attr, + &dev_attr_system_debug.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(subsys); + +struct bus_type subsys_bus_type = { + .name = "msm_subsys", + .dev_groups = subsys_groups, +}; +EXPORT_SYMBOL(subsys_bus_type); + +static DEFINE_IDA(subsys_ida); + +static int enable_ramdumps; +module_param(enable_ramdumps, int, 0644); + +static int enable_mini_ramdumps; +module_param(enable_mini_ramdumps, int, 0644); + +static struct workqueue_struct *ssr_wq; +static struct class *char_class; + +static LIST_HEAD(restart_log_list); +static LIST_HEAD(subsys_list); +static LIST_HEAD(ssr_order_list); +static DEFINE_MUTEX(soc_order_reg_lock); +static DEFINE_MUTEX(restart_log_mutex); +static DEFINE_MUTEX(subsys_list_lock); +static DEFINE_MUTEX(char_device_lock); +static DEFINE_MUTEX(ssr_order_mutex); + +static struct subsys_soc_restart_order * +update_restart_order(struct subsys_device *dev) +{ + int i; + struct subsys_soc_restart_order *order; + struct device_node *device = dev->desc->dev->of_node; + + mutex_lock(&soc_order_reg_lock); + list_for_each_entry(order, &ssr_order_list, list) { + for (i = 0; i < order->count; i++) { + if (order->device_ptrs[i] == device) { + order->subsys_ptrs[i] = dev; + goto found; + } + } + } + order = NULL; +found: + mutex_unlock(&soc_order_reg_lock); + + return order; +} + +static int max_restarts; +module_param(max_restarts, int, 0644); + +static long max_history_time = 3600; +module_param(max_history_time, long, 0644); + +static void do_epoch_check(struct subsys_device *dev) +{ + int n = 0; + struct timespec64 *time_first = NULL, *curr_time; + struct restart_log *r_log, *temp; + static int max_restarts_check; + static long max_history_time_check; + + mutex_lock(&restart_log_mutex); + + max_restarts_check = max_restarts; + max_history_time_check = max_history_time; + + /* Check if epoch checking is enabled */ + if (!max_restarts_check) + goto out; + + r_log = kmalloc(sizeof(struct restart_log), GFP_KERNEL); + if (!r_log) + goto out; + r_log->dev = dev; + ktime_get_real_ts64(&r_log->time); + curr_time = &r_log->time; + INIT_LIST_HEAD(&r_log->list); + + list_add_tail(&r_log->list, &restart_log_list); + + list_for_each_entry_safe(r_log, temp, &restart_log_list, list) { + + if ((curr_time->tv_sec - r_log->time.tv_sec) > + max_history_time_check) { + + pr_debug("Deleted node with restart_time = %ld\n", + r_log->time.tv_sec); + list_del(&r_log->list); + kfree(r_log); + continue; + } + if (!n) { + time_first = &r_log->time; + pr_debug("Time_first: %ld\n", time_first->tv_sec); + } + n++; + pr_debug("Restart_time: %ld\n", r_log->time.tv_sec); + } + + if (time_first && n >= max_restarts_check) { + if ((curr_time->tv_sec - time_first->tv_sec) < + max_history_time_check) + panic("Subsystems have crashed %d times in less than %ld seconds!", + max_restarts_check, max_history_time_check); + } + +out: + mutex_unlock(&restart_log_mutex); +} + +static int is_ramdump_enabled(struct subsys_device *dev) +{ + if (dev->desc->ramdump_disable_irq) + return !dev->desc->ramdump_disable; + + return enable_ramdumps; +} + +#ifdef CONFIG_SETUP_SSR_NOTIF_TIMEOUTS +static void notif_timeout_handler(struct timer_list *t) +{ + char *sysmon_msg = "Sysmon communication from %s to %s taking too long"; + char *subsys_notif_msg = "Subsys notifier chain for %s taking too long"; + char *sysmon_shutdwn_msg = "sysmon_send_shutdown to %s taking too long"; + char *unknown_err_msg = "Unknown communication occurred"; + struct subsys_notif_timeout *timeout_data = + from_timer(timeout_data, t, timer); + enum ssr_comm comm_type = timeout_data->comm_type; + + switch (comm_type) { + case SUBSYS_TO_SUBSYS_SYSMON: + SSR_NOTIF_TIMEOUT_WARN(sysmon_msg, timeout_data->source_name, + timeout_data->dest_name); + break; + case SUBSYS_TO_HLOS: + SSR_NOTIF_TIMEOUT_WARN(subsys_notif_msg, + timeout_data->source_name); + break; + case HLOS_TO_SUBSYS_SYSMON_SHUTDOWN: + SSR_NOTIF_TIMEOUT_WARN(sysmon_shutdwn_msg, + timeout_data->dest_name); + break; + default: + SSR_NOTIF_TIMEOUT_WARN(unknown_err_msg); + } + +} + +static void _setup_timeout(struct subsys_desc *source_ss, + struct subsys_desc *dest_ss, enum ssr_comm comm_type) +{ + struct subsys_notif_timeout *timeout_data; + unsigned long timeout; + + switch (comm_type) { + case SUBSYS_TO_SUBSYS_SYSMON: + timeout_data = &source_ss->timeout_data; + timeout_data->dest_name = dest_ss->name; + timeout_data->source_name = source_ss->name; + break; + case SUBSYS_TO_HLOS: + timeout_data = &source_ss->timeout_data; + timeout_data->dest_name = NULL; + timeout_data->source_name = source_ss->name; + break; + case HLOS_TO_SUBSYS_SYSMON_SHUTDOWN: + timeout_data = &dest_ss->timeout_data; + timeout_data->dest_name = dest_ss->name; + timeout_data->source_name = NULL; + break; + default: + return; + } + + timeout_data->comm_type = comm_type; + timeout = jiffies + msecs_to_jiffies(timeout_vals[comm_type]); + mod_timer(&timeout_data->timer, timeout); +} + +static void _init_subsys_timer(struct subsys_desc *subsys) +{ + timer_setup(&subsys->timeout_data.timer, notif_timeout_handler, 0); +} + +#endif /* CONFIG_SETUP_SSR_NOTIF_TIMEOUTS */ + +static void send_sysmon_notif(struct subsys_device *dev) +{ + struct subsys_device *subsys; + + mutex_lock(&subsys_list_lock); + list_for_each_entry(subsys, &subsys_list, list) + if ((subsys->notif_state > 0) && (subsys != dev)) { + setup_timeout(subsys->desc, dev->desc, + SUBSYS_TO_SUBSYS_SYSMON); + sysmon_send_event(dev->desc, subsys->desc, + subsys->notif_state); + cancel_timeout(subsys->desc); + } + mutex_unlock(&subsys_list_lock); +} + +static int for_each_subsys_device(struct subsys_device **list, + unsigned int count, void *data, + int (*fn)(struct subsys_device *, void *)) +{ + int ret; + + while (count--) { + struct subsys_device *dev = *list++; + + if (!dev) + continue; + ret = fn(dev, data); + if (ret) + return ret; + } + return 0; +} + +static void notify_each_subsys_device(struct subsys_device **list, + unsigned int count, + enum subsys_notif_type notif, void *data) +{ + struct subsys_device *subsys; + + while (count--) { + struct subsys_device *dev = *list++; + struct notif_data notif_data; + struct platform_device *pdev; + + if (!dev) + continue; + + pdev = container_of(dev->desc->dev, struct platform_device, + dev); + dev->notif_state = notif; + + mutex_lock(&subsys_list_lock); + list_for_each_entry(subsys, &subsys_list, list) + if (dev != subsys && + subsys->track.state == SUBSYS_ONLINE) { + setup_timeout(dev->desc, subsys->desc, + SUBSYS_TO_SUBSYS_SYSMON); + sysmon_send_event(subsys->desc, dev->desc, + notif); + cancel_timeout(dev->desc); + } + mutex_unlock(&subsys_list_lock); + + if (notif == SUBSYS_AFTER_POWERUP && + dev->track.state == SUBSYS_ONLINE) + send_sysmon_notif(dev); + + notif_data.crashed = subsys_get_crash_status(dev); + notif_data.enable_ramdump = is_ramdump_enabled(dev); + notif_data.enable_mini_ramdumps = enable_mini_ramdumps; + notif_data.no_auth = dev->desc->no_auth; + notif_data.pdev = pdev; + + trace_pil_notif("before_send_notif", notif, dev->desc->fw_name); + setup_timeout(dev->desc, NULL, SUBSYS_TO_HLOS); + subsys_notif_queue_notification(dev->notify, notif, + ¬if_data); + cancel_timeout(dev->desc); + trace_pil_notif("after_send_notif", notif, dev->desc->fw_name); + } +} + +static void enable_all_irqs(struct subsys_device *dev) +{ + if (dev->desc->err_ready_irq) + enable_irq(dev->desc->err_ready_irq); + if (dev->desc->wdog_bite_irq && dev->desc->wdog_bite_handler) { + enable_irq(dev->desc->wdog_bite_irq); + irq_set_irq_wake(dev->desc->wdog_bite_irq, 1); + } + if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler) + enable_irq(dev->desc->err_fatal_irq); + if (dev->desc->stop_ack_irq && dev->desc->stop_ack_handler) + enable_irq(dev->desc->stop_ack_irq); + if (dev->desc->shutdown_ack_irq && dev->desc->shutdown_ack_handler) + enable_irq(dev->desc->shutdown_ack_irq); + if (dev->desc->ramdump_disable_irq && + dev->desc->ramdump_disable_handler) + enable_irq(dev->desc->ramdump_disable_irq); + if (dev->desc->generic_irq && dev->desc->generic_handler) { + enable_irq(dev->desc->generic_irq); + irq_set_irq_wake(dev->desc->generic_irq, 1); + } +} + +static void disable_all_irqs(struct subsys_device *dev) +{ + if (dev->desc->err_ready_irq) + disable_irq(dev->desc->err_ready_irq); + if (dev->desc->wdog_bite_irq && dev->desc->wdog_bite_handler) { + disable_irq(dev->desc->wdog_bite_irq); + irq_set_irq_wake(dev->desc->wdog_bite_irq, 0); + } + if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler) + disable_irq(dev->desc->err_fatal_irq); + if (dev->desc->stop_ack_irq && dev->desc->stop_ack_handler) + disable_irq(dev->desc->stop_ack_irq); + if (dev->desc->shutdown_ack_irq && dev->desc->shutdown_ack_handler) + disable_irq(dev->desc->shutdown_ack_irq); + if (dev->desc->generic_irq && dev->desc->generic_handler) { + disable_irq(dev->desc->generic_irq); + irq_set_irq_wake(dev->desc->generic_irq, 0); + } +} + +static int wait_for_err_ready(struct subsys_device *subsys) +{ + int ret; + + /* + * If subsys is using generic_irq in which case err_ready_irq will be 0, + * don't return. + */ + if ((subsys->desc->generic_irq <= 0 && !subsys->desc->err_ready_irq) || + enable_debug == 1) + return 0; + + ret = wait_for_completion_timeout(&subsys->err_ready, + msecs_to_jiffies(10000)); + if (!ret) { + pr_err("[%s]: Error ready timed out\n", subsys->desc->name); + return -ETIMEDOUT; + } + + return 0; +} + +static int subsystem_shutdown(struct subsys_device *dev, void *data) +{ + const char *name = dev->desc->name; + int ret; + + pr_info("[%s:%d]: Shutting down %s\n", + current->comm, current->pid, name); + ret = dev->desc->shutdown(dev->desc, true); + if (ret < 0) { + if (!dev->desc->ignore_ssr_failure) { + panic("subsys-restart: [%s:%d]: Failed to shutdown %s!", + current->comm, current->pid, name); + } else { + pr_err("Shutdown failure on %s\n", name); + return ret; + } + } + dev->crash_count++; + subsys_set_state(dev, SUBSYS_OFFLINE); + disable_all_irqs(dev); + + return 0; +} + +static int subsystem_ramdump(struct subsys_device *dev, void *data) +{ + const char *name = dev->desc->name; + + if (dev->desc->ramdump) + if (dev->desc->ramdump(is_ramdump_enabled(dev), dev->desc) < 0) + pr_warn("%s[%s:%d]: Ramdump failed.\n", + name, current->comm, current->pid); + dev->do_ramdump_on_put = false; + return 0; +} + +static int subsystem_free_memory(struct subsys_device *dev, void *data) +{ + if (dev->desc->free_memory) + dev->desc->free_memory(dev->desc); + return 0; +} + +static int subsystem_powerup(struct subsys_device *dev, void *data) +{ + const char *name = dev->desc->name; + int ret; + + pr_info("[%s:%d]: Powering up %s\n", current->comm, current->pid, name); + reinit_completion(&dev->err_ready); + + ret = dev->desc->powerup(dev->desc); + if (ret < 0) { + notify_each_subsys_device(&dev, 1, SUBSYS_POWERUP_FAILURE, + NULL); + if (system_state == SYSTEM_RESTART + || system_state == SYSTEM_POWER_OFF) + WARN(1, "SSR aborted: %s, system reboot/shutdown is under way\n", + name); + else if (!dev->desc->ignore_ssr_failure) + panic("[%s:%d]: Powerup error: %s!", + current->comm, current->pid, name); + else + pr_err("Powerup failure on %s\n", name); + return ret; + } + enable_all_irqs(dev); + + ret = wait_for_err_ready(dev); + if (ret) { + notify_each_subsys_device(&dev, 1, SUBSYS_POWERUP_FAILURE, + NULL); + if (!dev->desc->ignore_ssr_failure) + panic("[%s:%d]: Timed out waiting for error ready: %s!", + current->comm, current->pid, name); + else + return ret; + } + subsys_set_state(dev, SUBSYS_ONLINE); + subsys_set_crash_status(dev, CRASH_STATUS_NO_CRASH); + + return 0; +} + +static int __find_subsys_device(struct device *dev, const void *data) +{ + struct subsys_device *subsys = to_subsys(dev); + + return !strcmp(subsys->desc->name, data); +} + +static struct subsys_device *find_subsys_device(const char *str) +{ + struct device *dev; + + if (!str) + return NULL; + + dev = bus_find_device(&subsys_bus_type, NULL, (void *)str, + __find_subsys_device); + return dev ? to_subsys(dev) : NULL; +} + +static int subsys_start(struct subsys_device *subsys) +{ + int ret; + + notify_each_subsys_device(&subsys, 1, SUBSYS_BEFORE_POWERUP, + NULL); + + reinit_completion(&subsys->err_ready); + ret = subsys->desc->powerup(subsys->desc); + if (ret) { + notify_each_subsys_device(&subsys, 1, SUBSYS_POWERUP_FAILURE, + NULL); + return ret; + } + enable_all_irqs(subsys); + + if (subsys->desc->is_not_loadable) { + subsys_set_state(subsys, SUBSYS_ONLINE); + return 0; + } + + ret = wait_for_err_ready(subsys); + if (ret) { + /* pil-boot succeeded but we need to shutdown + * the device because error ready timed out. + */ + notify_each_subsys_device(&subsys, 1, SUBSYS_POWERUP_FAILURE, + NULL); + subsys->desc->shutdown(subsys->desc, false); + disable_all_irqs(subsys); + return ret; + } + subsys_set_state(subsys, SUBSYS_ONLINE); + + notify_each_subsys_device(&subsys, 1, SUBSYS_AFTER_POWERUP, + NULL); + return ret; +} + +static void subsys_stop(struct subsys_device *subsys) +{ + const char *name = subsys->desc->name; + + notify_each_subsys_device(&subsys, 1, SUBSYS_BEFORE_SHUTDOWN, NULL); + if (!of_property_read_bool(subsys->desc->dev->of_node, + "qcom,pil-force-shutdown")) { + subsys_set_state(subsys, SUBSYS_OFFLINING); + setup_timeout(NULL, subsys->desc, + HLOS_TO_SUBSYS_SYSMON_SHUTDOWN); + subsys->desc->sysmon_shutdown_ret = + sysmon_send_shutdown(subsys->desc); + cancel_timeout(subsys->desc); + if (subsys->desc->sysmon_shutdown_ret) + pr_debug("Graceful shutdown failed for %s\n", name); + } + + subsys->desc->shutdown(subsys->desc, false); + subsys_set_state(subsys, SUBSYS_OFFLINE); + disable_all_irqs(subsys); + notify_each_subsys_device(&subsys, 1, SUBSYS_AFTER_SHUTDOWN, NULL); +} + +int subsystem_set_fwname(const char *name, const char *fw_name) +{ + struct subsys_device *subsys; + + if (!name) + return -EINVAL; + + if (!fw_name) + return -EINVAL; + + subsys = find_subsys_device(name); + if (!subsys) + return -EINVAL; + + pr_debug("Changing subsys [%s] fw_name to [%s]\n", name, fw_name); + strlcpy(subsys->desc->fw_name, fw_name, + sizeof(subsys->desc->fw_name)); + + return 0; +} +EXPORT_SYMBOL(subsystem_set_fwname); + +static void *__subsystem_get(const char *name, const char *fw_name) +{ + struct subsys_device *subsys; + struct subsys_device *subsys_d; + int ret; + void *retval; + struct subsys_tracking *track; + + if (!name) + return NULL; + + subsys = retval = find_subsys_device(name); + if (!subsys) + return ERR_PTR(-ENODEV); + if (!try_module_get(subsys->owner)) { + retval = ERR_PTR(-ENODEV); + goto err_module; + } + + subsys_d = subsystem_get(subsys->desc->pon_depends_on); + if (IS_ERR(subsys_d)) { + retval = subsys_d; + goto err_depends; + } + + track = subsys_get_track(subsys); + mutex_lock(&track->lock); + if (!subsys->count) { + if (fw_name) { + pr_info("Changing subsys fw_name to %s\n", fw_name); + strlcpy(subsys->desc->fw_name, fw_name, + sizeof(subsys->desc->fw_name)); + } + ret = subsys_start(subsys); + if (ret) { + retval = ERR_PTR(ret); + goto err_start; + } + } + subsys->count++; + mutex_unlock(&track->lock); + return retval; +err_start: + mutex_unlock(&track->lock); + subsystem_put(subsys_d); +err_depends: + module_put(subsys->owner); +err_module: + put_device(&subsys->dev); + return retval; +} + +/** + * subsytem_get() - Boot a subsystem + * @name: pointer to a string containing the name of the subsystem to boot + * + * This function returns a pointer if it succeeds. If an error occurs an + * ERR_PTR is returned. + * + * If this feature is disable, the value %NULL will be returned. + */ +void *subsystem_get(const char *name) +{ + return __subsystem_get(name, NULL); +} +EXPORT_SYMBOL(subsystem_get); + +/** + * subsystem_get_with_fwname() - Boot a subsystem using the firmware name passed + * @name: pointer to a string containing the name of the subsystem to boot + * @fw_name: pointer to a string containing the subsystem firmware image name + * + * This function returns a pointer if it succeeds. If an error occurs an + * ERR_PTR is returned. + * + * If this feature is disable, the value %NULL will be returned. + */ +void *subsystem_get_with_fwname(const char *name, const char *fw_name) +{ + return __subsystem_get(name, fw_name); +} +EXPORT_SYMBOL(subsystem_get_with_fwname); + +/** + * subsystem_put() - Shutdown a subsystem + * @peripheral_handle: pointer from a previous call to subsystem_get() + * + * This doesn't imply that a subsystem is shutdown until all callers of + * subsystem_get() have called subsystem_put(). + */ +void subsystem_put(void *subsystem) +{ + struct subsys_device *subsys_d, *subsys = subsystem; + struct subsys_tracking *track; + + if (IS_ERR_OR_NULL(subsys)) + return; + + subsys_d = find_subsys_device(subsys->desc->poff_depends_on); + if (subsys_d) + subsystem_put(subsys_d); + + track = subsys_get_track(subsys); + mutex_lock(&track->lock); + if (WARN(!subsys->count, "%s: %s: Reference count mismatch\n", + subsys->desc->name, __func__)) + goto err_out; + if (!--subsys->count) { + subsys_stop(subsys); + if (subsys->do_ramdump_on_put) + subsystem_ramdump(subsys, NULL); + subsystem_free_memory(subsys, NULL); + } + mutex_unlock(&track->lock); + + module_put(subsys->owner); + put_device(&subsys->dev); + return; +err_out: + mutex_unlock(&track->lock); +} +EXPORT_SYMBOL(subsystem_put); + +static void subsystem_restart_wq_func(struct work_struct *work) +{ + struct subsys_device *dev = container_of(work, + struct subsys_device, work); + struct subsys_device **list; + struct subsys_desc *desc = dev->desc; + struct subsys_soc_restart_order *order = dev->restart_order; + struct subsys_tracking *track; + unsigned int count; + unsigned long flags; + int ret; + + /* + * It's OK to not take the registration lock at this point. + * This is because the subsystem list inside the relevant + * restart order is not being traversed. + */ + if (order) { + list = order->subsys_ptrs; + count = order->count; + track = &order->track; + } else { + list = &dev; + count = 1; + track = &dev->track; + } + + /* + * If a system reboot/shutdown is under way, ignore subsystem errors. + * However, print a message so that we know that a subsystem behaved + * unexpectedly here. + */ + if (system_state == SYSTEM_RESTART + || system_state == SYSTEM_POWER_OFF) { + WARN(1, "SSR aborted: %s, system reboot/shutdown is under way\n", + desc->name); + return; + } + + mutex_lock(&track->lock); + do_epoch_check(dev); + + if (dev->track.state == SUBSYS_OFFLINE) { + mutex_unlock(&track->lock); + WARN(1, "SSR aborted: %s subsystem not online\n", desc->name); + return; + } + + /* + * It's necessary to take the registration lock because the subsystem + * list in the SoC restart order will be traversed and it shouldn't be + * changed until _this_ restart sequence completes. + */ + mutex_lock(&soc_order_reg_lock); + + pr_debug("[%s:%d]: Starting restart sequence for %s\n", + current->comm, current->pid, desc->name); + notify_each_subsys_device(list, count, SUBSYS_BEFORE_SHUTDOWN, NULL); + ret = for_each_subsys_device(list, count, NULL, subsystem_shutdown); + if (ret) + goto err; + notify_each_subsys_device(list, count, SUBSYS_AFTER_SHUTDOWN, NULL); + + notify_each_subsys_device(list, count, SUBSYS_RAMDUMP_NOTIFICATION, + NULL); + + spin_lock_irqsave(&track->s_lock, flags); + track->p_state = SUBSYS_RESTARTING; + spin_unlock_irqrestore(&track->s_lock, flags); + + /* Collect ram dumps for all subsystems in order here */ + for_each_subsys_device(list, count, NULL, subsystem_ramdump); + + for_each_subsys_device(list, count, NULL, subsystem_free_memory); + + notify_each_subsys_device(list, count, SUBSYS_BEFORE_POWERUP, NULL); + ret = for_each_subsys_device(list, count, NULL, subsystem_powerup); + if (ret) + goto err; + notify_each_subsys_device(list, count, SUBSYS_AFTER_POWERUP, NULL); + + pr_info("[%s:%d]: Restart sequence for %s completed.\n", + current->comm, current->pid, desc->name); + +err: + /* Reset subsys count */ + if (ret) + dev->count = 0; + + mutex_unlock(&soc_order_reg_lock); + mutex_unlock(&track->lock); + + spin_lock_irqsave(&track->s_lock, flags); + track->p_state = SUBSYS_NORMAL; + __pm_relax(&dev->ssr_wlock); + spin_unlock_irqrestore(&track->s_lock, flags); +} + +static void __subsystem_restart_dev(struct subsys_device *dev) +{ + struct subsys_desc *desc = dev->desc; + const char *name = dev->desc->name; + struct subsys_tracking *track; + unsigned long flags; + + pr_debug("Restarting %s [level=%s]!\n", desc->name, + restart_levels[dev->restart_level]); + + track = subsys_get_track(dev); + /* + * Allow drivers to call subsystem_restart{_dev}() as many times as + * they want up until the point where the subsystem is shutdown. + */ + spin_lock_irqsave(&track->s_lock, flags); + if (track->p_state != SUBSYS_CRASHED && + dev->track.state == SUBSYS_ONLINE) { + if (track->p_state != SUBSYS_RESTARTING) { + track->p_state = SUBSYS_CRASHED; + __pm_stay_awake(&dev->ssr_wlock); + queue_work(ssr_wq, &dev->work); + } else { + panic("Subsystem %s crashed during SSR!", name); + } + } else + WARN(dev->track.state == SUBSYS_OFFLINE, + "SSR aborted: %s subsystem not online\n", name); + spin_unlock_irqrestore(&track->s_lock, flags); +} + +static void device_restart_work_hdlr(struct work_struct *work) +{ + struct subsys_device *dev = container_of(work, struct subsys_device, + device_restart_work); + + notify_each_subsys_device(&dev, 1, SUBSYS_SOC_RESET, NULL); + /* + * Temporary workaround until ramdump userspace application calls + * sync() and fclose() on attempting the dump. + */ + msleep(100); + panic("subsys-restart: Resetting the SoC - %s crashed.", + dev->desc->name); +} + +int subsystem_restart_dev(struct subsys_device *dev) +{ + const char *name; + + if (!get_device(&dev->dev)) + return -ENODEV; + + if (!try_module_get(dev->owner)) { + put_device(&dev->dev); + return -ENODEV; + } + + name = dev->desc->name; + + subsys_send_early_notifications(dev->early_notify); + + /* + * If a system reboot/shutdown is underway, ignore subsystem errors. + * However, print a message so that we know that a subsystem behaved + * unexpectedly here. + */ + if (system_state == SYSTEM_RESTART + || system_state == SYSTEM_POWER_OFF) { + pr_err("%s crashed during a system poweroff/shutdown.\n", name); + return -EBUSY; + } + + pr_info("Restart sequence requested for %s, restart_level = %s.\n", + name, restart_levels[dev->restart_level]); + + if (disable_restart_work == DISABLE_SSR) { + pr_warn("subsys-restart: Ignoring restart request for %s\n", + name); + return 0; + } + + switch (dev->restart_level) { + + case RESET_SUBSYS_COUPLED: + __subsystem_restart_dev(dev); + break; + case RESET_SOC: + __pm_stay_awake(&dev->ssr_wlock); + schedule_work(&dev->device_restart_work); + return 0; + default: + panic("subsys-restart: Unknown restart level!\n"); + break; + } + module_put(dev->owner); + put_device(&dev->dev); + + return 0; +} +EXPORT_SYMBOL(subsystem_restart_dev); + +int subsystem_restart(const char *name) +{ + int ret; + struct subsys_device *dev = find_subsys_device(name); + + if (!dev) + return -ENODEV; + + ret = subsystem_restart_dev(dev); + put_device(&dev->dev); + return ret; +} +EXPORT_SYMBOL(subsystem_restart); + +int subsystem_crashed(const char *name) +{ + struct subsys_device *dev = find_subsys_device(name); + struct subsys_tracking *track; + + if (!dev) + return -ENODEV; + + if (!get_device(&dev->dev)) + return -ENODEV; + + track = subsys_get_track(dev); + + mutex_lock(&track->lock); + dev->do_ramdump_on_put = true; + /* + * TODO: Make this work with multiple consumers where one is calling + * subsystem_restart() and another is calling this function. To do + * so would require updating private state, etc. + */ + mutex_unlock(&track->lock); + + put_device(&dev->dev); + return 0; +} +EXPORT_SYMBOL(subsystem_crashed); + +void subsys_set_crash_status(struct subsys_device *dev, + enum crash_status crashed) +{ + dev->crashed = crashed; +} +EXPORT_SYMBOL(subsys_set_crash_status); + +enum crash_status subsys_get_crash_status(struct subsys_device *dev) +{ + return dev->crashed; +} +EXPORT_SYMBOL(subsys_get_crash_status); + +static struct subsys_device *desc_to_subsys(struct device *d) +{ + struct subsys_device *device, *subsys_dev = NULL; + + mutex_lock(&subsys_list_lock); + list_for_each_entry(device, &subsys_list, list) + if (device->desc->dev == d) + subsys_dev = device; + mutex_unlock(&subsys_list_lock); + return subsys_dev; +} + +void notify_proxy_vote(struct device *device) +{ + struct subsys_device *dev = desc_to_subsys(device); + + if (dev) + notify_each_subsys_device(&dev, 1, SUBSYS_PROXY_VOTE, NULL); +} +EXPORT_SYMBOL(notify_proxy_vote); + +void notify_proxy_unvote(struct device *device) +{ + struct subsys_device *dev = desc_to_subsys(device); + + if (dev) + notify_each_subsys_device(&dev, 1, SUBSYS_PROXY_UNVOTE, NULL); +} +EXPORT_SYMBOL(notify_proxy_unvote); + +void notify_before_auth_and_reset(struct device *device) +{ + struct subsys_device *dev = desc_to_subsys(device); + + if (dev) + notify_each_subsys_device(&dev, 1, + SUBSYS_BEFORE_AUTH_AND_RESET, NULL); +} +EXPORT_SYMBOL(notify_before_auth_and_reset); + +static int subsys_device_open(struct inode *inode, struct file *file) +{ + struct subsys_device *device, *subsys_dev = NULL; + void *retval; + + mutex_lock(&subsys_list_lock); + list_for_each_entry(device, &subsys_list, list) + if (MINOR(device->dev_no) == iminor(inode)) + subsys_dev = device; + mutex_unlock(&subsys_list_lock); + + if (!subsys_dev) + return -EINVAL; + + retval = subsystem_get_with_fwname(subsys_dev->desc->name, + subsys_dev->desc->fw_name); + if (PTR_ERR_OR_ZERO(retval)) + return PTR_ERR(retval); + + return 0; +} + +static int subsys_device_close(struct inode *inode, struct file *file) +{ + struct subsys_device *device, *subsys_dev = NULL; + + mutex_lock(&subsys_list_lock); + list_for_each_entry(device, &subsys_list, list) + if (MINOR(device->dev_no) == iminor(inode)) + subsys_dev = device; + mutex_unlock(&subsys_list_lock); + + if (!subsys_dev) + return -EINVAL; + + subsystem_put(subsys_dev); + return 0; +} + +static const struct file_operations subsys_device_fops = { + .owner = THIS_MODULE, + .open = subsys_device_open, + .release = subsys_device_close, +}; + +static void subsys_device_release(struct device *dev) +{ + struct subsys_device *subsys = to_subsys(dev); + + wakeup_source_trash(&subsys->ssr_wlock); + mutex_destroy(&subsys->track.lock); + ida_simple_remove(&subsys_ida, subsys->id); + kfree(subsys); +} +static irqreturn_t subsys_err_ready_intr_handler(int irq, void *subsys) +{ + struct subsys_device *subsys_dev = subsys; + + dev_info(subsys_dev->desc->dev, + "Subsystem error monitoring/handling services are up\n"); + + if (subsys_dev->desc->is_not_loadable) + return IRQ_HANDLED; + + complete(&subsys_dev->err_ready); + return IRQ_HANDLED; +} + +static int subsys_char_device_add(struct subsys_device *subsys_dev) +{ + int ret = 0; + static int major, minor; + dev_t dev_no; + + mutex_lock(&char_device_lock); + if (!major) { + ret = alloc_chrdev_region(&dev_no, 0, 4, "subsys"); + if (ret < 0) { + pr_err("Failed to alloc subsys_dev region, err %d\n", + ret); + goto fail; + } + major = MAJOR(dev_no); + minor = MINOR(dev_no); + } else + dev_no = MKDEV(major, minor); + + if (!device_create(char_class, subsys_dev->desc->dev, dev_no, + NULL, "subsys_%s", subsys_dev->desc->name)) { + pr_err("Failed to create subsys_%s device\n", + subsys_dev->desc->name); + goto fail_unregister_cdev_region; + } + + cdev_init(&subsys_dev->char_dev, &subsys_device_fops); + subsys_dev->char_dev.owner = THIS_MODULE; + ret = cdev_add(&subsys_dev->char_dev, dev_no, 1); + if (ret < 0) + goto fail_destroy_device; + + subsys_dev->dev_no = dev_no; + minor++; + mutex_unlock(&char_device_lock); + + return 0; + +fail_destroy_device: + device_destroy(char_class, dev_no); +fail_unregister_cdev_region: + unregister_chrdev_region(dev_no, 1); +fail: + mutex_unlock(&char_device_lock); + return ret; +} + +static void subsys_char_device_remove(struct subsys_device *subsys_dev) +{ + cdev_del(&subsys_dev->char_dev); + device_destroy(char_class, subsys_dev->dev_no); + unregister_chrdev_region(subsys_dev->dev_no, 1); +} + +static void subsys_remove_restart_order(struct device_node *device) +{ + struct subsys_soc_restart_order *order; + int i; + + mutex_lock(&ssr_order_mutex); + list_for_each_entry(order, &ssr_order_list, list) + for (i = 0; i < order->count; i++) + if (order->device_ptrs[i] == device) + order->subsys_ptrs[i] = NULL; + mutex_unlock(&ssr_order_mutex); +} + +static struct subsys_soc_restart_order *ssr_parse_restart_orders(struct + subsys_desc * desc) +{ + int i, j, count, num = 0; + struct subsys_soc_restart_order *order, *tmp; + struct device *dev = desc->dev; + struct device_node *ssr_node; + uint32_t len; + + if (!of_get_property(dev->of_node, "qcom,restart-group", &len)) + return NULL; + + count = len/sizeof(uint32_t); + + order = devm_kzalloc(dev, sizeof(*order), GFP_KERNEL); + if (!order) + return ERR_PTR(-ENOMEM); + + order->subsys_ptrs = devm_kzalloc(dev, + count * sizeof(struct subsys_device *), + GFP_KERNEL); + if (!order->subsys_ptrs) + return ERR_PTR(-ENOMEM); + + order->device_ptrs = devm_kzalloc(dev, + count * sizeof(struct device_node *), + GFP_KERNEL); + if (!order->device_ptrs) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < count; i++) { + ssr_node = of_parse_phandle(dev->of_node, + "qcom,restart-group", i); + if (!ssr_node) + return ERR_PTR(-ENXIO); + of_node_put(ssr_node); + pr_info("%s device has been added to %s's restart group\n", + ssr_node->name, desc->name); + order->device_ptrs[i] = ssr_node; + } + + /* + * Check for similar restart groups. If found, return + * without adding the new group to the ssr_order_list. + */ + mutex_lock(&ssr_order_mutex); + list_for_each_entry(tmp, &ssr_order_list, list) { + for (i = 0; i < count; i++) { + for (j = 0; j < count; j++) { + if (order->device_ptrs[j] != + tmp->device_ptrs[i]) + continue; + else + num++; + } + } + + if (num == count && tmp->count == count) + goto err; + else if (num) { + tmp = ERR_PTR(-EINVAL); + goto err; + } + } + + order->count = count; + mutex_init(&order->track.lock); + spin_lock_init(&order->track.s_lock); + + INIT_LIST_HEAD(&order->list); + list_add_tail(&order->list, &ssr_order_list); + mutex_unlock(&ssr_order_mutex); + + return order; +err: + mutex_unlock(&ssr_order_mutex); + return tmp; +} + +static int __get_irq(struct subsys_desc *desc, const char *prop, + unsigned int *irq) +{ + int irql = 0; + struct device_node *dnode = desc->dev->of_node; + + if (of_property_match_string(dnode, "interrupt-names", prop) < 0) + return -ENOENT; + + irql = of_irq_get_byname(dnode, prop); + if (irql < 0) { + pr_err("[%s]: Error getting IRQ \"%s\"\n", desc->name, + prop); + return irql; + } + *irq = irql; + return 0; +} + +static int __get_smem_state(struct subsys_desc *desc, const char *prop, + int *smem_bit) +{ + struct device_node *dnode = desc->dev->of_node; + + if (of_find_property(dnode, "qcom,smem-states", NULL)) { + desc->state = qcom_smem_state_get(desc->dev, prop, smem_bit); + if (IS_ERR_OR_NULL(desc->state)) { + pr_err("Could not get smem-states %s\n", prop); + return PTR_ERR(desc->state); + } + return 0; + } + return -ENOENT; +} + +static int subsys_parse_devicetree(struct subsys_desc *desc) +{ + struct subsys_soc_restart_order *order; + int ret; + struct platform_device *pdev = container_of(desc->dev, + struct platform_device, dev); + + ret = __get_irq(desc, "qcom,err-fatal", &desc->err_fatal_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(desc, "qcom,err-ready", &desc->err_ready_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(desc, "qcom,stop-ack", &desc->stop_ack_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(desc, "qcom,ramdump-disabled", + &desc->ramdump_disable_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(desc, "qcom,shutdown-ack", &desc->shutdown_ack_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(desc, "qcom,wdog", &desc->wdog_bite_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_smem_state(desc, "qcom,force-stop", &desc->force_stop_bit); + if (ret && ret != -ENOENT) + return ret; + + if (of_property_read_bool(pdev->dev.of_node, + "qcom,pil-generic-irq-handler")) { + ret = platform_get_irq(pdev, 0); + if (ret > 0) + desc->generic_irq = ret; + } + + desc->ignore_ssr_failure = of_property_read_bool(pdev->dev.of_node, + "qcom,ignore-ssr-failure"); + + order = ssr_parse_restart_orders(desc); + if (IS_ERR(order)) { + pr_err("Could not initialize SSR restart order, err = %ld\n", + PTR_ERR(order)); + return PTR_ERR(order); + } + + if (of_property_read_string(pdev->dev.of_node, "qcom,pon-depends-on", + &desc->pon_depends_on)) + pr_debug("pon-depends-on not set for %s\n", desc->name); + + if (of_property_read_string(pdev->dev.of_node, "qcom,poff-depends-on", + &desc->poff_depends_on)) + pr_debug("poff-depends-on not set for %s\n", desc->name); + + return 0; +} + +static int subsys_setup_irqs(struct subsys_device *subsys) +{ + struct subsys_desc *desc = subsys->desc; + int ret; + + if (desc->err_fatal_irq && desc->err_fatal_handler) { + ret = devm_request_threaded_irq(desc->dev, desc->err_fatal_irq, + NULL, + desc->err_fatal_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + desc->name, desc); + if (ret < 0) { + dev_err(desc->dev, "[%s]: Unable to register error fatal IRQ handler: %d, irq is %d\n", + desc->name, ret, desc->err_fatal_irq); + return ret; + } + disable_irq(desc->err_fatal_irq); + } + + if (desc->stop_ack_irq && desc->stop_ack_handler) { + ret = devm_request_threaded_irq(desc->dev, desc->stop_ack_irq, + NULL, + desc->stop_ack_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + desc->name, desc); + if (ret < 0) { + dev_err(desc->dev, "[%s]: Unable to register stop ack handler: %d\n", + desc->name, ret); + return ret; + } + disable_irq(desc->stop_ack_irq); + } + + if (desc->wdog_bite_irq && desc->wdog_bite_handler) { + ret = devm_request_irq(desc->dev, desc->wdog_bite_irq, + desc->wdog_bite_handler, + IRQF_TRIGGER_RISING, desc->name, desc); + if (ret < 0) { + dev_err(desc->dev, "[%s]: Unable to register wdog bite handler: %d\n", + desc->name, ret); + return ret; + } + disable_irq(desc->wdog_bite_irq); + } + + if (desc->shutdown_ack_irq && desc->shutdown_ack_handler) { + ret = devm_request_threaded_irq(desc->dev, + desc->shutdown_ack_irq, + NULL, + desc->shutdown_ack_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + desc->name, desc); + if (ret < 0) { + dev_err(desc->dev, "[%s]: Unable to register shutdown ack handler: %d\n", + desc->name, ret); + return ret; + } + disable_irq(desc->shutdown_ack_irq); + } + + if (desc->ramdump_disable_irq && desc->ramdump_disable_handler) { + ret = devm_request_threaded_irq(desc->dev, + desc->ramdump_disable_irq, + NULL, + desc->ramdump_disable_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + desc->name, desc); + if (ret < 0) { + dev_err(desc->dev, "[%s]: Unable to register shutdown ack handler: %d\n", + desc->name, ret); + return ret; + } + disable_irq(desc->ramdump_disable_irq); + } + + if (desc->generic_irq && desc->generic_handler) { + ret = devm_request_irq(desc->dev, desc->generic_irq, + desc->generic_handler, + IRQF_TRIGGER_HIGH, desc->name, desc); + if (ret < 0) { + dev_err(desc->dev, "[%s]: Unable to register generic irq handler: %d\n", + desc->name, ret); + return ret; + } + disable_irq(desc->generic_irq); + } + + if (desc->err_ready_irq) { + ret = devm_request_threaded_irq(desc->dev, + desc->err_ready_irq, + NULL, + subsys_err_ready_intr_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "error_ready_interrupt", subsys); + if (ret < 0) { + dev_err(desc->dev, + "[%s]: Unable to register err ready handler\n", + desc->name); + return ret; + } + disable_irq(desc->err_ready_irq); + } + + return 0; +} + +static void subsys_free_irqs(struct subsys_device *subsys) +{ + struct subsys_desc *desc = subsys->desc; + + if (desc->err_fatal_irq && desc->err_fatal_handler) + devm_free_irq(desc->dev, desc->err_fatal_irq, desc); + if (desc->stop_ack_irq && desc->stop_ack_handler) + devm_free_irq(desc->dev, desc->stop_ack_irq, desc); + if (desc->shutdown_ack_irq && desc->shutdown_ack_handler) + devm_free_irq(desc->dev, desc->shutdown_ack_irq, desc); + if (desc->ramdump_disable_irq && desc->ramdump_disable_handler) + devm_free_irq(desc->dev, desc->ramdump_disable_irq, desc); + if (desc->wdog_bite_irq && desc->wdog_bite_handler) + devm_free_irq(desc->dev, desc->wdog_bite_irq, desc); + if (desc->err_ready_irq) + devm_free_irq(desc->dev, desc->err_ready_irq, subsys); +} + +struct subsys_device *subsys_register(struct subsys_desc *desc) +{ + struct subsys_device *subsys; + struct device_node *ofnode = desc->dev->of_node; + int ret; + + subsys = kzalloc(sizeof(*subsys), GFP_KERNEL); + if (!subsys) + return ERR_PTR(-ENOMEM); + + subsys->desc = desc; + subsys->owner = desc->owner; + subsys->dev.parent = desc->dev; + subsys->dev.bus = &subsys_bus_type; + subsys->dev.release = subsys_device_release; + subsys->notif_state = -1; + subsys->desc->sysmon_pid = -1; + subsys->desc->state = NULL; + strlcpy(subsys->desc->fw_name, desc->name, + sizeof(subsys->desc->fw_name)); + + subsys->notify = subsys_notif_add_subsys(desc->name); + subsys->early_notify = subsys_get_early_notif_info(desc->name); + + snprintf(subsys->wlname, sizeof(subsys->wlname), "ssr(%s)", desc->name); + wakeup_source_init(&subsys->ssr_wlock, subsys->wlname); + INIT_WORK(&subsys->work, subsystem_restart_wq_func); + INIT_WORK(&subsys->device_restart_work, device_restart_work_hdlr); + spin_lock_init(&subsys->track.s_lock); + init_subsys_timer(desc); + + subsys->id = ida_simple_get(&subsys_ida, 0, 0, GFP_KERNEL); + if (subsys->id < 0) { + wakeup_source_trash(&subsys->ssr_wlock); + ret = subsys->id; + kfree(subsys); + return ERR_PTR(ret); + } + + dev_set_name(&subsys->dev, "subsys%d", subsys->id); + + mutex_init(&subsys->track.lock); + init_completion(&subsys->err_ready); + + ret = device_register(&subsys->dev); + if (ret) { + put_device(&subsys->dev); + return ERR_PTR(ret); + } + + ret = subsys_char_device_add(subsys); + if (ret) + goto err_register; + + if (ofnode) { + ret = subsys_parse_devicetree(desc); + if (ret) + goto err_register; + + subsys->restart_order = update_restart_order(subsys); + + if (of_property_read_u32(ofnode, "qcom,ssctl-instance-id", + &desc->ssctl_instance_id)) + pr_debug("Reading instance-id for %s failed\n", + desc->name); + + if (of_property_read_u32(ofnode, "qcom,sysmon-id", + &subsys->desc->sysmon_pid)) + pr_debug("Reading sysmon-id for %s failed\n", + desc->name); + + subsys->desc->edge = of_get_property(ofnode, "qcom,edge", + NULL); + if (!subsys->desc->edge) + pr_debug("Reading qcom,edge for %s failed\n", + desc->name); + } + + ret = sysmon_notifier_register(desc); + if (ret < 0) + goto err_sysmon_notifier; + + if (subsys->desc->edge) { + ret = sysmon_glink_register(desc); + if (ret < 0) + goto err_sysmon_glink_register; + } + mutex_lock(&subsys_list_lock); + INIT_LIST_HEAD(&subsys->list); + list_add_tail(&subsys->list, &subsys_list); + mutex_unlock(&subsys_list_lock); + + if (ofnode) { + ret = subsys_setup_irqs(subsys); + if (ret < 0) + goto err_setup_irqs; + } + + return subsys; +err_setup_irqs: + if (subsys->desc->edge) + sysmon_glink_unregister(desc); +err_sysmon_glink_register: + sysmon_notifier_unregister(subsys->desc); +err_sysmon_notifier: + if (ofnode) + subsys_remove_restart_order(ofnode); +err_register: + subsys_char_device_remove(subsys); + device_unregister(&subsys->dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(subsys_register); + +void subsys_unregister(struct subsys_device *subsys) +{ + struct subsys_device *subsys_dev, *tmp; + struct device_node *device = subsys->desc->dev->of_node; + + if (IS_ERR_OR_NULL(subsys)) + return; + + if (get_device(&subsys->dev)) { + mutex_lock(&subsys_list_lock); + list_for_each_entry_safe(subsys_dev, tmp, &subsys_list, list) + if (subsys_dev == subsys) + list_del(&subsys->list); + mutex_unlock(&subsys_list_lock); + + if (device) { + subsys_free_irqs(subsys); + subsys_remove_restart_order(device); + } + mutex_lock(&subsys->track.lock); + WARN_ON(subsys->count); + device_unregister(&subsys->dev); + mutex_unlock(&subsys->track.lock); + subsys_char_device_remove(subsys); + sysmon_notifier_unregister(subsys->desc); + if (subsys->desc->edge) + sysmon_glink_unregister(subsys->desc); + put_device(&subsys->dev); + } +} +EXPORT_SYMBOL(subsys_unregister); + +static int subsys_panic(struct device *dev, void *data) +{ + struct subsys_device *subsys = to_subsys(dev); + + if (subsys->desc->crash_shutdown) + subsys->desc->crash_shutdown(subsys->desc); + return 0; +} + +static int ssr_panic_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + bus_for_each_dev(&subsys_bus_type, NULL, NULL, subsys_panic); + return NOTIFY_DONE; +} + +static struct notifier_block panic_nb = { + .notifier_call = ssr_panic_handler, +}; + +static int __init subsys_restart_init(void) +{ + int ret; + + ssr_wq = alloc_workqueue("ssr_wq", + WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 0); + BUG_ON(!ssr_wq); + + ret = bus_register(&subsys_bus_type); + if (ret) + goto err_bus; + + char_class = class_create(THIS_MODULE, "subsys"); + if (IS_ERR(char_class)) { + ret = -ENOMEM; + pr_err("Failed to create subsys_dev class\n"); + goto err_class; + } + + ret = atomic_notifier_chain_register(&panic_notifier_list, &panic_nb); + if (ret) + goto err_soc; + + return 0; + +err_soc: + class_destroy(char_class); +err_class: + bus_unregister(&subsys_bus_type); +err_bus: + destroy_workqueue(ssr_wq); + return ret; +} +arch_initcall(subsys_restart_init); + +static void __exit subsys_restart_exit(void) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, &panic_nb); + class_destroy(char_class); + bus_unregister(&subsys_bus_type); + destroy_workqueue(ssr_wq); +} +module_exit(subsys_restart_exit); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Subsystem Restart Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/sysmon-qmi.c b/drivers/soc/qcom/sysmon-qmi.c new file mode 100644 index 000000000000..3913b4cfab12 --- /dev/null +++ b/drivers/soc/qcom/sysmon-qmi.c @@ -0,0 +1,719 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2019, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "sysmon-qmi: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define QMI_RESP_BIT_SHIFT(x) (x << 16) + +#define QMI_SSCTL_RESTART_REQ_V02 0x0020 +#define QMI_SSCTL_RESTART_RESP_V02 0x0020 +#define QMI_SSCTL_RESTART_READY_IND_V02 0x0020 +#define QMI_SSCTL_SHUTDOWN_REQ_V02 0x0021 +#define QMI_SSCTL_SHUTDOWN_RESP_V02 0x0021 +#define QMI_SSCTL_SHUTDOWN_READY_IND_V02 0x0021 +#define QMI_SSCTL_GET_FAILURE_REASON_REQ_V02 0x0022 +#define QMI_SSCTL_GET_FAILURE_REASON_RESP_V02 0x0022 +#define QMI_SSCTL_GET_FAILURE_REASON_IND_V02 0x0022 +#define QMI_SSCTL_SUBSYS_EVENT_REQ_V02 0x0023 +#define QMI_SSCTL_SUBSYS_EVENT_RESP_V02 0x0023 +#define QMI_SSCTL_SUBSYS_EVENT_READY_IND_V02 0x0023 + +#define QMI_SSCTL_ERROR_MSG_LENGTH 90 +#define QMI_SSCTL_SUBSYS_NAME_LENGTH 15 +#define QMI_SSCTL_SUBSYS_EVENT_REQ_LENGTH 40 +#define QMI_SSCTL_RESP_MSG_LENGTH 7 +#define QMI_SSCTL_EMPTY_MSG_LENGTH 0 + +#define SSCTL_SERVICE_ID 0x2B +#define SSCTL_VER_2 2 +#define SERVER_TIMEOUT 500 +#define SHUTDOWN_TIMEOUT 10000 + +#define QMI_EOTI_DATA_TYPE \ +{ \ + .data_type = QMI_EOTI, \ + .elem_len = 0, \ + .elem_size = 0, \ + .array_type = NO_ARRAY, \ + .tlv_type = 0x00, \ + .offset = 0, \ + .ei_array = NULL, \ +}, + +struct sysmon_qmi_data { + const char *name; + int instance_id; + bool connected; + struct subsys_desc *desc; + struct qmi_handle clnt_handle; + struct notifier_block notifier; + void *notif_handle; + bool legacy_version; + struct sockaddr_qrtr ssctl; + struct list_head list; +}; + +static LIST_HEAD(sysmon_list); +static DEFINE_MUTEX(sysmon_list_lock); + +static const int notif_map[SUBSYS_NOTIF_TYPE_COUNT] = { + [0 ... SUBSYS_NOTIF_TYPE_COUNT - 1] = SSCTL_SSR_EVENT_INVALID, + [SUBSYS_BEFORE_POWERUP] = SSCTL_SSR_EVENT_BEFORE_POWERUP, + [SUBSYS_AFTER_POWERUP] = SSCTL_SSR_EVENT_AFTER_POWERUP, + [SUBSYS_BEFORE_SHUTDOWN] = SSCTL_SSR_EVENT_BEFORE_SHUTDOWN, + [SUBSYS_AFTER_SHUTDOWN] = SSCTL_SSR_EVENT_AFTER_SHUTDOWN, +}; + +struct qmi_ssctl_shutdown_indication { +}; + +static struct qmi_elem_info qmi_ssctl_indication_ei[] = { + QMI_EOTI_DATA_TYPE +}; + +static void sysmon_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct sysmon_qmi_data *qmi_data = container_of(qmi, + struct sysmon_qmi_data, clnt_handle); + struct subsys_desc *desc = qmi_data->desc; + + pr_info("%s: Indication received from subsystem\n", qmi_data->name); + + if (desc) + complete_shutdown_ack(desc); + else + pr_err("Failed to find subsystem: %s for indication\n", + qmi_data->name); +} + +static struct qmi_msg_handler qmi_indication_handler[] = { + { + .type = QMI_INDICATION, + .msg_id = QMI_SSCTL_SHUTDOWN_READY_IND_V02, + .ei = qmi_ssctl_indication_ei, + .decoded_size = 0, + .fn = sysmon_ind_cb + }, + {} +}; + +static bool is_ssctl_event(enum subsys_notif_type notif) +{ + return notif_map[notif] != SSCTL_SSR_EVENT_INVALID; +} + +static int ssctl_new_server(struct qmi_handle *qmi, struct qmi_service *svc) +{ + struct sysmon_qmi_data *data = container_of(qmi, + struct sysmon_qmi_data, clnt_handle); + + pr_info("Connection established between QMI handle and %s's SSCTL service\n" + , data->name); + + data->ssctl.sq_family = AF_QIPCRTR; + data->ssctl.sq_node = svc->node; + data->ssctl.sq_port = svc->port; + data->connected = true; + return 0; +} + +static void ssctl_del_server(struct qmi_handle *qmi, struct qmi_service *svc) +{ + struct sysmon_qmi_data *data = container_of(qmi, + struct sysmon_qmi_data, clnt_handle); + + pr_info("Connection lost between QMI handle and %s's SSCTL service\n" + , data->name); + data->connected = false; +} + +static struct qmi_ops ssctl_ops = { + .new_server = ssctl_new_server, + .del_server = ssctl_del_server, +}; + +struct qmi_ssctl_subsys_event_req_msg { + uint8_t subsys_name_len; + char subsys_name[QMI_SSCTL_SUBSYS_NAME_LENGTH]; + enum ssctl_ssr_event_enum_type event; + uint8_t evt_driven_valid; + enum ssctl_ssr_event_driven_enum_type evt_driven; +}; + +struct qmi_ssctl_subsys_event_resp_msg { + struct qmi_response_type_v01 resp; +}; + +static struct qmi_elem_info qmi_ssctl_subsys_event_req_msg_ei[] = { + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg, + subsys_name_len), + .ei_array = NULL, + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_SSCTL_SUBSYS_NAME_LENGTH, + .elem_size = sizeof(char), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg, + subsys_name), + .ei_array = NULL, + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg, + event), + .ei_array = NULL, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg, + evt_driven_valid), + .ei_array = NULL, + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_ssctl_subsys_event_req_msg, + evt_driven), + .ei_array = NULL, + }, + QMI_EOTI_DATA_TYPE +}; + +static struct qmi_elem_info qmi_ssctl_subsys_event_resp_msg_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_ssctl_subsys_event_resp_msg, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + QMI_EOTI_DATA_TYPE +}; + +/** + * sysmon_send_event() - Notify a subsystem of another's state change + * @dest_desc: Subsystem descriptor of the subsystem the notification + * should be sent to + * @event_desc: Subsystem descriptor of the subsystem that generated the + * notification + * @notif: ID of the notification type (ex. SUBSYS_BEFORE_SHUTDOWN) + * + * Reverts to using legacy sysmon API (sysmon_send_event_no_qmi()) if + * client handle is not set. + * + * Returns 0 for success, -EINVAL for invalid destination or notification IDs, + * -ENODEV if the transport channel is not open, -ETIMEDOUT if the destination + * subsystem does not respond, and -EPROTO if the destination subsystem + * responds, but with something other than an acknowledgment. + * + * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0). + */ +int sysmon_send_event(struct subsys_desc *dest_desc, + struct subsys_desc *event_desc, + enum subsys_notif_type notif) +{ + struct qmi_ssctl_subsys_event_req_msg req; + struct qmi_ssctl_subsys_event_resp_msg resp = { { 0, 0 } }; + struct sysmon_qmi_data *data = NULL, *temp; + const char *event_ss = event_desc->name; + const char *dest_ss = dest_desc->name; + int ret; + struct qmi_txn txn; + + if (notif < 0 || notif >= SUBSYS_NOTIF_TYPE_COUNT || + !is_ssctl_event(notif) || event_ss == NULL || dest_ss == NULL) + return -EINVAL; + + mutex_lock(&sysmon_list_lock); + list_for_each_entry(temp, &sysmon_list, list) + if (!strcmp(temp->name, dest_desc->name)) + data = temp; + mutex_unlock(&sysmon_list_lock); + + if (!data) + return -EINVAL; + + if (data->instance_id < 0) { + pr_debug("No SSCTL_V2 support for %s. Revert to SSCTL_V0\n", + dest_ss); + ret = sysmon_send_event_no_qmi(dest_desc, event_desc, notif); + if (ret) + pr_debug("SSCTL_V0 implementation failed - %d\n", ret); + + return ret; + } + + if (!data->connected) + return -EAGAIN; + + snprintf(req.subsys_name, ARRAY_SIZE(req.subsys_name), "%s", event_ss); + req.subsys_name_len = strlen(req.subsys_name); + req.event = notif_map[notif]; + req.evt_driven_valid = 1; + req.evt_driven = SSCTL_SSR_EVENT_FORCED; + + ret = qmi_txn_init(&data->clnt_handle, &txn, + qmi_ssctl_subsys_event_resp_msg_ei, + &resp); + + if (ret < 0) { + pr_err("SYSMON QMI tx init failed to dest %s, ret - %d\n", + dest_ss, ret); + goto out; + } + + ret = qmi_send_request(&data->clnt_handle, &data->ssctl, &txn, + QMI_SSCTL_SUBSYS_EVENT_REQ_V02, + QMI_SSCTL_SUBSYS_EVENT_REQ_LENGTH, + qmi_ssctl_subsys_event_req_msg_ei, + &req); + if (ret < 0) { + pr_err("SYSMON QMI send req failed to dest %s, ret - %d\n", + dest_ss, ret); + qmi_txn_cancel(&txn); + goto out; + } + + ret = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT)); + if (ret < 0) { + pr_err("SYSMON QMI qmi txn wait failed for client %s, ret - %d\n", + dest_ss, ret); + goto out; + } + + /* Check the response */ + if (QMI_RESP_BIT_SHIFT(resp.resp.result) != QMI_RESULT_SUCCESS_V01) { + pr_err("SYSMON QMI request failed 0x%x\n", + QMI_RESP_BIT_SHIFT(resp.resp.error)); + ret = -EREMOTEIO; + } +out: + return ret; +} +EXPORT_SYMBOL(sysmon_send_event); + +struct qmi_ssctl_shutdown_req_msg { +}; + +struct qmi_ssctl_shutdown_resp_msg { + struct qmi_response_type_v01 resp; +}; + +static struct qmi_elem_info qmi_ssctl_shutdown_req_msg_ei[] = { + QMI_EOTI_DATA_TYPE +}; + +static struct qmi_elem_info qmi_ssctl_shutdown_resp_msg_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_ssctl_shutdown_resp_msg, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + QMI_EOTI_DATA_TYPE +}; + +static inline int wait_for_shutdown_ack(struct subsys_desc *desc) +{ + int ret; + + if (!desc) + return 0; + + ret = wait_for_completion_timeout(&desc->shutdown_ack, + msecs_to_jiffies(10000)); + if (!ret) { + pr_err("[%s]: Timed out waiting for shutdown ack\n", + desc->name); + return -ETIMEDOUT; + } + + return ret; +} + +/** + * sysmon_send_shutdown() - send shutdown command to a + * subsystem. + * @dest_desc: Subsystem descriptor of the subsystem to send to + * + * Reverts to using legacy sysmon API (sysmon_send_shutdown_no_qmi()) if + * client handle is not set. + * + * Returns 0 for success, -EINVAL for an invalid destination, -ENODEV if + * the SMD transport channel is not open, -ETIMEDOUT if the destination + * subsystem does not respond, and -EPROTO if the destination subsystem + * responds with something unexpected. + * + * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0). + */ +int sysmon_send_shutdown(struct subsys_desc *dest_desc) +{ + struct qmi_ssctl_shutdown_resp_msg resp = { { 0, 0 } }; + struct sysmon_qmi_data *data = NULL, *temp; + const char *dest_ss = dest_desc->name; + char req = 0; + int ret, shutdown_ack_ret; + struct qmi_txn txn; + + if (dest_ss == NULL) + return -EINVAL; + + mutex_lock(&sysmon_list_lock); + list_for_each_entry(temp, &sysmon_list, list) + if (!strcmp(temp->name, dest_desc->name)) + data = temp; + mutex_unlock(&sysmon_list_lock); + + if (!data) + return -EINVAL; + + if (data->instance_id < 0) { + pr_debug("No SSCTL_V2 support for %s. Revert to SSCTL_V0\n", + dest_ss); + ret = sysmon_send_shutdown_no_qmi(dest_desc); + if (ret) + pr_debug("SSCTL_V0 implementation failed - %d\n", ret); + + return ret; + } + + if (!data->connected) + return -EAGAIN; + + reinit_completion(&dest_desc->shutdown_ack); + + ret = qmi_txn_init(&data->clnt_handle, &txn, + qmi_ssctl_shutdown_resp_msg_ei, + &resp); + + if (ret < 0) { + pr_err("SYSMON QMI tx init failed to dest %s, ret - %d\n", + dest_ss, ret); + goto out; + } + + ret = qmi_send_request(&data->clnt_handle, &data->ssctl, &txn, + QMI_SSCTL_SHUTDOWN_REQ_V02, + QMI_SSCTL_EMPTY_MSG_LENGTH, + qmi_ssctl_shutdown_req_msg_ei, + &req); + if (ret < 0) { + pr_err("SYSMON QMI send req failed to dest %s, ret - %d\n", + dest_ss, ret); + qmi_txn_cancel(&txn); + goto out; + } + + ret = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT)); + if (ret < 0) { + pr_err("SYSMON QMI txn wait failed to dest %s, ret - %d\n", + dest_ss, ret); + } + + /* Check the response */ + if (ret != -ETIMEDOUT && QMI_RESP_BIT_SHIFT(resp.resp.result) != + QMI_RESULT_SUCCESS_V01) { + pr_err("SYSMON QMI request failed 0x%x\n", + QMI_RESP_BIT_SHIFT(resp.resp.error)); + ret = -EREMOTEIO; + goto out; + } + + shutdown_ack_ret = wait_for_shutdown_ack(dest_desc); + if (shutdown_ack_ret > 0) { + ret = 0; + goto out; + } else if (shutdown_ack_ret < 0) { + pr_err("shutdown acknowledgment not received for %s\n", + data->name); + ret = shutdown_ack_ret; + } +out: + return ret; +} +EXPORT_SYMBOL(sysmon_send_shutdown); + +struct qmi_ssctl_get_failure_reason_req_msg { +}; + +struct qmi_ssctl_get_failure_reason_resp_msg { + struct qmi_response_type_v01 resp; + uint8_t error_message_valid; + uint32_t error_message_len; + char error_message[QMI_SSCTL_ERROR_MSG_LENGTH]; +}; + +static struct qmi_elem_info qmi_ssctl_get_failure_reason_req_msg_ei[] = { + QMI_EOTI_DATA_TYPE +}; + +static struct qmi_elem_info qmi_ssctl_get_failure_reason_resp_msg_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct qmi_ssctl_get_failure_reason_resp_msg, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct qmi_ssctl_get_failure_reason_resp_msg, + error_message_valid), + .ei_array = NULL, + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct qmi_ssctl_get_failure_reason_resp_msg, + error_message_len), + .ei_array = NULL, + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_SSCTL_ERROR_MSG_LENGTH, + .elem_size = sizeof(char), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct qmi_ssctl_get_failure_reason_resp_msg, + error_message), + .ei_array = NULL, + }, + QMI_EOTI_DATA_TYPE +}; + +/** + * sysmon_get_reason() - Retrieve failure reason from a subsystem. + * @dest_desc: Subsystem descriptor of the subsystem to query + * @buf: Caller-allocated buffer for the returned NUL-terminated reason + * @len: Length of @buf + * + * Reverts to using legacy sysmon API (sysmon_get_reason_no_qmi()) if client + * handle is not set. + * + * Returns 0 for success, -EINVAL for an invalid destination, -ENODEV if + * the SMD transport channel is not open, -ETIMEDOUT if the destination + * subsystem does not respond, and -EPROTO if the destination subsystem + * responds with something unexpected. + * + * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0). + */ +int sysmon_get_reason(struct subsys_desc *dest_desc, char *buf, size_t len) +{ + struct qmi_ssctl_get_failure_reason_resp_msg resp; + struct sysmon_qmi_data *data = NULL, *temp; + struct qmi_txn txn; + const char *dest_ss = dest_desc->name; + static const char expect[] = "ssr:return:"; + char req = 0; + int ret; + + if (dest_ss == NULL || buf == NULL || len == 0) + return -EINVAL; + + mutex_lock(&sysmon_list_lock); + list_for_each_entry(temp, &sysmon_list, list) + if (!strcmp(temp->name, dest_desc->name)) + data = temp; + mutex_unlock(&sysmon_list_lock); + + if (!data) + return -EINVAL; + + if (data->instance_id < 0) { + pr_debug("No SSCTL_V2 support for %s. Revert to SSCTL_V0\n", + dest_ss); + ret = sysmon_get_reason_no_qmi(dest_desc, buf, len); + if (ret) + pr_debug("SSCTL_V0 implementation failed - %d\n", ret); + + return ret; + } + + if (!data->connected) + return -EAGAIN; + + ret = qmi_txn_init(&data->clnt_handle, &txn, + qmi_ssctl_get_failure_reason_resp_msg_ei, + &resp); + if (ret < 0) { + pr_err("SYSMON QMI tx init failed to dest %s, ret - %d\n", + dest_ss, ret); + goto out; + } + + ret = qmi_send_request(&data->clnt_handle, &data->ssctl, &txn, + QMI_SSCTL_GET_FAILURE_REASON_REQ_V02, + QMI_SSCTL_EMPTY_MSG_LENGTH, + qmi_ssctl_get_failure_reason_req_msg_ei, + &req); + if (ret < 0) { + pr_err("SYSMON QMI send req failed to dest %s, ret - %d\n", + dest_ss, ret); + qmi_txn_cancel(&txn); + goto out; + } + + ret = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT)); + if (ret < 0) { + pr_err("SYSMON QMI qmi txn wait failed to dest %s, ret - %d\n", + dest_ss, ret); + goto out; + } + + /* Check the response */ + if (QMI_RESP_BIT_SHIFT(resp.resp.result) != QMI_RESULT_SUCCESS_V01) { + pr_err("SYSMON QMI request failed 0x%x\n", + QMI_RESP_BIT_SHIFT(resp.resp.error)); + ret = -EREMOTEIO; + goto out; + } + + if (!strcmp(resp.error_message, expect)) { + pr_err("Unexpected response %s\n", resp.error_message); + ret = -EPROTO; + goto out; + } + strlcpy(buf, resp.error_message, resp.error_message_len); +out: + return ret; +} +EXPORT_SYMBOL(sysmon_get_reason); + +/** + * sysmon_notifier_register() - Initialize sysmon data for a subsystem. + * @dest_desc: Subsystem descriptor of the subsystem + * + * Returns 0 for success. If the subsystem does not support SSCTL v2, a + * value of 0 is returned after adding the subsystem entry to the sysmon_list. + * In addition, if the SSCTL v2 support exists, the notifier block to receive + * events from the SSCTL service on the subsystem is registered. + * + * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0). + */ +int sysmon_notifier_register(struct subsys_desc *desc) +{ + struct sysmon_qmi_data *data; + int rc = 0; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->desc = desc; + data->name = desc->name; + data->instance_id = desc->ssctl_instance_id; + data->legacy_version = false; + data->connected = false; + + if (data->instance_id <= 0) { + pr_debug("SSCTL instance id not defined\n"); + goto add_list; + } + + rc = qmi_handle_init(&data->clnt_handle, + QMI_SSCTL_RESP_MSG_LENGTH, &ssctl_ops, + qmi_indication_handler); + if (rc < 0) { + pr_err("Sysmon QMI handle init failed rc:%d\n", rc); + kfree(data); + return rc; + } + + qmi_add_lookup(&data->clnt_handle, SSCTL_SERVICE_ID, + SSCTL_VER_2, data->instance_id); +add_list: + init_completion(&desc->shutdown_ack); + mutex_lock(&sysmon_list_lock); + INIT_LIST_HEAD(&data->list); + list_add_tail(&data->list, &sysmon_list); + mutex_unlock(&sysmon_list_lock); + return rc; +} +EXPORT_SYMBOL(sysmon_notifier_register); + +/** + * sysmon_notifier_unregister() - Cleanup the subsystem's sysmon data. + * @dest_desc: Subsystem descriptor of the subsystem + * + * If the subsystem does not support SSCTL v2, its entry is simply removed from + * the sysmon_list. In addition, if the SSCTL v2 support exists, the notifier + * block to receive events from the SSCTL service is unregistered. + */ +void sysmon_notifier_unregister(struct subsys_desc *desc) +{ + struct sysmon_qmi_data *data = NULL, *sysmon_data, *tmp; + + mutex_lock(&sysmon_list_lock); + list_for_each_entry_safe(sysmon_data, tmp, &sysmon_list, list) + if (!strcmp(sysmon_data->name, desc->name)) { + data = sysmon_data; + list_del(&data->list); + } + mutex_unlock(&sysmon_list_lock); + + if (data == NULL) + return; + + if (data->instance_id > 0) + qmi_handle_release(&data->clnt_handle); + kfree(data); +} +EXPORT_SYMBOL(sysmon_notifier_unregister); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. System Monitor QMI driver"); diff --git a/include/soc/qcom/ramdump.h b/include/soc/qcom/ramdump.h new file mode 100644 index 000000000000..7b3829333291 --- /dev/null +++ b/include/soc/qcom/ramdump.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011-2014, 2017-2019, The Linux Foundation. All rights reserved. + */ + +#ifndef _RAMDUMP_HEADER +#define _RAMDUMP_HEADER + +struct device; + +struct ramdump_segment { + char *name; + unsigned long address; + void *v_address; + unsigned long size; +}; + +#if IS_ENABLED(CONFIG_MSM_SUBSYSTEM_RESTART) +extern void *create_ramdump_device(const char *dev_name, struct device *parent); +extern void destroy_ramdump_device(void *dev); +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, + struct device *parent) +{ + return NULL; +} + +static inline void destroy_ramdump_device(void *dev) +{ +} + +static inline int do_ramdump(void *handle, struct ramdump_segment *segments, + int nsegments) +{ + return -ENODEV; +} + +static inline int do_elf_ramdump(void *handle, struct ramdump_segment *segments, + int nsegments) +{ + return -ENODEV; +} +#endif /* CONFIG_MSM_SUBSYSTEM_RESTART */ + +#endif diff --git a/include/soc/qcom/service-locator.h b/include/soc/qcom/service-locator.h new file mode 100644 index 000000000000..c903d5657e93 --- /dev/null +++ b/include/soc/qcom/service-locator.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2016, 2018-2019, The Linux Foundation. All rights reserved. + */ +/* + * Process Domain Service Locator API header + * + */ + +#ifndef _SERVICE_LOCATOR_H +#define _SERVICE_LOCATOR_H + +#define QMI_SERVREG_LOC_NAME_LENGTH_V01 64 +#define QMI_SERVREG_LOC_LIST_LENGTH_V01 32 + +/* + * @name: The full process domain path for a process domain which provides + * a particular service + * @instance_id: The QMI instance id corresponding to the root process + * domain which is responsible for notifications for this + * process domain + * @service_data_valid: Indicates if service_data field has valid data + * @service_data: Optional service data provided by the service locator + */ +struct servreg_loc_entry_v01 { + char name[QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1]; + uint32_t instance_id; + uint8_t service_data_valid; + uint32_t service_data; +}; + +/* + * @client_name: Name of the client calling the api + * @service_name: Name of the service for which the list of process domains + * is requested + * @total_domains: Length of the process domain list + * @db_rev_count: Process domain list database revision number + * @domain_list: List of process domains providing the service + */ +struct pd_qmi_client_data { + char client_name[QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1]; + char service_name[QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1]; + int total_domains; + int db_rev_count; + struct servreg_loc_entry_v01 *domain_list; +}; + +enum service_locator_state { + LOCATOR_DOWN = 0x0F, + LOCATOR_UP = 0x1F, +}; + +#if IS_ENABLED(CONFIG_MSM_SERVICE_LOCATOR) +/* + * Use this api to request information regarding the process domains on + * which a particular service runs. The client name, the service name + * and notifier block pointer need to be provided by client calling the api. + * The total domains, db revision and the domain list will be filled in + * by the service locator. + * Returns 0 on success; otherwise a value < 0 if no valid subsystem is found. + */ +int get_service_location(char *client_name, char *service_name, + struct notifier_block *locator_nb); + +/* + * Use this api to request information regarding the subsystem the process + * domain runs on. + * @pd_path: The name field from inside the servreg_loc_entry that one + * gets back using the get_processdomains api. + * Returns 0 on success; otherwise a value < 0 if no valid subsystem is found. + */ +int find_subsys(const char *pd_path, char *subsys); + +#else + +static inline int get_service_location(char *client_name, + char *service_name, struct notifier_block *locator_nb) +{ + return -ENODEV; +} + +static inline int find_subsys(const char *pd_path, const char *subsys) +{ + return 0; +} + +#endif /* CONFIG_MSM_SERVICE_LOCATOR */ + +#endif diff --git a/include/soc/qcom/service-notifier.h b/include/soc/qcom/service-notifier.h new file mode 100644 index 000000000000..a60e05e56607 --- /dev/null +++ b/include/soc/qcom/service-notifier.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. + */ +/* + * Process Domain Service Notifier API header + * + */ + +#ifndef _SERVICE_NOTIFIER_H +#define _SERVICE_NOTIFIER_H + +enum qmi_servreg_notif_service_state_enum_type_v01 { + QMI_SERVREG_NOTIF_SERVICE_STATE_ENUM_TYPE_MIN_VAL_V01 = INT_MIN, + QMI_SERVREG_NOTIF_SERVICE_STATE_ENUM_TYPE_MAX_VAL_V01 = INT_MAX, + SERVREG_NOTIF_SERVICE_STATE_DOWN_V01 = 0x0FFFFFFF, + SERVREG_NOTIF_SERVICE_STATE_UP_V01 = 0x1FFFFFFF, + SERVREG_NOTIF_SERVICE_STATE_EARLY_DOWN_V01 = 0x2FFFFFFF, + SERVREG_NOTIF_SERVICE_STATE_UNINIT_V01 = 0x7FFFFFFF, +}; + +enum pd_subsys_state { + ROOT_PD_DOWN, + ROOT_PD_UP, + ROOT_PD_ERR_FATAL, + ROOT_PD_WDOG_BITE, + ROOT_PD_SHUTDOWN, + USER_PD_STATE_CHANGE, +}; +#if IS_ENABLED(CONFIG_MSM_SERVICE_NOTIFIER) + +/* service_notif_register_notifier() - Register a notifier for a service + * On success, it returns back a handle. It takes the following arguments: + * service_path: Individual service identifier path for which a client + * registers for notifications. + * instance_id: Instance id specific to a subsystem. + * current_state: Current state of service returned by the registration + * process. + * notifier block: notifier callback for service events. + */ +void *service_notif_register_notifier(const char *service_path, int instance_id, + struct notifier_block *nb, int *curr_state); + +/* service_notif_unregister_notifier() - Unregister a notifier for a service. + * service_notif_handle - The notifier handler that was provided by the + * service_notif_register_notifier function when the + * client registered for notifications. + * nb - The notifier block that was previously used during the registration. + */ +int service_notif_unregister_notifier(void *service_notif_handle, + struct notifier_block *nb); + +int service_notif_pd_restart(const char *service_path, int instance_id); + +#else + +static inline void *service_notif_register_notifier(const char *service_path, + int instance_id, struct notifier_block *nb, + int *curr_state) +{ + return ERR_PTR(-ENODEV); +} + +static inline int service_notif_unregister_notifier(void *service_notif_handle, + struct notifier_block *nb) +{ + return -ENODEV; +} + +static inline int service_notif_pd_restart(const char *service_path, + int instance_id) +{ + return -ENODEV; +} + +#endif /* CONFIG_MSM_SERVICE_NOTIFIER */ + +#endif diff --git a/include/soc/qcom/subsystem_notif.h b/include/soc/qcom/subsystem_notif.h new file mode 100644 index 000000000000..48f11fae81bd --- /dev/null +++ b/include/soc/qcom/subsystem_notif.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011, 2013-2014, 2018-2019, The Linux Foundation. All rights reserved. + */ +/* + * Subsystem restart notifier API header + * + */ + +#ifndef _SUBSYS_NOTIFIER_H +#define _SUBSYS_NOTIFIER_H + +#include + +enum subsys_notif_type { + SUBSYS_BEFORE_SHUTDOWN, + SUBSYS_AFTER_SHUTDOWN, + SUBSYS_BEFORE_POWERUP, + SUBSYS_AFTER_POWERUP, + SUBSYS_BEFORE_AUTH_AND_RESET, + SUBSYS_RAMDUMP_NOTIFICATION, + SUBSYS_POWERUP_FAILURE, + SUBSYS_PROXY_VOTE, + SUBSYS_PROXY_UNVOTE, + SUBSYS_SOC_RESET, + SUBSYS_NOTIF_TYPE_COUNT +}; + +enum early_subsys_notif_type { + XPORT_LAYER_NOTIF, + NUM_EARLY_NOTIFS +}; + +#if IS_ENABLED(CONFIG_MSM_SUBSYSTEM_RESTART) +/* Use the subsys_notif_register_notifier API to register for notifications for + * a particular subsystem. This API will return a handle that can be used to + * un-reg for notifications using the subsys_notif_unregister_notifier API by + * passing in that handle as an argument. + * + * On receiving a notification, the second (unsigned long) argument of the + * notifier callback will contain the notification type, and the third (void *) + * argument will contain the handle that was returned by + * subsys_notif_register_notifier. + */ +void *subsys_notif_register_notifier( + const char *subsys_name, struct notifier_block *nb); +int subsys_notif_unregister_notifier(void *subsys_handle, + struct notifier_block *nb); + +/* Use the subsys_notif_init_subsys API to initialize the notifier chains form + * a particular subsystem. This API will return a handle that can be used to + * queue notifications using the subsys_notif_queue_notification API by passing + * in that handle as an argument. + */ +void *subsys_notif_add_subsys(const char *subsys_name); +int subsys_notif_queue_notification(void *subsys_handle, + enum subsys_notif_type notif_type, + void *data); +void *subsys_get_early_notif_info(const char *subsys_name); +int subsys_register_early_notifier(const char *subsys_name, + enum early_subsys_notif_type notif_type, + void (*early_notif_cb)(void *), + void *data); +int subsys_unregister_early_notifier(const char *subsys_name, enum + early_subsys_notif_type notif_type); +void subsys_send_early_notifications(void *early_notif_handle); +#else + +static inline void *subsys_notif_register_notifier( + const char *subsys_name, struct notifier_block *nb) +{ + return NULL; +} + +static inline int subsys_notif_unregister_notifier(void *subsys_handle, + struct notifier_block *nb) +{ + return 0; +} + +static inline void *subsys_notif_add_subsys(const char *subsys_name) +{ + return NULL; +} + +static inline int subsys_notif_queue_notification(void *subsys_handle, + enum subsys_notif_type notif_type, + void *data) +{ + return 0; +} + +static inline void *subsys_get_early_notif_info(const char *subsys_name) +{ + return NULL; +} + +static inline int subsys_register_early_notifier(const char *subsys_name, + enum early_subsys_notif_type notif_type, + void (*early_notif_cb)(void *), + void *data) +{ + return -ENOTSUPP; +} + +static inline int subsys_unregister_early_notifier(const char *subsys_name, + enum early_subsys_notif_type + notif_type) +{ + return -ENOTSUPP; +} + +static inline void subsys_send_early_notifications(void *early_notif_handle) +{ +} +#endif /* CONFIG_MSM_SUBSYSTEM_RESTART */ + +#endif diff --git a/include/soc/qcom/subsystem_restart.h b/include/soc/qcom/subsystem_restart.h new file mode 100644 index 000000000000..54afc99d75ef --- /dev/null +++ b/include/soc/qcom/subsystem_restart.h @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014-2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __SUBSYS_RESTART_H +#define __SUBSYS_RESTART_H + +#include +#include +#include + +struct subsys_device; +extern struct bus_type subsys_bus_type; + +enum { + RESET_SOC = 0, + RESET_SUBSYS_COUPLED, + RESET_LEVEL_MAX +}; + +enum crash_status { + CRASH_STATUS_NO_CRASH = 0, + CRASH_STATUS_ERR_FATAL, + CRASH_STATUS_WDOG_BITE, +}; + +struct device; +struct module; + +enum ssr_comm { + SUBSYS_TO_SUBSYS_SYSMON, + SUBSYS_TO_HLOS, + HLOS_TO_SUBSYS_SYSMON_SHUTDOWN, + NUM_SSR_COMMS, +}; + +/** + * struct subsys_notif_timeout - timeout data used by notification timeout hdlr + * @comm_type: Specifies if the type of communication being tracked is + * through sysmon between two subsystems, subsystem notifier call chain, or + * sysmon shutdown. + * @dest_name: subsystem to which sysmon notification is being sent to + * @source_name: subsystem which generated event that notification is being sent + * for + * @timer: timer for scheduling timeout + */ +struct subsys_notif_timeout { + enum ssr_comm comm_type; + const char *dest_name; + const char *source_name; + struct timer_list timer; +}; + +/** + * struct subsys_desc - subsystem descriptor + * @name: name of subsystem + * @fw_name: firmware name + * @pon_depends_on: subsystem this subsystem wants to power-on first. If the + * dependednt subsystem is already powered-on, the framework won't try to power + * it back up again. + * @poff_depends_on: subsystem this subsystem wants to power-off first. If the + * dependednt subsystem is already powered-off, the framework won't try to power + * it off again. + * @dev: parent device + * @owner: module the descriptor belongs to + * @shutdown: Stop a subsystem + * @powerup: Start a subsystem + * @crash_shutdown: Shutdown a subsystem when the system crashes (can't sleep) + * @ramdump: Collect a ramdump of the subsystem + * @free_memory: Free the memory associated with this subsystem + * @is_not_loadable: Indicate if subsystem firmware is not loadable via pil + * framework + * @no_auth: Set if subsystem does not rely on PIL to authenticate and bring + * it out of reset + * @ssctl_instance_id: Instance id used to connect with SSCTL service + * @sysmon_pid: pdev id that sysmon is probed with for the subsystem + * @sysmon_shutdown_ret: Return value for the call to sysmon_send_shutdown + * @system_debug: If "set", triggers a device restart when the + * subsystem's wdog bite handler is invoked. + * @ignore_ssr_failure: SSR failures are usually fatal and results in panic. If + * set will ignore failure. + * @edge: GLINK logical name of the subsystem + */ +struct subsys_desc { + const char *name; + char fw_name[256]; + const char *pon_depends_on; + const char *poff_depends_on; + struct device *dev; + struct module *owner; + + int (*shutdown)(const struct subsys_desc *desc, bool force_stop); + int (*powerup)(const struct subsys_desc *desc); + void (*crash_shutdown)(const struct subsys_desc *desc); + int (*ramdump)(int need_dumps, const struct subsys_desc *desc); + void (*free_memory)(const struct subsys_desc *desc); + irqreturn_t (*err_fatal_handler)(int irq, void *dev_id); + irqreturn_t (*stop_ack_handler)(int irq, void *dev_id); + irqreturn_t (*shutdown_ack_handler)(int irq, void *dev_id); + irqreturn_t (*ramdump_disable_handler)(int irq, void *dev_id); + irqreturn_t (*wdog_bite_handler)(int irq, void *dev_id); + irqreturn_t (*generic_handler)(int irq, void *dev_id); + struct completion shutdown_ack; + int is_not_loadable; + int err_fatal_gpio; + unsigned int err_fatal_irq; + unsigned int err_ready_irq; + unsigned int stop_ack_irq; + unsigned int wdog_bite_irq; + unsigned int generic_irq; + int force_stop_bit; + int ramdump_disable_irq; + int shutdown_ack_irq; + int ramdump_disable; + bool no_auth; + bool pil_mss_memsetup; + int ssctl_instance_id; + u32 sysmon_pid; + int sysmon_shutdown_ret; + bool system_debug; + bool ignore_ssr_failure; + const char *edge; + struct qcom_smem_state *state; +#ifdef CONFIG_SETUP_SSR_NOTIF_TIMEOUTS + struct subsys_notif_timeout timeout_data; +#endif /* CONFIG_SETUP_SSR_NOTIF_TIMEOUTS */ +}; + +/** + * struct notif_data - additional notif information + * @crashed: indicates if subsystem has crashed due to wdog bite or err fatal + * @enable_ramdump: ramdumps disabled if set to 0 + * @enable_mini_ramdumps: enable flag for minimized critical-memory-only + * ramdumps + * @no_auth: set if subsystem does not use PIL to bring it out of reset + * @pdev: subsystem platform device pointer + */ +struct notif_data { + enum crash_status crashed; + int enable_ramdump; + int enable_mini_ramdumps; + bool no_auth; + struct platform_device *pdev; +}; + +#if IS_ENABLED(CONFIG_MSM_SUBSYSTEM_RESTART) + +extern int subsystem_restart_dev(struct subsys_device *dev); +extern int subsystem_restart(const char *name); +extern int subsystem_crashed(const char *name); + +extern void *subsystem_get(const char *name); +extern void *subsystem_get_with_fwname(const char *name, const char *fw_name); +extern int subsystem_set_fwname(const char *name, const char *fw_name); +extern void subsystem_put(void *subsystem); + +extern struct subsys_device *subsys_register(struct subsys_desc *desc); +extern void subsys_unregister(struct subsys_device *dev); + +extern void subsys_set_crash_status(struct subsys_device *dev, + enum crash_status crashed); +extern enum crash_status subsys_get_crash_status(struct subsys_device *dev); +void notify_proxy_vote(struct device *device); +void notify_proxy_unvote(struct device *device); +void notify_before_auth_and_reset(struct device *device); +void complete_err_ready(struct subsys_device *subsys); +static inline void complete_shutdown_ack(struct subsys_desc *desc) +{ + complete(&desc->shutdown_ack); +} +#else + +static inline int subsystem_restart_dev(struct subsys_device *dev) +{ + return 0; +} + +static inline int subsystem_restart(const char *name) +{ + return 0; +} + +static inline int subsystem_crashed(const char *name) +{ + return 0; +} + +static inline void *subsystem_get(const char *name) +{ + return NULL; +} + +static inline void *subsystem_get_with_fwname(const char *name, + const char *fw_name) { + return NULL; +} + +static inline int subsystem_set_fwname(const char *name, + const char *fw_name) { + return 0; +} + +static inline void subsystem_put(void *subsystem) { } + +static inline +struct subsys_device *subsys_register(struct subsys_desc *desc) +{ + return NULL; +} + +static inline void subsys_unregister(struct subsys_device *dev) { } + +static inline void subsys_set_crash_status(struct subsys_device *dev, + enum crash_status crashed) { } +static inline +enum crash_status subsys_get_crash_status(struct subsys_device *dev) +{ + return false; +} +static inline void notify_proxy_vote(struct device *device) { } +static inline void notify_proxy_unvote(struct device *device) { } +static inline void notify_before_auth_and_reset(struct device *device) { } +#endif /* CONFIG_MSM_SUBSYSTEM_RESTART */ + +/* Helper wrappers */ +static inline void wakeup_source_trash(struct wakeup_source *ws) +{ + if (!ws) + return; + + wakeup_source_remove(ws); + __pm_relax(ws); +} + +#endif diff --git a/include/soc/qcom/sysmon.h b/include/soc/qcom/sysmon.h new file mode 100644 index 000000000000..9549a6957699 --- /dev/null +++ b/include/soc/qcom/sysmon.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011-2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __MSM_SYSMON_H +#define __MSM_SYSMON_H + +#include +#include + +/** + * enum ssctl_ssr_event_enum_type - Subsystem notification type. + */ +enum ssctl_ssr_event_enum_type { + SSCTL_SSR_EVENT_ENUM_TYPE_MIN_ENUM_VAL = -2147483647, + SSCTL_SSR_EVENT_INVALID = -1, + SSCTL_SSR_EVENT_BEFORE_POWERUP = 0, + SSCTL_SSR_EVENT_AFTER_POWERUP = 1, + SSCTL_SSR_EVENT_BEFORE_SHUTDOWN = 2, + SSCTL_SSR_EVENT_AFTER_SHUTDOWN = 3, + SSCTL_SSR_EVENT_ENUM_TYPE_MAX_ENUM_VAL = 2147483647 +}; + +/** + * enum ssctl_ssr_event_driven_enum_type - Subsystem shutdown type. + */ +enum ssctl_ssr_event_driven_enum_type { + SSCTL_SSR_EVENT_DRIVEN_ENUM_TYPE_MIN_ENUM_VAL = -2147483647, + SSCTL_SSR_EVENT_FORCED = 0, + SSCTL_SSR_EVENT_GRACEFUL = 1, + SSCTL_SSR_EVENT_DRIVEN_ENUM_TYPE_MAX_ENUM_VAL = 2147483647 +}; + +#if IS_ENABLED(CONFIG_MSM_SUBSYSTEM_RESTART) +extern int sysmon_send_event(struct subsys_desc *dest_desc, + struct subsys_desc *event_desc, + enum subsys_notif_type notif); +extern int sysmon_get_reason(struct subsys_desc *dest_desc, char *buf, + size_t len); +extern int sysmon_send_shutdown(struct subsys_desc *dest_desc); +extern int sysmon_notifier_register(struct subsys_desc *desc); +extern void sysmon_notifier_unregister(struct subsys_desc *desc); +#else +static inline int sysmon_send_event(struct subsys_desc *dest_desc, + struct subsys_desc *event_desc, + enum subsys_notif_type notif) +{ + return 0; +} +static inline int sysmon_get_reason(struct subsys_desc *dest_desc, + char *buf, size_t len) +{ + return 0; +} +static inline int sysmon_send_shutdown(struct subsys_desc *dest_desc) +{ + return 0; +} +static inline int sysmon_notifier_register(struct subsys_desc *desc) +{ + return 0; +} +static inline void sysmon_notifier_unregister(struct subsys_desc *desc) +{ +} +#endif + +#if defined(CONFIG_MSM_SYSMON_GLINK_COMM) +extern int sysmon_glink_register(struct subsys_desc *desc); +extern void sysmon_glink_unregister(struct subsys_desc *desc); +extern int sysmon_send_shutdown_no_qmi(struct subsys_desc *dest_desc); +extern int sysmon_get_reason_no_qmi(struct subsys_desc *dest_desc, + char *buf, size_t len); +extern int sysmon_send_event_no_qmi(struct subsys_desc *dest_desc, + struct subsys_desc *event_desc, + enum subsys_notif_type notif); +#else +static inline int sysmon_get_reason_no_qmi(struct subsys_desc *dest_desc, + char *buf, size_t len) +{ + return 0; +} +static inline int sysmon_send_shutdown_no_qmi(struct subsys_desc *dest_desc) +{ + return 0; +} +static inline int sysmon_glink_register(struct subsys_desc *desc) +{ + return 0; +} +static inline void sysmon_glink_unregister(struct subsys_desc *desc) +{ +} +static inline int sysmon_send_event_no_qmi(struct subsys_desc *dest_desc, + struct subsys_desc *event_desc, + enum subsys_notif_type notif) +{ + return 0; +} +#endif +#endif diff --git a/include/trace/events/trace_msm_pil_event.h b/include/trace/events/trace_msm_pil_event.h new file mode 100644 index 000000000000..24b4b1c00abb --- /dev/null +++ b/include/trace/events/trace_msm_pil_event.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM msm_pil_event + +#if !defined(_TRACE_MSM_PIL_EVENT_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_MSM_PIL_EVENT_H_ + +#include +#include <../drivers/soc/qcom/peripheral-loader.h> + +TRACE_EVENT(pil_event, + + TP_PROTO(const char *event_name, struct pil_desc *desc), + + TP_ARGS(event_name, desc), + + TP_STRUCT__entry( + __string(event_name, event_name) + __string(fw_name, desc->fw_name) + ), + + TP_fast_assign( + __assign_str(event_name, event_name); + __assign_str(fw_name, desc->fw_name); + ), + + TP_printk("event_name=%s fw_name=%s", + __get_str(event_name), + __get_str(fw_name)) +); + +TRACE_EVENT(pil_func, + + TP_PROTO(const char *func_name), + + TP_ARGS(func_name), + + TP_STRUCT__entry( + __string(func_name, func_name) + ), + + TP_fast_assign( + __assign_str(func_name, func_name); + ), + + TP_printk("func_name=%s", + __get_str(func_name)) +); + +#endif +#define TRACE_INCLUDE_FILE trace_msm_pil_event +#include diff --git a/include/trace/events/trace_msm_ssr_event.h b/include/trace/events/trace_msm_ssr_event.h new file mode 100644 index 000000000000..b1c1f978d9fb --- /dev/null +++ b/include/trace/events/trace_msm_ssr_event.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM msm_pil_event + +#if !defined(_TRACE_MSM_SSR_EVENT_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_MSM_SSR_EVENT_H_ + +#include +#include <../drivers/soc/qcom/peripheral-loader.h> + +TRACE_EVENT(pil_notif, + + TP_PROTO(const char *event_name, unsigned long code, + const char *fw_name), + + TP_ARGS(event_name, code, fw_name), + + TP_STRUCT__entry( + __string(event_name, event_name) + __field(unsigned long, code) + __string(fw_name, fw_name) + ), + + TP_fast_assign( + __assign_str(event_name, event_name); + __entry->code = code; + __assign_str(fw_name, fw_name); + ), + + TP_printk("event_name=%s code=%lu fw=%s", + __get_str(event_name), + __entry->code, + __get_str(fw_name)) +); + +#endif +#define TRACE_INCLUDE_FILE trace_msm_ssr_event +#include