mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-09 21:59:12 -04:00
Merge "soc: qcom: Add snapshot of SSR, PIL, Sysmon, and PDR drivers"
This commit is contained in:
commit
2f4e024c0e
22 changed files with 9607 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
79
drivers/soc/qcom/minidump_private.h
Normal file
79
drivers/soc/qcom/minidump_private.h
Normal 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
|
||||
1678
drivers/soc/qcom/peripheral-loader.c
Normal file
1678
drivers/soc/qcom/peripheral-loader.c
Normal file
File diff suppressed because it is too large
Load diff
160
drivers/soc/qcom/peripheral-loader.h
Normal file
160
drivers/soc/qcom/peripheral-loader.h
Normal 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
646
drivers/soc/qcom/ramdump.c
Normal 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);
|
||||
365
drivers/soc/qcom/service-locator-private.h
Normal file
365
drivers/soc/qcom/service-locator-private.h
Normal 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
|
||||
392
drivers/soc/qcom/service-locator.c
Normal file
392
drivers/soc/qcom/service-locator.c
Normal 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");
|
||||
349
drivers/soc/qcom/service-notifier-private.h
Normal file
349
drivers/soc/qcom/service-notifier-private.h
Normal 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
|
||||
763
drivers/soc/qcom/service-notifier.c
Normal file
763
drivers/soc/qcom/service-notifier.c
Normal 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(¬if_add_lock);
|
||||
mutex_lock(&service_list_lock);
|
||||
rc = service_notif_queue_notification(service_notif,
|
||||
ind_info->curr_state, &state);
|
||||
if (rc & NOTIFY_STOP_MASK)
|
||||
pr_err("Notifier callback aborted for %s with error %d\n",
|
||||
data->service_path, rc);
|
||||
service_notif->curr_state = ind_info->curr_state;
|
||||
mutex_unlock(&service_list_lock);
|
||||
mutex_unlock(¬if_add_lock);
|
||||
}
|
||||
|
||||
req.transaction_id = ind_info->transaction_id;
|
||||
snprintf(req.service_name, ARRAY_SIZE(req.service_name), "%s",
|
||||
data->service_path);
|
||||
|
||||
rc = qmi_txn_init(&data->clnt_handle, &txn,
|
||||
qmi_servreg_notif_set_ack_resp_msg_v01_ei,
|
||||
&resp);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s QMI tx init failed , ret - %d\n",
|
||||
data->service_path, rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = qmi_send_request(&data->clnt_handle, &data->s_addr,
|
||||
&txn, SERVREG_NOTIF_SET_ACK_REQ,
|
||||
SERVREG_NOTIF_SET_ACK_REQ_MSG_LEN,
|
||||
qmi_servreg_notif_set_ack_req_msg_v01_ei,
|
||||
&req);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: QMI send ACK failed, ret - %d\n",
|
||||
data->service_path, rc);
|
||||
qmi_txn_cancel(&txn);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT));
|
||||
if (rc < 0) {
|
||||
pr_err("%s: QMI qmi txn wait failed, ret - %d\n",
|
||||
data->service_path, rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Check the response */
|
||||
if (resp.resp.result != QMI_RESULT_SUCCESS_V01)
|
||||
pr_err("QMI request failed 0x%x\n", resp.resp.error);
|
||||
pr_info("Indication ACKed for transid %d, service %s, instance %d!\n",
|
||||
ind_info->transaction_id, data->service_path,
|
||||
data->instance_id);
|
||||
out:
|
||||
kfree(ind_info);
|
||||
}
|
||||
|
||||
static void root_service_service_ind_cb(struct qmi_handle *qmi,
|
||||
struct sockaddr_qrtr *sq,
|
||||
struct qmi_txn *txn, const void *data)
|
||||
{
|
||||
struct qmi_client_info *qmi_data = container_of(qmi,
|
||||
struct qmi_client_info, clnt_handle);
|
||||
struct qmi_servreg_notif_state_updated_ind_msg_v01 ind_msg =
|
||||
*((struct qmi_servreg_notif_state_updated_ind_msg_v01 *)data);
|
||||
struct ind_req_resp *ind_info = kmalloc(sizeof(*ind_info), GFP_KERNEL);
|
||||
|
||||
if (!ind_info)
|
||||
return;
|
||||
|
||||
pr_info("Indication received from %s, state: 0x%x, trans-id: %d\n",
|
||||
ind_msg.service_name, ind_msg.curr_state,
|
||||
ind_msg.transaction_id);
|
||||
|
||||
ind_info->transaction_id = ind_msg.transaction_id;
|
||||
ind_info->curr_state = ind_msg.curr_state;
|
||||
ind_info->client_data = qmi_data;
|
||||
INIT_WORK(&ind_info->ind_ack, send_ind_ack);
|
||||
queue_work(qmi_data->ind_ack_wq, &ind_info->ind_ack);
|
||||
}
|
||||
|
||||
static int send_notif_listener_msg_req(struct service_notif_info *service_notif,
|
||||
struct qmi_client_info *data,
|
||||
bool register_notif, int *curr_state)
|
||||
{
|
||||
struct qmi_servreg_notif_register_listener_req_msg_v01 req;
|
||||
struct qmi_servreg_notif_register_listener_resp_msg_v01
|
||||
resp = { { 0, 0 } };
|
||||
struct qmi_txn txn;
|
||||
int rc;
|
||||
|
||||
snprintf(req.service_name, ARRAY_SIZE(req.service_name), "%s",
|
||||
service_notif->service_path);
|
||||
req.enable = register_notif;
|
||||
|
||||
rc = qmi_txn_init(&data->clnt_handle, &txn,
|
||||
qmi_servreg_notif_register_listener_resp_msg_v01_ei,
|
||||
&resp);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s QMI tx init failed , ret - %d\n",
|
||||
service_notif->service_path, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qmi_send_request(&data->clnt_handle, &data->s_addr,
|
||||
&txn, SERVREG_NOTIF_REGISTER_LISTENER_REQ,
|
||||
SERVREG_NOTIF_REGISTER_LISTENER_REQ_MSG_LEN,
|
||||
qmi_servreg_notif_register_listener_req_msg_v01_ei,
|
||||
&req);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: QMI send req failed, ret - %d\n",
|
||||
service_notif->service_path, rc);
|
||||
qmi_txn_cancel(&txn);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT));
|
||||
if (rc < 0) {
|
||||
pr_err("%s: QMI qmi txn wait failed, ret - %d\n",
|
||||
service_notif->service_path, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Check the response */
|
||||
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
|
||||
pr_err("QMI request failed 0x%x\n", resp.resp.error);
|
||||
return -EREMOTEIO;
|
||||
}
|
||||
|
||||
if ((int) resp.curr_state < QMI_STATE_MIN_VAL ||
|
||||
(int) resp.curr_state > QMI_STATE_MAX_VAL) {
|
||||
pr_err("Invalid indication notification state %d\n",
|
||||
resp.curr_state);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
*curr_state = resp.curr_state;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct qmi_msg_handler qmi_indication_handler[] = {
|
||||
{
|
||||
.type = QMI_INDICATION,
|
||||
.msg_id = SERVREG_NOTIF_STATE_UPDATED_IND_MSG,
|
||||
.ei = qmi_servreg_notif_state_updated_ind_msg_v01_ei,
|
||||
.decoded_size =
|
||||
sizeof(struct qmi_servreg_notif_state_updated_ind_msg_v01),
|
||||
.fn = root_service_service_ind_cb,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static int register_notif_listener(struct service_notif_info *service_notif,
|
||||
struct qmi_client_info *data,
|
||||
int *curr_state)
|
||||
{
|
||||
return send_notif_listener_msg_req(service_notif, data, true,
|
||||
curr_state);
|
||||
}
|
||||
|
||||
static int service_notifier_new_server(struct qmi_handle *qmi,
|
||||
struct qmi_service *svc)
|
||||
{
|
||||
struct qmi_client_info *data = container_of(qmi,
|
||||
struct qmi_client_info, clnt_handle);
|
||||
|
||||
data->s_addr.sq_family = AF_QIPCRTR;
|
||||
data->s_addr.sq_node = svc->node;
|
||||
data->s_addr.sq_port = svc->port;
|
||||
data->service_connected = true;
|
||||
pr_info("Connection established between QMI handle and %d service\n",
|
||||
data->instance_id);
|
||||
queue_work(data->svc_event_wq, &data->new_server);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void new_server_work(struct work_struct *work)
|
||||
{
|
||||
struct service_notif_info *service_notif = NULL;
|
||||
struct qmi_client_info *data = container_of(work,
|
||||
struct qmi_client_info, new_server);
|
||||
int rc = 0, curr_state = 0;
|
||||
|
||||
mutex_lock(¬if_add_lock);
|
||||
mutex_lock(&service_list_lock);
|
||||
list_for_each_entry(service_notif, &service_list, list) {
|
||||
if (service_notif->instance_id == data->instance_id && !strcmp
|
||||
(service_notif->service_path, data->service_path)) {
|
||||
enum pd_subsys_state state = ROOT_PD_UP;
|
||||
|
||||
rc = register_notif_listener(service_notif, data,
|
||||
&curr_state);
|
||||
if (rc < 0) {
|
||||
pr_err("Notifier registration failed for %s rc:%d\n",
|
||||
service_notif->service_path, rc);
|
||||
} else {
|
||||
rc = service_notif_queue_notification(
|
||||
service_notif, curr_state, &state);
|
||||
if (rc & NOTIFY_STOP_MASK)
|
||||
pr_err("Notifier callback aborted for %s error:%d\n",
|
||||
service_notif->service_path, rc);
|
||||
service_notif->curr_state = curr_state;
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock(&service_list_lock);
|
||||
mutex_unlock(¬if_add_lock);
|
||||
}
|
||||
|
||||
static void root_service_service_exit(struct qmi_client_info *data,
|
||||
enum pd_subsys_state state)
|
||||
{
|
||||
struct service_notif_info *service_notif = NULL;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Send service down notifications to all clients
|
||||
* of registered for notifications for that service.
|
||||
*/
|
||||
mutex_lock(¬if_add_lock);
|
||||
mutex_lock(&service_list_lock);
|
||||
list_for_each_entry(service_notif, &service_list, list) {
|
||||
if (service_notif->instance_id == data->instance_id && !strcmp
|
||||
(data->service_path, service_notif->service_path)) {
|
||||
rc = service_notif_queue_notification(service_notif,
|
||||
SERVREG_NOTIF_SERVICE_STATE_DOWN_V01,
|
||||
&state);
|
||||
if (rc & NOTIFY_STOP_MASK)
|
||||
pr_err("Notifier callback aborted for %s with error %d\n",
|
||||
service_notif->service_path, rc);
|
||||
service_notif->curr_state =
|
||||
SERVREG_NOTIF_SERVICE_STATE_DOWN_V01;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&service_list_lock);
|
||||
mutex_unlock(¬if_add_lock);
|
||||
}
|
||||
|
||||
static int ssr_event_notify(struct notifier_block *this,
|
||||
unsigned long code,
|
||||
void *data)
|
||||
{
|
||||
struct qmi_client_info *info = container_of(this,
|
||||
struct qmi_client_info, ssr_notifier);
|
||||
struct notif_data *notif = data;
|
||||
|
||||
switch (code) {
|
||||
case SUBSYS_BEFORE_SHUTDOWN:
|
||||
pr_debug("Root PD DOWN(SSR notification), state:%d\n",
|
||||
notif->crashed);
|
||||
switch (notif->crashed) {
|
||||
case CRASH_STATUS_ERR_FATAL:
|
||||
info->subsys_state = ROOT_PD_ERR_FATAL;
|
||||
break;
|
||||
case CRASH_STATUS_WDOG_BITE:
|
||||
info->subsys_state = ROOT_PD_WDOG_BITE;
|
||||
break;
|
||||
default:
|
||||
info->subsys_state = ROOT_PD_SHUTDOWN;
|
||||
break;
|
||||
}
|
||||
root_service_service_exit(info, info->subsys_state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static void del_server_work(struct work_struct *work)
|
||||
{
|
||||
struct qmi_client_info *data = container_of(work,
|
||||
struct qmi_client_info, del_server);
|
||||
|
||||
data->subsys_state = ROOT_PD_DOWN;
|
||||
root_service_service_exit(data, data->subsys_state);
|
||||
}
|
||||
|
||||
static void service_notifier_del_server(struct qmi_handle *qmi,
|
||||
struct qmi_service *svc)
|
||||
{
|
||||
struct qmi_client_info *data = container_of(qmi,
|
||||
struct qmi_client_info, clnt_handle);
|
||||
data->service_connected = false;
|
||||
pr_info("Connection lost between QMI handle and %d service\n",
|
||||
data->instance_id);
|
||||
queue_work(data->svc_event_wq, &data->del_server);
|
||||
}
|
||||
|
||||
static struct qmi_ops server_ops = {
|
||||
.new_server = service_notifier_new_server,
|
||||
.del_server = service_notifier_del_server,
|
||||
};
|
||||
|
||||
static void *add_service_notif(const char *service_path, int instance_id,
|
||||
int *curr_state)
|
||||
{
|
||||
struct service_notif_info *service_notif;
|
||||
struct qmi_client_info *tmp, *qmi_data;
|
||||
long rc;
|
||||
char subsys[SERVREG_NOTIF_NAME_LENGTH];
|
||||
|
||||
rc = find_subsys(service_path, subsys);
|
||||
if (rc < 0) {
|
||||
pr_err("Could not find subsys for %s\n", service_path);
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
service_notif = kzalloc(sizeof(struct service_notif_info), GFP_KERNEL);
|
||||
if (!service_notif)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
strlcpy(service_notif->service_path, service_path,
|
||||
ARRAY_SIZE(service_notif->service_path));
|
||||
service_notif->instance_id = instance_id;
|
||||
|
||||
/* If we already have a connection to the root PD on which the remote
|
||||
* service we are interested in notifications about runs, then use
|
||||
* the existing QMI connection.
|
||||
*/
|
||||
mutex_lock(&qmi_list_lock);
|
||||
list_for_each_entry(tmp, &qmi_client_list, list) {
|
||||
if (tmp->instance_id == instance_id && !strcmp
|
||||
(tmp->service_path, service_path)) {
|
||||
if (tmp->service_connected) {
|
||||
rc = register_notif_listener(service_notif, tmp,
|
||||
curr_state);
|
||||
if (rc < 0) {
|
||||
mutex_unlock(&qmi_list_lock);
|
||||
pr_err("Register notifier failed: %s\n",
|
||||
service_path);
|
||||
kfree(service_notif);
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
service_notif->curr_state = *curr_state;
|
||||
}
|
||||
mutex_unlock(&qmi_list_lock);
|
||||
goto add_service_list;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&qmi_list_lock);
|
||||
|
||||
qmi_data = kzalloc(sizeof(struct qmi_client_info), GFP_KERNEL);
|
||||
if (!qmi_data) {
|
||||
kfree(service_notif);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
qmi_data->instance_id = instance_id;
|
||||
strlcpy(qmi_data->service_path, service_path,
|
||||
ARRAY_SIZE(service_notif->service_path));
|
||||
qmi_data->svc_event_wq = create_singlethread_workqueue(subsys);
|
||||
if (!qmi_data->svc_event_wq) {
|
||||
rc = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
qmi_data->ind_ack_wq = alloc_ordered_workqueue("%s_pdr_wq", WQ_HIGHPRI,
|
||||
subsys);
|
||||
if (!qmi_data->ind_ack_wq) {
|
||||
rc = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
INIT_WORK(&qmi_data->new_server, new_server_work);
|
||||
INIT_WORK(&qmi_data->del_server, del_server_work);
|
||||
|
||||
*curr_state = service_notif->curr_state =
|
||||
SERVREG_NOTIF_SERVICE_STATE_UNINIT_V01;
|
||||
|
||||
rc = qmi_handle_init(&qmi_data->clnt_handle,
|
||||
SERVREG_NOTIF_STATE_UPDATED_IND_MSG_LEN,
|
||||
&server_ops,
|
||||
qmi_indication_handler);
|
||||
if (rc < 0) {
|
||||
pr_err("Service Notifier qmi handle init failed rc:%ld\n", rc);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
qmi_add_lookup(&qmi_data->clnt_handle,
|
||||
SERVREG_NOTIF_SERVICE_ID,
|
||||
SERVREG_NOTIF_SERVICE_VERS_V01,
|
||||
instance_id);
|
||||
|
||||
qmi_data->ssr_notifier.notifier_call = ssr_event_notify;
|
||||
qmi_data->ssr_handle = subsys_notif_register_notifier(subsys,
|
||||
&qmi_data->ssr_notifier);
|
||||
if (IS_ERR(qmi_data->ssr_handle)) {
|
||||
pr_err("SSR notif register for %s failed(instance-id: %d)\n",
|
||||
subsys, qmi_data->instance_id);
|
||||
rc = PTR_ERR(qmi_data->ssr_handle);
|
||||
qmi_handle_release(&qmi_data->clnt_handle);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mutex_lock(&qmi_list_lock);
|
||||
INIT_LIST_HEAD(&qmi_data->list);
|
||||
list_add_tail(&qmi_data->list, &qmi_client_list);
|
||||
mutex_unlock(&qmi_list_lock);
|
||||
|
||||
add_service_list:
|
||||
srcu_init_notifier_head(&service_notif->service_notif_rcvr_list);
|
||||
|
||||
mutex_lock(&service_list_lock);
|
||||
INIT_LIST_HEAD(&service_notif->list);
|
||||
list_add_tail(&service_notif->list, &service_list);
|
||||
mutex_unlock(&service_list_lock);
|
||||
|
||||
return service_notif;
|
||||
exit:
|
||||
if (qmi_data->ind_ack_wq)
|
||||
destroy_workqueue(qmi_data->ind_ack_wq);
|
||||
if (qmi_data->svc_event_wq)
|
||||
destroy_workqueue(qmi_data->svc_event_wq);
|
||||
kfree(qmi_data);
|
||||
kfree(service_notif);
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
static int send_pd_restart_req(const char *service_path,
|
||||
struct qmi_client_info *data)
|
||||
{
|
||||
struct qmi_servreg_notif_restart_pd_req_msg_v01 req;
|
||||
struct qmi_servreg_notif_register_listener_resp_msg_v01
|
||||
resp = { { 0, 0 } };
|
||||
struct qmi_txn txn;
|
||||
int rc;
|
||||
|
||||
snprintf(req.service_name, ARRAY_SIZE(req.service_name), "%s",
|
||||
service_path);
|
||||
|
||||
rc = qmi_txn_init(&data->clnt_handle, &txn,
|
||||
qmi_servreg_notif_restart_pd_resp_msg_v01_ei,
|
||||
&resp);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s QMI tx init failed , ret - %d\n", service_path, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qmi_send_request(&data->clnt_handle, &data->s_addr,
|
||||
&txn, QMI_SERVREG_NOTIF_RESTART_PD_REQ_V01,
|
||||
QMI_SERVREG_NOTIF_RESTART_PD_REQ_MSG_V01_MAX_MSG_LEN,
|
||||
qmi_servreg_notif_restart_pd_req_msg_v01_ei,
|
||||
&req);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: QMI send req failed, ret - %d\n",
|
||||
service_path, rc);
|
||||
qmi_txn_cancel(&txn);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT));
|
||||
if (rc < 0) {
|
||||
pr_err("%s: QMI qmi txn wait failed for client, ret - %d\n",
|
||||
service_path, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Check response if PDR is disabled */
|
||||
if (resp.resp.result == QMI_RESULT_FAILURE_V01 &&
|
||||
resp.resp.error == QMI_ERR_DISABLED_V01) {
|
||||
pr_err("PD restart is disabled 0x%x\n", resp.resp.error);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
/* Check the response for other error case*/
|
||||
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
|
||||
pr_err("QMI request for PD restart failed 0x%x\n",
|
||||
resp.resp.error);
|
||||
return -EREMOTEIO;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* service_notif_pd_restart() - Request PD restart
|
||||
* @service_path: Individual service identifier path for which restart is
|
||||
* being requested.
|
||||
* @instance_id: Instance id specific to a subsystem.
|
||||
*
|
||||
* @return: >=0 on success, standard Linux error codes on failure.
|
||||
*/
|
||||
int service_notif_pd_restart(const char *service_path, int instance_id)
|
||||
{
|
||||
struct qmi_client_info *tmp;
|
||||
int rc = 0;
|
||||
|
||||
list_for_each_entry(tmp, &qmi_client_list, list) {
|
||||
if (tmp->instance_id == instance_id && !strcmp
|
||||
(tmp->service_path, service_path)) {
|
||||
if (tmp->service_connected) {
|
||||
pr_info("Restarting service %s, instance-id %d\n",
|
||||
service_path, instance_id);
|
||||
rc = send_pd_restart_req(service_path, tmp);
|
||||
} else
|
||||
pr_info("Service %s is not connected\n",
|
||||
service_path);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(service_notif_pd_restart);
|
||||
|
||||
/* service_notif_register_notifier() - Register a notifier for a service
|
||||
* On success, it returns back a handle. It takes the following arguments:
|
||||
* service_path: Individual service identifier path for which a client
|
||||
* registers for notifications.
|
||||
* instance_id: Instance id specific to a subsystem.
|
||||
* current_state: Current state of service returned by the registration
|
||||
* process.
|
||||
* notifier block: notifier callback for service events.
|
||||
*/
|
||||
void *service_notif_register_notifier(const char *service_path, int instance_id,
|
||||
struct notifier_block *nb, int *curr_state)
|
||||
{
|
||||
struct service_notif_info *service_notif;
|
||||
int ret = 0;
|
||||
|
||||
if (!service_path || !instance_id || !nb)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
mutex_lock(¬if_add_lock);
|
||||
service_notif = _find_service_info(service_path);
|
||||
if (!service_notif) {
|
||||
service_notif = (struct service_notif_info *)add_service_notif(
|
||||
service_path,
|
||||
instance_id,
|
||||
curr_state);
|
||||
if (IS_ERR(service_notif))
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = srcu_notifier_chain_register(
|
||||
&service_notif->service_notif_rcvr_list, nb);
|
||||
*curr_state = service_notif->curr_state;
|
||||
if (ret < 0)
|
||||
service_notif = ERR_PTR(ret);
|
||||
exit:
|
||||
mutex_unlock(¬if_add_lock);
|
||||
return service_notif;
|
||||
}
|
||||
EXPORT_SYMBOL(service_notif_register_notifier);
|
||||
|
||||
static void remove_service_notif(struct service_notif_info *service_notif)
|
||||
{
|
||||
mutex_lock(&service_list_lock);
|
||||
list_del(&service_notif->list);
|
||||
mutex_unlock(&service_list_lock);
|
||||
|
||||
kfree(service_notif);
|
||||
}
|
||||
|
||||
/* service_notif_unregister_notifier() - Unregister a notifier for a service.
|
||||
* service_notif_handle - The notifier handler that was provided by the
|
||||
* service_notif_register_notifier function when the
|
||||
* client registered for notifications.
|
||||
* nb - The notifier block that was previously used during the registration.
|
||||
*/
|
||||
int service_notif_unregister_notifier(void *service_notif_handle,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
struct service_notif_info *service_notif;
|
||||
|
||||
if (!service_notif_handle || !nb)
|
||||
return -EINVAL;
|
||||
|
||||
service_notif = (struct service_notif_info *)service_notif_handle;
|
||||
if (!service_notif)
|
||||
return -EINVAL;
|
||||
|
||||
ret = srcu_notifier_chain_unregister(
|
||||
&service_notif->service_notif_rcvr_list, nb);
|
||||
|
||||
if (!ret)
|
||||
remove_service_notif(service_notif);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(service_notif_unregister_notifier);
|
||||
|
||||
static void __exit service_notifier_exit(void)
|
||||
{
|
||||
struct qmi_client_info *tmp, *qmi_data;
|
||||
|
||||
mutex_lock(&qmi_list_lock);
|
||||
list_for_each_entry_safe(qmi_data, tmp, &qmi_client_list, list) {
|
||||
list_del(&qmi_data->list);
|
||||
|
||||
qmi_handle_release(&qmi_data->clnt_handle);
|
||||
|
||||
flush_work(&qmi_data->new_server);
|
||||
flush_work(&qmi_data->del_server);
|
||||
|
||||
if (qmi_data->ind_ack_wq)
|
||||
destroy_workqueue(qmi_data->ind_ack_wq);
|
||||
if (qmi_data->svc_event_wq)
|
||||
destroy_workqueue(qmi_data->svc_event_wq);
|
||||
|
||||
kfree(qmi_data);
|
||||
}
|
||||
mutex_unlock(&qmi_list_lock);
|
||||
}
|
||||
module_exit(service_notifier_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Service Notifier driver");
|
||||
1297
drivers/soc/qcom/subsys-pil-tz.c
Normal file
1297
drivers/soc/qcom/subsys-pil-tz.c
Normal file
File diff suppressed because it is too large
Load diff
327
drivers/soc/qcom/subsystem_notif.c
Normal file
327
drivers/soc/qcom/subsystem_notif.c
Normal 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(¬if_lock);
|
||||
list_for_each_entry(subsys, &subsystem_list, list)
|
||||
if (!strcmp(subsys->name, subsys_name)) {
|
||||
mutex_unlock(¬if_lock);
|
||||
return subsys;
|
||||
}
|
||||
mutex_unlock(¬if_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *subsys_notif_register_notifier(
|
||||
const char *subsys_name, struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
struct subsys_notif_info *subsys = _notif_find_subsys(subsys_name);
|
||||
|
||||
if (!subsys) {
|
||||
|
||||
/* Possible first time reference to this subsystem. Add it. */
|
||||
subsys = (struct subsys_notif_info *)
|
||||
subsys_notif_add_subsys(subsys_name);
|
||||
|
||||
if (!subsys)
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
ret = srcu_notifier_chain_register(
|
||||
&subsys->subsys_notif_rcvr_list, nb);
|
||||
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
return subsys;
|
||||
}
|
||||
EXPORT_SYMBOL(subsys_notif_register_notifier);
|
||||
|
||||
int subsys_notif_unregister_notifier(void *subsys_handle,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
struct subsys_notif_info *subsys =
|
||||
(struct subsys_notif_info *)subsys_handle;
|
||||
|
||||
if (!subsys)
|
||||
return -EINVAL;
|
||||
|
||||
ret = srcu_notifier_chain_unregister(
|
||||
&subsys->subsys_notif_rcvr_list, nb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(subsys_notif_unregister_notifier);
|
||||
|
||||
void subsys_send_early_notifications(void *early_notif_handle)
|
||||
{
|
||||
struct subsys_early_notif_info *early_info = early_notif_handle;
|
||||
unsigned long flags;
|
||||
unsigned int i;
|
||||
void (*notif_cb)(void *data);
|
||||
|
||||
if (!early_notif_handle)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&early_info->cb_lock, flags);
|
||||
for (i = 0; i < NUM_EARLY_NOTIFS; i++) {
|
||||
notif_cb = early_info->early_notif_cb[i];
|
||||
if (notif_cb)
|
||||
notif_cb(early_info->data[i]);
|
||||
}
|
||||
spin_unlock_irqrestore(&early_info->cb_lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL(subsys_send_early_notifications);
|
||||
|
||||
static bool valid_early_notif(enum early_subsys_notif_type notif_type)
|
||||
{
|
||||
return notif_type >= 0 && notif_type < NUM_EARLY_NOTIFS;
|
||||
}
|
||||
|
||||
/**
|
||||
* The early_notif_cb parameter must point to a function that conforms to the
|
||||
* same constraints placed upon interrupt handlers, as the function will be
|
||||
* called in an atomic context (i.e. these functions must not sleep or block).
|
||||
*/
|
||||
int subsys_register_early_notifier(const char *subsys_name,
|
||||
enum early_subsys_notif_type notif_type,
|
||||
void (*early_notif_cb)(void *), void *data)
|
||||
{
|
||||
struct subsys_notif_info *subsys;
|
||||
struct subsys_early_notif_info *early_notif_info;
|
||||
unsigned long flags;
|
||||
int rc = 0;
|
||||
|
||||
if (!subsys_name || !early_notif_cb || !valid_early_notif(notif_type))
|
||||
return -EINVAL;
|
||||
|
||||
subsys = _notif_find_subsys(subsys_name);
|
||||
if (!subsys)
|
||||
return -EINVAL;
|
||||
|
||||
early_notif_info = &subsys->early_notif_info;
|
||||
spin_lock_irqsave(&early_notif_info->cb_lock, flags);
|
||||
if (early_notif_info->early_notif_cb[notif_type]) {
|
||||
rc = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
early_notif_info->early_notif_cb[notif_type] = early_notif_cb;
|
||||
early_notif_info->data[notif_type] = data;
|
||||
out:
|
||||
spin_unlock_irqrestore(&early_notif_info->cb_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(subsys_register_early_notifier);
|
||||
|
||||
int subsys_unregister_early_notifier(const char *subsys_name, enum
|
||||
early_subsys_notif_type notif_type)
|
||||
{
|
||||
struct subsys_notif_info *subsys;
|
||||
struct subsys_early_notif_info *early_notif_info;
|
||||
unsigned long flags;
|
||||
|
||||
if (!subsys_name || !valid_early_notif(notif_type))
|
||||
return -EINVAL;
|
||||
|
||||
subsys = _notif_find_subsys(subsys_name);
|
||||
if (!subsys)
|
||||
return -EINVAL;
|
||||
|
||||
early_notif_info = &subsys->early_notif_info;
|
||||
spin_lock_irqsave(&early_notif_info->cb_lock, flags);
|
||||
early_notif_info->early_notif_cb[notif_type] = NULL;
|
||||
early_notif_info->data[notif_type] = NULL;
|
||||
spin_unlock_irqrestore(&early_notif_info->cb_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(subsys_unregister_early_notifier);
|
||||
|
||||
void *subsys_get_early_notif_info(const char *subsys_name)
|
||||
{
|
||||
struct subsys_notif_info *subsys;
|
||||
|
||||
if (!subsys_name)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
subsys = _notif_find_subsys(subsys_name);
|
||||
|
||||
if (!subsys)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return &subsys->early_notif_info;
|
||||
}
|
||||
EXPORT_SYMBOL(subsys_get_early_notif_info);
|
||||
|
||||
void *subsys_notif_add_subsys(const char *subsys_name)
|
||||
{
|
||||
struct subsys_notif_info *subsys = NULL;
|
||||
|
||||
if (!subsys_name)
|
||||
goto done;
|
||||
|
||||
mutex_lock(¬if_add_lock);
|
||||
|
||||
subsys = _notif_find_subsys(subsys_name);
|
||||
|
||||
if (subsys) {
|
||||
mutex_unlock(¬if_add_lock);
|
||||
goto done;
|
||||
}
|
||||
|
||||
subsys = kmalloc(sizeof(struct subsys_notif_info), GFP_KERNEL);
|
||||
|
||||
if (!subsys) {
|
||||
mutex_unlock(¬if_add_lock);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
strlcpy(subsys->name, subsys_name, ARRAY_SIZE(subsys->name));
|
||||
|
||||
srcu_init_notifier_head(&subsys->subsys_notif_rcvr_list);
|
||||
|
||||
memset(&subsys->early_notif_info, 0, sizeof(struct
|
||||
subsys_early_notif_info));
|
||||
spin_lock_init(&subsys->early_notif_info.cb_lock);
|
||||
INIT_LIST_HEAD(&subsys->list);
|
||||
|
||||
mutex_lock(¬if_lock);
|
||||
list_add_tail(&subsys->list, &subsystem_list);
|
||||
mutex_unlock(¬if_lock);
|
||||
|
||||
#if defined(SUBSYS_RESTART_DEBUG)
|
||||
subsys_notif_reg_test_notifier(subsys->name);
|
||||
#endif
|
||||
|
||||
mutex_unlock(¬if_add_lock);
|
||||
|
||||
done:
|
||||
return subsys;
|
||||
}
|
||||
EXPORT_SYMBOL(subsys_notif_add_subsys);
|
||||
|
||||
int subsys_notif_queue_notification(void *subsys_handle,
|
||||
enum subsys_notif_type notif_type,
|
||||
void *data)
|
||||
{
|
||||
struct subsys_notif_info *subsys = subsys_handle;
|
||||
|
||||
if (!subsys)
|
||||
return -EINVAL;
|
||||
|
||||
if (notif_type < 0 || notif_type >= SUBSYS_NOTIF_TYPE_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
return srcu_notifier_call_chain(&subsys->subsys_notif_rcvr_list,
|
||||
notif_type, data);
|
||||
}
|
||||
EXPORT_SYMBOL(subsys_notif_queue_notification);
|
||||
|
||||
#if defined(SUBSYS_RESTART_DEBUG)
|
||||
static const char *notif_to_string(enum subsys_notif_type notif_type)
|
||||
{
|
||||
switch (notif_type) {
|
||||
|
||||
case SUBSYS_BEFORE_SHUTDOWN:
|
||||
return __stringify(SUBSYS_BEFORE_SHUTDOWN);
|
||||
|
||||
case SUBSYS_AFTER_SHUTDOWN:
|
||||
return __stringify(SUBSYS_AFTER_SHUTDOWN);
|
||||
|
||||
case SUBSYS_BEFORE_POWERUP:
|
||||
return __stringify(SUBSYS_BEFORE_POWERUP);
|
||||
|
||||
case SUBSYS_AFTER_POWERUP:
|
||||
return __stringify(SUBSYS_AFTER_POWERUP);
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static int subsys_notifier_test_call(struct notifier_block *this,
|
||||
unsigned long code,
|
||||
void *data)
|
||||
{
|
||||
switch (code) {
|
||||
|
||||
default:
|
||||
pr_warn("%s: Notification %s from subsystem %pK\n",
|
||||
__func__, notif_to_string(code), data);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block nb = {
|
||||
.notifier_call = subsys_notifier_test_call,
|
||||
};
|
||||
|
||||
static void subsys_notif_reg_test_notifier(const char *subsys_name)
|
||||
{
|
||||
void *handle = subsys_notif_register_notifier(subsys_name, &nb);
|
||||
|
||||
pr_warn("%s: Registered test notifier, handle=%pK\n",
|
||||
__func__, handle);
|
||||
}
|
||||
#endif
|
||||
|
||||
MODULE_DESCRIPTION("Subsystem Restart Notifier");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
1957
drivers/soc/qcom/subsystem_restart.c
Normal file
1957
drivers/soc/qcom/subsystem_restart.c
Normal file
File diff suppressed because it is too large
Load diff
719
drivers/soc/qcom/sysmon-qmi.c
Normal file
719
drivers/soc/qcom/sysmon-qmi.c
Normal 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");
|
||||
52
include/soc/qcom/ramdump.h
Normal file
52
include/soc/qcom/ramdump.h
Normal 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
|
||||
89
include/soc/qcom/service-locator.h
Normal file
89
include/soc/qcom/service-locator.h
Normal 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
|
||||
78
include/soc/qcom/service-notifier.h
Normal file
78
include/soc/qcom/service-notifier.h
Normal 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
|
||||
118
include/soc/qcom/subsystem_notif.h
Normal file
118
include/soc/qcom/subsystem_notif.h
Normal 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
|
||||
236
include/soc/qcom/subsystem_restart.h
Normal file
236
include/soc/qcom/subsystem_restart.h
Normal 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
102
include/soc/qcom/sysmon.h
Normal 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
|
||||
56
include/trace/events/trace_msm_pil_event.h
Normal file
56
include/trace/events/trace_msm_pil_event.h
Normal 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>
|
||||
42
include/trace/events/trace_msm_ssr_event.h
Normal file
42
include/trace/events/trace_msm_ssr_event.h
Normal 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>
|
||||
Loading…
Reference in a new issue