[PATCH 0/4] ASoC: SOF: Intel: Harden the IPC4 low level sequencing
Hi,
The IPC4 use of doorbell registers leaves some corner cases not well defined and the 'correct sequences' are subjective in a sense. The DSP doorbell registers are used as separate and independent channels and the sequences for host -> DSP -> host (reply) can be racy.
For example: The ACKing of a received message can happen before the firmware sends the reply or it can as well happen after the reply has been sent and received by the host. Both can be considered 'correct sequences' but they need different handling.
This series will allow the kernel to service any interpretation of the sequencing on the firmware side.
Regards, Peter --- Peter Ujfalusi (4): ASoC: SOF: ipc4: Log the tx message before sending it ASoC: SOF: Intel: ipc4: Read the interrupt reason registers at the same time ASoC: SOF: Intel: ipc4: Wait for channel to be free before sending a message ASoC: SOF: Intel: ipc4: Ack a received reply or notification separately
sound/soc/sof/intel/cnl.c | 26 ++++++++++++++++++++++---- sound/soc/sof/intel/hda-ipc.c | 27 +++++++++++++++++++++++---- sound/soc/sof/intel/hda.c | 11 +++++++++++ sound/soc/sof/intel/hda.h | 9 +++++++++ sound/soc/sof/intel/mtl.c | 24 +++++++++++++++++++++--- sound/soc/sof/ipc4.c | 4 ++-- 6 files changed, 88 insertions(+), 13 deletions(-)
It makes more sense to log the message before it is sent to the DSP.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Rander Wang rander.wang@intel.com --- sound/soc/sof/ipc4.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index 6eaa18e27e5a..3c9b8692984a 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -342,6 +342,8 @@ static int ipc4_tx_msg_unlocked(struct snd_sof_ipc *ipc, if (msg_bytes > ipc->max_payload_size || reply_bytes > ipc->max_payload_size) return -EINVAL;
+ sof_ipc4_log_header(sdev->dev, "ipc tx ", msg_data, true); + ret = sof_ipc_send_msg(sdev, msg_data, msg_bytes, reply_bytes); if (ret) { dev_err_ratelimited(sdev->dev, @@ -350,8 +352,6 @@ static int ipc4_tx_msg_unlocked(struct snd_sof_ipc *ipc, return ret; }
- sof_ipc4_log_header(sdev->dev, "ipc tx ", msg_data, true); - /* now wait for completion */ return ipc4_wait_tx_done(ipc, reply_data); }
Read both registers as the first step in the interrupt handler to make sure that we are handling the event which triggered the interrupt.
The delayed reading of the target request register might reflect incorrect information about the reason why the interrupt was risen.
Note also that the IPC3 interrupt handler is implemented in this way also.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Rander Wang rander.wang@intel.com --- sound/soc/sof/intel/cnl.c | 2 +- sound/soc/sof/intel/hda-ipc.c | 3 ++- sound/soc/sof/intel/mtl.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 19d0b1909bfd..2f2bcde42759 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -41,6 +41,7 @@ irqreturn_t cnl_ipc4_irq_thread(int irq, void *context) u32 hipcida, hipctdr;
hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA); + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR); if (hipcida & CNL_DSP_REG_HIPCIDA_DONE) { /* DSP received the message */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, @@ -51,7 +52,6 @@ irqreturn_t cnl_ipc4_irq_thread(int irq, void *context) ipc_irq = true; }
- hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR); if (hipctdr & CNL_DSP_REG_HIPCTDR_BUSY) { /* Message from DSP (reply or notification) */ u32 hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c index 9b3667c705e4..4118532faf3f 100644 --- a/sound/soc/sof/intel/hda-ipc.c +++ b/sound/soc/sof/intel/hda-ipc.c @@ -126,6 +126,8 @@ irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context) u32 hipcie, hipct;
hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE); + hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); + if (hipcie & HDA_DSP_REG_HIPCIE_DONE) { /* DSP received the message */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL, @@ -135,7 +137,6 @@ irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context) ipc_irq = true; }
- hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); if (hipct & HDA_DSP_REG_HIPCT_BUSY) { /* Message from DSP (reply or notification) */ u32 hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 10298532816f..a9b31b31a4e4 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -497,6 +497,7 @@ static irqreturn_t mtl_ipc_irq_thread(int irq, void *context) u32 hipctdr;
hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA); + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR);
/* reply message from DSP */ if (hipcida & MTL_DSP_REG_HFIPCXIDA_DONE) { @@ -509,7 +510,6 @@ static irqreturn_t mtl_ipc_irq_thread(int irq, void *context) ipc_irq = true; }
- hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR); if (hipctdr & MTL_DSP_REG_HFIPCXTDR_BUSY) { /* Message from DSP (reply or notification) */ u32 extension = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDDY);
Before attempting to send a message to the DSP we need to check if the downstream BUSY flag has been cleared by the firmware to avoid lost IPC messages by the firmware.
This is required by a firmware which only acks the received message after it has sent a reply to the host. With a bad luck, the host would send a message before the firmware gets to the clearing the flag and thus losing a message.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Rander Wang rander.wang@intel.com --- sound/soc/sof/intel/cnl.c | 17 +++++++++++++++++ sound/soc/sof/intel/hda-ipc.c | 17 +++++++++++++++++ sound/soc/sof/intel/hda.c | 11 +++++++++++ sound/soc/sof/intel/hda.h | 9 +++++++++ sound/soc/sof/intel/mtl.c | 17 +++++++++++++++++ 5 files changed, 71 insertions(+)
diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 2f2bcde42759..4bf233787757 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -37,6 +37,7 @@ irqreturn_t cnl_ipc4_irq_thread(int irq, void *context) { struct sof_ipc4_msg notification_data = {{ 0 }}; struct snd_sof_dev *sdev = context; + bool ack_received = false; bool ipc_irq = false; u32 hipcida, hipctdr;
@@ -50,6 +51,7 @@ irqreturn_t cnl_ipc4_irq_thread(int irq, void *context) cnl_ipc_dsp_done(sdev);
ipc_irq = true; + ack_received = true; }
if (hipctdr & CNL_DSP_REG_HIPCTDR_BUSY) { @@ -98,6 +100,13 @@ irqreturn_t cnl_ipc4_irq_thread(int irq, void *context) /* This interrupt is not shared so no need to return IRQ_NONE. */ dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
+ if (ack_received) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + + if (hdev->delayed_ipc_tx_msg) + cnl_ipc4_send_msg(sdev, hdev->delayed_ipc_tx_msg); + } + return IRQ_HANDLED; }
@@ -251,8 +260,16 @@ static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg,
int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; struct sof_ipc4_msg *msg_data = msg->msg_data;
+ if (hda_ipc4_tx_is_busy(sdev)) { + hdev->delayed_ipc_tx_msg = msg; + return 0; + } + + hdev->delayed_ipc_tx_msg = NULL; + /* send the message via mailbox */ if (msg_data->data_size) sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr, diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c index 4118532faf3f..b4668c969a29 100644 --- a/sound/soc/sof/intel/hda-ipc.c +++ b/sound/soc/sof/intel/hda-ipc.c @@ -69,8 +69,16 @@ int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; struct sof_ipc4_msg *msg_data = msg->msg_data;
+ if (hda_ipc4_tx_is_busy(sdev)) { + hdev->delayed_ipc_tx_msg = msg; + return 0; + } + + hdev->delayed_ipc_tx_msg = NULL; + /* send the message via mailbox */ if (msg_data->data_size) sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr, @@ -122,6 +130,7 @@ irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context) { struct sof_ipc4_msg notification_data = {{ 0 }}; struct snd_sof_dev *sdev = context; + bool ack_received = false; bool ipc_irq = false; u32 hipcie, hipct;
@@ -135,6 +144,7 @@ irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context) hda_dsp_ipc_dsp_done(sdev);
ipc_irq = true; + ack_received = true; }
if (hipct & HDA_DSP_REG_HIPCT_BUSY) { @@ -187,6 +197,13 @@ irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context) /* This interrupt is not shared so no need to return IRQ_NONE. */ dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
+ if (ack_received) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + + if (hdev->delayed_ipc_tx_msg) + hda_dsp_ipc4_send_msg(sdev, hdev->delayed_ipc_tx_msg); + } + return IRQ_HANDLED; }
diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 1188ec51816b..eddd20beea0d 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -681,6 +681,17 @@ void hda_ipc4_dump(struct snd_sof_dev *sdev) hipci, hipcie, hipct, hipcte, hipcctl); }
+bool hda_ipc4_tx_is_busy(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + u32 val; + + val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->ipc_req); + + return !!(val & chip->ipc_req_mask); +} + static int hda_init(struct snd_sof_dev *sdev) { struct hda_bus *hbus; diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 2ab3c3840b92..65657d145dc2 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -521,6 +521,14 @@ struct sof_intel_hda_dev {
/* Intel NHLT information */ struct nhlt_acpi_table *nhlt; + + /* + * Pointing to the IPC message if immediate sending was not possible + * because the downlink communication channel was BUSY at the time. + * The message will be re-tried when the channel becomes free (the ACK + * is received from the DSP for the previous message) + */ + struct snd_sof_ipc_msg *delayed_ipc_tx_msg; };
static inline struct hdac_bus *sof_to_bus(struct snd_sof_dev *s) @@ -852,6 +860,7 @@ int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask); irqreturn_t cnl_ipc4_irq_thread(int irq, void *context); int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg); irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context); +bool hda_ipc4_tx_is_busy(struct snd_sof_dev *sdev); int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg); void hda_ipc4_dump(struct snd_sof_dev *sdev); extern struct sdw_intel_ops sdw_callback; diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index a9b31b31a4e4..9d1bc74395e7 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -90,8 +90,16 @@ static bool mtl_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
static int mtl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; struct sof_ipc4_msg *msg_data = msg->msg_data;
+ if (hda_ipc4_tx_is_busy(sdev)) { + hdev->delayed_ipc_tx_msg = msg; + return 0; + } + + hdev->delayed_ipc_tx_msg = NULL; + /* send the message via mailbox */ if (msg_data->data_size) sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr, @@ -492,6 +500,7 @@ static irqreturn_t mtl_ipc_irq_thread(int irq, void *context) { struct sof_ipc4_msg notification_data = {{ 0 }}; struct snd_sof_dev *sdev = context; + bool ack_received = false; bool ipc_irq = false; u32 hipcida; u32 hipctdr; @@ -508,6 +517,7 @@ static irqreturn_t mtl_ipc_irq_thread(int irq, void *context) mtl_ipc_dsp_done(sdev);
ipc_irq = true; + ack_received = true; }
if (hipctdr & MTL_DSP_REG_HFIPCXTDR_BUSY) { @@ -558,6 +568,13 @@ static irqreturn_t mtl_ipc_irq_thread(int irq, void *context) dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n"); }
+ if (ack_received) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + + if (hdev->delayed_ipc_tx_msg) + mtl_ipc_send_msg(sdev, hdev->delayed_ipc_tx_msg); + } + return IRQ_HANDLED; }
By acking a received message we tell the DSP that we have processed the message (reply or notification) and we are open to receive a new one.
The original implementation did this in a common code after the received message got handled as reply or notification.
With right timing this opens up a small window when we have processed the reply and let the other thread proceed to send a new message to the DSP, which is allowed as the DSP is free to receive message. But when the message is received and processed by the DSP and it wants to send a reply it will still see that the previous message has not been acked, so it fails to send a reply. Later the first reply got acked by the kernel, but it is too late and the in-flight message got a timeout due to firmware not responding (which it tried, but could not).
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Rander Wang rander.wang@intel.com --- sound/soc/sof/intel/cnl.c | 7 ++++--- sound/soc/sof/intel/hda-ipc.c | 7 ++++--- sound/soc/sof/intel/mtl.c | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 4bf233787757..da26f0ce9abc 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -72,6 +72,7 @@ irqreturn_t cnl_ipc4_irq_thread(int irq, void *context) spin_lock_irq(&sdev->ipc_lock);
snd_sof_ipc_get_reply(sdev); + cnl_ipc_host_done(sdev); snd_sof_ipc_reply(sdev, data->primary);
spin_unlock_irq(&sdev->ipc_lock); @@ -88,10 +89,10 @@ irqreturn_t cnl_ipc4_irq_thread(int irq, void *context) sdev->ipc->msg.rx_data = ¬ification_data; snd_sof_ipc_msgs_rx(sdev); sdev->ipc->msg.rx_data = NULL; - }
- /* Let DSP know that we have finished processing the message */ - cnl_ipc_host_done(sdev); + /* Let DSP know that we have finished processing the message */ + cnl_ipc_host_done(sdev); + }
ipc_irq = true; } diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c index b4668c969a29..a7c454e03952 100644 --- a/sound/soc/sof/intel/hda-ipc.c +++ b/sound/soc/sof/intel/hda-ipc.c @@ -169,6 +169,7 @@ irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context) spin_lock_irq(&sdev->ipc_lock);
snd_sof_ipc_get_reply(sdev); + hda_dsp_ipc_host_done(sdev); snd_sof_ipc_reply(sdev, data->primary);
spin_unlock_irq(&sdev->ipc_lock); @@ -185,10 +186,10 @@ irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context) sdev->ipc->msg.rx_data = ¬ification_data; snd_sof_ipc_msgs_rx(sdev); sdev->ipc->msg.rx_data = NULL; - }
- /* Let DSP know that we have finished processing the message */ - hda_dsp_ipc_host_done(sdev); + /* Let DSP know that we have finished processing the message */ + hda_dsp_ipc_host_done(sdev); + }
ipc_irq = true; } diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 9d1bc74395e7..054b9ab721ff 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -540,6 +540,7 @@ static irqreturn_t mtl_ipc_irq_thread(int irq, void *context) spin_lock_irq(&sdev->ipc_lock);
snd_sof_ipc_get_reply(sdev); + mtl_ipc_host_done(sdev); snd_sof_ipc_reply(sdev, data->primary);
spin_unlock_irq(&sdev->ipc_lock); @@ -556,9 +557,9 @@ static irqreturn_t mtl_ipc_irq_thread(int irq, void *context) sdev->ipc->msg.rx_data = ¬ification_data; snd_sof_ipc_msgs_rx(sdev); sdev->ipc->msg.rx_data = NULL; - }
- mtl_ipc_host_done(sdev); + mtl_ipc_host_done(sdev); + }
ipc_irq = true; }
On Tue, 18 Oct 2022 15:40:04 +0300, Peter Ujfalusi wrote:
The IPC4 use of doorbell registers leaves some corner cases not well defined and the 'correct sequences' are subjective in a sense. The DSP doorbell registers are used as separate and independent channels and the sequences for host -> DSP -> host (reply) can be racy.
For example: The ACKing of a received message can happen before the firmware sends the reply or it can as well happen after the reply has been sent and received by the host. Both can be considered 'correct sequences' but they need different handling.
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[1/4] ASoC: SOF: ipc4: Log the tx message before sending it commit: 2d91d5715f5f3b24456ede20dbbe967a1d2a0a3e [2/4] ASoC: SOF: Intel: ipc4: Read the interrupt reason registers at the same time commit: c8ed7ce242db83ca2c4e9eab557a88adbae5ef6a [3/4] ASoC: SOF: Intel: ipc4: Wait for channel to be free before sending a message commit: 483e4cdfb502e6bea6b0a226a3ff7c22e60153de [4/4] ASoC: SOF: Intel: ipc4: Ack a received reply or notification separately commit: 010c050fe9ea263e3fc17493822117610a23f662
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (2)
-
Mark Brown
-
Peter Ujfalusi