From e928d5ceaecb054ec119b8a25a3f46513ee9e68d Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 11 May 2026 15:41:17 +0300 Subject: [PATCH 1/5] 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/5] 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 0b9b34b57f3f104d75e43e2edbe7470274a05466 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Mon, 15 Jun 2026 21:08:08 +0800 Subject: [PATCH 3/5] soundwire: intel_ace2x: don't print -ENXIO error hda_sdw_bpt_open() will return -ENXIO when it is called before the Intel audio DSP FW not boot complete yet. We will fallback to normal sdw read/write method. Don't print the error message to scare the user. Signed-off-by: Bard Liao --- drivers/soundwire/intel_ace2x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c index 642b33ab552606..938c9e183ad314 100644 --- a/drivers/soundwire/intel_ace2x.c +++ b/drivers/soundwire/intel_ace2x.c @@ -252,7 +252,7 @@ static int intel_ace2x_bpt_open_stream(struct sdw_intel *sdw, struct sdw_slave * &sdw->bpt_ctx.dmab_tx_bdl, pdi0_buffer_size, tx_dma_bandwidth, &sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl, pdi1_buffer_size, rx_dma_bandwidth); - if (ret < 0) { + if (ret < 0 && ret != -ENXIO) { dev_err(cdns->dev, "%s: hda_sdw_bpt_open failed %d\n", __func__, ret); goto deprepare_stream; } From 0f2b6f77e499246382cbab753d995c865a2c87e0 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 11 Jun 2026 21:58:23 +0800 Subject: [PATCH 4/5] soundwire: bus: add bpt_w/r_threshold We need a threshold to use BPT/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..f2a1ab5cf7aed1 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->bpt_w_threshold) + bus->bpt_w_threshold = DEFAULT_BRA_WRITE_THRESHOLD; + if (!bus->bpt_r_threshold) + bus->bpt_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..cfcc7228bc199a 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 + * @bpt_w_threshold: Message-size threshold (bytes) above which BPT write is used. + * If set to 0, a default is used. + * @bpt_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 bpt_w_threshold; + unsigned int bpt_r_threshold; }; struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name, enum sdw_stream_type type); From 734df25a9b3903978e521b9a7bdea86239975f1a Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 9 Jun 2026 13:22:10 +0800 Subject: [PATCH 5/5] 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 f2a1ab5cf7aed1..fbf3f3cba1a3c9 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) {