Merge "soc: qcom: Add snapshot of SSR, PIL, Sysmon, and PDR drivers"

This commit is contained in:
qctecmdr 2019-08-20 11:28:12 -07:00 • committed by Gerrit - the friendly Code Review server
commit 2f4e024c0e
22 changed files with 9607 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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 <linux/mailbox_client.h>
#include <linux/mailbox/qmp.h>
#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

646
drivers/soc/qcom/ramdump.c Normal file
View file

@ -0,0 +1,646 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2011-2019, The Linux Foundation. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/workqueue.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/uaccess.h>
#include <linux/elf.h>
#include <linux/wait.h>
#include <linux/cdev.h>
#include <linux/atomic.h>
#include <soc/qcom/ramdump.h>
#include <linux/of.h>
#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);

View file

@ -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 <linux/soc/qcom/qmi.h>
#include <soc/qcom/service-locator.h>
#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

View file

@ -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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/completion.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/soc/qcom/qmi.h>
#include <soc/qcom/service-locator.h>
#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");

View file

@ -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 <linux/soc/qcom/qmi.h>
#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

View file

@ -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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/completion.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/uaccess.h>
#include <soc/qcom/subsystem_restart.h>
#include <soc/qcom/subsystem_notif.h>
#include <soc/qcom/sysmon.h>
#include <soc/qcom/service-locator.h>
#include <soc/qcom/service-notifier.h>
#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(&notif_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(&notif_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(&notif_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(&notif_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(&notif_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(&notif_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(&notif_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(&notif_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");

File diff suppressed because it is too large Load diff

View file

@ -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 <linux/notifier.h>
#include <linux/init.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/stringify.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <soc/qcom/subsystem_notif.h>
/**
* 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(&notif_lock);
list_for_each_entry(subsys, &subsystem_list, list)
if (!strcmp(subsys->name, subsys_name)) {
mutex_unlock(&notif_lock);
return subsys;
}
mutex_unlock(&notif_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(&notif_add_lock);
subsys = _notif_find_subsys(subsys_name);
if (subsys) {
mutex_unlock(&notif_add_lock);
goto done;
}
subsys = kmalloc(sizeof(struct subsys_notif_info), GFP_KERNEL);
if (!subsys) {
mutex_unlock(&notif_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(&notif_lock);
list_add_tail(&subsys->list, &subsystem_list);
mutex_unlock(&notif_lock);
#if defined(SUBSYS_RESTART_DEBUG)
subsys_notif_reg_test_notifier(subsys->name);
#endif
mutex_unlock(&notif_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");

File diff suppressed because it is too large Load diff

View file

@ -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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/completion.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <soc/qcom/subsystem_restart.h>
#include <soc/qcom/subsystem_notif.h>
#include <linux/soc/qcom/qmi.h>
#include <soc/qcom/sysmon.h>
#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");

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 <linux/notifier.h>
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

View file

@ -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 <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/device.h>
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

102
include/soc/qcom/sysmon.h Normal file
View file

@ -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 <soc/qcom/subsystem_notif.h>
#include <soc/qcom/subsystem_restart.h>
/**
* 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

View file

@ -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 <linux/tracepoint.h>
#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 <trace/define_trace.h>

View file

@ -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 <linux/tracepoint.h>
#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 <trace/define_trace.h>