* [PATCH RFC v2 1/4] drm: Introduce DisplayPort connector helpers with link training state
From: Kory Maincent @ 2026-06-19 14:08 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
Drew Davenport, Louis Chauvet, Luca Ceresoli, dri-devel,
linux-kernel, intel-gfx, intel-xe, linux-mediatek,
linux-arm-kernel, Kory Maincent
In-Reply-To: <20260619-feat_link_cap-v2-0-a3dec4c02ad9@bootlin.com>
Add managed and unmanaged DisplayPort connector initialization helpers,
drmm_connector_dp_init() and drm_connector_dp_init_with_ddc(), modeled
after the existing HDMI counterpart drmm_connector_hdmi_init().
These helpers initialize DP-specific connector state and expose link
training capabilities and state to userspace via sysfs attributes under
dp_link:
- source_link_rates_caps: Array of source-supported link rates
- source_max_lane_count_caps: Source maximum lane count capability
- source_dsc_caps: Source Display Stream Compression support
- sink_max_link_rate_caps: Sink maximum link rate capability
- sink_max_lane_count_caps: Sink maximum lane count capability
- sink_dsc_caps: Sink DSC support
- cur_link_rate: Current negotiated link rate
- cur_lane_count: Current negotiated lane count
- dsc_en: DSC enabled in current link training
- max_link_rate: Maximum achievable link rate (limited by source/sink)
- max_lane_count: Maximum achievable lane count (limited by source/sink)
Link rates are passed by the driver in deca-kbps, following the DRM
convention, but exposed to userspace in kbps for clarity.
Additional helpers are provided to manage link capabilities and parameters
at runtime:
- drm_dp_sink_set_link_caps(): Set sink capabilities after DPCD read
- drm_dp_sink_reset_link_caps(): Reset sink capabilities on disconnect
- drm_dp_set_cur_link_params(): Update current link training parameters
- drm_dp_set_max_link_params(): Update maximum achievable parameters
The aim of such development is to guide users to select the most suitable
DisplayPort connector for their needs. For example, if you have a USB-C
hub with lesser capabilities than your computer’s native DisplayPort
connector (such as HBR2 versus HBR3 support), the system could recommend
connecting high-resolution displays directly to the computer’s port
instead of through the hub to ensure optimal performance.
Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Changes in v2:
- Remove voltage swing and pre-emphasis properties
- Expose link training state via sysfs dp_link/ group instead of
connector properties
- Rename variables from link_train to link as they relate directly to
the link capabilities
- Add comprehensive sysfs attributes for both source and sink capabilities
- Add separate helpers for managing sink capabilities and for current and
maximum link parameters
---
drivers/gpu/drm/display/drm_dp_helper.c | 144 ++++++++++++++++++++++++++++++++
drivers/gpu/drm/drm_connector.c | 122 +++++++++++++++++++++++++++
drivers/gpu/drm/drm_sysfs.c | 100 ++++++++++++++++++++++
include/drm/display/drm_dp_helper.h | 7 ++
include/drm/drm_connector.h | 105 +++++++++++++++++++++++
5 files changed, 478 insertions(+)
diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c
index 9c31e14cc413b..bd0e1eb657412 100644
--- a/drivers/gpu/drm/display/drm_dp_helper.c
+++ b/drivers/gpu/drm/display/drm_dp_helper.c
@@ -4900,3 +4900,147 @@ int drm_dp_max_dprx_data_rate(int max_link_rate, int max_lanes)
1000000 * 8);
}
EXPORT_SYMBOL(drm_dp_max_dprx_data_rate);
+
+static int drm_dp_dpcd_read_link_rate_caps(struct drm_dp_aux *aux)
+{
+ u8 data;
+ int ret;
+
+ ret = drm_dp_dpcd_read_byte(aux, DP_DP13_DPCD_REV + DP_MAIN_LINK_CHANNEL_CODING, &data);
+ if (ret < 0)
+ return ret;
+
+ if (data & DP_CAP_ANSI_128B132B) {
+ ret = drm_dp_dpcd_read_byte(aux, DP_128B132B_SUPPORTED_LINK_RATES, &data);
+ if (ret < 0)
+ return ret;
+
+ if (data & DP_UHBR20)
+ return 20000000;
+ if (data & DP_UHBR13_5)
+ return 13500000;
+ if (data & DP_UHBR10)
+ return 10000000;
+ }
+
+ ret = drm_dp_dpcd_read_byte(aux, DP_MAX_LINK_RATE, &data);
+ if (ret < 0)
+ return ret;
+
+ return data * 270000;
+}
+
+/**
+ * drm_dp_sink_set_link_caps - Set DisplayPort sink link capabilities
+ * @connector: DisplayPort connector
+ * @aux: The DP AUX channel to use
+ *
+ * This function sets the DisplayPort sink (monitor) link training capabilities
+ * for the given connector. These capabilities are typically read from the
+ * sink's DPCD registers during HPD processing.
+ */
+void drm_dp_sink_set_link_caps(struct drm_connector *connector,
+ struct drm_dp_aux *aux)
+{
+ u32 lane_count, link_rate;
+ u8 data;
+ int ret;
+
+ if (!connector)
+ return;
+
+ WARN_ON(!drm_modeset_is_locked(&connector->dev->mode_config.connection_mutex));
+
+ ret = drm_dp_dpcd_read_byte(aux, DP_MAX_LANE_COUNT, &data);
+ if (ret < 0)
+ return;
+
+ lane_count = data & DP_MAX_LANE_COUNT_MASK;
+
+ ret = drm_dp_dpcd_read_link_rate_caps(aux);
+ if (ret < 0)
+ return;
+
+ link_rate = ret;
+
+ ret = drm_dp_dpcd_read_byte(aux, DP_DSC_SUPPORT, &data);
+ if (ret < 0)
+ return;
+
+ connector->dp.sink_max_lane_count_caps = lane_count;
+ connector->dp.sink_max_link_rate_caps = link_rate;
+ connector->dp.sink_dsc_caps = ret & DP_DSC_DECOMPRESSION_IS_SUPPORTED;
+}
+EXPORT_SYMBOL_GPL(drm_dp_sink_set_link_caps);
+
+/**
+ * drm_dp_set_cur_link_params - Set current DisplayPort link parameters
+ * @connector: DisplayPort connector
+ * @link_rate: Current link rate in deca-kbps
+ * @lane_count: Current lane count
+ * @dsc_en: Display Stream Compression enabled
+ *
+ * This function sets the current active DisplayPort link parameters after
+ * link training has completed. These parameters represent the actual link
+ * configuration being used for display output.
+ */
+void drm_dp_set_cur_link_params(struct drm_connector *connector,
+ u32 link_rate, u32 lane_count, bool dsc_en)
+{
+ if (!connector)
+ return;
+
+ WARN_ON(!drm_modeset_is_locked(&connector->dev->mode_config.connection_mutex));
+
+ /* Convert deca-kbps in kbps */
+ connector->dp.cur_link_rate = link_rate * 10;
+ connector->dp.cur_lane_count = lane_count;
+ connector->dp.dsc_en = dsc_en;
+}
+EXPORT_SYMBOL_GPL(drm_dp_set_cur_link_params);
+
+/**
+ * drm_dp_set_max_link_params - Set maximum DisplayPort link parameters
+ * @connector: DisplayPort connector
+ * @link_rate: Maximum link rate in kbps
+ * @lane_count: Maximum lane count
+ *
+ * This function sets the maximum achievable DisplayPort link parameters,
+ * which represent the intersection of source and sink capabilities. These
+ * values are the upper bounds for link training attempts.
+ */
+void drm_dp_set_max_link_params(struct drm_connector *connector, u32 link_rate,
+ u32 lane_count)
+{
+ if (!connector)
+ return;
+
+ WARN_ON(!drm_modeset_is_locked(&connector->dev->mode_config.connection_mutex));
+
+ connector->dp.max_link_rate = link_rate;
+ connector->dp.max_lane_count = lane_count;
+}
+EXPORT_SYMBOL_GPL(drm_dp_set_max_link_params);
+
+/**
+ * drm_dp_sink_reset_link_caps - Reset DisplayPort sink capabilities
+ * @connector: DisplayPort connector
+ *
+ * This function resets all DisplayPort sink link capabilities and parameters
+ * to their default state. This should be called when a sink is disconnected
+ * to clear stale capability information.
+ */
+void drm_dp_sink_reset_link_caps(struct drm_connector *connector)
+{
+ if (!connector)
+ return;
+
+ WARN_ON(!drm_modeset_is_locked(&connector->dev->mode_config.connection_mutex));
+
+ drm_dp_set_cur_link_params(connector, 0, 0, false);
+ drm_dp_set_max_link_params(connector, 0, 0);
+ connector->dp.sink_max_link_rate_caps = 0;
+ connector->dp.sink_max_lane_count_caps = 0;
+ connector->dp.sink_dsc_caps = false;
+}
+EXPORT_SYMBOL_GPL(drm_dp_sink_reset_link_caps);
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index a5d13b92b665c..259af3240c057 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -489,6 +489,128 @@ int drm_connector_init_with_ddc(struct drm_device *dev,
}
EXPORT_SYMBOL(drm_connector_init_with_ddc);
+static int drm_dp_source_set_link_caps(struct drm_connector *connector,
+ const struct drm_connector_dp_link_caps *link_caps)
+{
+ u32 *_link_rates;
+
+ _link_rates = devm_kmemdup_array(connector->dev->dev,
+ link_caps->link_rates,
+ link_caps->nlink_rates,
+ sizeof(*link_caps->link_rates),
+ GFP_KERNEL);
+ if (!_link_rates)
+ return -ENOMEM;
+
+ for (int i = 0; i < link_caps->nlink_rates; i++)
+ /* Convert deca-kbps in kbps */
+ _link_rates[i] *= 10;
+
+ connector->dp.source_link_rates_caps = _link_rates;
+ connector->dp.source_num_link_rates_caps = link_caps->nlink_rates;
+ connector->dp.source_max_lane_count_caps = link_caps->nlanes;
+ connector->dp.source_dsc_caps = link_caps->dsc;
+
+ return 0;
+}
+
+/**
+ * drmm_connector_dp_init - Init a preallocated DisplayPort connector
+ * @dev: DRM device
+ * @connector: A pointer to the DisplayPort connector to init
+ * @funcs: callbacks for this connector
+ * @dp_link_caps: DisplayPort link training capabilities. The pointer
+ * is not kept by the DRM core
+ * @connector_type: user visible type of the connector
+ * @ddc: optional pointer to the associated ddc adapter
+ *
+ * Initialises a preallocated DisplayPort connector. Connectors can be
+ * subclassed as part of driver connector objects.
+ *
+ * Cleanup is automatically handled with a call to
+ * drm_connector_cleanup() in a DRM-managed action.
+ *
+ * The connector structure should be allocated with drmm_kzalloc().
+ *
+ * The @drm_connector_funcs.destroy hook must be NULL.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int drmm_connector_dp_init(struct drm_device *dev,
+ struct drm_connector *connector,
+ const struct drm_connector_funcs *funcs,
+ const struct drm_connector_dp_link_caps *dp_link_caps,
+ int connector_type,
+ struct i2c_adapter *ddc)
+{
+ int ret;
+
+ if (!(connector_type == DRM_MODE_CONNECTOR_DisplayPort ||
+ connector_type == DRM_MODE_CONNECTOR_eDP))
+ return -EINVAL;
+
+ if (!dp_link_caps)
+ return -EINVAL;
+
+ ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc);
+ if (ret)
+ return ret;
+
+ return drm_dp_source_set_link_caps(connector, dp_link_caps);
+}
+EXPORT_SYMBOL(drmm_connector_dp_init);
+
+/**
+ * drm_connector_dp_init_with_ddc - Init a preallocated DisplayPort connector
+ * @dev: DRM device
+ * @connector: A pointer to the DisplayPort connector to init
+ * @funcs: callbacks for this connector
+ * @dp_link_caps: DisplayPort link training capabilities. The pointer
+ * is not kept by the DRM core
+ * @connector_type: user visible type of the connector
+ * @ddc: optional pointer to the associated ddc adapter
+ *
+ * Initialises a preallocated connector. Connectors should be
+ * subclassed as part of driver connector objects.
+ *
+ * At driver unload time the driver's &drm_connector_funcs.destroy hook
+ * should call drm_connector_cleanup() and free the connector structure.
+ * The connector structure should not be allocated with devm_kzalloc().
+ *
+ * Ensures that the ddc field of the connector is correctly set.
+ *
+ * Note: consider using drmm_connector_dp_init() instead of
+ * drm_connector_dp_init_with_ddc() to let the DRM managed resource
+ * infrastructure take care of cleanup and deallocation.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int drm_connector_dp_init_with_ddc(struct drm_device *dev,
+ struct drm_connector *connector,
+ const struct drm_connector_funcs *funcs,
+ const struct drm_connector_dp_link_caps *dp_link_caps,
+ int connector_type,
+ struct i2c_adapter *ddc)
+{
+ int ret;
+
+ if (!(connector_type == DRM_MODE_CONNECTOR_DisplayPort ||
+ connector_type == DRM_MODE_CONNECTOR_eDP))
+ return -EINVAL;
+
+ if (!dp_link_caps)
+ return -EINVAL;
+
+ ret = drm_connector_init_with_ddc(dev, connector, funcs, connector_type, ddc);
+ if (ret)
+ return ret;
+
+ return drm_dp_source_set_link_caps(connector, dp_link_caps);
+}
+EXPORT_SYMBOL(drm_connector_dp_init_with_ddc);
+
static void drm_connector_cleanup_action(struct drm_device *dev,
void *ptr)
{
diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
index ef4e923a87284..653fecf23d717 100644
--- a/drivers/gpu/drm/drm_sysfs.c
+++ b/drivers/gpu/drm/drm_sysfs.c
@@ -340,6 +340,95 @@ static const struct attribute_group *connector_dev_groups[] = {
NULL
};
+static ssize_t drm_link_rates_show(u32 num_link_rates, const u32 *link_rates, char *buf)
+{
+ int size = 0;
+
+ if (!num_link_rates)
+ return 0;
+
+ size += sysfs_emit_at(buf, size, "%d", link_rates[0]);
+ for (int i = 1; i < num_link_rates; i++)
+ size += sysfs_emit_at(buf, size, " %d", link_rates[i]);
+
+ size += sysfs_emit_at(buf, size, "\n");
+ return size;
+}
+
+static ssize_t source_link_rates_caps_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct drm_connector *connector = to_drm_connector(device);
+ ssize_t size;
+
+ drm_modeset_lock(&connector->dev->mode_config.connection_mutex, NULL);
+ size = drm_link_rates_show(connector->dp.source_num_link_rates_caps,
+ connector->dp.source_link_rates_caps, buf);
+ drm_modeset_unlock(&connector->dev->mode_config.connection_mutex);
+ return size;
+}
+
+#define DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(_name) \
+static ssize_t _name##_show(struct device *device, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct drm_connector *connector = to_drm_connector(device); \
+ int ret; \
+ drm_modeset_lock(&connector->dev->mode_config.connection_mutex, NULL); \
+ if (!connector->dp._name) { \
+ drm_modeset_unlock(&connector->dev->mode_config.connection_mutex); \
+ return 0; \
+ } \
+ ret = sysfs_emit(buf, "%d\n", connector->dp._name); \
+ drm_modeset_unlock(&connector->dev->mode_config.connection_mutex); \
+ return ret; \
+}
+
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(source_max_lane_count_caps);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(source_dsc_caps);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(sink_dsc_caps);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(sink_max_link_rate_caps);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(sink_max_lane_count_caps);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(cur_link_rate);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(cur_lane_count);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(dsc_en);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(max_link_rate);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE(max_lane_count);
+
+static DEVICE_ATTR_RO(source_link_rates_caps);
+static DEVICE_ATTR_RO(source_max_lane_count_caps);
+static DEVICE_ATTR_RO(source_dsc_caps);
+static DEVICE_ATTR_RO(sink_max_link_rate_caps);
+static DEVICE_ATTR_RO(sink_max_lane_count_caps);
+static DEVICE_ATTR_RO(sink_dsc_caps);
+static DEVICE_ATTR_RO(cur_link_rate);
+static DEVICE_ATTR_RO(cur_lane_count);
+static DEVICE_ATTR_RO(dsc_en);
+static DEVICE_ATTR_RO(max_link_rate);
+static DEVICE_ATTR_RO(max_lane_count);
+
+static struct attribute *connector_dp_link_attrs[] = {
+ &dev_attr_source_link_rates_caps.attr,
+ &dev_attr_source_max_lane_count_caps.attr,
+ &dev_attr_source_dsc_caps.attr,
+ &dev_attr_sink_max_link_rate_caps.attr,
+ &dev_attr_sink_max_lane_count_caps.attr,
+ &dev_attr_sink_dsc_caps.attr,
+ &dev_attr_cur_link_rate.attr,
+ &dev_attr_cur_lane_count.attr,
+ &dev_attr_dsc_en.attr,
+ &dev_attr_max_link_rate.attr,
+ &dev_attr_max_lane_count.attr,
+ NULL
+};
+
+static const struct attribute_group connector_dp_link_group = {
+ .name = "dp_link",
+ .attrs = connector_dp_link_attrs,
+};
+
int drm_sysfs_connector_add(struct drm_connector *connector)
{
struct drm_device *dev = connector->dev;
@@ -376,6 +465,15 @@ int drm_sysfs_connector_add(struct drm_connector *connector)
connector->kdev = kdev;
+ if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort ||
+ connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
+ r = sysfs_create_group(&connector->kdev->kobj, &connector_dp_link_group);
+ if (r) {
+ drm_err(dev, "failed to create DP connector sysfs: %d\n", r);
+ goto err_dp_sysfs;
+ }
+ }
+
if (dev_fwnode(kdev)) {
r = component_add(kdev, &typec_connector_ops);
if (r)
@@ -384,6 +482,8 @@ int drm_sysfs_connector_add(struct drm_connector *connector)
return 0;
+err_dp_sysfs:
+ device_del(kdev);
err_free:
put_device(kdev);
return r;
diff --git a/include/drm/display/drm_dp_helper.h b/include/drm/display/drm_dp_helper.h
index 8c2d77a032f06..e7620ecb2380a 100644
--- a/include/drm/display/drm_dp_helper.h
+++ b/include/drm/display/drm_dp_helper.h
@@ -1031,4 +1031,11 @@ ssize_t drm_dp_vsc_sdp_pack(const struct drm_dp_vsc_sdp *vsc, struct dp_sdp *sdp
int drm_dp_link_symbol_cycles(int lane_count, int pixels, int dsc_slice_count,
int bpp_x16, int symbol_size, bool is_mst);
+void drm_dp_sink_set_link_caps(struct drm_connector *connector, struct drm_dp_aux *aux);
+void drm_dp_sink_reset_link_caps(struct drm_connector *connector);
+void drm_dp_set_cur_link_params(struct drm_connector *connector, u32 link_rate,
+ u32 lane_count, bool dsc_en);
+void drm_dp_set_max_link_params(struct drm_connector *connector, u32 link_rate,
+ u32 lane_count);
+
#endif /* _DRM_DP_HELPER_H_ */
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 529755c2e8620..a08b8e9766abe 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -2003,6 +2003,93 @@ struct drm_connector_cec {
void *data;
};
+/**
+ * struct drm_connector_dp_link_caps - DRM DisplayPort link capabilities
+ */
+struct drm_connector_dp_link_caps {
+ /**
+ * @nlanes: Maximum number of lanes number supported
+ */
+ u8 nlanes;
+
+ /**
+ * @nlink_rates: Number of link rates supported
+ */
+ u32 nlink_rates;
+
+ /**
+ * @link_rates: Array listing the supported link rates in deca-kbps
+ */
+ const u32 *link_rates;
+
+ /**
+ * @dsc: Display Stream Compression supported
+ */
+ bool dsc;
+};
+
+/**
+ * struct drm_connector_dp - DRM Connector DisplayPort-related structure
+ */
+struct drm_connector_dp {
+ /**
+ * @source_link_rates_caps: Array of supported link rates by the
+ * source in kbps
+ */
+ const u32 *source_link_rates_caps;
+ /**
+ * @source_num_link_rates_caps: Number of link rates in
+ * @source_link_rates_caps array
+ */
+ u32 source_num_link_rates_caps;
+ /**
+ * @source_max_lane_count_caps: Maximum number of lanes supported by
+ * the source
+ */
+ u32 source_max_lane_count_caps;
+ /**
+ * @source_dsc_caps: Display Stream Compression capability of the
+ * source
+ */
+ bool source_dsc_caps;
+ /**
+ * @sink_max_link_rate_caps: Maximum link rate supported by the sink
+ * in kbps
+ */
+ u32 sink_max_link_rate_caps;
+ /**
+ * @sink_max_lane_count_caps: Maximum number of lanes supported by the
+ * sink
+ */
+ u32 sink_max_lane_count_caps;
+ /**
+ * @sink_dsc_caps: Display Stream Compression capability of the sink
+ */
+ bool sink_dsc_caps;
+ /**
+ * @cur_link_rate: Current negotiated link rate in kbps
+ */
+ u32 cur_link_rate;
+ /**
+ * @cur_lane_count: Current negotiated number of lanes
+ */
+ u32 cur_lane_count;
+ /**
+ * @dsc_en: Display Stream Compression enabled status
+ */
+ bool dsc_en;
+ /**
+ * @max_link_rate: Maximum achievable link rate considering both
+ * source and sink capabilities in deca-kbps
+ */
+ u32 max_link_rate;
+ /**
+ * @max_lane_count: Maximum achievable lane count considering both
+ * source and sink capabilities
+ */
+ u32 max_lane_count;
+};
+
/**
* struct drm_connector - central DRM connector control structure
*
@@ -2426,6 +2513,11 @@ struct drm_connector {
* @cec: CEC-related data.
*/
struct drm_connector_cec cec;
+
+ /**
+ * @dp: DisplayPort-related variable and properties.
+ */
+ struct drm_connector_dp dp;
};
#define obj_to_connector(x) container_of(x, struct drm_connector, base)
@@ -2458,6 +2550,19 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
struct i2c_adapter *ddc,
unsigned long supported_formats,
unsigned int max_bpc);
+int drm_connector_dp_init_with_ddc(struct drm_device *dev,
+ struct drm_connector *connector,
+ const struct drm_connector_funcs *funcs,
+ const struct drm_connector_dp_link_caps *dp_link_caps,
+ int connector_type,
+ struct i2c_adapter *ddc);
+
+int drmm_connector_dp_init(struct drm_device *dev,
+ struct drm_connector *connector,
+ const struct drm_connector_funcs *funcs,
+ const struct drm_connector_dp_link_caps *dp_link_caps,
+ int connector_type,
+ struct i2c_adapter *ddc);
void drm_connector_attach_edid_property(struct drm_connector *connector);
int drm_connector_register(struct drm_connector *connector);
int drm_connector_dynamic_register(struct drm_connector *connector);
--
2.43.0
^ permalink raw reply related
* [PATCH RFC v2 0/4] Add support for DisplayPort link training information report
From: Kory Maincent @ 2026-06-19 14:08 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
Drew Davenport, Louis Chauvet, Luca Ceresoli, dri-devel,
linux-kernel, intel-gfx, intel-xe, linux-mediatek,
linux-arm-kernel, Kory Maincent
DisplayPort link training negotiates the physical-layer parameters needed
for a reliable connection: lane count, link rate, and optionally Display
Stream Compression (DSC). Currently, each driver exposes this state in
its own way, often through driver-specific debugfs entries, with no
standard interface for userspace diagnostic and monitoring tools.
This series introduces generic, managed and unmanaged DisplayPort
connector initialization helpers, for exposing DP link capabilities and
state as standard sysfs entries, modeled after the existing HDMI helper
drmm_connector_hdmi_init().
The aim of such development is to guide users to select the most suitable
DisplayPort connector for their needs. For example, if you have a USB-C
hub with lesser capabilities than your computer’s native DisplayPort
connector (such as HBR2 versus HBR3 support), the system could recommend
connecting high-resolution displays directly to the computer’s port
instead of through the hub to ensure optimal performance.
These new drmm_connector_dp_init() and drm_connector_dp_init_with_ddc()
helpers initialize a DP connector and expose link training capabilities
and state to userspace via sysfs attributes under dp_link.
Additional helpers are provided to manage link capabilities and parameters
at runtime.
Two drivers are updated as reference implementations: i915 (direct
connector path) and MediaTek (via the bridge connector framework using a
new DRM_BRIDGE_OP_DP flag).
The changes updating the i915 driver to use DRM managed resources have been
removed due to cleanup path issues. The core problem is that some functions
do not consistently propagate errors through their call paths (whether this
is intentional or not) making it difficult to properly handle cleanup of
DRM objects (planes, encoders, connectors). A potential solution would be
to implement something similar to devres_group for each DRM object type,
but this represents a substantial undertaking that falls outside the scope
of this patch series.
The MST case in i915 driver is not supported yet.
Patch 1: Introduce the core drmm_connector_dp_init() framework
Patch 2: Wire the i915 DP connector to use the new helpers
Patch 3: Introduce DRM_BRIDGE_OP_DP and wire bridge connectors
Patch 4: Wire the MediaTek DP bridge to the new helpers [untested]
Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Changes in v2:
- Removed work converting i915 to DRM managed resource
- Remove voltage swing and pre-emphasis properties
- Expose link training state via sysfs dp_link/ group instead of
connector properties
- Add comprehensive sysfs attributes for both source and sink capabilities
- Add new helpers for managing sink capabilities and for current link
parameters
- Link to v1: https://lore.kernel.org/r/20260409-feat_link_cap-v1-0-7069e8199ce2@bootlin.com
---
Kory Maincent (4):
drm: Introduce DisplayPort connector helpers with link training state
drm/i915/display/dp: Adopt dp_connector helpers to expose link training state
drm/bridge: Wire drmm_connector_dp_init() via new DRM_BRIDGE_OP_DP flag
drm/mediatek: Use dp_connector helpers to report link training state
drivers/gpu/drm/display/drm_bridge_connector.c | 24 ++++
drivers/gpu/drm/display/drm_dp_helper.c | 144 +++++++++++++++++++++
drivers/gpu/drm/drm_connector.c | 122 +++++++++++++++++
drivers/gpu/drm/drm_sysfs.c | 100 ++++++++++++++
drivers/gpu/drm/i915/display/intel_dp.c | 26 +++-
.../gpu/drm/i915/display/intel_dp_link_training.c | 17 +++
drivers/gpu/drm/mediatek/mtk_dp.c | 23 ++++
include/drm/display/drm_dp_helper.h | 7 +
include/drm/drm_bridge.h | 13 ++
include/drm/drm_connector.h | 105 +++++++++++++++
10 files changed, 577 insertions(+), 4 deletions(-)
---
base-commit: 4d75f8bd845c10f126e0e66bcdd264e1f9772bde
change-id: 20260226-feat_link_cap-20cbb6f31d40
Best regards,
--
Köry Maincent, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com
^ permalink raw reply
* [PATCH RFC v2 2/4] drm/i915/display/dp: Adopt dp_connector helpers to expose link training state
From: Kory Maincent @ 2026-06-19 14:08 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
Drew Davenport, Louis Chauvet, Luca Ceresoli, dri-devel,
linux-kernel, intel-gfx, intel-xe, linux-mediatek,
linux-arm-kernel, Kory Maincent
In-Reply-To: <20260619-feat_link_cap-v2-0-a3dec4c02ad9@bootlin.com>
Switch the i915 DP connector initialization from
drm_connector_init_with_ddc() to drm_connector_dp_init_with_ddc(),
providing the source link capabilities (supported lane counts, link rates
and DSC support).
Add intel_dp_report_link_train() to collect the negotiated link
parameters (rate, lane count and DSC enable) and report them via
drm_dp_set_max_link_params() and drm_dp_set_cur_link_params() once
link training completes successfully.
Reset the link properties via drm_dp_reset_link_params()
when the connector is reported as disconnected or when the display device
is disabled, so the exposed state always reflects the current link status.
Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Changes in v2:
- Remove voltage swing and pre emphasis properties.
---
drivers/gpu/drm/i915/display/intel_dp.c | 26 ++++++++++++++++++----
.../gpu/drm/i915/display/intel_dp_link_training.c | 17 ++++++++++++++
2 files changed, 39 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
index f01a6eed38395..46c06c76952e0 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -6414,8 +6414,10 @@ intel_dp_detect(struct drm_connector *_connector,
drm_WARN_ON(display->drm,
!drm_modeset_is_locked(&display->drm->mode_config.connection_mutex));
- if (!intel_display_device_enabled(display))
+ if (!intel_display_device_enabled(display)) {
+ drm_dp_sink_reset_link_caps(_connector);
return connector_status_disconnected;
+ }
if (!intel_display_driver_check_access(display))
return connector->base.status;
@@ -6465,6 +6467,8 @@ intel_dp_detect(struct drm_connector *_connector,
intel_dp_tunnel_disconnect(intel_dp);
+ drm_dp_sink_reset_link_caps(_connector);
+
goto out_unset_edid;
}
@@ -7240,10 +7244,12 @@ intel_dp_init_connector(struct intel_digital_port *dig_port,
struct intel_connector *connector)
{
struct intel_display *display = to_intel_display(dig_port);
+ struct drm_connector_dp_link_caps link_caps;
struct intel_dp *intel_dp = &dig_port->dp;
struct intel_encoder *encoder = &dig_port->base;
struct drm_device *dev = encoder->base.dev;
enum port port = encoder->port;
+ u32 *rates;
int type;
if (drm_WARN(dev, dig_port->max_lanes < 1,
@@ -7291,8 +7297,21 @@ intel_dp_init_connector(struct intel_digital_port *dig_port,
type == DRM_MODE_CONNECTOR_eDP ? "eDP" : "DP",
encoder->base.base.id, encoder->base.name);
- drm_connector_init_with_ddc(dev, &connector->base, &intel_dp_connector_funcs,
- type, &intel_dp->aux.ddc);
+ intel_dp_set_source_rates(intel_dp);
+ link_caps.nlanes = 4;
+ link_caps.nlink_rates = intel_dp->num_source_rates;
+ rates = kmemdup_array(intel_dp->source_rates, intel_dp->num_source_rates,
+ sizeof(*rates), GFP_KERNEL);
+ if (!rates)
+ goto fail;
+
+ link_caps.link_rates = rates;
+ link_caps.dsc = HAS_DSC(display);
+
+ drm_connector_dp_init_with_ddc(dev, &connector->base, &intel_dp_connector_funcs,
+ &link_caps, type, &intel_dp->aux.ddc);
+ kfree(rates);
+
drm_connector_helper_add(&connector->base, &intel_dp_connector_helper_funcs);
if (!HAS_GMCH(display) && DISPLAY_VER(display) < 12)
@@ -7315,7 +7334,6 @@ intel_dp_init_connector(struct intel_digital_port *dig_port,
goto fail;
}
- intel_dp_set_source_rates(intel_dp);
intel_dp_set_common_rates(intel_dp);
intel_dp_reset_link_params(intel_dp);
diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
index a26094223f780..25e0e957fe36d 100644
--- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c
+++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
@@ -1231,6 +1231,18 @@ intel_dp_128b132b_intra_hop(struct intel_dp *intel_dp,
return sink_status & DP_INTRA_HOP_AUX_REPLY_INDICATION ? 1 : 0;
}
+static void intel_dp_report_link_train(struct intel_dp *intel_dp)
+{
+ struct intel_connector *connector = intel_dp->attached_connector;
+
+ drm_dp_set_max_link_params(&connector->base, intel_dp->link_rate,
+ intel_dp->lane_count);
+
+ drm_dp_set_cur_link_params(&connector->base, intel_dp->link_rate,
+ intel_dp->lane_count,
+ connector->dp.dsc_decompression_enabled);
+}
+
/**
* intel_dp_stop_link_train - stop link training
* @intel_dp: DP struct
@@ -1259,6 +1271,9 @@ void intel_dp_stop_link_train(struct intel_dp *intel_dp,
intel_dp_program_link_training_pattern(intel_dp, crtc_state, DP_PHY_DPRX,
DP_TRAINING_PATTERN_DISABLE);
+ if (!intel_dp->is_mst)
+ intel_dp_report_link_train(intel_dp);
+
if (intel_dp_is_uhbr(crtc_state)) {
ret = poll_timeout_us(ret = intel_dp_128b132b_intra_hop(intel_dp, crtc_state),
ret == 0,
@@ -1772,6 +1787,8 @@ void intel_dp_start_link_train(struct intel_atomic_state *state,
*/
int lttpr_count;
+ drm_dp_sink_set_link_caps(&intel_dp->attached_connector->base, &intel_dp->aux);
+
intel_hpd_block(encoder);
lttpr_count = intel_dp_init_lttpr_and_dprx_caps(intel_dp);
--
2.43.0
^ permalink raw reply related
* [PATCH RFC v2 3/4] drm/bridge: Wire drmm_connector_dp_init() via new DRM_BRIDGE_OP_DP flag
From: Kory Maincent @ 2026-06-19 14:08 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
Drew Davenport, Louis Chauvet, Luca Ceresoli, dri-devel,
linux-kernel, intel-gfx, intel-xe, linux-mediatek,
linux-arm-kernel, Kory Maincent
In-Reply-To: <20260619-feat_link_cap-v2-0-a3dec4c02ad9@bootlin.com>
Introduce DRM_BRIDGE_OP_DP, a new bridge operation flag for bridges
that provide DisplayPort connector operations with link training support.
Bridges advertising this flag must fill the dp_link_caps field in
struct drm_bridge with their link capabilities.
In drm_bridge_connector_init(), when a bridge sets DRM_BRIDGE_OP_DP,
use drmm_connector_dp_init() instead of the generic drmm_connector_init()
so the connector exposes link training state properties to userspace.
This mirrors the existing pattern used for HDMI bridges.
Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
drivers/gpu/drm/display/drm_bridge_connector.c | 24 ++++++++++++++++++++++++
include/drm/drm_bridge.h | 13 +++++++++++++
2 files changed, 37 insertions(+)
diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index cafa498c38482..6ea4d45e3146b 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -108,6 +108,13 @@ struct drm_bridge_connector {
* HDMI Audio infrastructure, if any (see &DRM_BRIDGE_OP_HDMI_AUDIO).
*/
struct drm_bridge *bridge_hdmi_audio;
+ /**
+ * @bridge_dp:
+ *
+ * The bridge in the chain that implements necessary support for the
+ * DisplayPort connector infrastructure, if any (see &DRM_BRIDGE_OP_DP).
+ */
+ struct drm_bridge *bridge_dp;
/**
* @bridge_dp_audio:
*
@@ -773,6 +780,7 @@ static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
drm_bridge_put(bridge_connector->bridge_hdmi_audio);
drm_bridge_put(bridge_connector->bridge_dp_audio);
drm_bridge_put(bridge_connector->bridge_hdmi_cec);
+ drm_bridge_put(bridge_connector->bridge_dp);
}
/**
@@ -908,6 +916,15 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
bridge_connector->bridge_hdmi_audio = drm_bridge_get(bridge);
}
+ if (bridge->ops & DRM_BRIDGE_OP_DP) {
+ if (bridge_connector->bridge_dp)
+ return ERR_PTR(-EBUSY);
+ if (!bridge->dp_link_caps)
+ return ERR_PTR(-EINVAL);
+
+ bridge_connector->bridge_dp = drm_bridge_get(bridge);
+ }
+
if (bridge->ops & DRM_BRIDGE_OP_DP_AUDIO) {
if (bridge_connector->bridge_dp_audio)
return ERR_PTR(-EBUSY);
@@ -996,6 +1013,13 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
max_bpc);
if (ret)
return ERR_PTR(ret);
+ } else if (bridge_connector->bridge_dp) {
+ ret = drmm_connector_dp_init(drm, connector,
+ &drm_bridge_connector_funcs,
+ bridge_connector->bridge_dp->dp_link_caps,
+ connector_type, ddc);
+ if (ret)
+ return ERR_PTR(ret);
} else {
ret = drmm_connector_init(drm, connector,
&drm_bridge_connector_funcs,
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 4ba3a5deef9a6..02411e0b71c35 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -1092,6 +1092,14 @@ enum drm_bridge_ops {
* &drm_bridge_funcs->hdmi_clear_spd_infoframe callbacks.
*/
DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME = BIT(10),
+ /**
+ * @DRM_BRIDGE_OP_DP: The bridge provides DisplayPort connector
+ * operations, including link training support. Bridges that set
+ * this flag must provide DisplayPort-related information and
+ * fill the &drm_bridge->dp_link_train_caps link training
+ * capabilities.
+ */
+ DRM_BRIDGE_OP_DP = BIT(11),
};
/**
@@ -1267,6 +1275,11 @@ struct drm_bridge {
*/
void *hpd_data;
+ /**
+ * @dp_link_caps: DisplayPort link capabilities
+ */
+ const struct drm_connector_dp_link_caps *dp_link_caps;
+
/**
* @next_bridge: Pointer to the following bridge, automatically put
* when this bridge is freed (i.e. at destroy time). This is for
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v3 00/78] drm/bridge: Convert all reset users to create_state
From: Luca Ceresoli @ 2026-06-19 13:42 UTC (permalink / raw)
To: Maxime Ripard, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: Dmitry Baryshkov, dri-devel, Laurent Pinchart, Jagan Teki,
Liu Ying, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, imx, linux-arm-kernel, Andy Yan, Phong LE,
Douglas Anderson, Inki Dae, Marek Szyprowski, Philipp Zabel,
Paul Cercueil, linux-mips, AngeloGioacchino Del Regno,
Chun-Kuang Hu, Matthias Brugger, linux-mediatek, linux-kernel,
Kevin Hilman, Jerome Brunet, Martin Blumenstingl, linux-amlogic,
Tomi Valkeinen, Geert Uytterhoeven, Magnus Damm, Kieran Bingham,
linux-renesas-soc, Biju Das, Heiko Stuebner, Sandy Huang,
linux-rockchip, Yannick Fertre, Raphael Gallais-Pou,
Philippe Cornu, Maxime Coquelin, Alexandre Torgue, linux-stm32,
Jyri Sarha, Tomi Valkeinen, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance, Icenowy Zheng, Laurent Pinchart,
Michal Simek
In-Reply-To: <20260619-drm-no-more-bridge-reset-v3-0-ff399263111b@kernel.org>
On Fri Jun 19, 2026 at 2:24 PM CEST, Maxime Ripard wrote:
> Hi,
>
> All the bridges use reset to create a blank state only and don't use it
> to reset the hardware at all. This is what the new atomic_create_state
> is exactly supposed to be doing, so we can convert all existing bridge
> users to it, and remove the reset hook and helpers.
>
> Let me know what you think,
> Maxime
>
> Signed-off-by: Maxime Ripard <mripard@kernel.org>
Whole series:
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH] Bluetooth: btmtksdio: fix infinite loop in btmtksdio_txrx_work()
From: Takashi Iwai @ 2026-06-19 13:27 UTC (permalink / raw)
To: Sean Wang
Cc: Sergey Senozhatsky, Marcel Holtmann, Luiz Augusto von Dentz,
Mark-yw Chen, Sean Wang, Tomasz Figa, linux-bluetooth,
linux-kernel, linux-arm-kernel, linux-mediatek, stable
In-Reply-To: <CAGp9LzpBUReZtrTEKgUr-+yvB+3tcs5hw7ziC4WaMRFNa2AYpg@mail.gmail.com>
On Wed, 10 Jun 2026 08:52:31 +0200,
Sean Wang wrote:
>
> Hi,
>
> On Tue, Jun 9, 2026 at 7:19 AM Sergey Senozhatsky
> <senozhatsky@chromium.org> wrote:
> >
> > Every once in a while we see a hung btmtksdio_flush() task:
> >
> > INFO: task kworker/u17:0:189 blocked for more than 122 seconds.
> > __cancel_work_timer+0x3f4/0x460
> > cancel_work_sync+0x1c/0x2c
> > btmtksdio_flush+0x2c/0x40
> > hci_dev_open_sync+0x10c4/0x2190
> > [..]
> >
> > It all boils down to incorrect time_is_before_jiffies() usage in
> > btmtksdio_txrx_work(). The btmtksdio_txrx_work() loop is expected
> > to be terminated if running for longer than 5*HZ. However the
> > timeout check is twisted: time_is_before_jiffies(old_jiffies + 5*HZ)
> > evaluates to true when old_jiffies + 5*HZ is in the past i.e. when a
> > timeout has occurred. Using OR with time_is_before_jiffies(txrx_timeout)
> > means that:
> > - before the 5-second timeout: the condition is `int_status || false`,
> > so it loops as long as there are pending interrupts.
> > - after the 5-second timeout: the condition becomes `int_status || true`,
> > which is always true.
> >
> > When the loop becomes infinite btmtksdio_txrx_work() loop never
> > terminates and never releases the SDIO host.
> >
> > Fix loop termination condition to actually enforce a 5*HZ timeout.
> >
> > Fixes: 26270bc189ea4 ("Bluetooth: btmtksdio: move interrupt service to work")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
> > ---
> > drivers/bluetooth/btmtksdio.c | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c
> > index 5b0fab7b89b5..c6f80c419e90 100644
> > --- a/drivers/bluetooth/btmtksdio.c
> > +++ b/drivers/bluetooth/btmtksdio.c
> > @@ -620,7 +620,7 @@ static void btmtksdio_txrx_work(struct work_struct *work)
> > if (btmtksdio_rx_packet(bdev, rx_size) < 0)
> > bdev->hdev->stat.err_rx++;
> > }
> > - } while (int_status || time_is_before_jiffies(txrx_timeout));
> > + } while (int_status && time_is_after_jiffies(txrx_timeout));
>
> yes, loop continues only while there is interrupt work and the timeout
> deadline is still in the future
I stumbled on this while backporting to distro kernels, and I wonder
whether this change is correct.
IIUC, this essentially makes the loop exiting right after the first
cycle; the patch changed from time_is_before_jiffies() to *_after_*(),
not only the logical OR to AND, and *_after_*() returns false, so the
whole condition becomes false, too.
thanks,
Takashi
^ permalink raw reply
* [PATCH v3 net] net: airoha: Fix TX scheduler queue mask loop upper bound
From: Wayen Yan @ 2026-06-19 13:12 UTC (permalink / raw)
To: netdev
Cc: lorenzo, horms, pabeni, kuba, edumazet, andrew+netdev,
angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
linux-mediatek
In airoha_qdma_set_chan_tx_sched(), the loop clearing queue mask was
using AIROHA_NUM_TX_RING (32) instead of AIROHA_NUM_QOS_QUEUES (8).
Each channel has 8 queues, and TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i)
computes BIT(i + (channel * 8)). With i ranging 0..31, this causes:
- channel 0: clears bit 0..31 (all 4 channels) instead of 0..7
- channel 1: clears bit 8..31 (channels 1-3) instead of 8..15
- channel 2: clears bit 16..31 (channels 2-3) instead of 16..23
- channel 3: clears bit 24..31 (channel 3 only) - correct by accident
While BIT(32+) on arm64 produces 64-bit values truncated to 0 in u32
mask parameter, the loop still incorrectly clears queues within the
same channel beyond queue 7.
Even though this is functionally harmless (the register resets to 0
and is only ever cleared, never set — so clearing extra bits is a
no-op), the loop bound is semantically wrong and should be fixed for
correctness and clarity.
Fix by using AIROHA_NUM_QOS_QUEUES (8) as the loop upper bound.
Fixes: ef1ca9271313 ("net: airoha: Add sched HTB offload support")
Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Wayen Yan <win847@gmail.com>
---
Changes in v3:
- Rebase on top of current net tree (Lorenzo pointed out v2 was
not based on latest net HEAD).
- No code changes from v2.
drivers/net/ethernet/airoha/airoha_eth.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 64dde6464f..47fb32517a 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2395,7 +2395,7 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
struct airoha_gdm_dev *dev = netdev_priv(netdev);
int i;
- for (i = 0; i < AIROHA_NUM_TX_RING; i++)
+ for (i = 0; i < AIROHA_NUM_QOS_QUEUES; i++)
airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel),
TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
--
2.51.0
^ permalink raw reply related
* [PATCH v3 53/78] drm/mediatek: hdmi_v2: Switch to atomic_create_state
From: Maxime Ripard @ 2026-06-19 12:24 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter
Cc: Dmitry Baryshkov, dri-devel, Maxime Ripard,
AngeloGioacchino Del Regno, Chun-Kuang Hu, Philipp Zabel,
Matthias Brugger, linux-mediatek, linux-kernel, linux-arm-kernel
In-Reply-To: <20260619-drm-no-more-bridge-reset-v3-0-ff399263111b@kernel.org>
The drm_bridge_funcs.atomic_reset callback and its
drm_atomic_helper_bridge_reset() helper are deprecated.
Switch to the atomic_create_state callback and its
drm_atomic_helper_bridge_create_state() counterpart.
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
To: Chun-Kuang Hu <chunkuang.hu@kernel.org>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-mediatek@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
drivers/gpu/drm/mediatek/mtk_hdmi_v2.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c
index 7bbf463056c9..d9316d30fd5f 100644
--- a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c
@@ -1324,11 +1324,11 @@ static const struct drm_bridge_funcs mtk_v2_hdmi_bridge_funcs = {
.atomic_enable = mtk_hdmi_v2_bridge_enable,
.atomic_disable = mtk_hdmi_v2_bridge_disable,
.atomic_post_disable = mtk_hdmi_v2_bridge_post_disable,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
- .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_create_state = drm_atomic_helper_bridge_create_state,
.detect = mtk_hdmi_v2_bridge_detect,
.edid_read = mtk_hdmi_v2_bridge_edid_read,
.hpd_enable = mtk_hdmi_v2_hpd_enable,
.hpd_disable = mtk_hdmi_v2_hpd_disable,
.hdmi_tmds_char_rate_valid = mtk_hdmi_v2_hdmi_tmds_char_rate_valid,
--
2.54.0
^ permalink raw reply related
* [PATCH v3 52/78] drm/mediatek: hdmi: Switch to atomic_create_state
From: Maxime Ripard @ 2026-06-19 12:24 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter
Cc: Dmitry Baryshkov, dri-devel, Maxime Ripard,
AngeloGioacchino Del Regno, Chun-Kuang Hu, Philipp Zabel,
Matthias Brugger, linux-mediatek, linux-kernel, linux-arm-kernel
In-Reply-To: <20260619-drm-no-more-bridge-reset-v3-0-ff399263111b@kernel.org>
The drm_bridge_funcs.atomic_reset callback and its
drm_atomic_helper_bridge_reset() helper are deprecated.
Switch to the atomic_create_state callback and its
drm_atomic_helper_bridge_create_state() counterpart.
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
To: Chun-Kuang Hu <chunkuang.hu@kernel.org>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-mediatek@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
drivers/gpu/drm/mediatek/mtk_hdmi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c
index 38a7c7953874..738090a47176 100644
--- a/drivers/gpu/drm/mediatek/mtk_hdmi.c
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c
@@ -1070,11 +1070,11 @@ static void mtk_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
static const struct drm_bridge_funcs mtk_hdmi_bridge_funcs = {
.mode_valid = mtk_hdmi_bridge_mode_valid,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
- .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_create_state = drm_atomic_helper_bridge_create_state,
.attach = mtk_hdmi_bridge_attach,
.mode_fixup = mtk_hdmi_bridge_mode_fixup,
.atomic_disable = mtk_hdmi_bridge_atomic_disable,
.atomic_post_disable = mtk_hdmi_bridge_atomic_post_disable,
.mode_set = mtk_hdmi_bridge_mode_set,
--
2.54.0
^ permalink raw reply related
* [PATCH v3 51/78] drm/mediatek: dsi: Switch to atomic_create_state
From: Maxime Ripard @ 2026-06-19 12:24 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter
Cc: Dmitry Baryshkov, dri-devel, Maxime Ripard,
AngeloGioacchino Del Regno, Chun-Kuang Hu, Philipp Zabel,
Matthias Brugger, linux-mediatek, linux-kernel, linux-arm-kernel
In-Reply-To: <20260619-drm-no-more-bridge-reset-v3-0-ff399263111b@kernel.org>
The drm_bridge_funcs.atomic_reset callback and its
drm_atomic_helper_bridge_reset() helper are deprecated.
Switch to the atomic_create_state callback and its
drm_atomic_helper_bridge_create_state() counterpart.
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
To: Chun-Kuang Hu <chunkuang.hu@kernel.org>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-mediatek@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
drivers/gpu/drm/mediatek/mtk_dsi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index eb325e68aa59..3f3f56eed3f9 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -892,11 +892,11 @@ static const struct drm_bridge_funcs mtk_dsi_bridge_funcs = {
.atomic_disable = mtk_dsi_bridge_atomic_disable,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_enable = mtk_dsi_bridge_atomic_enable,
.atomic_pre_enable = mtk_dsi_bridge_atomic_pre_enable,
.atomic_post_disable = mtk_dsi_bridge_atomic_post_disable,
- .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_create_state = drm_atomic_helper_bridge_create_state,
.mode_valid = mtk_dsi_bridge_mode_valid,
.mode_set = mtk_dsi_bridge_mode_set,
};
void mtk_dsi_ddp_start(struct device *dev)
--
2.54.0
^ permalink raw reply related
* [PATCH v3 50/78] drm/mediatek: dpi: Switch to atomic_create_state
From: Maxime Ripard @ 2026-06-19 12:24 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter
Cc: Dmitry Baryshkov, dri-devel, Maxime Ripard,
AngeloGioacchino Del Regno, Chun-Kuang Hu, Philipp Zabel,
Matthias Brugger, linux-mediatek, linux-kernel, linux-arm-kernel
In-Reply-To: <20260619-drm-no-more-bridge-reset-v3-0-ff399263111b@kernel.org>
The drm_bridge_funcs.atomic_reset callback and its
drm_atomic_helper_bridge_reset() helper are deprecated.
Switch to the atomic_create_state callback and its
drm_atomic_helper_bridge_create_state() counterpart.
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
To: Chun-Kuang Hu <chunkuang.hu@kernel.org>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-mediatek@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
drivers/gpu/drm/mediatek/mtk_dpi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c
index bb4b7e6f1e29..959c994eef24 100644
--- a/drivers/gpu/drm/mediatek/mtk_dpi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dpi.c
@@ -987,11 +987,11 @@ static const struct drm_bridge_funcs mtk_dpi_bridge_funcs = {
.atomic_check = mtk_dpi_bridge_atomic_check,
.atomic_get_output_bus_fmts = mtk_dpi_bridge_atomic_get_output_bus_fmts,
.atomic_get_input_bus_fmts = mtk_dpi_bridge_atomic_get_input_bus_fmts,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
- .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_create_state = drm_atomic_helper_bridge_create_state,
.debugfs_init = mtk_dpi_debugfs_init,
};
static const struct drm_encoder_funcs mtk_dpi_encoder_funcs = {
.destroy = drm_encoder_cleanup,
--
2.54.0
^ permalink raw reply related
* [PATCH v3 49/78] drm/mediatek: dp: Switch to atomic_create_state
From: Maxime Ripard @ 2026-06-19 12:24 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter
Cc: Dmitry Baryshkov, dri-devel, Maxime Ripard,
AngeloGioacchino Del Regno, Chun-Kuang Hu, Philipp Zabel,
Matthias Brugger, linux-mediatek, linux-kernel, linux-arm-kernel
In-Reply-To: <20260619-drm-no-more-bridge-reset-v3-0-ff399263111b@kernel.org>
The drm_bridge_funcs.atomic_reset callback and its
drm_atomic_helper_bridge_reset() helper are deprecated.
Switch to the atomic_create_state callback and its
drm_atomic_helper_bridge_create_state() counterpart.
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
To: Chun-Kuang Hu <chunkuang.hu@kernel.org>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-mediatek@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
drivers/gpu/drm/mediatek/mtk_dp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/mediatek/mtk_dp.c b/drivers/gpu/drm/mediatek/mtk_dp.c
index eefbc7e0f9c8..f656b85b8421 100644
--- a/drivers/gpu/drm/mediatek/mtk_dp.c
+++ b/drivers/gpu/drm/mediatek/mtk_dp.c
@@ -2576,11 +2576,11 @@ static const struct drm_bridge_funcs mtk_dp_bridge_funcs = {
.atomic_check = mtk_dp_bridge_atomic_check,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_get_output_bus_fmts = mtk_dp_bridge_atomic_get_output_bus_fmts,
.atomic_get_input_bus_fmts = mtk_dp_bridge_atomic_get_input_bus_fmts,
- .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_create_state = drm_atomic_helper_bridge_create_state,
.attach = mtk_dp_bridge_attach,
.detach = mtk_dp_bridge_detach,
.atomic_enable = mtk_dp_bridge_atomic_enable,
.atomic_disable = mtk_dp_bridge_atomic_disable,
.mode_valid = mtk_dp_bridge_mode_valid,
--
2.54.0
^ permalink raw reply related
* [PATCH v3 00/78] drm/bridge: Convert all reset users to create_state
From: Maxime Ripard @ 2026-06-19 12:24 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter
Cc: Dmitry Baryshkov, dri-devel, Maxime Ripard, Laurent Pinchart,
Jagan Teki, Liu Ying, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, imx, linux-arm-kernel,
Andy Yan, Phong LE, Douglas Anderson, Inki Dae, Marek Szyprowski,
Philipp Zabel, Paul Cercueil, linux-mips,
AngeloGioacchino Del Regno, Chun-Kuang Hu, Matthias Brugger,
linux-mediatek, linux-kernel, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, linux-amlogic, Tomi Valkeinen,
Geert Uytterhoeven, Magnus Damm, Kieran Bingham,
linux-renesas-soc, Biju Das, Heiko Stuebner, Sandy Huang,
linux-rockchip, Yannick Fertre, Raphael Gallais-Pou,
Philippe Cornu, Maxime Coquelin, Alexandre Torgue, linux-stm32,
Jyri Sarha, Tomi Valkeinen, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance, Icenowy Zheng, Laurent Pinchart,
Michal Simek
Hi,
All the bridges use reset to create a blank state only and don't use it
to reset the hardware at all. This is what the new atomic_create_state
is exactly supposed to be doing, so we can convert all existing bridge
users to it, and remove the reset hook and helpers.
Let me know what you think,
Maxime
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
Changes in v3:
- Rebase on current drm-misc-next
- Link to v2: https://lore.kernel.org/r/20260608-drm-no-more-bridge-reset-v2-0-0a91018bf886@kernel.org
Changes in v2:
- Split the return value fix for cadence bridges into their own patches
- Fix bisection
- Collect tags
- Link to v1: https://lore.kernel.org/r/20260530-drm-no-more-bridge-reset-v1-0-875d828d31bc@kernel.org
---
Maxime Ripard (78):
drm/bridge: cdns-dsi: Return an error pointer on allocation failure
drm/bridge: cdns-mhdp8546: Return an error pointer on allocation failure
drm/atomic-state-helper: Rename __drm_atomic_helper_bridge_reset()
drm/atomic-state-helper: Reorder __drm_atomic_helper_bridge_state_init() arguments
drm/atomic-state-helper: Drop memset from __drm_atomic_helper_bridge_state_init()
drm/bridge: Add new atomic_create_state callback
drm/atomic-state-helper: Add drm_atomic_helper_bridge_create_state()
drm/bridge: adv7511: Switch to atomic_create_state
drm/bridge: analogix_dp: Switch to atomic_create_state
drm/bridge: anx7625: Switch to atomic_create_state
drm/bridge: chipone-icn6211: Switch to atomic_create_state
drm/bridge: display-connector: Switch to atomic_create_state
drm/bridge: fsl-ldb: Switch to atomic_create_state
drm/bridge: imx8mp-hdmi-pvi: Switch to atomic_create_state
drm/bridge: imx8qm-ldb: Switch to atomic_create_state
drm/bridge: imx8qxp-ldb: Switch to atomic_create_state
drm/bridge: imx8qxp-pixel-combiner: Switch to atomic_create_state
drm/bridge: imx8qxp-pixel-link: Switch to atomic_create_state
drm/bridge: imx8qxp-pxl2dpi: Switch to atomic_create_state
drm/bridge: inno-hdmi: Switch to atomic_create_state
drm/bridge: ite-it6263: Switch to atomic_create_state
drm/bridge: ite-it6505: Switch to atomic_create_state
drm/bridge: ite-it66121: Switch to atomic_create_state
drm/bridge: lontium-lt9211: Switch to atomic_create_state
drm/bridge: lontium-lt9611: Switch to atomic_create_state
drm/bridge: lvds-codec: Switch to atomic_create_state
drm/bridge: nwl-dsi: Switch to atomic_create_state
drm/bridge: panel: Switch to atomic_create_state
drm/bridge: parade-ps8640: Switch to atomic_create_state
drm/bridge: samsung-dsim: Switch to atomic_create_state
drm/bridge: sii902x: Switch to atomic_create_state
drm/bridge: ssd2825: Switch to atomic_create_state
drm/bridge: dw-dp: Switch to atomic_create_state
drm/bridge: dw-hdmi-qp: Switch to atomic_create_state
drm/bridge: dw-hdmi: Switch to atomic_create_state
drm/bridge: dw-mipi-dsi: Switch to atomic_create_state
drm/bridge: dw-mipi-dsi2: Switch to atomic_create_state
drm/bridge: tc358762: Switch to atomic_create_state
drm/bridge: tc358767: Switch to atomic_create_state
drm/bridge: tc358768: Switch to atomic_create_state
drm/bridge: tc358775: Switch to atomic_create_state
drm/bridge: ti-dlpc3433: Switch to atomic_create_state
drm/bridge: ti-sn65dsi83: Switch to atomic_create_state
drm/bridge: ti-sn65dsi86: Switch to atomic_create_state
drm/bridge: ti-tdp158: Switch to atomic_create_state
drm/bridge: ti-tfp410: Switch to atomic_create_state
drm/imx: parallel-display: Switch to atomic_create_state
drm/ingenic: Switch to atomic_create_state
drm/mediatek: dp: Switch to atomic_create_state
drm/mediatek: dpi: Switch to atomic_create_state
drm/mediatek: dsi: Switch to atomic_create_state
drm/mediatek: hdmi: Switch to atomic_create_state
drm/mediatek: hdmi_v2: Switch to atomic_create_state
drm/meson: encoder_cvbs: Switch to atomic_create_state
drm/meson: encoder_dsi: Switch to atomic_create_state
drm/meson: encoder_hdmi: Switch to atomic_create_state
drm/msm: dp: Switch to atomic_create_state
drm/msm: hdmi: Switch to atomic_create_state
drm/omap: hdmi4: Switch to atomic_create_state
drm/omap: hdmi5: Switch to atomic_create_state
drm/renesas: rcar-du: lvds: Switch to atomic_create_state
drm/renesas: rcar-du: mipi_dsi: Switch to atomic_create_state
drm/renesas: rz-du: mipi_dsi: Switch to atomic_create_state
drm/rockchip: cdn-dp: Switch to atomic_create_state
drm/rockchip: rk3066_hdmi: Switch to atomic_create_state
drm/rockchip: lvds: Switch to atomic_create_state
drm/stm: lvds: Switch to atomic_create_state
drm/tests: bridge: Switch to atomic_create_state
drm/tidss: encoder: Switch to atomic_create_state
drm/tidss: oldi: Switch to atomic_create_state
drm/vc4: dsi: Switch to atomic_create_state
drm/verisilicon: Switch to atomic_create_state
drm/xlnx: zynqmp_dp: Switch to atomic_create_state
drm/atomic-state-helper: Remove drm_atomic_helper_bridge_reset()
drm/bridge: cdns-dsi: Use __drm_atomic_helper_bridge_state_init()
drm/bridge: cdns-dsi: Switch to atomic_create_state
drm/bridge: cdns-mhdp8546: Switch to atomic_create_state
drm/bridge: Remove atomic_reset support
drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 2 +-
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 2 +-
drivers/gpu/drm/bridge/analogix/anx7625.c | 2 +-
drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 9 +++--
.../gpu/drm/bridge/cadence/cdns-mhdp8546-core.c | 8 ++---
drivers/gpu/drm/bridge/chipone-icn6211.c | 2 +-
drivers/gpu/drm/bridge/display-connector.c | 2 +-
drivers/gpu/drm/bridge/fsl-ldb.c | 2 +-
drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pvi.c | 2 +-
drivers/gpu/drm/bridge/imx/imx8qm-ldb.c | 2 +-
drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c | 2 +-
.../gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c | 2 +-
drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c | 2 +-
drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c | 2 +-
drivers/gpu/drm/bridge/inno-hdmi.c | 2 +-
drivers/gpu/drm/bridge/ite-it6263.c | 2 +-
drivers/gpu/drm/bridge/ite-it6505.c | 2 +-
drivers/gpu/drm/bridge/ite-it66121.c | 2 +-
drivers/gpu/drm/bridge/lontium-lt9211.c | 2 +-
drivers/gpu/drm/bridge/lontium-lt9611.c | 2 +-
drivers/gpu/drm/bridge/lvds-codec.c | 2 +-
drivers/gpu/drm/bridge/nwl-dsi.c | 2 +-
drivers/gpu/drm/bridge/panel.c | 2 +-
drivers/gpu/drm/bridge/parade-ps8640.c | 2 +-
drivers/gpu/drm/bridge/samsung-dsim.c | 2 +-
drivers/gpu/drm/bridge/sii902x.c | 2 +-
drivers/gpu/drm/bridge/ssd2825.c | 2 +-
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2 +-
drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 2 +-
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 +-
drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 2 +-
drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c | 2 +-
drivers/gpu/drm/bridge/tc358762.c | 2 +-
drivers/gpu/drm/bridge/tc358767.c | 4 +--
drivers/gpu/drm/bridge/tc358768.c | 2 +-
drivers/gpu/drm/bridge/tc358775.c | 2 +-
drivers/gpu/drm/bridge/ti-dlpc3433.c | 2 +-
drivers/gpu/drm/bridge/ti-sn65dsi83.c | 2 +-
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 2 +-
drivers/gpu/drm/bridge/ti-tdp158.c | 2 +-
drivers/gpu/drm/bridge/ti-tfp410.c | 2 +-
drivers/gpu/drm/drm_atomic_state_helper.c | 40 ++++++++++++----------
drivers/gpu/drm/drm_bridge.c | 4 +--
drivers/gpu/drm/imx/ipuv3/parallel-display.c | 2 +-
drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 2 +-
drivers/gpu/drm/mediatek/mtk_dp.c | 2 +-
drivers/gpu/drm/mediatek/mtk_dpi.c | 2 +-
drivers/gpu/drm/mediatek/mtk_dsi.c | 2 +-
drivers/gpu/drm/mediatek/mtk_hdmi.c | 2 +-
drivers/gpu/drm/mediatek/mtk_hdmi_v2.c | 2 +-
drivers/gpu/drm/meson/meson_encoder_cvbs.c | 2 +-
drivers/gpu/drm/meson/meson_encoder_dsi.c | 2 +-
drivers/gpu/drm/meson/meson_encoder_hdmi.c | 2 +-
drivers/gpu/drm/msm/dp/dp_drm.c | 4 +--
drivers/gpu/drm/msm/hdmi/hdmi_bridge.c | 2 +-
drivers/gpu/drm/omapdrm/dss/hdmi4.c | 2 +-
drivers/gpu/drm/omapdrm/dss/hdmi5.c | 2 +-
drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c | 2 +-
drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c | 2 +-
drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c | 2 +-
drivers/gpu/drm/rockchip/cdn-dp-core.c | 2 +-
drivers/gpu/drm/rockchip/rk3066_hdmi.c | 2 +-
drivers/gpu/drm/rockchip/rockchip_lvds.c | 2 +-
drivers/gpu/drm/stm/lvds.c | 2 +-
drivers/gpu/drm/tests/drm_bridge_test.c | 6 ++--
drivers/gpu/drm/tidss/tidss_encoder.c | 2 +-
drivers/gpu/drm/tidss/tidss_oldi.c | 2 +-
drivers/gpu/drm/vc4/vc4_dsi.c | 2 +-
drivers/gpu/drm/verisilicon/vs_bridge.c | 4 +--
drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +-
include/drm/drm_atomic_state_helper.h | 6 ++--
include/drm/drm_bridge.h | 33 ++++++------------
72 files changed, 117 insertions(+), 125 deletions(-)
---
base-commit: 4c85094a73431c26d5774aa74e422fc6bb94a7d5
change-id: 20260530-drm-no-more-bridge-reset-ca20d5e22740
Best regards,
--
Maxime Ripard <mripard@kernel.org>
^ permalink raw reply
* Re: [PATCH net v2] net: airoha: Fix TX scheduler queue mask loop upper bound
From: Lorenzo Bianconi @ 2026-06-19 11:39 UTC (permalink / raw)
To: Wayen Yan
Cc: netdev, horms, pabeni, kuba, edumazet, andrew+netdev,
angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
linux-mediatek
In-Reply-To: <178185574223.2378148.13454900445528174929@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 2499 bytes --]
> In airoha_qdma_set_chan_tx_sched(), the loop clearing queue mask was
> using AIROHA_NUM_TX_RING (32) instead of AIROHA_NUM_QOS_QUEUES (8).
>
> Each channel has 8 queues, and TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i)
> computes BIT(i + (channel * 8)). With i ranging 0..31, this causes:
> - channel 0: clears bit 0..31 (all 4 channels) instead of 0..7
> - channel 1: clears bit 8..31 (channels 1-3) instead of 8..15
> - channel 2: clears bit 16..31 (channels 2-3) instead of 16..23
> - channel 3: clears bit 24..31 (channel 3 only) - correct by accident
>
> While BIT(32+) on arm64 produces 64-bit values truncated to 0 in u32
> mask parameter, the loop still incorrectly clears queues within the
> same channel beyond queue 7.
>
> Even though this is functionally harmless (the register resets to 0
> and is only ever cleared, never set — so clearing extra bits is a
> no-op), the loop bound is semantically wrong and should be fixed for
> correctness and clarity.
>
> Fix by using AIROHA_NUM_QOS_QUEUES (8) as the loop upper bound.
>
> Fixes: ef1ca9271313 ("net: airoha: Add sched HTB offload support")
> Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
> Signed-off-by: Wayen Yan <win847@gmail.com>
> ---
> Changes in v2:
> - Add Lorenzo's Acked-by tag.
> - Clarify in commit message that this is semantically wrong but
> functionally harmless (register resets to 0, only cleared), as
> Lorenzo pointed out in review.
> - Rebase on current net tree.
>
> Link: https://lore.kernel.org/netdev/ajJIWMs4dVbfkHZ5@lore-desk/
> Link: https://lore.kernel.org/netdev/CAL_ptrs6J3Ryw_4mVTq5VgzkB4RreF5S0huHyLvd9YwWr1m6jAA@mail.gmail.com/
>
> drivers/net/ethernet/airoha/airoha_eth.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index d0c0c0ec8a..ca77747b44 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -2212,7 +2212,7 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *dev,
> struct airoha_gdm_port *port = netdev_priv(dev);
it seems you have not rebased on top of net tree.
Regards,
Lorenzo
> int i;
>
> - for (i = 0; i < AIROHA_NUM_TX_RING; i++)
> + for (i = 0; i < AIROHA_NUM_QOS_QUEUES; i++)
> airoha_qdma_clear(port->qdma, REG_QUEUE_CLOSE_CFG(channel),
> TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
>
> --
> 2.51.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* [PATCH net v2 2/2] net: airoha: fix netif_set_real_num_tx_queues for sparse QoS channels
From: Lorenzo Bianconi @ 2026-06-19 11:37 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: Simon Horman, Wayen Yan, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260619-airoha-qos-fixes-v2-0-5c43485038f9@kernel.org>
airoha_tc_htb_alloc_leaf_queue() assigns queue IDs based on the channel
index (opt->qid = AIROHA_NUM_TX_RING + channel), but updates
real_num_tx_queues with a simple increment (num_tx_queues + 1). When QoS
channels are allocated sparsely (e.g., channels 0 and 3 without 1 and
2), the returned qid can exceed real_num_tx_queues, causing out-of-bounds
accesses in the networking stack.
For example, allocating channel 0 then channel 3 results in
real_num_tx_queues = 34 but qid = 35, which is out of range [0, 34).
Fix this by computing real_num_tx_queues based on the highest active
channel index rather than using a simple counter, in both the allocation
and deletion paths.
Fixes: ef1ca9271313b ("net: airoha: Add sched HTB offload support")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index aa98d1823ab6..aa2ddfd3af9f 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2789,7 +2789,7 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
struct tc_htb_qopt_offload *opt)
{
u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
- int err, num_tx_queues = netdev->real_num_tx_queues;
+ int err, num_tx_queues = AIROHA_NUM_TX_RING + channel + 1;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_qdma *qdma = dev->qdma;
@@ -2806,13 +2806,15 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
if (err)
goto error;
- err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
- if (err) {
- airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
- opt->quantum);
- NL_SET_ERR_MSG_MOD(opt->extack,
- "failed setting real_num_tx_queues");
- goto error;
+ if (num_tx_queues > netdev->real_num_tx_queues) {
+ err = netif_set_real_num_tx_queues(netdev, num_tx_queues);
+ if (err) {
+ airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
+ opt->quantum);
+ NL_SET_ERR_MSG_MOD(opt->extack,
+ "failed setting real_num_tx_queues");
+ goto error;
+ }
}
set_bit(channel, dev->qos_sq_bmap);
@@ -3003,13 +3005,18 @@ static int airoha_dev_setup_tc_block(struct net_device *dev,
static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ int num_tx_queues = AIROHA_NUM_TX_RING;
struct airoha_qdma *qdma = dev->qdma;
- netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
airoha_qdma_set_tx_rate_limit(netdev, queue, 0, 0);
clear_bit(queue, qdma->qos_channel_map);
clear_bit(queue, dev->qos_sq_bmap);
+
+ if (!bitmap_empty(dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS))
+ num_tx_queues += find_last_bit(dev->qos_sq_bmap,
+ AIROHA_NUM_QOS_CHANNELS) + 1;
+ netif_set_real_num_tx_queues(netdev, num_tx_queues);
}
static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
--
2.54.0
^ permalink raw reply related
* [PATCH net v2 1/2] net: airoha: Fix off-by-one in airoha_tc_remove_htb_queue()
From: Lorenzo Bianconi @ 2026-06-19 11:37 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: Simon Horman, Wayen Yan, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260619-airoha-qos-fixes-v2-0-5c43485038f9@kernel.org>
airoha_tc_htb_alloc_leaf_queue() computes the HTB QoS channel index
as opt->classid % AIROHA_NUM_QOS_CHANNELS and stores it in qos_sq_bmap.
However, airoha_tc_remove_htb_queue() clears the HTB configuration
using queue + 1 as the channel index, causing an off-by-one error.
Use queue directly as the QoS channel index to match the allocation
logic.
Fixes: ef1ca9271313b ("net: airoha: Add sched HTB offload support")
Reviewed-by: Simon Horman <horms@kernel.org>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 64dde6464f3f..aa98d1823ab6 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -3006,7 +3006,7 @@ static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
struct airoha_qdma *qdma = dev->qdma;
netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
- airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
+ airoha_qdma_set_tx_rate_limit(netdev, queue, 0, 0);
clear_bit(queue, qdma->qos_channel_map);
clear_bit(queue, dev->qos_sq_bmap);
--
2.54.0
^ permalink raw reply related
* [PATCH net v2 0/2] airoha: fixes for sched HTB offload support
From: Lorenzo Bianconi @ 2026-06-19 11:37 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: Simon Horman, Wayen Yan, linux-arm-kernel, linux-mediatek, netdev
---
Changes in v2:
- cosmetics
- Link to v1: https://lore.kernel.org/r/20260618-airoha-qos-fixes-v1-0-37192652157f@kernel.org
---
Lorenzo Bianconi (2):
net: airoha: Fix off-by-one in airoha_tc_remove_htb_queue()
net: airoha: fix netif_set_real_num_tx_queues for sparse QoS channels
drivers/net/ethernet/airoha/airoha_eth.c | 27 +++++++++++++++++----------
1 file changed, 17 insertions(+), 10 deletions(-)
---
base-commit: 96e7f9122aae0ed000ee321f324b812a447906d9
change-id: 20260618-airoha-qos-fixes-b6460b085680
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply
* Re: [PATCH net 2/2] net: airoha: fix netif_set_real_num_tx_queues for sparse QoS channels
From: Lorenzo Bianconi @ 2026-06-19 11:34 UTC (permalink / raw)
To: Simon Horman
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Wayen Yan, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260619093529.GV827683@horms.kernel.org>
[-- Attachment #1: Type: text/plain, Size: 2052 bytes --]
> On Thu, Jun 18, 2026 at 08:00:30AM +0200, Lorenzo Bianconi wrote:
> > airoha_tc_htb_alloc_leaf_queue() assigns queue IDs based on the channel
> > index (opt->qid = AIROHA_NUM_TX_RING + channel), but updates
> > real_num_tx_queues with a simple increment (num_tx_queues + 1). When QoS
> > channels are allocated sparsely (e.g., channels 0 and 3 without 1 and
> > 2), the returned qid can exceed real_num_tx_queues, causing out-of-bounds
> > accesses in the networking stack.
> > For example, allocating channel 0 then channel 3 results in
> > real_num_tx_queues = 34 but qid = 35, which is out of range [0, 34).
> > Fix this by computing real_num_tx_queues based on the highest active
> > channel index rather than using a simple counter, in both the allocation
> > and deletion paths.
> >
> > Fixes: ef1ca9271313b ("net: airoha: Add sched HTB offload support")
> > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> > ---
> > drivers/net/ethernet/airoha/airoha_eth.c | 15 ++++++++++++---
> > 1 file changed, 12 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
>
> ...
>
> > @@ -2806,7 +2806,10 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> > if (err)
> > goto error;
> >
> > - err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
> > + if (num_tx_queues <= netdev->real_num_tx_queues)
> > + goto set_qos_sq_bmap;
> > +
> > + err = netif_set_real_num_tx_queues(netdev, num_tx_queues);
> > if (err) {
> > airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
> > opt->quantum);
> > @@ -2815,6 +2818,7 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> > goto error;
> > }
> >
> > +set_qos_sq_bmap:
>
> I would prefer if this could be achieved without a goto.
ack, I will fix it in v2.
Regards,
Lorenzo
>
> > set_bit(channel, dev->qos_sq_bmap);
> > opt->qid = AIROHA_NUM_TX_RING + channel;
> >
>
> ...
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH 15/78] ASoC: codecs: cs42l43: Use guard() for mutex locks
From: Takashi Iwai @ 2026-06-19 11:14 UTC (permalink / raw)
To: Bui Duc Phuc
Cc: Charles Keepax, David Laight, Mark Brown, Liam Girdwood,
Jaroslav Kysela, Takashi Iwai, Cheng-Yi Chiang, Tzung-Bi Shih,
Guenter Roeck, Benson Leung, David Rhodes, Richard Fitzgerald,
povik+lin, Support Opensource, Nick Li, Herve Codina,
Srinivas Kandagatla, Matthias Brugger, AngeloGioacchino Del Regno,
Shenghao Ding, Kevin Lu, Baojun Xu, Sen Wang, Oder Chiou,
Lars-Peter Clausen, nuno.sa, Steven Eckhoff, patches,
chrome-platform, asahi, linux-arm-msm, linux-sound, linux-kernel,
linux-arm-kernel, linux-mediatek
In-Reply-To: <CAABR9nGRCZ1zv0cyBGc3yuM8MeFNqBB1MkGUL7bTbtC_LcKKzA@mail.gmail.com>
On Fri, 19 Jun 2026 12:57:57 +0200,
Bui Duc Phuc wrote:
>
> Hi Charles, David,
>
>
>
> > > > > > I believe you have to use scoped_guard here, as there is a return
> > > > > > from the function above, if memory serves it attempts to release
> > > > > > the mutex on that path despite it being above the guard.
> > > > >
> > > > > Indeed.
> > > > > I believe clang will complain.
> > > > > That makes these mechanical conversions of existing code dangerous churn.
> > > > >
> > > > > While using guard() (etc) can make it easier to ensure the lock is released
> > > > > when functions have multiple error exits, I'm not convinced it makes the
> > > > > code any easier to read (other people may disagree).
> > > >
> > > > I built the code with both GCC and Clang and didn't see any warnings.
> > > >
> > > > My understanding was that the early return exits the function before
> > > > the guard is instantiated, so it should not affect the guard's cleanup
> > > > handling.
> > >
> > > When a variable is defined (and initialised) part way down a block the
> > > compiler moves the definition to the top of the block but doesn't initialise
> > > it at all, the first assignment happens where the code contains the
> > > definition.
> > >
> > > However the destructor is always called at the end of the block.
> > > So if you return from a function before the definition the destructor
> > > is called with an uninitialised argument.
> >
> > My understanding was exactly as your David, but it seems that isn't
> > the whole story and indeed I had to fix a bug in our SDCA code
> > that hit this. However testing this out, results in some things I
> > find very hard to explain.
> >
> > It seems as far as I have managed to test, the code below works
> > fine as Phuc suggests. It does not appear to run the mutex_unlock
> > on the error path.
> >
> > int function()
> > {
> > if (error)
> > return;
> >
> > guard(mutex)(&mutex);
> >
> > stuff();
> >
> > return;
> > }
> >
>
> Thanks both for the clarification.
>
> > The situation I hit this in before that doesn't work was actually
> > this:
> >
> > int function()
> > {
> > if (error)
> > goto error_label;
> >
> > guard(mutex)(&mutex);
> >
> > stuff();
> >
> > error_label;
> > return;
> > }
> >
> > Which in this case it does run the mutex_unlock and NULL pointer.
> > Will try to find sometime to look at the generated assembly, but
> > this basically totally blows my mind. Very unclear as to if this
> > is supposed to work this way or just does by pure luck.
> >
>
> As stated in cleanup.h, mixing goto-based cleanup and scope-based
> cleanup helpers in the same function is not expected, so I think
> we should keep a consistent approach here.
Right, and IIRC, clang would complain the mixed goto case at least
with W=1.
Takashi
^ permalink raw reply
* Re: [PATCH 15/78] ASoC: codecs: cs42l43: Use guard() for mutex locks
From: Bui Duc Phuc @ 2026-06-19 10:57 UTC (permalink / raw)
To: Charles Keepax
Cc: David Laight, Mark Brown, Liam Girdwood, Jaroslav Kysela,
Takashi Iwai, Cheng-Yi Chiang, Tzung-Bi Shih, Guenter Roeck,
Benson Leung, David Rhodes, Richard Fitzgerald, povik+lin,
Support Opensource, Nick Li, Herve Codina, Srinivas Kandagatla,
Matthias Brugger, AngeloGioacchino Del Regno, Shenghao Ding,
Kevin Lu, Baojun Xu, Sen Wang, Oder Chiou, Lars-Peter Clausen,
nuno.sa, Steven Eckhoff, patches, chrome-platform, asahi,
linux-arm-msm, linux-sound, linux-kernel, linux-arm-kernel,
linux-mediatek
In-Reply-To: <ajUcTfG3vSGz3n3d@opensource.cirrus.com>
Hi Charles, David,
> > > > > I believe you have to use scoped_guard here, as there is a return
> > > > > from the function above, if memory serves it attempts to release
> > > > > the mutex on that path despite it being above the guard.
> > > >
> > > > Indeed.
> > > > I believe clang will complain.
> > > > That makes these mechanical conversions of existing code dangerous churn.
> > > >
> > > > While using guard() (etc) can make it easier to ensure the lock is released
> > > > when functions have multiple error exits, I'm not convinced it makes the
> > > > code any easier to read (other people may disagree).
> > >
> > > I built the code with both GCC and Clang and didn't see any warnings.
> > >
> > > My understanding was that the early return exits the function before
> > > the guard is instantiated, so it should not affect the guard's cleanup
> > > handling.
> >
> > When a variable is defined (and initialised) part way down a block the
> > compiler moves the definition to the top of the block but doesn't initialise
> > it at all, the first assignment happens where the code contains the
> > definition.
> >
> > However the destructor is always called at the end of the block.
> > So if you return from a function before the definition the destructor
> > is called with an uninitialised argument.
>
> My understanding was exactly as your David, but it seems that isn't
> the whole story and indeed I had to fix a bug in our SDCA code
> that hit this. However testing this out, results in some things I
> find very hard to explain.
>
> It seems as far as I have managed to test, the code below works
> fine as Phuc suggests. It does not appear to run the mutex_unlock
> on the error path.
>
> int function()
> {
> if (error)
> return;
>
> guard(mutex)(&mutex);
>
> stuff();
>
> return;
> }
>
Thanks both for the clarification.
> The situation I hit this in before that doesn't work was actually
> this:
>
> int function()
> {
> if (error)
> goto error_label;
>
> guard(mutex)(&mutex);
>
> stuff();
>
> error_label;
> return;
> }
>
> Which in this case it does run the mutex_unlock and NULL pointer.
> Will try to find sometime to look at the generated assembly, but
> this basically totally blows my mind. Very unclear as to if this
> is supposed to work this way or just does by pure luck.
>
As stated in cleanup.h, mixing goto-based cleanup and scope-based
cleanup helpers in the same function is not expected, so I think
we should keep a consistent approach here.
Best regards,
Phuc
^ permalink raw reply
* Re: [PATCH 15/78] ASoC: codecs: cs42l43: Use guard() for mutex locks
From: Charles Keepax @ 2026-06-19 10:39 UTC (permalink / raw)
To: David Laight
Cc: Bui Duc Phuc, Mark Brown, Liam Girdwood, Jaroslav Kysela,
Takashi Iwai, Cheng-Yi Chiang, Tzung-Bi Shih, Guenter Roeck,
Benson Leung, David Rhodes, Richard Fitzgerald, povik+lin,
Support Opensource, Nick Li, Herve Codina, Srinivas Kandagatla,
Matthias Brugger, AngeloGioacchino Del Regno, Shenghao Ding,
Kevin Lu, Baojun Xu, Sen Wang, Oder Chiou, Lars-Peter Clausen,
nuno.sa, Steven Eckhoff, patches, chrome-platform, asahi,
linux-arm-msm, linux-sound, linux-kernel, linux-arm-kernel,
linux-mediatek
In-Reply-To: <20260619101346.2ec49087@pumpkin>
On Fri, Jun 19, 2026 at 10:13:46AM +0100, David Laight wrote:
> On Fri, 19 Jun 2026 15:20:37 +0700
> Bui Duc Phuc <phucduc.bui@gmail.com> wrote:
> > > > I believe you have to use scoped_guard here, as there is a return
> > > > from the function above, if memory serves it attempts to release
> > > > the mutex on that path despite it being above the guard.
> > >
> > > Indeed.
> > > I believe clang will complain.
> > > That makes these mechanical conversions of existing code dangerous churn.
> > >
> > > While using guard() (etc) can make it easier to ensure the lock is released
> > > when functions have multiple error exits, I'm not convinced it makes the
> > > code any easier to read (other people may disagree).
> >
> > I built the code with both GCC and Clang and didn't see any warnings.
> >
> > My understanding was that the early return exits the function before
> > the guard is instantiated, so it should not affect the guard's cleanup
> > handling.
>
> When a variable is defined (and initialised) part way down a block the
> compiler moves the definition to the top of the block but doesn't initialise
> it at all, the first assignment happens where the code contains the
> definition.
>
> However the destructor is always called at the end of the block.
> So if you return from a function before the definition the destructor
> is called with an uninitialised argument.
My understanding was exactly as your David, but it seems that isn't
the whole story and indeed I had to fix a bug in our SDCA code
that hit this. However testing this out, results in some things I
find very hard to explain.
It seems as far as I have managed to test, the code below works
fine as Phuc suggests. It does not appear to run the mutex_unlock
on the error path.
int function()
{
if (error)
return;
guard(mutex)(&mutex);
stuff();
return;
}
The situation I hit this in before that doesn't work was actually
this:
int function()
{
if (error)
goto error_label;
guard(mutex)(&mutex);
stuff();
error_label;
return;
}
Which in this case it does run the mutex_unlock and NULL pointer.
Will try to find sometime to look at the generated assembly, but
this basically totally blows my mind. Very unclear as to if this
is supposed to work this way or just does by pure luck.
Thanks,
Charles
^ permalink raw reply
* [PATCH net v2] net: airoha: fix BQL underflow and UAF in shared QDMA TX ring
From: Lorenzo Bianconi @ 2026-06-19 10:30 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: Wayen Yan, linux-arm-kernel, linux-mediatek, netdev
When multiple netdevs share a QDMA TX ring and one device is stopped,
netdev_tx_reset_subqueue() zeroes that device's BQL counters while its
pending skbs remain in the shared HW TX ring. When NAPI later completes
those skbs via netdev_tx_completed_queue(), the already-zeroed
dql->num_queued counter underflows.
Moreover, in the airoha_remove() path, netdevs are unregistered
sequentially while skbs from previously unregistered netdevs may still
reference freed net_device memory via skb->dev, causing a use-after-free
during BQL accounting.
Fix both issues:
- Remove netdev_tx_reset_subqueue() from airoha_dev_stop() so pending
skbs are completed naturally by NAPI with proper BQL accounting.
- Introduce airoha_qdma_tx_flush() to stop NAPI and flush BQL counters
for all pending skbs while skb->dev references are still valid.
- Guard airoha_dev_xmit() with DEV_STATE_FLUSH to drop packets during
teardown.
- Move DMA engine start into probe and stop into airoha_qdma_cleanup().
Fixes: a9c2ca61fec7 ("net: airoha: Support multiple net_devices for a single FE GDM port")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
Changes in v2:
- Introduce airoha_qdma_tx_flush() to account BQL in airoha_remove() or
airoha_probe() error path.
- Fix possible NULL pointer dereference in airoha_qdma_cleanup().
- Introduce DEV_STATE_FLUSH().
- Move back airoha_hw_cleanup().
- Set proper Fixes tag.
- Link to v1: https://lore.kernel.org/r/20260618-airoha-bql-fixes-v1-1-ffd2c2089518@kernel.org
---
drivers/net/ethernet/airoha/airoha_eth.c | 87 +++++++++++++++++++++++---------
drivers/net/ethernet/airoha/airoha_eth.h | 1 +
2 files changed, 63 insertions(+), 25 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 64dde6464f3f..e81cd806b57b 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1004,6 +1004,7 @@ static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
e = &q->entry[index];
skb = e->skb;
+ e->skb = NULL;
dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
DMA_TO_DEVICE);
@@ -1523,10 +1524,26 @@ static int airoha_qdma_init(struct platform_device *pdev,
return airoha_qdma_hw_init(qdma);
}
-static void airoha_qdma_cleanup(struct airoha_qdma *qdma)
+static void airoha_qdma_cleanup(struct airoha_eth *eth,
+ struct airoha_qdma *qdma)
{
int i;
+ if (test_bit(DEV_STATE_INITIALIZED, ð->state)) {
+ u32 status;
+
+ airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_TX_DMA_EN_MASK |
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+ if (read_poll_timeout(airoha_qdma_rr, status,
+ !(status & (GLOBAL_CFG_TX_DMA_BUSY_MASK |
+ GLOBAL_CFG_RX_DMA_BUSY_MASK)),
+ USEC_PER_MSEC, 50 * USEC_PER_MSEC, true,
+ qdma, REG_QDMA_GLOBAL_CFG))
+ dev_warn(eth->dev,
+ "QDMA DMA engine busy timeout\n");
+ }
+
for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
if (!qdma->q_rx[i].ndesc)
continue;
@@ -1593,7 +1610,7 @@ static int airoha_hw_init(struct platform_device *pdev,
return 0;
error:
for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
- airoha_qdma_cleanup(ð->qdma[i]);
+ airoha_qdma_cleanup(eth, ð->qdma[i]);
return err;
}
@@ -1603,7 +1620,7 @@ static void airoha_hw_cleanup(struct airoha_eth *eth)
int i;
for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
- airoha_qdma_cleanup(ð->qdma[i]);
+ airoha_qdma_cleanup(eth, ð->qdma[i]);
airoha_ppe_deinit(eth);
}
@@ -1637,6 +1654,35 @@ static void airoha_qdma_stop_napi(struct airoha_qdma *qdma)
}
}
+static void airoha_qdma_tx_flush(struct airoha_qdma *qdma)
+{
+ int i;
+
+ airoha_qdma_stop_napi(qdma);
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+ struct airoha_queue *q = &qdma->q_tx[i];
+ int j;
+
+ if (!q->ndesc)
+ continue;
+
+ spin_lock_bh(&q->lock);
+ for (j = 0; j < q->ndesc; j++) {
+ struct airoha_queue_entry *e = &q->entry[j];
+ struct sk_buff *skb = e->skb;
+ struct netdev_queue *txq;
+
+ if (!skb)
+ continue;
+
+ txq = skb_get_tx_queue(skb->dev, skb);
+ netdev_tx_completed_queue(txq, 1, skb->len);
+ }
+ spin_unlock_bh(&q->lock);
+ }
+}
+
static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
{
struct airoha_gdm_port *port = dev->port;
@@ -1837,9 +1883,6 @@ static int airoha_dev_open(struct net_device *netdev)
}
port->users++;
- airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
- GLOBAL_CFG_TX_DMA_EN_MASK |
- GLOBAL_CFG_RX_DMA_EN_MASK);
qdma->users++;
if (!airoha_is_lan_gdm_dev(dev) &&
@@ -1880,12 +1923,9 @@ static int airoha_dev_stop(struct net_device *netdev)
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
struct airoha_qdma *qdma = dev->qdma;
- int i;
netif_tx_disable(netdev);
airoha_set_vip_for_gdm_port(dev, false);
- for (i = 0; i < netdev->num_tx_queues; i++)
- netdev_tx_reset_subqueue(netdev, i);
if (--port->users)
airoha_set_port_mtu(dev->eth, port);
@@ -1893,19 +1933,7 @@ static int airoha_dev_stop(struct net_device *netdev)
airoha_set_gdm_port_fwd_cfg(qdma->eth,
REG_GDM_FWD_CFG(port->id),
FE_PSE_PORT_DROP);
-
- if (!--qdma->users) {
- airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
- GLOBAL_CFG_TX_DMA_EN_MASK |
- GLOBAL_CFG_RX_DMA_EN_MASK);
-
- for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
- if (!qdma->q_tx[i].ndesc)
- continue;
-
- airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
- }
- }
+ qdma->users--;
return 0;
}
@@ -2191,6 +2219,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
u16 index;
u8 fport;
+ if (test_bit(DEV_STATE_FLUSH, &dev->eth->state))
+ goto error;
+
qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
tag = airoha_get_dsa_tag(skb, netdev);
@@ -3413,8 +3444,12 @@ static int airoha_probe(struct platform_device *pdev)
if (err)
goto error_netdev_free;
- for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
+ for (i = 0; i < ARRAY_SIZE(eth->qdma); i++) {
airoha_qdma_start_napi(ð->qdma[i]);
+ airoha_qdma_set(ð->qdma[i], REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_TX_DMA_EN_MASK |
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+ }
for_each_child_of_node(pdev->dev.of_node, np) {
if (!of_device_is_compatible(np, "airoha,eth-mac"))
@@ -3437,8 +3472,9 @@ static int airoha_probe(struct platform_device *pdev)
return 0;
error_napi_stop:
+ set_bit(DEV_STATE_FLUSH, ð->state);
for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
- airoha_qdma_stop_napi(ð->qdma[i]);
+ airoha_qdma_tx_flush(ð->qdma[i]);
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
@@ -3474,8 +3510,9 @@ static void airoha_remove(struct platform_device *pdev)
struct airoha_eth *eth = platform_get_drvdata(pdev);
int i;
+ set_bit(DEV_STATE_FLUSH, ð->state);
for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
- airoha_qdma_stop_napi(ð->qdma[i]);
+ airoha_qdma_tx_flush(ð->qdma[i]);
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 41d2e7a1f9fb..f6dce5e74e02 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -92,6 +92,7 @@ enum {
enum {
DEV_STATE_INITIALIZED,
DEV_STATE_REGISTERED,
+ DEV_STATE_FLUSH,
};
enum {
---
base-commit: a887f2c7da66a805a55fd8706d45faec85f646db
change-id: 20260618-airoha-bql-fixes-f57b2d108573
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* Re: [PATCH net 2/2] net: airoha: fix netif_set_real_num_tx_queues for sparse QoS channels
From: Simon Horman @ 2026-06-19 9:35 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Wayen Yan, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260618-airoha-qos-fixes-v1-2-37192652157f@kernel.org>
On Thu, Jun 18, 2026 at 08:00:30AM +0200, Lorenzo Bianconi wrote:
> airoha_tc_htb_alloc_leaf_queue() assigns queue IDs based on the channel
> index (opt->qid = AIROHA_NUM_TX_RING + channel), but updates
> real_num_tx_queues with a simple increment (num_tx_queues + 1). When QoS
> channels are allocated sparsely (e.g., channels 0 and 3 without 1 and
> 2), the returned qid can exceed real_num_tx_queues, causing out-of-bounds
> accesses in the networking stack.
> For example, allocating channel 0 then channel 3 results in
> real_num_tx_queues = 34 but qid = 35, which is out of range [0, 34).
> Fix this by computing real_num_tx_queues based on the highest active
> channel index rather than using a simple counter, in both the allocation
> and deletion paths.
>
> Fixes: ef1ca9271313b ("net: airoha: Add sched HTB offload support")
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 15 ++++++++++++---
> 1 file changed, 12 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
...
> @@ -2806,7 +2806,10 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> if (err)
> goto error;
>
> - err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
> + if (num_tx_queues <= netdev->real_num_tx_queues)
> + goto set_qos_sq_bmap;
> +
> + err = netif_set_real_num_tx_queues(netdev, num_tx_queues);
> if (err) {
> airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
> opt->quantum);
> @@ -2815,6 +2818,7 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> goto error;
> }
>
> +set_qos_sq_bmap:
I would prefer if this could be achieved without a goto.
> set_bit(channel, dev->qos_sq_bmap);
> opt->qid = AIROHA_NUM_TX_RING + channel;
>
...
^ permalink raw reply
* Re: [PATCH net 1/2] net: airoha: Fix off-by-one in airoha_tc_remove_htb_queue()
From: Simon Horman @ 2026-06-19 9:34 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Wayen Yan, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260618-airoha-qos-fixes-v1-1-37192652157f@kernel.org>
On Thu, Jun 18, 2026 at 08:00:29AM +0200, Lorenzo Bianconi wrote:
> airoha_tc_htb_alloc_leaf_queue() computes the HTB QoS channel index
> as opt->classid % AIROHA_NUM_QOS_CHANNELS and stores it in qos_sq_bmap.
> However, airoha_tc_remove_htb_queue() clears the HTB configuration
> using queue + 1 as the channel index, causing an off-by-one error.
> Use queue directly as the QoS channel index to match the allocation
> logic.
>
> Fixes: ef1ca9271313b ("net: airoha: Add sched HTB offload support")
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Reviewed-by: Simon Horman <horms@kernel.org>
^ permalink raw reply
* Re: [PATCH 15/78] ASoC: codecs: cs42l43: Use guard() for mutex locks
From: David Laight @ 2026-06-19 9:13 UTC (permalink / raw)
To: Bui Duc Phuc
Cc: Charles Keepax, Mark Brown, Liam Girdwood, Jaroslav Kysela,
Takashi Iwai, Cheng-Yi Chiang, Tzung-Bi Shih, Guenter Roeck,
Benson Leung, David Rhodes, Richard Fitzgerald, povik+lin,
Support Opensource, Nick Li, Herve Codina, Srinivas Kandagatla,
Matthias Brugger, AngeloGioacchino Del Regno, Shenghao Ding,
Kevin Lu, Baojun Xu, Sen Wang, Oder Chiou, Lars-Peter Clausen,
nuno.sa, Steven Eckhoff, patches, chrome-platform, asahi,
linux-arm-msm, linux-sound, linux-kernel, linux-arm-kernel,
linux-mediatek
In-Reply-To: <CAABR9nG+6gOj4KnWmTyykgGN93xy6jKQh+-_f8Xxn=Jkv28vBA@mail.gmail.com>
On Fri, 19 Jun 2026 15:20:37 +0700
Bui Duc Phuc <phucduc.bui@gmail.com> wrote:
> Hi Charles, David,
>
> Thanks for the review.
>
> > >
> > > I believe you have to use scoped_guard here, as there is a return
> > > from the function above, if memory serves it attempts to release
> > > the mutex on that path despite it being above the guard.
> >
> > Indeed.
> > I believe clang will complain.
> > That makes these mechanical conversions of existing code dangerous churn.
> >
> > While using guard() (etc) can make it easier to ensure the lock is released
> > when functions have multiple error exits, I'm not convinced it makes the
> > code any easier to read (other people may disagree).
> >
>
> I built the code with both GCC and Clang and didn't see any warnings.
>
> My understanding was that the early return exits the function before
> the guard is instantiated, so it should not affect the guard's cleanup
> handling.
>
> Could you explain what issue you are referring to? I may be missing
> something.
When a variable is defined (and initialised) part way down a block the
compiler moves the definition to the top of the block but doesn't initialise
it at all, the first assignment happens where the code contains the
definition.
However the destructor is always called at the end of the block.
So if you return from a function before the definition the destructor
is called with an uninitialised argument.
This has always been a problem with C++.
It usually happens when you define a variable inside a switch statement.
David
>
> Best regards,
> Phuc
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox