[v8,3/5] drm/scdc-helper: Implement parsing and printing HDMI 2.1 fields
Commit Message
HDMI 2.1 redefines previously reserved fields in SCDC for various new
uses. No version check needs to be performed, as an HDMI 2.0 sink's
reserved SCDC fields are well-defined to be 0, and any zero-ness of
these fields for an HDMI 2.0 sink is not a surprise for SCDC parsers for
HDMI 2.1.
Implement reading and outputting these fields over debugfs.
Reviewed-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
drivers/gpu/drm/display/drm_scdc_helper.c | 99 ++++++++++++++++++++++++++++++-
include/drm/display/drm_scdc.h | 21 ++++++-
include/drm/display/drm_scdc_helper.h | 69 ++++++++++++++++++++-
3 files changed, 182 insertions(+), 7 deletions(-)
Comments
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] Reed-Solomon (RS) corrections are incorrectly restricted to 4-lane FRL configurations.
Pre-existing issues:
- [High] Missing lock release (deadlock) in scdc_status_show error path.
--
--- Patch [3]: [PATCH v8 3/5] drm/scdc-helper: Implement parsing and printing HDMI 2.1 fields ---
commit a8f03be96ae72bbee1ab3aab07506aef81f2031e
Author: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
drm/scdc-helper: Implement parsing and printing HDMI 2.1 fields
HDMI 2.1 redefines previously reserved fields in SCDC for various new
uses. No version check needs to be performed, as an HDMI 2.0 sink's
reserved SCDC fields are well-defined to be 0, and any zero-ness of
these fields for an HDMI 2.0 sink is not a surprise for SCDC parsers for
HDMI 2.1.
Implement reading and outputting these fields over debugfs.
> diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c
> index 97256b272329a..73d46de33ab55 100644
> --- a/drivers/gpu/drm/display/drm_scdc_helper.c
> +++ b/drivers/gpu/drm/display/drm_scdc_helper.c
[ ... ]
> @@ -350,11 +417,26 @@ int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state *
>
> state->scrambling_detected = buf[SCDC_SCRAMBLER_STATUS] & SCDC_SCRAMBLING_STATUS;
>
> + state->rate = FIELD_GET(SCDC_FRL_RATE, buf[SCDC_CONFIG_1]);
> + num_lanes = drm_scdc_num_frl_lanes(state->rate);
> + if (num_lanes < 0)
> + return num_lanes;
> + if (!num_lanes)
> + num_lanes = 3;
> +
> + state->ffe_levels = FIELD_GET(SCDC_FFE_LEVELS, buf[SCDC_CONFIG_1]);
> +
> drm_scdc_parse_status0_flags(buf[SCDC_STATUS_FLAGS_0], &state->stf);
> - ret = drm_scdc_parse_error_counters(buf, state->error_count);
> + drm_scdc_parse_status1_2_flags(buf[SCDC_STATUS_FLAGS_1],
> + buf[SCDC_STATUS_FLAGS_2], &state->stf);
> + ret = drm_scdc_parse_error_counters(buf, state->error_count, num_lanes);
> if (ret)
> return ret;
>
> + if (num_lanes == 4 && (buf[SCDC_ERR_DET_RS_H] & SCDC_CHANNEL_VALID))
> + state->rs_corrections = (buf[SCDC_ERR_DET_RS_H] & ~SCDC_CHANNEL_VALID) << 8 |
> + buf[SCDC_ERR_DET_RS_L];
[Severity: Medium]
Since HDMI 2.1 Fixed Rate Link (FRL) uses Reed-Solomon Forward Error
Correction for both 3-lane and 4-lane configurations, is it intentional
to only read RS corrections when num_lanes == 4?
Valid RS correction data for 3-lane FRL links (e.g., 3 Gbps and 6 Gbps on
3 lanes) seems to be silently ignored here.
> +
> return 0;
> }
> EXPORT_SYMBOL(drm_scdc_read_state);
> @@ -416,6 +498,8 @@ static int scdc_status_show(struct seq_file *m, void *data)
[Severity: High]
This is a pre-existing issue, but could the early exit for a disconnected
connector create a deadlock in this function?
When a connector is disconnected, the code jumps to err_conn_put:
ret = mutex_lock_interruptible(&connector->dev->mode_config.mutex);
if (ret)
goto err_conn_put;
if (connector->status != connector_status_connected) {
ret = -ENODEV;
goto err_conn_put;
}
But the err_conn_put label skips err_unlock:
err_unlock:
mutex_unlock(&connector->dev->mode_config.mutex);
err_conn_put:
drm_connector_put(connector);
Does this leave mode_config.mutex permanently locked?
>
> scdc_print_flag(m, "Scrambling Enabled", st->scrambling_enabled);
> scdc_print_flag(m, "Scrambling Detected", st->scrambling_detected);
> + scdc_print_str(m, "FRL Rate", drm_scdc_frl_rate_str(st->rate));
> + scdc_print_dec(m, "FFE Levels", st->ffe_levels);
>
> if (st->tmds_bclk_x40)
> @@ -426,10 +510,19 @@ static int scdc_status_show(struct seq_file *m, void *data)
> scdc_print_flag(m, "Channel 0 Locked", st->stf.ch0_locked);
> scdc_print_flag(m, "Channel 1 Locked", st->stf.ch1_locked);
> scdc_print_flag(m, "Channel 2 Locked", st->stf.ch2_locked);
> + if (drm_scdc_num_frl_lanes(st->rate) == 4)
> + scdc_print_flag(m, "Lane 3 Locked", st->stf.ln3_locked);
> +
> + scdc_print_flag(m, "Sink Ready For Link Training", st->stf.flt_ready);
> + scdc_print_flag(m, "Sink Failed To Decode DSC", st->stf.dsc_fail);
>
> scdc_print_dec(m, "Channel 0 Errors", st->error_count[0]);
> scdc_print_dec(m, "Channel 1 Errors", st->error_count[1]);
> scdc_print_dec(m, "Channel 2 Errors", st->error_count[2]);
> + if (drm_scdc_num_frl_lanes(st->rate) == 4) {
> + scdc_print_dec(m, "Lane 3 Errors", st->error_count[3]);
> + scdc_print_dec(m, "Reed-Solomon Corrections", st->rs_corrections);
[Severity: Medium]
Similarly here, shouldn't Reed-Solomon corrections also be printed for 3-lane
FRL configurations?
> + }
>
> kfree(st);
>
@@ -21,6 +21,7 @@
* DEALINGS IN THE SOFTWARE.
*/
+#include <linux/bitfield.h>
#include <linux/export.h>
#include <linux/i2c.h>
#include <linux/slab.h>
@@ -57,6 +58,38 @@
#define SCDC_I2C_SLAVE_ADDRESS 0x54
+static const char *drm_scdc_frl_rate_str(enum drm_scdc_frl_rate rate)
+{
+ switch (rate) {
+ case SCDC_FRL_RATE_OFF:
+ return "Off";
+ case SCDC_FRL_RATE_3X3:
+ return "3 Gbit/s x 3 lanes";
+ case SCDC_FRL_RATE_6X3:
+ return "6 Gbit/s x 3 lanes";
+ case SCDC_FRL_RATE_6X4:
+ return "6 Gbit/s x 4 lanes";
+ case SCDC_FRL_RATE_8X4:
+ return "8 Gbit/s x 4 lanes";
+ case SCDC_FRL_RATE_10X4:
+ return "10 Gbit/s x 4 lanes";
+ case SCDC_FRL_RATE_12X4:
+ return "12 Gbit/s x 4 lanes";
+ case SCDC_FRL_RATE_RESV_7:
+ case SCDC_FRL_RATE_RESV_8:
+ case SCDC_FRL_RATE_RESV_9:
+ case SCDC_FRL_RATE_RESV_10:
+ case SCDC_FRL_RATE_RESV_11:
+ case SCDC_FRL_RATE_RESV_12:
+ case SCDC_FRL_RATE_RESV_13:
+ case SCDC_FRL_RATE_RESV_14:
+ case SCDC_FRL_RATE_RESV_15:
+ return "(Reserved)";
+ default:
+ return NULL;
+ }
+}
+
/**
* drm_scdc_read - read a block of data from SCDC
* @adapter: I2C controller
@@ -286,14 +319,41 @@ drm_scdc_parse_status0_flags(u8 val, struct drm_scdc_status_flags *flags)
flags->ch0_locked = val & SCDC_CH0_LOCK;
flags->ch1_locked = val & SCDC_CH1_LOCK;
flags->ch2_locked = val & SCDC_CH2_LOCK;
+ flags->ln3_locked = val & SCDC_LN3_LOCK;
+ flags->flt_ready = val & SCDC_FLT_READY;
+ flags->dsc_fail = val & SCDC_DSC_FAIL;
+}
+
+static void
+drm_scdc_parse_status1_2_flags(u8 val_flag1, u8 val_flag2,
+ struct drm_scdc_status_flags *flags)
+{
+ flags->ln0_training_pattern = FIELD_GET(SCDC_LN_EVEN_TRAIN_PTRN, val_flag1);
+ flags->ln1_training_pattern = FIELD_GET(SCDC_LN_ODD_TRAIN_PTRN, val_flag1);
+
+ flags->ln2_training_pattern = FIELD_GET(SCDC_LN_EVEN_TRAIN_PTRN, val_flag2);
+ flags->ln3_training_pattern = FIELD_GET(SCDC_LN_ODD_TRAIN_PTRN, val_flag2);
}
-static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[3])
+static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[4],
+ unsigned int num_lanes)
{
+ u8 end_reg;
u8 sum = 0;
int i;
- for (i = SCDC_ERR_DET_0_L; i <= SCDC_ERR_DET_CHECKSUM ; i++)
+ switch (num_lanes) {
+ case 3:
+ end_reg = SCDC_ERR_DET_CHECKSUM;
+ break;
+ case 4:
+ end_reg = SCDC_ERR_DET_3_H;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ for (i = SCDC_ERR_DET_0_L; i <= end_reg; i++)
sum = wrapping_add(u8, sum, scdc[i]);
if (sum)
@@ -308,6 +368,12 @@ static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[3])
counter[i] = 0;
}
+ if (num_lanes == 4 && scdc[SCDC_ERR_DET_3_H] & SCDC_CHANNEL_VALID)
+ counter[3] = (scdc[SCDC_ERR_DET_3_H] & ~SCDC_CHANNEL_VALID) << 8 |
+ scdc[SCDC_ERR_DET_3_L];
+ else
+ counter[3] = 0;
+
return 0;
}
@@ -325,6 +391,7 @@ int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state *
struct i2c_adapter *ddc;
struct drm_scdc *scdc;
u8 *buf = state->scdc;
+ int num_lanes;
int ret;
if (!state || !connector)
@@ -350,11 +417,26 @@ int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state *
state->scrambling_detected = buf[SCDC_SCRAMBLER_STATUS] & SCDC_SCRAMBLING_STATUS;
+ state->rate = FIELD_GET(SCDC_FRL_RATE, buf[SCDC_CONFIG_1]);
+ num_lanes = drm_scdc_num_frl_lanes(state->rate);
+ if (num_lanes < 0)
+ return num_lanes;
+ if (!num_lanes)
+ num_lanes = 3;
+
+ state->ffe_levels = FIELD_GET(SCDC_FFE_LEVELS, buf[SCDC_CONFIG_1]);
+
drm_scdc_parse_status0_flags(buf[SCDC_STATUS_FLAGS_0], &state->stf);
- ret = drm_scdc_parse_error_counters(buf, state->error_count);
+ drm_scdc_parse_status1_2_flags(buf[SCDC_STATUS_FLAGS_1],
+ buf[SCDC_STATUS_FLAGS_2], &state->stf);
+ ret = drm_scdc_parse_error_counters(buf, state->error_count, num_lanes);
if (ret)
return ret;
+ if (num_lanes == 4 && (buf[SCDC_ERR_DET_RS_H] & SCDC_CHANNEL_VALID))
+ state->rs_corrections = (buf[SCDC_ERR_DET_RS_H] & ~SCDC_CHANNEL_VALID) << 8 |
+ buf[SCDC_ERR_DET_RS_L];
+
return 0;
}
EXPORT_SYMBOL(drm_scdc_read_state);
@@ -416,6 +498,8 @@ static int scdc_status_show(struct seq_file *m, void *data)
scdc_print_flag(m, "Scrambling Enabled", st->scrambling_enabled);
scdc_print_flag(m, "Scrambling Detected", st->scrambling_detected);
+ scdc_print_str(m, "FRL Rate", drm_scdc_frl_rate_str(st->rate));
+ scdc_print_dec(m, "FFE Levels", st->ffe_levels);
if (st->tmds_bclk_x40)
scdc_print_str(m, "TMDS Bit Clock Ratio", "1/40");
@@ -426,10 +510,19 @@ static int scdc_status_show(struct seq_file *m, void *data)
scdc_print_flag(m, "Channel 0 Locked", st->stf.ch0_locked);
scdc_print_flag(m, "Channel 1 Locked", st->stf.ch1_locked);
scdc_print_flag(m, "Channel 2 Locked", st->stf.ch2_locked);
+ if (drm_scdc_num_frl_lanes(st->rate) == 4)
+ scdc_print_flag(m, "Lane 3 Locked", st->stf.ln3_locked);
+
+ scdc_print_flag(m, "Sink Ready For Link Training", st->stf.flt_ready);
+ scdc_print_flag(m, "Sink Failed To Decode DSC", st->stf.dsc_fail);
scdc_print_dec(m, "Channel 0 Errors", st->error_count[0]);
scdc_print_dec(m, "Channel 1 Errors", st->error_count[1]);
scdc_print_dec(m, "Channel 2 Errors", st->error_count[2]);
+ if (drm_scdc_num_frl_lanes(st->rate) == 4) {
+ scdc_print_dec(m, "Lane 3 Errors", st->error_count[3]);
+ scdc_print_dec(m, "Reed-Solomon Corrections", st->rs_corrections);
+ }
kfree(st);
@@ -29,6 +29,8 @@
#define SCDC_SOURCE_VERSION 0x02
#define SCDC_UPDATE_0 0x10
+#define SCDC_RSED_UPDATE (1 << 6)
+#define SCDC_FLT_UPDATE (1 << 5)
#define SCDC_READ_REQUEST_TEST (1 << 2)
#define SCDC_CED_UPDATE (1 << 1)
#define SCDC_STATUS_UPDATE (1 << 0)
@@ -46,14 +48,25 @@
#define SCDC_CONFIG_0 0x30
#define SCDC_READ_REQUEST_ENABLE (1 << 0)
+#define SCDC_CONFIG_1 0x31
+#define SCDC_FRL_RATE 0x0f
+#define SCDC_FFE_LEVELS 0xf0
+
#define SCDC_STATUS_FLAGS_0 0x40
+#define SCDC_DSC_FAIL (1 << 7)
+#define SCDC_FLT_READY (1 << 6)
+#define SCDC_LN3_LOCK (1 << 4)
#define SCDC_CH2_LOCK (1 << 3)
#define SCDC_CH1_LOCK (1 << 2)
#define SCDC_CH0_LOCK (1 << 1)
-#define SCDC_CH_LOCK_MASK (SCDC_CH2_LOCK | SCDC_CH1_LOCK | SCDC_CH0_LOCK)
+#define SCDC_CH_LOCK_MASK (SCDC_LN3_LOCK | SCDC_CH2_LOCK | SCDC_CH1_LOCK | \
+ SCDC_CH0_LOCK)
#define SCDC_CLOCK_DETECT (1 << 0)
#define SCDC_STATUS_FLAGS_1 0x41
+#define SCDC_LN_EVEN_TRAIN_PTRN 0x0f
+#define SCDC_LN_ODD_TRAIN_PTRN 0xf0
+#define SCDC_STATUS_FLAGS_2 0x42
#define SCDC_ERR_DET_0_L 0x50
#define SCDC_ERR_DET_0_H 0x51
@@ -65,6 +78,12 @@
#define SCDC_ERR_DET_CHECKSUM 0x56
+#define SCDC_ERR_DET_3_L 0x57
+#define SCDC_ERR_DET_3_H 0x58
+
+#define SCDC_ERR_DET_RS_L 0x59
+#define SCDC_ERR_DET_RS_H 0x5a
+
#define SCDC_TEST_CONFIG_0 0xc0
#define SCDC_TEST_READ_REQUEST (1 << 7)
#define SCDC_TEST_READ_REQUEST_DELAY(x) ((x) & 0x7f)
@@ -24,6 +24,7 @@
#ifndef DRM_SCDC_HELPER_H
#define DRM_SCDC_HELPER_H
+#include <linux/errno.h>
#include <linux/types.h>
#include <drm/display/drm_scdc.h>
@@ -38,8 +39,65 @@ struct drm_scdc_status_flags {
bool ch0_locked;
bool ch1_locked;
bool ch2_locked;
+ bool ln3_locked;
+ bool flt_ready;
+ bool dsc_fail;
+
+ /* Status Register 1 */
+ u8 ln0_training_pattern : 4;
+ u8 ln1_training_pattern : 4;
+
+ /* Status Register 2 */
+ u8 ln2_training_pattern : 4;
+ u8 ln3_training_pattern : 4;
+};
+
+enum drm_scdc_frl_rate {
+ SCDC_FRL_RATE_OFF = 0,
+ SCDC_FRL_RATE_3X3 = 1,
+ SCDC_FRL_RATE_6X3 = 2,
+ SCDC_FRL_RATE_6X4 = 3,
+ SCDC_FRL_RATE_8X4 = 4,
+ SCDC_FRL_RATE_10X4 = 5,
+ SCDC_FRL_RATE_12X4 = 6,
+ SCDC_FRL_RATE_RESV_7 = 7,
+ SCDC_FRL_RATE_RESV_8 = 8,
+ SCDC_FRL_RATE_RESV_9 = 9,
+ SCDC_FRL_RATE_RESV_10 = 10,
+ SCDC_FRL_RATE_RESV_11 = 11,
+ SCDC_FRL_RATE_RESV_12 = 12,
+ SCDC_FRL_RATE_RESV_13 = 13,
+ SCDC_FRL_RATE_RESV_14 = 14,
+ SCDC_FRL_RATE_RESV_15 = 15
};
+/**
+ * drm_scdc_num_frl_lanes - get number of lanes for a given FRL rate
+ * @rate: one of &enum drm_scdc_frl_rate
+ *
+ * For a given @rate, return the number of lanes it uses.
+ *
+ * Returns: %-EINVAL if @rate is not a valid FRL rate, or the number of lanes
+ * for a given &enum drm_scdc_frl_rate on success (including %0 for "off")
+ */
+static inline __pure int drm_scdc_num_frl_lanes(enum drm_scdc_frl_rate rate)
+{
+ switch (rate) {
+ case SCDC_FRL_RATE_OFF:
+ return 0;
+ case SCDC_FRL_RATE_3X3:
+ case SCDC_FRL_RATE_6X3:
+ return 3;
+ case SCDC_FRL_RATE_6X4:
+ case SCDC_FRL_RATE_8X4:
+ case SCDC_FRL_RATE_10X4:
+ case SCDC_FRL_RATE_12X4:
+ return 4;
+ default:
+ return -EINVAL;
+ }
+}
+
struct drm_scdc_state {
/** @stf: contents of the status flag registers */
struct drm_scdc_status_flags stf;
@@ -52,9 +110,14 @@ struct drm_scdc_state {
* clock period, false if it's 1/10th of the clock period.
*/
bool tmds_bclk_x40;
- /** @error_count: character error counts for each channel */
- u16 error_count[3];
-
+ /** @rate: FRL rate set by the source */
+ enum drm_scdc_frl_rate rate : 4;
+ /** @ffe_levels: The FFE levels for @rate set by the source */
+ u8 ffe_levels : 4;
+ /** @error_count: character error counts for each channel/link */
+ u16 error_count[4];
+ /** @rs_corrections: number of Reed-Solomon Corrections */
+ u16 rs_corrections;
/** @scdc: raw SCDC data buffer */
u8 scdc[256];
};