]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
firmware: ti_sci: Add support for restoring IRQs during resume
authorThomas Richard (TI) <thomas.richard@bootlin.com>
Tue, 19 May 2026 15:06:57 +0000 (17:06 +0200)
committerNishanth Menon <nm@ti.com>
Tue, 26 May 2026 11:44:10 +0000 (06:44 -0500)
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>
drivers/firmware/ti_sci.c
drivers/firmware/ti_sci.h

index a8dcdbe874b65445b55a758a6f308b086197ba84..f6bffeae30f54e6e9594e6f4fac6e2e0945ca865 100644 (file)
 #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 @@ fail:
        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));
index d5e06769b01ca5300182b466a99b0f3f23a8732d..ad69c765d6146159db9a0eaa1280a440283270b8 100644 (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;