From 4fcd37a47089c6472df204311ed9deaad1d56d13 Mon Sep 17 00:00:00 2001 From: Raghavendra Rao Ananta Date: Fri, 28 Jun 2019 11:18:49 -0700 Subject: [PATCH] soc: qcom: Add snapshot of SSR, PIL, Sysmon, and PDR drivers Add snapshot for Subsystem Restart (SSR), Peripheral Image Loader framework (PIL), Secure PIL driver, System Monitor (sysmon), and Protection Domain Restart (PDR) drivers from msm-4.19 as of commit <61502d56cb5f9b1> (Merge "drivers: llcc_perfmon: support for dual memory controller added"). Change-Id: Id06c338121486705dfd716567244650b4dd1742c [rananta@codeaurora.org: Make changes to make the drivers modular] Signed-off-by: Raghavendra Rao Ananta --- drivers/soc/qcom/Kconfig | 96 + drivers/soc/qcom/Makefile | 6 + drivers/soc/qcom/minidump_private.h | 79 + drivers/soc/qcom/peripheral-loader.c | 1678 ++++++++++++++++ drivers/soc/qcom/peripheral-loader.h | 160 ++ drivers/soc/qcom/ramdump.c | 646 ++++++ drivers/soc/qcom/service-locator-private.h | 365 ++++ drivers/soc/qcom/service-locator.c | 392 ++++ drivers/soc/qcom/service-notifier-private.h | 349 ++++ drivers/soc/qcom/service-notifier.c | 763 ++++++++ drivers/soc/qcom/subsys-pil-tz.c | 1297 ++++++++++++ drivers/soc/qcom/subsystem_notif.c | 327 ++++ drivers/soc/qcom/subsystem_restart.c | 1957 +++++++++++++++++++ drivers/soc/qcom/sysmon-qmi.c | 719 +++++++ include/soc/qcom/ramdump.h | 52 + include/soc/qcom/service-locator.h | 89 + include/soc/qcom/service-notifier.h | 78 + include/soc/qcom/subsystem_notif.h | 118 ++ include/soc/qcom/subsystem_restart.h | 236 +++ include/soc/qcom/sysmon.h | 102 + include/trace/events/trace_msm_pil_event.h | 56 + include/trace/events/trace_msm_ssr_event.h | 42 + 22 files changed, 9607 insertions(+) create mode 100644 drivers/soc/qcom/minidump_private.h create mode 100644 drivers/soc/qcom/peripheral-loader.c create mode 100644 drivers/soc/qcom/peripheral-loader.h create mode 100644 drivers/soc/qcom/ramdump.c create mode 100644 drivers/soc/qcom/service-locator-private.h create mode 100644 drivers/soc/qcom/service-locator.c create mode 100644 drivers/soc/qcom/service-notifier-private.h create mode 100644 drivers/soc/qcom/service-notifier.c create mode 100644 drivers/soc/qcom/subsys-pil-tz.c create mode 100644 drivers/soc/qcom/subsystem_notif.c create mode 100644 drivers/soc/qcom/subsystem_restart.c create mode 100644 drivers/soc/qcom/sysmon-qmi.c create mode 100644 include/soc/qcom/ramdump.h create mode 100644 include/soc/qcom/service-locator.h create mode 100644 include/soc/qcom/service-notifier.h create mode 100644 include/soc/qcom/subsystem_notif.h create mode 100644 include/soc/qcom/subsystem_restart.h create mode 100644 include/soc/qcom/sysmon.h create mode 100644 include/trace/events/trace_msm_pil_event.h create mode 100644 include/trace/events/trace_msm_ssr_event.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 3f8c6cdd9cb7..80a6ee2b378e 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 3f9232d4d6c7..592eda47d51e 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