Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

soundwire: cadence: fix race condition between suspend and Slave device alerts

In system suspend stress cases, the SOF CI reports timeouts. The root
cause is that an alert is generated while the system suspends. The
interrupt handling generates transactions on the bus that will never
be handled because the interrupts are disabled in parallel.

As a result, the transaction never completes and times out on resume.
This error doesn't seem too problematic since it happens in a work
queue, and the system recovers without issues.

Nevertheless, this race condition should not happen. When doing a
system suspend, or when disabling interrupts, we should make sure the
current transaction can complete, and prevent new work from being
queued.

BugLink: https://github.com/thesofproject/linux/issues/2344
Signed-off-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@linux.intel.com>
Signed-off-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Acked-by: Jaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20200817222340.18042-1-yung-chuan.liao@linux.intel.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>

authored by

Pierre-Louis Bossart and committed by
Vinod Koul
d2068da5 8564551e

+24 -1
+23 -1
drivers/soundwire/cadence_master.c
··· 790 790 CDNS_MCP_INT_SLAVE_MASK, 0); 791 791 792 792 int_status &= ~CDNS_MCP_INT_SLAVE_MASK; 793 - schedule_work(&cdns->work); 793 + 794 + /* 795 + * Deal with possible race condition between interrupt 796 + * handling and disabling interrupts on suspend. 797 + * 798 + * If the master is in the process of disabling 799 + * interrupts, don't schedule a workqueue 800 + */ 801 + if (cdns->interrupt_enabled) 802 + schedule_work(&cdns->work); 794 803 } 795 804 796 805 cdns_writel(cdns, CDNS_MCP_INTSTAT, int_status); ··· 932 923 slave_state = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT1); 933 924 cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT1, slave_state); 934 925 } 926 + cdns->interrupt_enabled = state; 927 + 928 + /* 929 + * Complete any on-going status updates before updating masks, 930 + * and cancel queued status updates. 931 + * 932 + * There could be a race with a new interrupt thrown before 933 + * the 3 mask updates below are complete, so in the interrupt 934 + * we use the 'interrupt_enabled' status to prevent new work 935 + * from being queued. 936 + */ 937 + if (!state) 938 + cancel_work_sync(&cdns->work); 935 939 936 940 cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK0, slave_intmask0); 937 941 cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK1, slave_intmask1);
+1
drivers/soundwire/cadence_master.h
··· 133 133 134 134 bool link_up; 135 135 unsigned int msg_count; 136 + bool interrupt_enabled; 136 137 137 138 struct work_struct work; 138 139