mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-11 23:24:22 -04:00
soc: qcom: Refactor subsystem registration process
Restructure subsystem registration process and IRQ registration step such that, both subsystem restart framework and secure pil drivers should be able to handle all the asychronous events coming from subsystem. If both subsystem restart framework and secure pil drivers are not in proper state to handle asychronous events from subsystem leading to kernel faults. Change-Id: Iacda38a9d7d4aeb4549f45533c91791f096c3b1c Signed-off-by: Prasad Sodagudi <psodagud@codeaurora.org> Signed-off-by: Prakruthi Deepak Heragu <pheragu@codeaurora.org>
This commit is contained in:
parent
b38b5109bf
commit
65a9fe67f3
3 changed files with 330 additions and 363 deletions
|
|
@ -27,7 +27,6 @@
|
|||
#include <soc/qcom/subsystem_restart.h>
|
||||
#include <soc/qcom/subsystem_notif.h>
|
||||
#include <soc/qcom/sysmon.h>
|
||||
#include <linux/soc/qcom/smem_state.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of.h>
|
||||
#include <asm/current.h>
|
||||
|
|
@ -43,9 +42,6 @@
|
|||
static uint disable_restart_work;
|
||||
module_param(disable_restart_work, uint, 0644);
|
||||
|
||||
static int enable_debug;
|
||||
module_param(enable_debug, int, 0644);
|
||||
|
||||
/* The maximum shutdown timeout is the product of MAX_LOOPS and DELAY_MS. */
|
||||
#define SHUTDOWN_ACK_MAX_LOOPS 100
|
||||
#define SHUTDOWN_ACK_DELAY_MS 100
|
||||
|
|
@ -176,7 +172,6 @@ struct restart_log {
|
|||
* @restart_order: order of other devices this devices restarts with
|
||||
* @crash_count: number of times the device has crashed
|
||||
* @do_ramdump_on_put: ramdump on subsystem_put() if true
|
||||
* @err_ready: completion variable to record error ready from subsystem
|
||||
* @crashed: indicates if subsystem has crashed
|
||||
* @notif_state: current state of subsystem in terms of subsys notifications
|
||||
*/
|
||||
|
|
@ -200,7 +195,6 @@ struct subsys_device {
|
|||
bool do_ramdump_on_put;
|
||||
struct cdev char_dev;
|
||||
dev_t dev_no;
|
||||
struct completion err_ready;
|
||||
enum crash_status crashed;
|
||||
int notif_state;
|
||||
struct list_head list;
|
||||
|
|
@ -211,12 +205,6 @@ static struct subsys_device *to_subsys(struct device *d)
|
|||
return container_of(d, struct subsys_device, dev);
|
||||
}
|
||||
|
||||
void complete_err_ready(struct subsys_device *subsys)
|
||||
{
|
||||
complete(&subsys->err_ready);
|
||||
}
|
||||
EXPORT_SYMBOL(complete_err_ready);
|
||||
|
||||
static struct subsys_tracking *subsys_get_track(struct subsys_device *subsys)
|
||||
{
|
||||
struct subsys_soc_restart_order *order = subsys->restart_order;
|
||||
|
|
@ -637,62 +625,6 @@ static void notify_each_subsys_device(struct subsys_device **list,
|
|||
}
|
||||
}
|
||||
|
||||
static void enable_all_irqs(struct subsys_device *dev)
|
||||
{
|
||||
if (dev->desc->err_ready_irq)
|
||||
enable_irq(dev->desc->err_ready_irq);
|
||||
if (dev->desc->wdog_bite_irq && dev->desc->wdog_bite_handler) {
|
||||
enable_irq(dev->desc->wdog_bite_irq);
|
||||
irq_set_irq_wake(dev->desc->wdog_bite_irq, 1);
|
||||
}
|
||||
if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler)
|
||||
enable_irq(dev->desc->err_fatal_irq);
|
||||
if (dev->desc->stop_ack_irq && dev->desc->stop_ack_handler)
|
||||
enable_irq(dev->desc->stop_ack_irq);
|
||||
if (dev->desc->shutdown_ack_irq && dev->desc->shutdown_ack_handler)
|
||||
enable_irq(dev->desc->shutdown_ack_irq);
|
||||
if (dev->desc->ramdump_disable_irq &&
|
||||
dev->desc->ramdump_disable_handler)
|
||||
enable_irq(dev->desc->ramdump_disable_irq);
|
||||
if (dev->desc->generic_irq && dev->desc->generic_handler) {
|
||||
enable_irq(dev->desc->generic_irq);
|
||||
irq_set_irq_wake(dev->desc->generic_irq, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void disable_all_irqs(struct subsys_device *dev)
|
||||
{
|
||||
if (dev->desc->err_ready_irq)
|
||||
disable_irq(dev->desc->err_ready_irq);
|
||||
if (dev->desc->wdog_bite_irq && dev->desc->wdog_bite_handler) {
|
||||
disable_irq(dev->desc->wdog_bite_irq);
|
||||
irq_set_irq_wake(dev->desc->wdog_bite_irq, 0);
|
||||
}
|
||||
if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler)
|
||||
disable_irq(dev->desc->err_fatal_irq);
|
||||
if (dev->desc->stop_ack_irq && dev->desc->stop_ack_handler)
|
||||
disable_irq(dev->desc->stop_ack_irq);
|
||||
if (dev->desc->shutdown_ack_irq && dev->desc->shutdown_ack_handler)
|
||||
disable_irq(dev->desc->shutdown_ack_irq);
|
||||
if (dev->desc->generic_irq && dev->desc->generic_handler) {
|
||||
disable_irq(dev->desc->generic_irq);
|
||||
irq_set_irq_wake(dev->desc->generic_irq, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int wait_for_err_ready(struct subsys_device *subsys)
|
||||
{
|
||||
/*
|
||||
* If subsys is using generic_irq in which case err_ready_irq will be 0,
|
||||
* don't return.
|
||||
*/
|
||||
if ((subsys->desc->generic_irq <= 0 && !subsys->desc->err_ready_irq) ||
|
||||
enable_debug == 1)
|
||||
return 0;
|
||||
|
||||
return wait_for_completion_interruptible(&subsys->err_ready);
|
||||
}
|
||||
|
||||
static int subsystem_shutdown(struct subsys_device *dev, void *data)
|
||||
{
|
||||
const char *name = dev->desc->name;
|
||||
|
|
@ -712,7 +644,6 @@ static int subsystem_shutdown(struct subsys_device *dev, void *data)
|
|||
}
|
||||
dev->crash_count++;
|
||||
subsys_set_state(dev, SUBSYS_OFFLINE);
|
||||
disable_all_irqs(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -742,7 +673,6 @@ static int subsystem_powerup(struct subsys_device *dev, void *data)
|
|||
int ret;
|
||||
|
||||
pr_info("[%s:%d]: Powering up %s\n", current->comm, current->pid, name);
|
||||
reinit_completion(&dev->err_ready);
|
||||
|
||||
ret = dev->desc->powerup(dev->desc);
|
||||
if (ret < 0) {
|
||||
|
|
@ -759,9 +689,7 @@ static int subsystem_powerup(struct subsys_device *dev, void *data)
|
|||
pr_err("Powerup failure on %s\n", name);
|
||||
return ret;
|
||||
}
|
||||
enable_all_irqs(dev);
|
||||
|
||||
ret = wait_for_err_ready(dev);
|
||||
if (ret) {
|
||||
notify_each_subsys_device(&dev, 1, SUBSYS_POWERUP_FAILURE,
|
||||
NULL);
|
||||
|
|
@ -803,32 +731,12 @@ static int subsys_start(struct subsys_device *subsys)
|
|||
|
||||
notify_each_subsys_device(&subsys, 1, SUBSYS_BEFORE_POWERUP,
|
||||
NULL);
|
||||
|
||||
reinit_completion(&subsys->err_ready);
|
||||
ret = subsys->desc->powerup(subsys->desc);
|
||||
if (ret) {
|
||||
notify_each_subsys_device(&subsys, 1, SUBSYS_POWERUP_FAILURE,
|
||||
NULL);
|
||||
return ret;
|
||||
}
|
||||
enable_all_irqs(subsys);
|
||||
|
||||
if (subsys->desc->is_not_loadable) {
|
||||
subsys_set_state(subsys, SUBSYS_ONLINE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = wait_for_err_ready(subsys);
|
||||
if (ret) {
|
||||
/* pil-boot succeeded but we need to shutdown
|
||||
* the device because error ready timed out.
|
||||
*/
|
||||
notify_each_subsys_device(&subsys, 1, SUBSYS_POWERUP_FAILURE,
|
||||
NULL);
|
||||
subsys->desc->shutdown(subsys->desc, false);
|
||||
disable_all_irqs(subsys);
|
||||
return ret;
|
||||
}
|
||||
subsys_set_state(subsys, SUBSYS_ONLINE);
|
||||
|
||||
notify_each_subsys_device(&subsys, 1, SUBSYS_AFTER_POWERUP,
|
||||
|
|
@ -855,7 +763,6 @@ static void subsys_stop(struct subsys_device *subsys)
|
|||
|
||||
subsys->desc->shutdown(subsys->desc, false);
|
||||
subsys_set_state(subsys, SUBSYS_OFFLINE);
|
||||
disable_all_irqs(subsys);
|
||||
notify_each_subsys_device(&subsys, 1, SUBSYS_AFTER_SHUTDOWN, NULL);
|
||||
}
|
||||
|
||||
|
|
@ -1351,19 +1258,6 @@ static void subsys_device_release(struct device *dev)
|
|||
ida_simple_remove(&subsys_ida, subsys->id);
|
||||
kfree(subsys);
|
||||
}
|
||||
static irqreturn_t subsys_err_ready_intr_handler(int irq, void *subsys)
|
||||
{
|
||||
struct subsys_device *subsys_dev = subsys;
|
||||
|
||||
dev_info(subsys_dev->desc->dev,
|
||||
"Subsystem error monitoring/handling services are up\n");
|
||||
|
||||
if (subsys_dev->desc->is_not_loadable)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
complete(&subsys_dev->err_ready);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int subsys_char_device_add(struct subsys_device *subsys_dev)
|
||||
{
|
||||
|
|
@ -1511,84 +1405,12 @@ err:
|
|||
return tmp;
|
||||
}
|
||||
|
||||
static int __get_irq(struct subsys_desc *desc, const char *prop,
|
||||
unsigned int *irq)
|
||||
{
|
||||
int irql = 0;
|
||||
struct device_node *dnode = desc->dev->of_node;
|
||||
|
||||
if (of_property_match_string(dnode, "interrupt-names", prop) < 0)
|
||||
return -ENOENT;
|
||||
|
||||
irql = of_irq_get_byname(dnode, prop);
|
||||
if (irql < 0) {
|
||||
pr_err("[%s]: Error getting IRQ \"%s\"\n", desc->name,
|
||||
prop);
|
||||
return irql;
|
||||
}
|
||||
*irq = irql;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __get_smem_state(struct subsys_desc *desc, const char *prop,
|
||||
int *smem_bit)
|
||||
{
|
||||
struct device_node *dnode = desc->dev->of_node;
|
||||
|
||||
if (of_find_property(dnode, "qcom,smem-states", NULL)) {
|
||||
desc->state = qcom_smem_state_get(desc->dev, prop, smem_bit);
|
||||
if (IS_ERR_OR_NULL(desc->state)) {
|
||||
pr_err("Could not get smem-states %s\n", prop);
|
||||
return PTR_ERR(desc->state);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int subsys_parse_devicetree(struct subsys_desc *desc)
|
||||
{
|
||||
struct subsys_soc_restart_order *order;
|
||||
int ret;
|
||||
struct platform_device *pdev = container_of(desc->dev,
|
||||
struct platform_device, dev);
|
||||
|
||||
ret = __get_irq(desc, "qcom,err-fatal", &desc->err_fatal_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(desc, "qcom,err-ready", &desc->err_ready_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(desc, "qcom,stop-ack", &desc->stop_ack_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(desc, "qcom,ramdump-disabled",
|
||||
&desc->ramdump_disable_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(desc, "qcom,shutdown-ack", &desc->shutdown_ack_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(desc, "qcom,wdog", &desc->wdog_bite_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_smem_state(desc, "qcom,force-stop", &desc->force_stop_bit);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
if (of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,pil-generic-irq-handler")) {
|
||||
ret = platform_get_irq(pdev, 0);
|
||||
if (ret > 0)
|
||||
desc->generic_irq = ret;
|
||||
}
|
||||
|
||||
desc->ignore_ssr_failure = of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,ignore-ssr-failure");
|
||||
|
||||
|
|
@ -1610,130 +1432,6 @@ static int subsys_parse_devicetree(struct subsys_desc *desc)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int subsys_setup_irqs(struct subsys_device *subsys)
|
||||
{
|
||||
struct subsys_desc *desc = subsys->desc;
|
||||
int ret;
|
||||
|
||||
if (desc->err_fatal_irq && desc->err_fatal_handler) {
|
||||
ret = devm_request_threaded_irq(desc->dev, desc->err_fatal_irq,
|
||||
NULL,
|
||||
desc->err_fatal_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
desc->name, desc);
|
||||
if (ret < 0) {
|
||||
dev_err(desc->dev, "[%s]: Unable to register error fatal IRQ handler: %d, irq is %d\n",
|
||||
desc->name, ret, desc->err_fatal_irq);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(desc->err_fatal_irq);
|
||||
}
|
||||
|
||||
if (desc->stop_ack_irq && desc->stop_ack_handler) {
|
||||
ret = devm_request_threaded_irq(desc->dev, desc->stop_ack_irq,
|
||||
NULL,
|
||||
desc->stop_ack_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
desc->name, desc);
|
||||
if (ret < 0) {
|
||||
dev_err(desc->dev, "[%s]: Unable to register stop ack handler: %d\n",
|
||||
desc->name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(desc->stop_ack_irq);
|
||||
}
|
||||
|
||||
if (desc->wdog_bite_irq && desc->wdog_bite_handler) {
|
||||
ret = devm_request_irq(desc->dev, desc->wdog_bite_irq,
|
||||
desc->wdog_bite_handler,
|
||||
IRQF_TRIGGER_RISING, desc->name, desc);
|
||||
if (ret < 0) {
|
||||
dev_err(desc->dev, "[%s]: Unable to register wdog bite handler: %d\n",
|
||||
desc->name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(desc->wdog_bite_irq);
|
||||
}
|
||||
|
||||
if (desc->shutdown_ack_irq && desc->shutdown_ack_handler) {
|
||||
ret = devm_request_threaded_irq(desc->dev,
|
||||
desc->shutdown_ack_irq,
|
||||
NULL,
|
||||
desc->shutdown_ack_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
desc->name, desc);
|
||||
if (ret < 0) {
|
||||
dev_err(desc->dev, "[%s]: Unable to register shutdown ack handler: %d\n",
|
||||
desc->name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(desc->shutdown_ack_irq);
|
||||
}
|
||||
|
||||
if (desc->ramdump_disable_irq && desc->ramdump_disable_handler) {
|
||||
ret = devm_request_threaded_irq(desc->dev,
|
||||
desc->ramdump_disable_irq,
|
||||
NULL,
|
||||
desc->ramdump_disable_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
desc->name, desc);
|
||||
if (ret < 0) {
|
||||
dev_err(desc->dev, "[%s]: Unable to register shutdown ack handler: %d\n",
|
||||
desc->name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(desc->ramdump_disable_irq);
|
||||
}
|
||||
|
||||
if (desc->generic_irq && desc->generic_handler) {
|
||||
ret = devm_request_irq(desc->dev, desc->generic_irq,
|
||||
desc->generic_handler,
|
||||
IRQF_TRIGGER_HIGH, desc->name, desc);
|
||||
if (ret < 0) {
|
||||
dev_err(desc->dev, "[%s]: Unable to register generic irq handler: %d\n",
|
||||
desc->name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(desc->generic_irq);
|
||||
}
|
||||
|
||||
if (desc->err_ready_irq) {
|
||||
ret = devm_request_threaded_irq(desc->dev,
|
||||
desc->err_ready_irq,
|
||||
NULL,
|
||||
subsys_err_ready_intr_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
"error_ready_interrupt", subsys);
|
||||
if (ret < 0) {
|
||||
dev_err(desc->dev,
|
||||
"[%s]: Unable to register err ready handler\n",
|
||||
desc->name);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(desc->err_ready_irq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void subsys_free_irqs(struct subsys_device *subsys)
|
||||
{
|
||||
struct subsys_desc *desc = subsys->desc;
|
||||
|
||||
if (desc->err_fatal_irq && desc->err_fatal_handler)
|
||||
devm_free_irq(desc->dev, desc->err_fatal_irq, desc);
|
||||
if (desc->stop_ack_irq && desc->stop_ack_handler)
|
||||
devm_free_irq(desc->dev, desc->stop_ack_irq, desc);
|
||||
if (desc->shutdown_ack_irq && desc->shutdown_ack_handler)
|
||||
devm_free_irq(desc->dev, desc->shutdown_ack_irq, desc);
|
||||
if (desc->ramdump_disable_irq && desc->ramdump_disable_handler)
|
||||
devm_free_irq(desc->dev, desc->ramdump_disable_irq, desc);
|
||||
if (desc->wdog_bite_irq && desc->wdog_bite_handler)
|
||||
devm_free_irq(desc->dev, desc->wdog_bite_irq, desc);
|
||||
if (desc->err_ready_irq)
|
||||
devm_free_irq(desc->dev, desc->err_ready_irq, subsys);
|
||||
}
|
||||
|
||||
struct subsys_device *subsys_register(struct subsys_desc *desc)
|
||||
{
|
||||
struct subsys_device *subsys;
|
||||
|
|
@ -1775,7 +1473,6 @@ struct subsys_device *subsys_register(struct subsys_desc *desc)
|
|||
dev_set_name(&subsys->dev, "subsys%d", subsys->id);
|
||||
|
||||
mutex_init(&subsys->track.lock);
|
||||
init_completion(&subsys->err_ready);
|
||||
|
||||
ret = device_register(&subsys->dev);
|
||||
if (ret) {
|
||||
|
|
@ -1832,16 +1529,7 @@ struct subsys_device *subsys_register(struct subsys_desc *desc)
|
|||
list_add_tail(&subsys->list, &subsys_list);
|
||||
mutex_unlock(&subsys_list_lock);
|
||||
|
||||
if (ofnode) {
|
||||
ret = subsys_setup_irqs(subsys);
|
||||
if (ret < 0)
|
||||
goto err_setup_irqs;
|
||||
}
|
||||
|
||||
return subsys;
|
||||
err_setup_irqs:
|
||||
if (subsys->desc->edge)
|
||||
sysmon_glink_unregister(desc);
|
||||
err_sysmon_glink_register:
|
||||
sysmon_notifier_unregister(subsys->desc);
|
||||
err_sysmon_notifier:
|
||||
|
|
@ -1873,7 +1561,6 @@ void subsys_unregister(struct subsys_device *subsys)
|
|||
mutex_unlock(&subsys_list_lock);
|
||||
|
||||
if (device) {
|
||||
subsys_free_irqs(subsys);
|
||||
subsys_remove_restart_order(device);
|
||||
}
|
||||
mutex_lock(&subsys->track.lock);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
|
@ -83,6 +84,7 @@ struct reg_info {
|
|||
* @u32 bits_arr[2]: array of bit positions in SCSR registers
|
||||
*/
|
||||
struct pil_tz_data {
|
||||
struct device *dev;
|
||||
struct reg_info *regs;
|
||||
struct reg_info *proxy_regs;
|
||||
struct clk **clks;
|
||||
|
|
@ -98,6 +100,7 @@ struct pil_tz_data {
|
|||
struct icc_path *bus_client;
|
||||
bool enable_bus_scaling;
|
||||
bool keep_proxy_regs_on;
|
||||
struct completion err_ready;
|
||||
struct completion stop_ack;
|
||||
struct completion shutdown_ack;
|
||||
struct pil_desc desc;
|
||||
|
|
@ -109,6 +112,15 @@ struct pil_tz_data {
|
|||
void __iomem *err_status;
|
||||
void __iomem *err_status_spare;
|
||||
u32 bits_arr[2];
|
||||
int err_fatal_irq;
|
||||
int err_ready_irq;
|
||||
int stop_ack_irq;
|
||||
int wdog_bite_irq;
|
||||
int generic_irq;
|
||||
int ramdump_disable_irq;
|
||||
int shutdown_ack_irq;
|
||||
int force_stop_bit;
|
||||
struct qcom_smem_state *state;
|
||||
};
|
||||
|
||||
enum pas_id {
|
||||
|
|
@ -130,6 +142,34 @@ static struct icc_path *scm_perf_client;
|
|||
static int scm_pas_bw_count;
|
||||
static DEFINE_MUTEX(scm_pas_bw_mutex);
|
||||
|
||||
static void subsys_disable_all_irqs(struct pil_tz_data *d);
|
||||
static void subsys_enable_all_irqs(struct pil_tz_data *d);
|
||||
|
||||
static int enable_debug;
|
||||
module_param(enable_debug, int, 0644);
|
||||
|
||||
static int wait_for_err_ready(struct pil_tz_data *d)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* If subsys is using generic_irq in which case err_ready_irq will be 0,
|
||||
* don't return.
|
||||
*/
|
||||
if ((d->generic_irq <= 0 && !d->err_ready_irq) ||
|
||||
enable_debug == 1 || pil_is_timeout_disabled())
|
||||
return 0;
|
||||
|
||||
ret = wait_for_completion_interruptible_timeout(&d->err_ready,
|
||||
msecs_to_jiffies(10000));
|
||||
if (!ret) {
|
||||
pr_err("[%s]: Error ready timed out\n", d->desc.name);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scm_pas_enable_bw(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
|
@ -711,20 +751,21 @@ static int subsys_shutdown(const struct subsys_desc *subsys, bool force_stop)
|
|||
int ret;
|
||||
|
||||
if (!subsys_get_crash_status(d->subsys) && force_stop &&
|
||||
subsys->state) {
|
||||
qcom_smem_state_update_bits(subsys->state,
|
||||
BIT(subsys->force_stop_bit),
|
||||
BIT(subsys->force_stop_bit));
|
||||
d->state) {
|
||||
qcom_smem_state_update_bits(d->state,
|
||||
BIT(d->force_stop_bit),
|
||||
BIT(d->force_stop_bit));
|
||||
ret = wait_for_completion_timeout(&d->stop_ack,
|
||||
msecs_to_jiffies(STOP_ACK_TIMEOUT_MS));
|
||||
if (!ret)
|
||||
pr_warn("Timed out on stop ack from %s.\n",
|
||||
subsys->name);
|
||||
qcom_smem_state_update_bits(subsys->state,
|
||||
BIT(subsys->force_stop_bit), 0);
|
||||
qcom_smem_state_update_bits(d->state,
|
||||
BIT(d->force_stop_bit), 0);
|
||||
}
|
||||
|
||||
pil_shutdown(&d->desc);
|
||||
subsys_disable_all_irqs(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -733,11 +774,28 @@ static int subsys_powerup(const struct subsys_desc *subsys)
|
|||
struct pil_tz_data *d = subsys_to_data(subsys);
|
||||
int ret = 0;
|
||||
|
||||
if (subsys->stop_ack_irq)
|
||||
reinit_completion(&d->err_ready);
|
||||
|
||||
if (d->stop_ack_irq)
|
||||
reinit_completion(&d->stop_ack);
|
||||
|
||||
d->desc.fw_name = subsys->fw_name;
|
||||
ret = pil_boot(&d->desc);
|
||||
if (ret) {
|
||||
pr_err("pil_boot failed for %s\n", d->subsys_desc.name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_info("pil_boot is successful from %s and waiting for error ready\n",
|
||||
d->subsys_desc.name);
|
||||
subsys_enable_all_irqs(d);
|
||||
ret = wait_for_err_ready(d);
|
||||
if (ret) {
|
||||
pr_err("%s failed to get error ready for %s\n", __func__,
|
||||
d->subsys_desc.name);
|
||||
pil_shutdown(&d->desc);
|
||||
subsys_disable_all_irqs(d);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -763,17 +821,28 @@ static void subsys_crash_shutdown(const struct subsys_desc *subsys)
|
|||
{
|
||||
struct pil_tz_data *d = subsys_to_data(subsys);
|
||||
|
||||
if (subsys->state && !subsys_get_crash_status(d->subsys)) {
|
||||
qcom_smem_state_update_bits(subsys->state,
|
||||
BIT(subsys->force_stop_bit),
|
||||
BIT(subsys->force_stop_bit));
|
||||
if (d->state && !subsys_get_crash_status(d->subsys)) {
|
||||
qcom_smem_state_update_bits(d->state,
|
||||
BIT(d->force_stop_bit),
|
||||
BIT(d->force_stop_bit));
|
||||
mdelay(CRASH_STOP_ACK_TO_MS);
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_err_fatal_intr_handler (int irq, void *dev_id)
|
||||
|
||||
static irqreturn_t subsys_err_ready_intr_handler(int irq, void *drv_data)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(dev_id);
|
||||
struct pil_tz_data *d = drv_data;
|
||||
|
||||
pr_info("Subsystem error monitoring/handling services are up from%s\n",
|
||||
d->subsys_desc.name);
|
||||
complete(&d->err_ready);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_err_fatal_intr_handler (int irq, void *drv_data)
|
||||
{
|
||||
struct pil_tz_data *d = drv_data;
|
||||
|
||||
pr_err("Fatal error on %s!\n", d->subsys_desc.name);
|
||||
if (subsys_get_crash_status(d->subsys)) {
|
||||
|
|
@ -788,9 +857,9 @@ static irqreturn_t subsys_err_fatal_intr_handler (int irq, void *dev_id)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_wdog_bite_irq_handler(int irq, void *dev_id)
|
||||
static irqreturn_t subsys_wdog_bite_irq_handler(int irq, void *drv_data)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(dev_id);
|
||||
struct pil_tz_data *d = drv_data;
|
||||
|
||||
if (subsys_get_crash_status(d->subsys))
|
||||
return IRQ_HANDLED;
|
||||
|
|
@ -806,18 +875,18 @@ static irqreturn_t subsys_wdog_bite_irq_handler(int irq, void *dev_id)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_stop_ack_intr_handler(int irq, void *dev_id)
|
||||
static irqreturn_t subsys_stop_ack_intr_handler(int irq, void *drv_data)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(dev_id);
|
||||
struct pil_tz_data *d = drv_data;
|
||||
|
||||
pr_info("Received stop ack interrupt from %s\n", d->subsys_desc.name);
|
||||
complete(&d->stop_ack);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_shutdown_ack_intr_handler(int irq, void *dev_id)
|
||||
static irqreturn_t subsys_shutdown_ack_intr_handler(int irq, void *drv_data)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(dev_id);
|
||||
struct pil_tz_data *d = drv_data;
|
||||
|
||||
pr_info("Received stop shutdown interrupt from %s\n",
|
||||
d->subsys_desc.name);
|
||||
|
|
@ -825,9 +894,9 @@ static irqreturn_t subsys_shutdown_ack_intr_handler(int irq, void *dev_id)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_ramdump_disable_intr_handler(int irq, void *dev_id)
|
||||
static irqreturn_t subsys_ramdump_disable_intr_handler(int irq, void *drv_data)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(dev_id);
|
||||
struct pil_tz_data *d = drv_data;
|
||||
|
||||
pr_info("Received ramdump disable interrupt from %s\n",
|
||||
d->subsys_desc.name);
|
||||
|
|
@ -876,7 +945,7 @@ static void clear_err_ready(struct pil_tz_data *d)
|
|||
d->subsys_desc.name);
|
||||
|
||||
__raw_writel(BIT(d->bits_arr[ERR_READY]), d->irq_clear);
|
||||
complete_err_ready(d->subsys);
|
||||
complete(&d->err_ready);
|
||||
}
|
||||
|
||||
static void clear_sw_init_done_error(struct pil_tz_data *d, int err)
|
||||
|
|
@ -914,9 +983,9 @@ static void clear_wdog(struct pil_tz_data *d)
|
|||
}
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_generic_handler(int irq, void *dev_id)
|
||||
static irqreturn_t subsys_generic_handler(int irq, void *drv_data)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(dev_id);
|
||||
struct pil_tz_data *d = drv_data;
|
||||
uint32_t status_val, err_value;
|
||||
|
||||
err_value = __raw_readl(d->err_status_spare);
|
||||
|
|
@ -946,6 +1015,229 @@ static void mask_scsr_irqs(struct pil_tz_data *d)
|
|||
~BIT(d->bits_arr[PBL_DONE]), d->irq_mask);
|
||||
}
|
||||
|
||||
static void subsys_enable_all_irqs(struct pil_tz_data *d)
|
||||
{
|
||||
if (d->err_ready_irq)
|
||||
enable_irq(d->err_ready_irq);
|
||||
if (d->wdog_bite_irq) {
|
||||
enable_irq(d->wdog_bite_irq);
|
||||
irq_set_irq_wake(d->wdog_bite_irq, 1);
|
||||
}
|
||||
if (d->err_fatal_irq)
|
||||
enable_irq(d->err_fatal_irq);
|
||||
if (d->stop_ack_irq)
|
||||
enable_irq(d->stop_ack_irq);
|
||||
if (d->shutdown_ack_irq)
|
||||
enable_irq(d->shutdown_ack_irq);
|
||||
if (d->ramdump_disable_irq)
|
||||
enable_irq(d->ramdump_disable_irq);
|
||||
if (d->generic_irq) {
|
||||
enable_irq(d->generic_irq);
|
||||
irq_set_irq_wake(d->generic_irq, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void subsys_disable_all_irqs(struct pil_tz_data *d)
|
||||
{
|
||||
if (d->err_ready_irq)
|
||||
disable_irq(d->err_ready_irq);
|
||||
if (d->wdog_bite_irq) {
|
||||
disable_irq(d->wdog_bite_irq);
|
||||
irq_set_irq_wake(d->wdog_bite_irq, 0);
|
||||
}
|
||||
if (d->err_fatal_irq)
|
||||
disable_irq(d->err_fatal_irq);
|
||||
if (d->stop_ack_irq)
|
||||
disable_irq(d->stop_ack_irq);
|
||||
if (d->shutdown_ack_irq)
|
||||
disable_irq(d->shutdown_ack_irq);
|
||||
if (d->generic_irq) {
|
||||
disable_irq(d->generic_irq);
|
||||
irq_set_irq_wake(d->generic_irq, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int __get_irq(struct platform_device *pdev, const char *prop,
|
||||
unsigned int *irq)
|
||||
{
|
||||
int irql = 0;
|
||||
struct device_node *dnode = pdev->dev.of_node;
|
||||
|
||||
if (of_property_match_string(dnode, "interrupt-names", prop) < 0)
|
||||
return -ENOENT;
|
||||
|
||||
irql = of_irq_get_byname(dnode, prop);
|
||||
if (irql < 0) {
|
||||
pr_err("[%s]: Error getting IRQ \"%s\"\n", pdev->name,
|
||||
prop);
|
||||
return irql;
|
||||
}
|
||||
*irq = irql;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __get_smem_state(struct pil_tz_data *d, const char *prop,
|
||||
int *smem_bit)
|
||||
{
|
||||
struct device_node *dnode = d->dev->of_node;
|
||||
|
||||
if (of_find_property(dnode, "qcom,smem-states", NULL)) {
|
||||
d->state = qcom_smem_state_get(d->dev, prop, smem_bit);
|
||||
if (IS_ERR_OR_NULL(d->state)) {
|
||||
pr_err("Could not get smem-states %s\n", prop);
|
||||
return PTR_ERR(d->state);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
|
||||
static int subsys_parse_irqs(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct pil_tz_data *d = platform_get_drvdata(pdev);
|
||||
|
||||
ret = __get_irq(pdev, "qcom,err-fatal", &d->err_fatal_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(pdev, "qcom,err-ready", &d->err_ready_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(pdev, "qcom,stop-ack", &d->stop_ack_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(pdev, "qcom,ramdump-disabled",
|
||||
&d->ramdump_disable_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(pdev, "qcom,shutdown-ack", &d->shutdown_ack_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_irq(pdev, "qcom,wdog", &d->wdog_bite_irq);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
ret = __get_smem_state(d, "qcom,force-stop", &d->force_stop_bit);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
if (of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,pil-generic-irq-handler")) {
|
||||
ret = platform_get_irq(pdev, 0);
|
||||
if (ret > 0)
|
||||
d->generic_irq = ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int subsys_setup_irqs(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct pil_tz_data *d = platform_get_drvdata(pdev);
|
||||
|
||||
if (d->err_fatal_irq) {
|
||||
ret = devm_request_threaded_irq(&pdev->dev, d->err_fatal_irq,
|
||||
NULL, subsys_err_fatal_intr_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
d->desc.name, d);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "[%s]: Unable to register error fatal IRQ handler: %d, irq is %d\n",
|
||||
d->desc.name, ret, d->err_fatal_irq);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(d->err_fatal_irq);
|
||||
}
|
||||
|
||||
if (d->stop_ack_irq) {
|
||||
ret = devm_request_threaded_irq(&pdev->dev, d->stop_ack_irq,
|
||||
NULL, subsys_stop_ack_intr_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
d->desc.name, d);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "[%s]: Unable to register stop ack handler: %d\n",
|
||||
d->desc.name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(d->stop_ack_irq);
|
||||
}
|
||||
|
||||
if (d->wdog_bite_irq) {
|
||||
ret = devm_request_irq(&pdev->dev, d->wdog_bite_irq,
|
||||
subsys_wdog_bite_irq_handler,
|
||||
IRQF_TRIGGER_RISING, d->desc.name, d);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "[%s]: Unable to register wdog bite handler: %d\n",
|
||||
d->desc.name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(d->wdog_bite_irq);
|
||||
}
|
||||
|
||||
if (d->shutdown_ack_irq) {
|
||||
ret = devm_request_threaded_irq(&pdev->dev,
|
||||
d->shutdown_ack_irq,
|
||||
NULL, subsys_shutdown_ack_intr_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
d->desc.name, d);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "[%s]: Unable to register shutdown ack handler: %d\n",
|
||||
d->desc.name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(d->shutdown_ack_irq);
|
||||
}
|
||||
|
||||
if (d->ramdump_disable_irq) {
|
||||
ret = devm_request_threaded_irq(d->dev,
|
||||
d->ramdump_disable_irq,
|
||||
NULL, subsys_ramdump_disable_intr_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
d->desc.name, d);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "[%s]: Unable to register shutdown ack handler: %d\n",
|
||||
d->desc.name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(d->ramdump_disable_irq);
|
||||
}
|
||||
|
||||
if (d->generic_irq) {
|
||||
ret = devm_request_irq(&pdev->dev, d->generic_irq,
|
||||
subsys_generic_handler,
|
||||
IRQF_TRIGGER_HIGH, d->desc.name, d);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "[%s]: Unable to register generic irq handler: %d\n",
|
||||
d->desc.name, ret);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(d->generic_irq);
|
||||
}
|
||||
|
||||
if (d->err_ready_irq) {
|
||||
ret = devm_request_threaded_irq(d->dev,
|
||||
d->err_ready_irq,
|
||||
NULL, subsys_err_ready_intr_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
"error_ready_interrupt", d);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"[%s]: Unable to register err ready handler\n",
|
||||
d->desc.name);
|
||||
return ret;
|
||||
}
|
||||
disable_irq(d->err_ready_irq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pil_tz_generic_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pil_tz_data *d;
|
||||
|
|
@ -992,11 +1284,16 @@ static int pil_tz_generic_probe(struct platform_device *pdev)
|
|||
}
|
||||
}
|
||||
|
||||
d->dev = &pdev->dev;
|
||||
d->desc.dev = &pdev->dev;
|
||||
d->desc.owner = THIS_MODULE;
|
||||
d->desc.ops = &pil_ops_trusted;
|
||||
|
||||
d->desc.proxy_timeout = PROXY_TIMEOUT_MS;
|
||||
rc = subsys_parse_irqs(pdev);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
d->desc.clear_fw_region = true;
|
||||
|
||||
rc = of_property_read_u32(pdev->dev.of_node, "qcom,proxy-timeout-ms",
|
||||
|
|
@ -1023,6 +1320,8 @@ static int pil_tz_generic_probe(struct platform_device *pdev)
|
|||
goto err_deregister_bus;
|
||||
|
||||
init_completion(&d->stop_ack);
|
||||
init_completion(&d->err_ready);
|
||||
|
||||
d->subsys_desc.name = d->desc.name;
|
||||
d->subsys_desc.owner = THIS_MODULE;
|
||||
d->subsys_desc.dev = &pdev->dev;
|
||||
|
|
@ -1033,7 +1332,6 @@ static int pil_tz_generic_probe(struct platform_device *pdev)
|
|||
d->subsys_desc.crash_shutdown = subsys_crash_shutdown;
|
||||
if (of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,pil-generic-irq-handler")) {
|
||||
d->subsys_desc.generic_handler = subsys_generic_handler;
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"sp2soc_irq_status");
|
||||
d->irq_status = devm_ioremap_resource(&pdev->dev, res);
|
||||
|
|
@ -1089,17 +1387,8 @@ static int pil_tz_generic_probe(struct platform_device *pdev)
|
|||
goto err_ramdump;
|
||||
}
|
||||
mask_scsr_irqs(d);
|
||||
|
||||
} else {
|
||||
d->subsys_desc.err_fatal_handler =
|
||||
subsys_err_fatal_intr_handler;
|
||||
d->subsys_desc.wdog_bite_handler = subsys_wdog_bite_irq_handler;
|
||||
d->subsys_desc.stop_ack_handler = subsys_stop_ack_intr_handler;
|
||||
d->subsys_desc.shutdown_ack_handler =
|
||||
subsys_shutdown_ack_intr_handler;
|
||||
d->subsys_desc.ramdump_disable_handler =
|
||||
subsys_ramdump_disable_intr_handler;
|
||||
}
|
||||
|
||||
d->desc.signal_aop = of_property_read_bool(pdev->dev.of_node,
|
||||
"qcom,signal-aop");
|
||||
if (d->desc.signal_aop) {
|
||||
|
|
@ -1139,6 +1428,12 @@ static int pil_tz_generic_probe(struct platform_device *pdev)
|
|||
goto err_subsys;
|
||||
}
|
||||
|
||||
rc = subsys_setup_irqs(pdev);
|
||||
if (rc) {
|
||||
subsys_unregister(d->subsys);
|
||||
goto err_subsys;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_subsys:
|
||||
destroy_ramdump_device(d->minidump_dev);
|
||||
|
|
|
|||
|
|
@ -69,8 +69,6 @@ struct subsys_notif_timeout {
|
|||
* @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
|
||||
|
|
@ -95,20 +93,8 @@ struct subsys_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;
|
||||
|
|
@ -164,7 +150,6 @@ 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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue