From e928d5ceaecb054ec119b8a25a3f46513ee9e68d Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 11 May 2026 15:41:17 +0300 Subject: [PATCH 1/4] soundwire: bus: serialize BPT/BRA transfers per bus On Intel ACE2+ platforms, each SoundWire link uses a single pair of PDIs (PDI0 for TX, PDI1 for RX) and associated DMA resources for Bulk Port Transfers. When two codecs on the same link initiate BRA transfers concurrently (e.g. during firmware download), the second sdw_bpt_send_async() call races with the first: - intel_ace2x_bpt_open_stream() has a TOCTOU on the bpt_stream pointer: both callers see it as NULL and proceed - The second open overwrites the first's stream allocation, leaking the original sdw_stream_runtime - PCMSyCM mappings for the first transfer get overwritten, causing chain DMA to operate with wrong PDI assignments - IOC timeouts, use-after-free, and double-free follow on close Add a per-bus bpt_lock mutex held across the send_async/wait span to serialize BPT transfers. The lock is acquired in sdw_bpt_send_async() before calling the master ops and released in sdw_bpt_wait() after the transfer completes. On send_async failure, the lock is released immediately. Cross-link transfers (different sdw_bus instances) remain concurrent since each bus has its own lock. Signed-off-by: Peter Ujfalusi --- drivers/soundwire/bus.c | 20 ++++++++++++++++++-- include/linux/soundwire/sdw.h | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 6013f963f4821b..57bb095d022140 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -87,6 +87,8 @@ int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent, lockdep_register_key(&bus->bus_lock_key); __mutex_init(&bus->bus_lock, "bus_lock", &bus->bus_lock_key); + mutex_init(&bus->bpt_lock); + INIT_LIST_HEAD(&bus->slaves); INIT_LIST_HEAD(&bus->m_rt_list); @@ -2094,6 +2096,7 @@ EXPORT_SYMBOL(sdw_clear_slave_status); int sdw_bpt_send_async(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg) { int len = 0; + int ret; int i; for (i = 0; i < msg->sections; i++) @@ -2118,13 +2121,26 @@ int sdw_bpt_send_async(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_ return -EOPNOTSUPP; } - return bus->ops->bpt_send_async(bus, slave, msg); + /* Serialize BPT/BRA transfers per bus: PDIs and DMA resources are shared */ + mutex_lock(&bus->bpt_lock); + + ret = bus->ops->bpt_send_async(bus, slave, msg); + if (ret < 0) + mutex_unlock(&bus->bpt_lock); + + /* on success the lock is held until sdw_bpt_wait() */ + return ret; } EXPORT_SYMBOL(sdw_bpt_send_async); int sdw_bpt_wait(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg) { - return bus->ops->bpt_wait(bus, slave, msg); + int ret; + + ret = bus->ops->bpt_wait(bus, slave, msg); + mutex_unlock(&bus->bpt_lock); + + return ret; } EXPORT_SYMBOL(sdw_bpt_wait); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 3e0d21132ef2f7..dd1743e2f25874 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -1044,6 +1044,7 @@ struct sdw_bus { int stream_refcount; int bpt_stream_refcount; struct sdw_stream_runtime *bpt_stream; + struct mutex bpt_lock; /* serialize BPT/BRA transfers per bus */ const struct sdw_master_ops *ops; const struct sdw_master_port_ops *port_ops; struct sdw_master_prop prop; From 35eb1172ace6775509258a367dab9f0a315ad51e Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 9 Jun 2026 20:15:35 +0800 Subject: [PATCH 2/4] ASoC: Intel: hda-sdw-bpt: return ENXIO if SOF DSP is not first booted The BPT stream needs SOF DSP's support. We can only open a BPT stream after the SOF DSP FW is downloaded and booted completed. It also doesn't make sense to wait for SOF FW boot instead of starting common read/write immediately. Signed-off-by: Bard Liao --- sound/soc/sof/intel/hda-sdw-bpt.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/sof/intel/hda-sdw-bpt.c b/sound/soc/sof/intel/hda-sdw-bpt.c index bb52ae5dc79d9a..245d97ea4188c2 100644 --- a/sound/soc/sof/intel/hda-sdw-bpt.c +++ b/sound/soc/sof/intel/hda-sdw-bpt.c @@ -273,6 +273,11 @@ int hda_sdw_bpt_open(struct device *dev, int link_id, struct hdac_ext_stream **b int ret1; int ret; + if (!sdev->dspless_mode_selected && sdev->first_boot) { + dev_dbg(dev, "SOF FW boot not complete yet\n"); + return -ENXIO; + } + num_channels_tx = DIV_ROUND_UP(tx_dma_bandwidth, BPT_FREQUENCY * 32); ret = hda_sdw_bpt_dma_prepare(dev, bpt_tx_stream, dmab_tx_bdl, bpt_tx_num_bytes, From 861c55b149e5465794739753e705e78cab79e8a0 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 11 Jun 2026 21:58:23 +0800 Subject: [PATCH 3/4] soundwire: bus: add bra_w/r_threshold We need a threshold to use BRA. The threshold can be set in the bus struct when sdw_bus_master_add() is called. Signed-off-by: Bard Liao --- drivers/soundwire/bus.c | 8 ++++++++ include/linux/soundwire/sdw.h | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 57bb095d022140..05393e6e47e37d 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -13,6 +13,9 @@ #include "irq.h" #include "sysfs_local.h" +#define DEFAULT_BRA_WRITE_THRESHOLD 800 +#define DEFAULT_BRA_READ_THRESHOLD 400 + static DEFINE_IDA(sdw_bus_ida); static int sdw_get_id(struct sdw_bus *bus) @@ -165,6 +168,11 @@ int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent, bus->params.curr_bank = SDW_BANK0; bus->params.next_bank = SDW_BANK1; + if (!bus->bra_w_threshold) + bus->bra_w_threshold = DEFAULT_BRA_WRITE_THRESHOLD; + if (!bus->bra_r_threshold) + bus->bra_r_threshold = DEFAULT_BRA_READ_THRESHOLD; + return 0; } EXPORT_SYMBOL(sdw_bus_master_add); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index dd1743e2f25874..dad9806311af73 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -1028,6 +1028,10 @@ struct sdw_stream_runtime { * are supported. This flag is populated by drivers after reading * appropriate firmware (ACPI/DT). * @lane_used_bandwidth: how much bandwidth in bits per second is used by each lane + * @bra_w_threshold: Message-size threshold (bytes) above which BPT write is used. + * If set to 0, a default is used. + * @bra_r_threshold: Message-size threshold (bytes) above which BPT read is used. + * If set to 0, a default is used. */ struct sdw_bus { struct device *dev; @@ -1064,6 +1068,8 @@ struct sdw_bus { #endif bool multi_link; unsigned int lane_used_bandwidth[SDW_MAX_LANES]; + unsigned int bra_w_threshold; + unsigned int bra_r_threshold; }; struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name, enum sdw_stream_type type); From 4e63a2968b036372fbcfbabc04b412e49c46e4eb Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 9 Jun 2026 13:22:10 +0800 Subject: [PATCH 4/4] soundwire: use bra if msg size > threshold It is more efficient to read/write with bra if the message size is larger than a predefined threshold. Signed-off-by: Bard Liao --- drivers/soundwire/bus.c | 82 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 05393e6e47e37d..0d4d0ac7dbe9d1 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -449,6 +449,46 @@ static int sdw_ntransfer_no_pm(struct sdw_slave *slave, u32 addr, u8 flags, return 0; } +static int sdw_ntransfer_no_pm_bpt(struct sdw_slave *slave, u32 addr, u8 flags, + size_t count, u8 *val) +{ + struct sdw_bpt_section sec; + struct sdw_bpt_msg msg; + size_t size; + int retry = 5; + int ret; + + msg.sections = 1; + msg.dev_num = slave->dev_num; + msg.flags = flags; + msg.sec = &sec; + + while (count) { + size = min_t(size_t, count, SDW_BPT_MSG_MAX_BYTES); + + sec.addr = addr; + sec.len = size; + sec.buf = val; + + do { + ret = sdw_bpt_send_sync(slave->bus, slave, &msg); + if (ret == -EAGAIN) + msleep(10); + retry--; + } while (ret == -EAGAIN && retry > 0); + + if (ret < 0) + return ret; + + addr += size; + val += size; + count -= size; + retry = 5; + } + + return 0; +} + /** * sdw_nread_no_pm() - Read "n" contiguous SDW Slave registers with no PM * @slave: SDW Slave @@ -457,10 +497,26 @@ static int sdw_ntransfer_no_pm(struct sdw_slave *slave, u32 addr, u8 flags, * @val: Buffer for values to be read * * Note that if the message crosses a page boundary each page will be - * transferred under a separate invocation of the msg_lock. + * transferred under a separate invocation of the msg_lock if it is not + * transferred via BPT. */ int sdw_nread_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) { + struct sdw_bus *bus = slave->bus; + int ret; + + if (!bus->ops->bpt_send_async || !bus->ops->bpt_wait || + count < bus->bra_r_threshold) + goto fallback; + + ret = sdw_ntransfer_no_pm_bpt(slave, addr, SDW_MSG_FLAG_READ, count, val); + if (!ret) + return 0; + + dev_dbg(&slave->dev, + "BPT read failed for addr %x, count %zu, ret %d fallback to normal read\n", + addr, count, ret); +fallback: return sdw_ntransfer_no_pm(slave, addr, SDW_MSG_FLAG_READ, count, val); } EXPORT_SYMBOL(sdw_nread_no_pm); @@ -473,10 +529,26 @@ EXPORT_SYMBOL(sdw_nread_no_pm); * @val: Buffer for values to be written * * Note that if the message crosses a page boundary each page will be - * transferred under a separate invocation of the msg_lock. + * transferred under a separate invocation of the msg_lock if it is not + * transferred via BPT. */ int sdw_nwrite_no_pm(struct sdw_slave *slave, u32 addr, size_t count, const u8 *val) { + struct sdw_bus *bus = slave->bus; + int ret; + + if (!bus->ops->bpt_send_async || !bus->ops->bpt_wait || + count < bus->bra_w_threshold) + goto fallback; + + ret = sdw_ntransfer_no_pm_bpt(slave, addr, SDW_MSG_FLAG_WRITE, count, (u8 *)val); + if (!ret) + return 0; + + dev_dbg(&slave->dev, + "BPT write failed for addr %x, count %zu, ret %d fallback to normal write\n", + addr, count, ret); +fallback: return sdw_ntransfer_no_pm(slave, addr, SDW_MSG_FLAG_WRITE, count, (u8 *)val); } EXPORT_SYMBOL(sdw_nwrite_no_pm); @@ -614,7 +686,8 @@ EXPORT_SYMBOL(sdw_update); * This version of the function will take a PM reference to the slave * device. * Note that if the message crosses a page boundary each page will be - * transferred under a separate invocation of the msg_lock. + * transferred under a separate invocation of the msg_lock if it is not + * transferred via BPT. */ int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) { @@ -645,7 +718,8 @@ EXPORT_SYMBOL(sdw_nread); * This version of the function will take a PM reference to the slave * device. * Note that if the message crosses a page boundary each page will be - * transferred under a separate invocation of the msg_lock. + * transferred under a separate invocation of the msg_lock if it is not + * transferred via BPT. */ int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, const u8 *val) {