Skip to content

Commit f8c35d6

Browse files
plbossartvinodkoul
authored andcommitted
soundwire: cadence: re-check Peripheral status with delayed_work
The SoundWire peripheral enumeration is entirely based on interrupts, more specifically sticky bits tracking state changes. This patch adds a defensive programming check on the actual status reported in PING frames. If for some reason an interrupt was lost or delayed, the delayed work would detect a peripheral change of status after the bus starts. The 100ms defined for the delay is not completely arbitrary, if a Peripheral didn't join the bus within that delay then probably the hardware link is broken, and conversely if the detection didn't happen because of software issues the 100ms is still acceptable in terms of user experience. The overhead of the one-shot workqueue is minimal, and the mutual exclusion ensures that the interrupt and delayed work cannot update the status concurrently. Reviewed-by: Liam Girdwood <[email protected]> Signed-off-by: Pierre-Louis Bossart <[email protected]> Signed-off-by: Bard Liao <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Vinod Koul <[email protected]>
1 parent 663229e commit f8c35d6

File tree

5 files changed

+56
-2
lines changed

5 files changed

+56
-2
lines changed

drivers/soundwire/cadence_master.c

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,8 +890,14 @@ static int cdns_update_slave_status(struct sdw_cdns *cdns,
890890
}
891891
}
892892

893-
if (is_slave)
894-
return sdw_handle_slave_status(&cdns->bus, status);
893+
if (is_slave) {
894+
int ret;
895+
896+
mutex_lock(&cdns->status_update_lock);
897+
ret = sdw_handle_slave_status(&cdns->bus, status);
898+
mutex_unlock(&cdns->status_update_lock);
899+
return ret;
900+
}
895901

896902
return 0;
897903
}
@@ -988,6 +994,31 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id)
988994
}
989995
EXPORT_SYMBOL(sdw_cdns_irq);
990996

997+
static void cdns_check_attached_status_dwork(struct work_struct *work)
998+
{
999+
struct sdw_cdns *cdns =
1000+
container_of(work, struct sdw_cdns, attach_dwork.work);
1001+
enum sdw_slave_status status[SDW_MAX_DEVICES + 1];
1002+
u32 val;
1003+
int ret;
1004+
int i;
1005+
1006+
val = cdns_readl(cdns, CDNS_MCP_SLAVE_STAT);
1007+
1008+
for (i = 0; i <= SDW_MAX_DEVICES; i++) {
1009+
status[i] = val & 0x3;
1010+
if (status[i])
1011+
dev_dbg(cdns->dev, "Peripheral %d status: %d\n", i, status[i]);
1012+
val >>= 2;
1013+
}
1014+
1015+
mutex_lock(&cdns->status_update_lock);
1016+
ret = sdw_handle_slave_status(&cdns->bus, status);
1017+
mutex_unlock(&cdns->status_update_lock);
1018+
if (ret < 0)
1019+
dev_err(cdns->dev, "%s: sdw_handle_slave_status failed: %d\n", __func__, ret);
1020+
}
1021+
9911022
/**
9921023
* cdns_update_slave_status_work - update slave status in a work since we will need to handle
9931024
* other interrupts eg. CDNS_MCP_INT_RX_WL during the update slave
@@ -1740,7 +1771,11 @@ int sdw_cdns_probe(struct sdw_cdns *cdns)
17401771
init_completion(&cdns->tx_complete);
17411772
cdns->bus.port_ops = &cdns_port_ops;
17421773

1774+
mutex_init(&cdns->status_update_lock);
1775+
17431776
INIT_WORK(&cdns->work, cdns_update_slave_status_work);
1777+
INIT_DELAYED_WORK(&cdns->attach_dwork, cdns_check_attached_status_dwork);
1778+
17441779
return 0;
17451780
}
17461781
EXPORT_SYMBOL(sdw_cdns_probe);

drivers/soundwire/cadence_master.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ struct sdw_cdns_dai_runtime {
117117
* @link_up: Link status
118118
* @msg_count: Messages sent on bus
119119
* @dai_runtime_array: runtime context for each allocated DAI.
120+
* @status_update_lock: protect concurrency between interrupt-based and delayed work
121+
* status update
120122
*/
121123
struct sdw_cdns {
122124
struct device *dev;
@@ -148,10 +150,13 @@ struct sdw_cdns {
148150
bool interrupt_enabled;
149151

150152
struct work_struct work;
153+
struct delayed_work attach_dwork;
151154

152155
struct list_head list;
153156

154157
struct sdw_cdns_dai_runtime **dai_runtime_array;
158+
159+
struct mutex status_update_lock; /* add mutual exclusion to sdw_handle_slave_status() */
155160
};
156161

157162
#define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus)

drivers/soundwire/intel.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ static inline void intel_writew(void __iomem *base, int offset, u16 value)
103103

104104
#define INTEL_MASTER_RESET_ITERATIONS 10
105105

106+
#define SDW_INTEL_DELAYED_ENUMERATION_MS 100
107+
106108
#define SDW_INTEL_CHECK_OPS(sdw, cb) ((sdw) && (sdw)->link_res && (sdw)->link_res->hw_ops && \
107109
(sdw)->link_res->hw_ops->cb)
108110
#define SDW_INTEL_OPS(sdw, cb) ((sdw)->link_res->hw_ops->cb)

drivers/soundwire/intel_auxdevice.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ static void intel_link_remove(struct auxiliary_device *auxdev)
475475
*/
476476
if (!bus->prop.hw_disabled) {
477477
sdw_intel_debugfs_exit(sdw);
478+
cancel_delayed_work_sync(&cdns->attach_dwork);
478479
sdw_cdns_enable_interrupt(cdns, false);
479480
}
480481
sdw_bus_master_delete(bus);

drivers/soundwire/intel_bus_common.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ int intel_start_bus(struct sdw_intel *sdw)
6060
sdw_cdns_check_self_clearing_bits(cdns, __func__,
6161
true, INTEL_MASTER_RESET_ITERATIONS);
6262

63+
schedule_delayed_work(&cdns->attach_dwork,
64+
msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS));
65+
6366
return 0;
6467
}
6568

@@ -151,6 +154,9 @@ int intel_start_bus_after_reset(struct sdw_intel *sdw)
151154
}
152155
sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS);
153156

157+
schedule_delayed_work(&cdns->attach_dwork,
158+
msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS));
159+
154160
return 0;
155161
}
156162

@@ -184,6 +190,9 @@ int intel_start_bus_after_clock_stop(struct sdw_intel *sdw)
184190

185191
sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS);
186192

193+
schedule_delayed_work(&cdns->attach_dwork,
194+
msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS));
195+
187196
return 0;
188197
}
189198

@@ -194,6 +203,8 @@ int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop)
194203
bool wake_enable = false;
195204
int ret;
196205

206+
cancel_delayed_work_sync(&cdns->attach_dwork);
207+
197208
if (clock_stop) {
198209
ret = sdw_cdns_clock_stop(cdns, true);
199210
if (ret < 0)

0 commit comments

Comments
 (0)