From 7869d8bafafd7b2ababf0f796cbbbad5d4785285 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 3 Nov 2026 19:34:18 +0800 Subject: [PATCH 1/4] soundwire: Pass prepare context to parameter computation The compute_params callback is called from both stream prepare and deprepare. A followup commit will make it perform different actions depending on which operation invokes it. No functional change. Signed-off-by: Bard Liao --- drivers/soundwire/amd_manager.c | 3 ++- drivers/soundwire/generic_bandwidth_allocation.c | 4 +++- drivers/soundwire/qcom.c | 3 ++- drivers/soundwire/stream.c | 4 ++-- include/linux/soundwire/sdw.h | 9 ++++++--- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index a3316efdf8ac27..f40a3af4351f6e 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -465,7 +465,8 @@ static u32 amd_sdw_read_ping_status(struct sdw_bus *bus) return slave_stat; } -static int amd_sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream) +static int amd_sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream, + bool prepare) { struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); struct sdw_transport_data t_data = {0}; diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c index 29d9fcef8c709b..4660a7c390b679 100644 --- a/drivers/soundwire/generic_bandwidth_allocation.c +++ b/drivers/soundwire/generic_bandwidth_allocation.c @@ -659,8 +659,10 @@ static int sdw_compute_bus_params(struct sdw_bus *bus) * * @bus: SDW Bus instance * @stream: Soundwire stream + * @prepare: True when preparing, false when depreparing */ -int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream) +int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream, + bool prepare) { int ret; diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 6e0c5fb10efad4..f9500d387c7b49 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -1215,7 +1215,8 @@ static const struct sdw_master_ops qcom_swrm_ops = { .pre_bank_switch = qcom_swrm_pre_bank_switch, }; -static int qcom_swrm_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream) +static int qcom_swrm_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream, + bool prepare) { struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); struct sdw_master_runtime *m_rt; diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index c158bf91e020f9..403e00b7327a8a 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1513,7 +1513,7 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream, /* Compute params */ if (bus->compute_params) { - ret = bus->compute_params(bus, stream); + ret = bus->compute_params(bus, stream, true); if (ret < 0) { dev_err(bus->dev, "Compute params failed: %d\n", ret); @@ -1821,7 +1821,7 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) /* Compute params */ if (bus->compute_params) { - ret = bus->compute_params(bus, stream); + ret = bus->compute_params(bus, stream, false); if (ret < 0) { dev_err(bus->dev, "Compute params failed: %d\n", ret); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 326db122bbe6ea..b5fe0774332c56 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -1021,7 +1021,8 @@ struct sdw_stream_runtime { * @controller_id: system-unique controller ID. If set to -1, the bus @id will be used. * @link_id: Link id number, can be 0 to N, unique for each Controller * @id: bus system-wide unique id - * @compute_params: points to Bus resource management implementation + * @compute_params: points to Bus resource management implementation; the prepare + * argument is true when preparing a stream and false when depreparing it * @assigned: Bitmap for Slave device numbers. * Bit set implies used number, bit clear implies unused number. * @clk_stop_timeout: Clock stop timeout computed @@ -1058,7 +1059,8 @@ struct sdw_bus { int controller_id; unsigned int link_id; int id; - int (*compute_params)(struct sdw_bus *bus, struct sdw_stream_runtime *stream); + int (*compute_params)(struct sdw_bus *bus, struct sdw_stream_runtime *stream, + bool prepare); DECLARE_BITMAP(assigned, SDW_MAX_DEVICES); unsigned int clk_stop_timeout; u32 bank_switch_timeout; @@ -1074,7 +1076,8 @@ struct sdw_bus { struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name, enum sdw_stream_type type); void sdw_release_stream(struct sdw_stream_runtime *stream); -int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream); +int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream, + bool prepare); int sdw_stream_add_master(struct sdw_bus *bus, struct sdw_stream_config *stream_config, From 77ffd2bf84e204c89029480d827d2622bad0fb6d Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 3 Nov 2026 20:03:41 +0800 Subject: [PATCH 2/4] soundwire: Select the correct runtime for lane allocation When multiple master runtimes exist on the same bus, sdw_compute_bus_params() selects the first runtime for which an additional lane is available. This can select an already running stream instead of the stream being prepared. Changing that runtime's port lanes accounts for its bandwidth again. If preparing the new stream fails, restoring the bus parameters leaves the active stream's lanes inconsistent with its bandwidth reservation. Depreparing the active stream can then underflow the lane bandwidth and leave lane 0 bandwidth reserved. Pass the current stream and prepare context to sdw_compute_bus_params() and restrict lane allocation to that stream's runtime during prepare. Skip lane allocation during deprepare, when its bandwidth reservation has already been released. Keep clock recomputation and the existing group and port filtering unchanged. Signed-off-by: Bard Liao --- .../soundwire/generic_bandwidth_allocation.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c index 4660a7c390b679..47a58cb1521ca5 100644 --- a/drivers/soundwire/generic_bandwidth_allocation.c +++ b/drivers/soundwire/generic_bandwidth_allocation.c @@ -538,8 +538,11 @@ static int get_manager_lane(struct sdw_bus *bus, struct sdw_master_runtime *m_rt * sdw_compute_bus_params: Compute bus parameters * * @bus: SDW Bus instance + * @stream: SoundWire stream being prepared or deprepared + * @prepare: True when preparing, false when depreparing */ -static int sdw_compute_bus_params(struct sdw_bus *bus) +static int sdw_compute_bus_params(struct sdw_bus *bus, + struct sdw_stream_runtime *stream, bool prepare) { struct sdw_master_prop *mstr_prop = &bus->prop; struct sdw_slave_prop *slave_prop; @@ -582,6 +585,14 @@ static int sdw_compute_bus_params(struct sdw_bus *bus) break; list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + /* + * Select only the current stream's runtime. Check prepare + * to avoid computing m_lane during deprepare, when the + * stream's bandwidth reservation has already been released. + */ + if (m_rt->stream != stream || !prepare) + continue; + /* * Get the first s_rt that will be used to find the available lane that * can be used. No need to check all Peripherals because we can't use @@ -629,8 +640,8 @@ static int sdw_compute_bus_params(struct sdw_bus *bus) } } /* - * Set Manager lanes. Configure the last m_rt in bus->m_rt_list only since - * we don't want to touch other m_rts that are already working. + * Set Manager lanes for the stream being prepared without changing + * other runtimes that are already working. */ list_for_each_entry(m_p_rt, &m_rt->port_list, port_node) { m_p_rt->lane = m_lane; @@ -667,7 +678,7 @@ int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream, int ret; /* Computes clock frequency, frame shape and frame frequency */ - ret = sdw_compute_bus_params(bus); + ret = sdw_compute_bus_params(bus, stream, prepare); if (ret < 0) return ret; From cb335f0a19594786437e67cd071cbd6090cbc2ca Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 3 Nov 2026 20:03:51 +0800 Subject: [PATCH 3/4] soundwire: Include the current stream when preparing again sdw_prepare_stream() accepts a stream in the DEPREPARED state without changing its state before parameter computation. However, the generic allocator excludes DEPREPARED streams from rate groups and port computation, and only includes a CONFIGURED current stream in group payload calculations. Consequently, preparing an existing stream again can reserve bandwidth without computing the corresponding transport parameters. The filters need to distinguish preparing that stream again from depreparing it. Pass the prepare context through the group and port helpers. Include the current stream during prepare even when it is still DEPREPARED, while preserving the filtering of all other runtimes and the behavior during deprepare. This supports preparing again without temporarily changing stream state. Signed-off-by: Bard Liao --- .../soundwire/generic_bandwidth_allocation.c | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c index 47a58cb1521ca5..2f5fd02ce0b3c0 100644 --- a/drivers/soundwire/generic_bandwidth_allocation.c +++ b/drivers/soundwire/generic_bandwidth_allocation.c @@ -190,7 +190,8 @@ static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt, } static void _sdw_compute_port_params(struct sdw_bus *bus, - struct sdw_group_params *params, int count) + struct sdw_group_params *params, int count, + struct sdw_stream_runtime *stream, bool prepare) { struct sdw_master_runtime *m_rt; int port_bo, i, l; @@ -211,9 +212,12 @@ static void _sdw_compute_port_params(struct sdw_bus *bus, /* * Only runtimes with CONFIGURED, PREPARED, ENABLED, and DISABLED * states should be included in the bandwidth calculation. + * Include the current stream during prepare even if it is + * still in the DEPREPARED state. */ - if (m_rt->stream->state > SDW_STREAM_DISABLED || - m_rt->stream->state < SDW_STREAM_CONFIGURED) + if (!(prepare && m_rt->stream == stream) && + (m_rt->stream->state > SDW_STREAM_DISABLED || + m_rt->stream->state < SDW_STREAM_CONFIGURED)) continue; sdw_compute_master_ports(m_rt, ¶ms[i], &port_bo, hstop); } @@ -226,7 +230,7 @@ static void _sdw_compute_port_params(struct sdw_bus *bus, static int sdw_compute_group_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream, struct sdw_group_params *params, - struct sdw_group *group) + struct sdw_group *group, bool prepare) { struct sdw_master_runtime *m_rt; struct sdw_port_runtime *p_rt; @@ -242,20 +246,16 @@ static int sdw_compute_group_params(struct sdw_bus *bus, } list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { - if (m_rt->stream == stream) { - /* Only runtime during prepare should be added */ - if (stream->state != SDW_STREAM_CONFIGURED) - continue; - } else { - /* - * Include runtimes with running (ENABLED/PREPARED state) and - * paused (DISABLED state) streams - */ - if (m_rt->stream->state != SDW_STREAM_ENABLED && - m_rt->stream->state != SDW_STREAM_PREPARED && - m_rt->stream->state != SDW_STREAM_DISABLED) - continue; - } + /* + * Include the current stream during prepare even if it is still + * DEPREPARED. Otherwise, include only other running or paused streams. + */ + if (!(prepare && m_rt->stream == stream) && + (m_rt->stream == stream || + (m_rt->stream->state != SDW_STREAM_ENABLED && + m_rt->stream->state != SDW_STREAM_PREPARED && + m_rt->stream->state != SDW_STREAM_DISABLED))) + continue; list_for_each_entry(p_rt, &m_rt->port_list, port_node) { rate = m_rt->stream->params.rate; bps = m_rt->stream->params.bps; @@ -333,7 +333,8 @@ static int sdw_add_element_group_count(struct sdw_group *group, } static int sdw_get_group_count(struct sdw_bus *bus, - struct sdw_group *group) + struct sdw_group *group, + struct sdw_stream_runtime *stream, bool prepare) { struct sdw_master_runtime *m_rt; struct sdw_port_runtime *p_rt; @@ -354,7 +355,12 @@ static int sdw_get_group_count(struct sdw_bus *bus, } list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { - if (m_rt->stream->state == SDW_STREAM_DEPREPARED) + /* + * Include the current stream during prepare even if it is still + * in the DEPREPARED state. + */ + if (!(prepare && m_rt->stream == stream) && + m_rt->stream->state == SDW_STREAM_DEPREPARED) continue; rate = m_rt->stream->params.rate; @@ -385,14 +391,16 @@ static int sdw_get_group_count(struct sdw_bus *bus, * * @bus: SDW Bus instance * @stream: Soundwire stream + * @prepare: True when preparing, false when depreparing */ -static int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream) +static int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream, + bool prepare) { struct sdw_group_params *params = NULL; struct sdw_group group; int ret; - ret = sdw_get_group_count(bus, &group); + ret = sdw_get_group_count(bus, &group, stream, prepare); if (ret < 0) return ret; @@ -406,11 +414,11 @@ static int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtim } /* Compute transport parameters for grouped streams */ - ret = sdw_compute_group_params(bus, stream, params, &group); + ret = sdw_compute_group_params(bus, stream, params, &group, prepare); if (ret < 0) goto free_params; - _sdw_compute_port_params(bus, params, group.count); + _sdw_compute_port_params(bus, params, group.count, stream, prepare); free_params: kfree(params); @@ -688,7 +696,7 @@ int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream, } /* Compute transport and port params */ - ret = sdw_compute_port_params(bus, stream); + ret = sdw_compute_port_params(bus, stream, prepare); if (ret < 0) { dev_err(bus->dev, "Compute transport params failed: %d\n", ret); return ret; From 07e21a629c734ea4ebea04fd5c2e73bca990fcf8 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 3 Nov 2026 21:00:08 +0800 Subject: [PATCH 4/4] fixup! soundwire: stream: Fix bandwidth leak in multi-link aggregation failure A failed prepare restores the bus parameters but leaves the master and peripheral port lane assignments unchanged. If a subsequent prepare selects lane 0, the existing nonzero-lane-only assignment path leaves the ports using the stale lanes from the failed attempt. Rather than saving and restoring the original port lanes on failure, apply the newly computed lane assignments on each successful bus parameter calculation during prepare. Explicitly clear the current stream's master and peripheral port lanes when lane 0 is selected; the existing path already updates them when a nonzero lane is selected. This ensures that a subsequent prepare uses the newly selected lanes, including when retrying with the same port runtimes, instead of relying on the lane values left by the failed attempt. Lane assignments are not changed during deprepare. Signed-off-by: Bard Liao --- drivers/soundwire/generic_bandwidth_allocation.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c index 2f5fd02ce0b3c0..d7a3de324b17d1 100644 --- a/drivers/soundwire/generic_bandwidth_allocation.c +++ b/drivers/soundwire/generic_bandwidth_allocation.c @@ -654,6 +654,20 @@ static int sdw_compute_bus_params(struct sdw_bus *bus, list_for_each_entry(m_p_rt, &m_rt->port_list, port_node) { m_p_rt->lane = m_lane; } + } else if (prepare) { + /* Clear stale lane assignments when preparing on lane 0. */ + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + if (m_rt->stream != stream) + continue; + + list_for_each_entry(m_p_rt, &m_rt->port_list, port_node) + m_p_rt->lane = 0; + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) + list_for_each_entry(s_p_rt, &s_rt->port_list, port_node) + s_p_rt->lane = 0; + break; + } } if (!mstr_prop->default_frame_rate || !mstr_prop->default_row)