[RFC-V3,2/2] mailbox: pcc: Support shared interrupt for multiple subspaces

Message ID 20221203095150.45422-3-lihuisong@huawei.com
State New
Headers
Series mailbox: pcc: Support platform notification for type4 and shared interrupt |

Commit Message

Huisong Li Dec. 3, 2022, 9:51 a.m. UTC
  If the platform acknowledge interrupt is level triggered, then it can
be shared by multiple subspaces provided each one has a unique platform
interrupt ack preserve and ack set masks.

If it can be shared, then we can request the irq with IRQF_SHARED and
IRQF_ONESHOT flags. The first one indicating it can be shared and the
latter one to keep the interrupt disabled until the hardirq handler
finished.

Further, since there is no way to detect if the interrupt is for a given
channel as the interrupt ack preserve and ack set masks are for clearing
the interrupt and not for reading the status(in case Irq Ack register
may be write-only on some platforms), we need a way to identify if the
given channel is in use and expecting the interrupt.

PCC type0, type1 and type5 do not support shared level triggered interrupt.
The methods of determining whether a given channel for remaining types
should respond to an interrupt are as follows:
 - type2: Whether the interrupt belongs to a given channel is only
          determined by the status field in Generic Communications Channel
          Shared Memory Region, which is done in rx_callback of PCC client.
 - type3: This channel checks chan_in_use flag first and then checks the
          command complete bit(value '1' indicates that the command has
          been completed).
 - type4: Platform ensure that the default value of the command complete
          bit corresponding to the type4 channel is '1'. This command
          complete bit is '0' when receive a platform notification.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 drivers/mailbox/pcc.c | 46 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 43 insertions(+), 3 deletions(-)
  

Patch

diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c
index ad6d0b7d50fc..90e9cc324081 100644
--- a/drivers/mailbox/pcc.c
+++ b/drivers/mailbox/pcc.c
@@ -99,6 +99,10 @@  enum pcc_chan_comm_flow_dir_type {
  * @error: PCC register bundle for the error status register
  * @plat_irq: platform interrupt
  * @comm_flow_dir: direction of communication flow supported by the channel
+ * @plat_irq_flags: platform interrupt flags
+ * @chan_in_use: flag indicating whether the channel is in use or not when use
+ *               platform interrupt, and only use it for communication from OSPM
+ *               to Platform.
  */
 struct pcc_chan_info {
 	struct pcc_mbox_chan chan;
@@ -109,6 +113,8 @@  struct pcc_chan_info {
 	struct pcc_chan_reg error;
 	int plat_irq;
 	u8 comm_flow_dir;
+	unsigned int plat_irq_flags;
+	bool chan_in_use;
 };
 
 #define to_pcc_chan_info(c) container_of(c, struct pcc_chan_info, chan)
@@ -232,6 +238,12 @@  static int pcc_map_interrupt(u32 interrupt, u32 flags)
 	return acpi_register_gsi(NULL, interrupt, trigger, polarity);
 }
 
+static bool pcc_chan_plat_irq_can_be_shared(struct pcc_chan_info *pchan)
+{
+	return (pchan->plat_irq_flags & ACPI_PCCT_INTERRUPT_MODE) ==
+		ACPI_LEVEL_SENSITIVE;
+}
+
 static bool pcc_chan_need_rsp_irq(struct pcc_chan_info *pchan,
 				  u64 cmd_complete_reg_val)
 {
@@ -284,6 +296,9 @@  static irqreturn_t pcc_mbox_irq(int irq, void *p)
 	int ret;
 
 	pchan = chan->con_priv;
+	if (pchan->comm_flow_dir == PCC_ONLY_OSPM_TO_PLATFORM &&
+	    !pchan->chan_in_use)
+		return IRQ_NONE;
 
 	ret = pcc_chan_reg_read(&pchan->cmd_complete, &val);
 	if (ret)
@@ -310,9 +325,14 @@  static irqreturn_t pcc_mbox_irq(int irq, void *p)
 	 * For communication flow from Platform to OSPM (like, slave subspace),
 	 * need to set the command complete bit and ring doorbell after
 	 * processing message.
+	 *
+	 * For communication flow from OSPM to Platform, clear chan_in_use flag
+	 * to free this channel.
 	 */
 	if (pchan->comm_flow_dir == PCC_ONLY_PLATFORM_TO_OSPM)
 		pcc_send_data(chan, NULL);
+	else if (pchan->comm_flow_dir == PCC_ONLY_OSPM_TO_PLATFORM)
+		pchan->chan_in_use = false;
 
 	return IRQ_HANDLED;
 }
@@ -361,10 +381,13 @@  pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id)
 	spin_unlock_irqrestore(&chan->lock, flags);
 
 	if (pchan->plat_irq > 0) {
+		unsigned long irqflags;
 		int rc;
 
-		rc = devm_request_irq(dev, pchan->plat_irq, pcc_mbox_irq, 0,
-				      MBOX_IRQ_NAME, chan);
+		irqflags = pcc_chan_plat_irq_can_be_shared(pchan) ?
+					IRQF_SHARED | IRQF_ONESHOT : 0;
+		rc = devm_request_irq(dev, pchan->plat_irq, pcc_mbox_irq,
+				      irqflags, MBOX_IRQ_NAME, chan);
 		if (unlikely(rc)) {
 			dev_err(dev, "failed to register PCC interrupt %d\n",
 				pchan->plat_irq);
@@ -426,7 +449,17 @@  static int pcc_send_data(struct mbox_chan *chan, void *data)
 	if (ret)
 		return ret;
 
-	return pcc_chan_reg_read_modify_write(&pchan->db);
+	ret = pcc_chan_reg_read_modify_write(&pchan->db);
+	/*
+	 * For communication flow from OSPM to Platform, set chan_in_use flag
+	 * of this channel to true after ring doorbell, and clear this flag
+	 * when the reply message is processed.
+	 */
+	if (!ret && pchan->comm_flow_dir == PCC_ONLY_OSPM_TO_PLATFORM &&
+	    pchan->plat_irq > 0)
+		pchan->chan_in_use = true;
+
+	return ret;
 }
 
 static const struct mbox_chan_ops pcc_chan_ops = {
@@ -509,6 +542,7 @@  static int pcc_parse_subspace_irq(struct pcc_chan_info *pchan,
 		       pcct_ss->platform_interrupt);
 		return -EINVAL;
 	}
+	pchan->plat_irq_flags = pcct_ss->flags;
 
 	if (pcct_ss->header.type == ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2) {
 		struct acpi_pcct_hw_reduced_type2 *pcct2_ss = (void *)pcct_ss;
@@ -530,6 +564,12 @@  static int pcc_parse_subspace_irq(struct pcc_chan_info *pchan,
 					"PLAT IRQ ACK");
 	}
 
+	if (pcc_chan_plat_irq_can_be_shared(pchan) &&
+	    !pchan->plat_irq_ack.gas) {
+		pr_err("PCC subspace has level IRQ with no ACK register\n");
+		return -EINVAL;
+	}
+
 	return ret;
 }