firmware: ti_sci: Add support for restoring IRQs during resume

Some DM-Firmware are not able to restore the IRQ context after
a suspend-resume. The IRQ_CONTEXT_LOST firmware capability has
been introduced to identify this characteristic. In this case the
responsibility is delegated to the ti_sci driver, which maintains
an internal list of all requested IRQs. This list is updated on
each set()/free() operation, and all IRQs are restored during the
resume_noirq() phase.

Reviewed-by: Dhruva Gole <d-gole@ti.com>
Reviewed-by: Kendall Willis <k-willis@ti.com>
Signed-off-by: Thomas Richard (TI) <thomas.richard@bootlin.com>
Link: https://patch.msgid.link/20260519-ti-sci-jacinto-s2r-restore-irq-v9-2-c550a8ae0f31@bootlin.com
Signed-off-by: Nishanth Menon <nm@ti.com>
This commit is contained in:
Thomas Richard (TI) 2026-05-19 17:06:57 +02:00 committed by Nishanth Menon
parent e9f06763b9
commit 703b341fdd
2 changed files with 181 additions and 12 deletions

View File

@ -12,11 +12,13 @@
#include <linux/cpu.h>
#include <linux/debugfs.h>
#include <linux/export.h>
#include <linux/hashtable.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
@ -87,6 +89,16 @@ struct ti_sci_desc {
int max_msg_size;
};
/**
* struct ti_sci_irq - Description of allocated irqs
* @node: Link to hash table
* @desc: Description of the irq
*/
struct ti_sci_irq {
struct hlist_node node;
struct ti_sci_msg_req_manage_irq desc;
};
/**
* struct ti_sci_info - Structure representing a TI SCI instance
* @dev: Device pointer
@ -101,6 +113,8 @@ struct ti_sci_desc {
* @chan_rx: Receive mailbox channel
* @minfo: Message info
* @node: list head
* @irqs: List of allocated irqs
* @irq_lock: Protection for irq hash list
* @host_id: Host ID
* @fw_caps: FW/SoC low power capabilities
* @users: Number of users of this instance
@ -118,6 +132,8 @@ struct ti_sci_info {
struct mbox_chan *chan_rx;
struct ti_sci_xfers_info minfo;
struct list_head node;
DECLARE_HASHTABLE(irqs, 8);
struct mutex irq_lock;
u8 host_id;
u64 fw_caps;
/* protected by ti_sci_list_mutex */
@ -2299,6 +2315,32 @@ static int ti_sci_manage_irq(const struct ti_sci_handle *handle,
return ret;
}
/**
* ti_sci_irq_hash() - Helper API to compute irq hash for the hash table.
* @irq: irq to hash
*
* Return: the computed hash value.
*/
static int ti_sci_irq_hash(struct ti_sci_msg_req_manage_irq *irq)
{
return irq->src_id ^ irq->src_index;
}
/**
* ti_sci_irq_equal() - Helper API to compare two irqs (generic headers are not
* compared)
* @irq_a: irq_a to compare
* @irq_b: irq_b to compare
*
* Return: true if the two irqs are equal, else false.
*/
static bool ti_sci_irq_equal(struct ti_sci_msg_req_manage_irq *irq_a,
struct ti_sci_msg_req_manage_irq *irq_b)
{
return !memcmp(&irq_a->valid_params, &irq_b->valid_params,
sizeof(*irq_a) - sizeof(irq_a->hdr));
}
/**
* ti_sci_set_irq() - Helper api to configure the irq route between the
* requested source and destination
@ -2322,15 +2364,53 @@ static int ti_sci_set_irq(const struct ti_sci_handle *handle, u32 valid_params,
u16 dst_host_irq, u16 ia_id, u16 vint,
u16 global_event, u8 vint_status_bit, u8 s_host)
{
struct ti_sci_info *info = handle_to_ti_sci_info(handle);
struct ti_sci_msg_req_manage_irq *desc;
struct ti_sci_irq *irq;
int ret;
/* Lock for set_irq() and free_irq() to keep the IRQ hash list consistent */
guard(mutex)(&info->irq_lock);
pr_debug("%s: IRQ set with valid_params = 0x%x from src = %d, index = %d, to dst = %d, irq = %d,via ia_id = %d, vint = %d, global event = %d,status_bit = %d\n",
__func__, valid_params, src_id, src_index,
dst_id, dst_host_irq, ia_id, vint, global_event,
vint_status_bit);
return ti_sci_manage_irq(handle, valid_params, src_id, src_index,
dst_id, dst_host_irq, ia_id, vint,
global_event, vint_status_bit, s_host,
TI_SCI_MSG_SET_IRQ);
ret = ti_sci_manage_irq(handle, valid_params, src_id, src_index,
dst_id, dst_host_irq, ia_id, vint,
global_event, vint_status_bit, s_host,
TI_SCI_MSG_SET_IRQ);
if (ret || !(info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST))
goto end;
irq = kzalloc_obj(*irq, GFP_KERNEL);
if (!irq) {
ti_sci_manage_irq(handle, valid_params, src_id, src_index,
dst_id, dst_host_irq, ia_id, vint,
global_event, vint_status_bit, s_host,
TI_SCI_MSG_FREE_IRQ);
ret = -ENOMEM;
goto end;
}
desc = &irq->desc;
desc->valid_params = valid_params;
desc->src_id = src_id;
desc->src_index = src_index;
desc->dst_id = dst_id;
desc->dst_host_irq = dst_host_irq;
desc->ia_id = ia_id;
desc->vint = vint;
desc->global_event = global_event;
desc->vint_status_bit = vint_status_bit;
desc->secondary_host = s_host;
hash_add(info->irqs, &irq->node, ti_sci_irq_hash(desc));
end:
return ret;
}
/**
@ -2356,15 +2436,53 @@ static int ti_sci_free_irq(const struct ti_sci_handle *handle, u32 valid_params,
u16 dst_host_irq, u16 ia_id, u16 vint,
u16 global_event, u8 vint_status_bit, u8 s_host)
{
struct ti_sci_info *info = handle_to_ti_sci_info(handle);
struct ti_sci_msg_req_manage_irq irq_desc;
struct device *dev = info->dev;
struct ti_sci_irq *this_irq;
struct hlist_node *tmp_node;
int ret;
guard(mutex)(&info->irq_lock);
pr_debug("%s: IRQ release with valid_params = 0x%x from src = %d, index = %d, to dst = %d, irq = %d,via ia_id = %d, vint = %d, global event = %d,status_bit = %d\n",
__func__, valid_params, src_id, src_index,
dst_id, dst_host_irq, ia_id, vint, global_event,
vint_status_bit);
return ti_sci_manage_irq(handle, valid_params, src_id, src_index,
dst_id, dst_host_irq, ia_id, vint,
global_event, vint_status_bit, s_host,
TI_SCI_MSG_FREE_IRQ);
ret = ti_sci_manage_irq(handle, valid_params, src_id, src_index,
dst_id, dst_host_irq, ia_id, vint,
global_event, vint_status_bit, s_host,
TI_SCI_MSG_FREE_IRQ);
if (ret || !(info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST))
goto end;
irq_desc.valid_params = valid_params;
irq_desc.src_id = src_id;
irq_desc.src_index = src_index;
irq_desc.dst_id = dst_id;
irq_desc.dst_host_irq = dst_host_irq;
irq_desc.ia_id = ia_id;
irq_desc.vint = vint;
irq_desc.global_event = global_event;
irq_desc.vint_status_bit = vint_status_bit;
irq_desc.secondary_host = s_host;
hash_for_each_possible_safe(info->irqs, this_irq, tmp_node, node,
ti_sci_irq_hash(&irq_desc)) {
if (ti_sci_irq_equal(&irq_desc, &this_irq->desc)) {
hlist_del(&this_irq->node);
kfree(this_irq);
goto end;
}
}
dev_warn(dev, "%s: should not be here, IRQ was not found in hash list\n",
__func__);
end:
return ret;
}
/**
@ -3845,7 +3963,10 @@ static int ti_sci_suspend_noirq(struct device *dev)
static int ti_sci_resume_noirq(struct device *dev)
{
struct ti_sci_info *info = dev_get_drvdata(dev);
int ret = 0;
struct ti_sci_msg_req_manage_irq *irq_desc;
struct ti_sci_irq *irq;
struct hlist_node *tmp_node;
int ret = 0, err = 0, i;
u32 source;
u64 time;
u8 pin;
@ -3857,13 +3978,50 @@ static int ti_sci_resume_noirq(struct device *dev)
return ret;
}
switch (pm_suspend_target_state) {
case PM_SUSPEND_MEM:
if (info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST) {
hash_for_each_safe(info->irqs, i, tmp_node, irq, node) {
irq_desc = &irq->desc;
ret = ti_sci_manage_irq(&info->handle,
irq_desc->valid_params,
irq_desc->src_id,
irq_desc->src_index,
irq_desc->dst_id,
irq_desc->dst_host_irq,
irq_desc->ia_id,
irq_desc->vint,
irq_desc->global_event,
irq_desc->vint_status_bit,
irq_desc->secondary_host,
TI_SCI_MSG_SET_IRQ);
if (ret) {
dev_err(dev, "failed to restore IRQ with valid_params = 0x%x from src = %d, index = %d, to dst = %d, irq = %d, via ia_id = %d, vint = %d, global event = %d,status_bit = %d\n",
irq_desc->valid_params,
irq_desc->src_id,
irq_desc->src_index,
irq_desc->dst_id,
irq_desc->dst_host_irq,
irq_desc->ia_id,
irq_desc->vint,
irq_desc->global_event,
irq_desc->vint_status_bit);
err = ret;
}
}
}
break;
default:
break;
}
ret = ti_sci_msg_cmd_lpm_wake_reason(&info->handle, &source, &time, &pin, &mode);
/* Do not fail to resume on error as the wake reason is not critical */
if (!ret)
dev_info(dev, "ti_sci: wakeup source:0x%x, pin:0x%x, mode:0x%x\n",
source, pin, mode);
return 0;
return err;
}
static void ti_sci_pm_complete(struct device *dev)
@ -4012,13 +4170,14 @@ static int ti_sci_probe(struct platform_device *pdev)
}
ti_sci_msg_cmd_query_fw_caps(&info->handle, &info->fw_caps);
dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s%s\n",
dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s%s%s\n",
info->fw_caps & MSG_FLAG_CAPS_GENERIC ? "Generic" : "",
info->fw_caps & MSG_FLAG_CAPS_LPM_PARTIAL_IO ? " Partial-IO" : "",
info->fw_caps & MSG_FLAG_CAPS_LPM_DM_MANAGED ? " DM-Managed" : "",
info->fw_caps & MSG_FLAG_CAPS_LPM_ABORT ? " LPM-Abort" : "",
info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION ? " IO-Isolation" : "",
info->fw_caps & MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED ? " BoardConfig-Managed" : ""
info->fw_caps & MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED ? " BoardConfig-Managed" : "",
info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST ? " IRQ-Context-Lost" : ""
);
ti_sci_setup_ops(info);
@ -4051,6 +4210,13 @@ static int ti_sci_probe(struct platform_device *pdev)
list_add_tail(&info->node, &ti_sci_list);
mutex_unlock(&ti_sci_list_mutex);
ret = devm_mutex_init(dev, &info->irq_lock);
if (ret)
goto out;
if (info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST)
hash_init(info->irqs);
ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
if (ret) {
dev_err(dev, "platform_populate failed %pe\n", ERR_PTR(ret));

View File

@ -152,6 +152,8 @@ struct ti_sci_msg_req_reboot {
* MSG_FLAG_CAPS_IO_ISOLATION: IO Isolation support
* MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED: LPM config done statically
* for the DM via boardcfg
* MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST: DM is not able to restore IRQ
* context
*
* Response to a generic message with message type TI_SCI_MSG_QUERY_FW_CAPS
* providing currently available SOC/firmware capabilities. SoC that don't
@ -165,6 +167,7 @@ struct ti_sci_msg_resp_query_fw_caps {
#define MSG_FLAG_CAPS_LPM_ABORT TI_SCI_MSG_FLAG(9)
#define MSG_FLAG_CAPS_IO_ISOLATION TI_SCI_MSG_FLAG(7)
#define MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED TI_SCI_MSG_FLAG(12)
#define MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST TI_SCI_MSG_FLAG(14)
#define MSG_MASK_CAPS_LPM GENMASK_ULL(4, 1)
u64 fw_caps;
} __packed;