This is an automatic generated email to let you know that the following patch were queued:
Subject: edid-decode: add options to read EDID and HDCP data directly from DDC Author: Hans Verkuil <hverkuil-ci...@xs4all.nl> Date: Fri Aug 16 12:40:54 2024 +0200 Add options to read the EDID and HDCP data directly from the DDC bus, provided it is exposed as a /dev/i2c-X device. Signed-off-by: Hans Verkuil <hverkuil-ci...@xs4all.nl> ddc.cpp | 346 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ edid-decode.1 | 23 ++++ edid-decode.cpp | 43 ++++++- edid-decode.h | 5 + meson.build | 1 + 5 files changed, 416 insertions(+), 2 deletions(-) --- diff --git a/ddc.cpp b/ddc.cpp new file mode 100644 index 000000000000..939e968f9b2c --- /dev/null +++ b/ddc.cpp @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Author: Hans Verkuil <hverkuil-ci...@xs4all.nl> + */ + +#include <cctype> +#include <cerrno> +#include <csignal> +#include <cstring> +#include <ctime> +#include <string> + +#include <fcntl.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <sys/epoll.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> +#include <math.h> +#include <dirent.h> + +#include <linux/i2c-dev.h> +#include <linux/i2c.h> +#include <linux/types.h> + +#include "edid-decode.h" + +// i2c addresses for edid +#define EDID_ADDR 0x50 +#define SEGMENT_POINTER_ADDR 0x30 + +// i2c address for SCDC +#define SCDC_ADDR 0x54 + +// i2c addresses for HDCP +#define HDCP_PRIM_ADDR 0x3a +#define HDCP_SEC_ADDR 0x3b + +int request_i2c_adapter(unsigned adapnr) +{ + std::string i2c_adapter = "/dev/i2c-" + std::to_string(adapnr); + int fd = open(i2c_adapter.c_str(), O_RDWR); + + if (fd >= 0) + return fd; + fprintf(stderr, "Error accessing i2c adapter %s\n", i2c_adapter.c_str()); + return fd; +} + +static int read_edid_block(int adapter_fd, __u8 *edid, + uint8_t segment, uint8_t offset, uint8_t blocks) +{ + struct i2c_rdwr_ioctl_data data; + struct i2c_msg write_message; + struct i2c_msg read_message; + struct i2c_msg seg_message; + int err; + + seg_message = { + .addr = SEGMENT_POINTER_ADDR, + .len = 1, + .buf = &segment + }; + write_message = { + .addr = EDID_ADDR, + .len = 1, + .buf = &offset + }; + read_message = { + .addr = EDID_ADDR, + .flags = I2C_M_RD, + .len = (__u16)(blocks * EDID_PAGE_SIZE), + .buf = edid + }; + + if (segment) { + struct i2c_msg msgs[2] = { seg_message, read_message }; + + data.msgs = msgs; + data.nmsgs = ARRAY_SIZE(msgs); + err = ioctl(adapter_fd, I2C_RDWR, &data); + } else { + struct i2c_msg msgs[2] = { write_message, read_message }; + + data.msgs = msgs; + data.nmsgs = ARRAY_SIZE(msgs); + err = ioctl(adapter_fd, I2C_RDWR, &data); + } + + if (err < 0) { + fprintf(stderr, "Unable to read edid: %s\n", strerror(errno)); + return err; + } + return 0; +} + +int read_edid(int adapter_fd, unsigned char *edid) +{ + unsigned n_extension_blocks; + int err; + + err = read_edid_block(adapter_fd, edid, 0, 0, 2); + if (err) + return err; + n_extension_blocks = edid[126]; + if (!n_extension_blocks) + return 1; + for (unsigned i = 2; i <= n_extension_blocks; i += 2) { + err = read_edid_block(adapter_fd, edid + i * 128, i / 2, 0, + (i + 1 > n_extension_blocks ? 1 : 2)); + if (err) + return err; + } + return n_extension_blocks + 1; +} + +static int read_hdcp_registers(int adapter_fd, __u8 *hdcp_prim, __u8 *hdcp_sec, __u8 *ksv_fifo) +{ + struct i2c_rdwr_ioctl_data data; + struct i2c_msg write_message; + struct i2c_msg read_message; + __u8 offset = 0; + __u8 ksv_fifo_offset = 0x43; + int err; + + write_message = { + .addr = HDCP_PRIM_ADDR, + .len = 1, + .buf = &offset + }; + read_message = { + .addr = HDCP_PRIM_ADDR, + .flags = I2C_M_RD, + .len = 256, + .buf = hdcp_prim + }; + + struct i2c_msg msgs[2] = { write_message, read_message }; + + data.msgs = msgs; + data.nmsgs = ARRAY_SIZE(msgs); + err = ioctl(adapter_fd, I2C_RDWR, &data); + + if (err < 0) { + fprintf(stderr, "Unable to read Primary Link HDCP: %s\n", + strerror(errno)); + return -1; + } + + write_message = { + .addr = HDCP_PRIM_ADDR, + .len = 1, + .buf = &ksv_fifo_offset + }; + read_message = { + .addr = HDCP_PRIM_ADDR, + .flags = I2C_M_RD, + .len = (__u16)((hdcp_prim[0x41] & 0x7f) * 5), + .buf = ksv_fifo + }; + + if (read_message.len) { + struct i2c_msg ksv_fifo_msgs[2] = { write_message, read_message }; + + data.msgs = ksv_fifo_msgs; + data.nmsgs = ARRAY_SIZE(msgs); + err = ioctl(adapter_fd, I2C_RDWR, &data); + if (err < 0) { + fprintf(stderr, "Unable to read KSV FIFO: %s\n", + strerror(errno)); + return -1; + } + } + + write_message = { + .addr = HDCP_SEC_ADDR, + .len = 1, + .buf = &offset + }; + read_message = { + .addr = HDCP_SEC_ADDR, + .flags = I2C_M_RD, + .len = 256, + .buf = hdcp_sec + }; + + struct i2c_msg sec_msgs[2] = { write_message, read_message }; + data.msgs = sec_msgs; + data.nmsgs = ARRAY_SIZE(msgs); + ioctl(adapter_fd, I2C_RDWR, &data); + + return 0; +} + +int read_hdcp(int adapter_fd) +{ + __u8 hdcp_prim[256]; + __u8 hdcp_sec[256]; + __u8 ksv_fifo[128 * 5]; + + hdcp_prim[5] = 0xdd; + hdcp_sec[5] = 0xdd; + if (read_hdcp_registers(adapter_fd, hdcp_prim, hdcp_sec, ksv_fifo)) + return -1; + printf("HDCP Primary Link Hex Dump:\n\n"); + hex_block("", hdcp_prim, 128, false); + printf("\n"); + hex_block("", hdcp_prim + 128, 128, false); + printf("\n"); + if (hdcp_sec[5] != 0xdd) { + printf("HDCP Secondary Link Hex Dump:\n\n"); + hex_block("", hdcp_sec, 128, false); + printf("\n"); + hex_block("", hdcp_sec + 128, 128, false); + printf("\n"); + } + printf("HDCP Primary Link:\n\n"); + printf("Bksv: %02x %02x %02x %02x %02x\n", + hdcp_prim[0], hdcp_prim[1], hdcp_prim[2], hdcp_prim[3], hdcp_prim[4]); + printf("Ri': %02x %02x\n", hdcp_prim[9], hdcp_prim[8]); + printf("Pj': %02x\n", hdcp_prim[0x0a]); + printf("Aksv: %02x %02x %02x %02x %02x\n", + hdcp_prim[0x10], hdcp_prim[0x11], hdcp_prim[0x12], hdcp_prim[0x13], hdcp_prim[0x14]); + printf("Ainfo: %02x\n", hdcp_prim[0x15]); + printf("An: %02x %02x %02x %02x %02x %02x %02x %02x\n", + hdcp_prim[0x18], hdcp_prim[0x19], hdcp_prim[0x1a], hdcp_prim[0x1b], + hdcp_prim[0x1c], hdcp_prim[0x1d], hdcp_prim[0x1e], hdcp_prim[0x1f]); + printf("V'.H0: %02x %02x %02x %02x\n", + hdcp_prim[0x20], hdcp_prim[0x21], hdcp_prim[0x22], hdcp_prim[0x23]); + printf("V'.H1: %02x %02x %02x %02x\n", + hdcp_prim[0x24], hdcp_prim[0x25], hdcp_prim[0x26], hdcp_prim[0x27]); + printf("V'.H2: %02x %02x %02x %02x\n", + hdcp_prim[0x28], hdcp_prim[0x29], hdcp_prim[0x2a], hdcp_prim[0x2b]); + printf("V'.H3: %02x %02x %02x %02x\n", + hdcp_prim[0x2c], hdcp_prim[0x2d], hdcp_prim[0x2e], hdcp_prim[0x2f]); + printf("V'.H4: %02x %02x %02x %02x\n", + hdcp_prim[0x30], hdcp_prim[0x31], hdcp_prim[0x32], hdcp_prim[0x33]); + __u8 v = hdcp_prim[0x40]; + printf("Bcaps: %02x\n", v); + if (v & 0x01) + printf("\tFAST_REAUTHENTICATION\n"); + if (v & 0x02) + printf("\t1.1_FEATURES\n"); + if (v & 0x10) + printf("\tFAST\n"); + if (v & 0x20) + printf("\tREADY\n"); + if (v & 0x40) + printf("\tREPEATER\n"); + __u16 vv = hdcp_prim[0x41] | (hdcp_prim[0x42] << 8); + printf("Bstatus: %04x\n", vv); + printf("\tDEVICE_COUNT: %u\n", vv & 0x7f); + if (vv & 0x80) + printf("\tMAX_DEVS_EXCEEDED\n"); + printf("\tDEPTH: %u\n", (vv >> 8) & 0x07); + if (vv & 0x800) + printf("\tMAX_CASCADE_EXCEEDED\n"); + if (vv & 0x1000) + printf("\tHDMI_MODE\n"); + if (vv & 0x7f) { + printf("KSV FIFO:\n"); + for (unsigned i = 0; i < (vv & 0x7f); i++) + printf("\t%03u: %02x %02x %02x %02x %02x\n", i, + ksv_fifo[i * 5], ksv_fifo[i * 5 + 1], + ksv_fifo[i * 5 + 2], ksv_fifo[i * 5 + 3], + ksv_fifo[i * 5 + 4]); + } + v = hdcp_prim[0x50]; + printf("HDCP2Version: %02x\n", v); + if (v & 4) + printf("\tHDCP2.2\n"); + vv = hdcp_prim[0x70] | (hdcp_prim[0x71] << 8); + printf("RxStatus: %04x\n", vv); + if (vv & 0x3ff) + printf("\tMessage_Size: %u\n", vv & 0x3ff); + if (vv & 0x400) + printf("\tREADY\n"); + if (vv & 0x800) + printf("\tREAUTH_REQ\n"); + + if (hdcp_sec[5] == 0xdd) + return 0; + + printf("HDCP Secondary Link:\n\n"); + printf("Bksv: %02x %02x %02x %02x %02x\n", + hdcp_sec[0], hdcp_sec[1], hdcp_sec[2], hdcp_sec[3], hdcp_sec[4]); + printf("Ri': %02x %02x\n", hdcp_sec[9], hdcp_sec[9]); + printf("Pj': %02x\n", hdcp_sec[0x0a]); + printf("Aksv: %02x %02x %02x %02x %02x\n", + hdcp_sec[0x10], hdcp_sec[0x11], hdcp_sec[0x12], hdcp_sec[0x13], hdcp_sec[0x14]); + printf("Ainfo: %02x\n", hdcp_sec[0x15]); + printf("An: %02x %02x %02x %02x %02x %02x %02x %02x\n", + hdcp_sec[0x18], hdcp_sec[0x19], hdcp_sec[0x1a], hdcp_sec[0x1b], + hdcp_sec[0x1c], hdcp_sec[0x1d], hdcp_sec[0x1e], hdcp_sec[0x1f]); + return 0; +} + +static int read_hdcp_ri_register(int adapter_fd, __u16 *v) +{ + struct i2c_rdwr_ioctl_data data; + struct i2c_msg write_message; + struct i2c_msg read_message; + __u8 ri[2]; + __u8 offset = 8; + int err; + + write_message = { + .addr = HDCP_PRIM_ADDR, + .len = 1, + .buf = &offset + }; + read_message = { + .addr = HDCP_PRIM_ADDR, + .flags = I2C_M_RD, + .len = 2, + .buf = ri + }; + + struct i2c_msg msgs[2] = { write_message, read_message }; + + data.msgs = msgs; + data.nmsgs = ARRAY_SIZE(msgs); + err = ioctl(adapter_fd, I2C_RDWR, &data); + + if (err < 0) + fprintf(stderr, "Unable to read Ri: %s\n", strerror(errno)); + else + *v = ri[1] << 8 | ri[0]; + + return err < 0 ? err : 0; +} + +int read_hdcp_ri(int adapter_fd, double ri_time) +{ + __u16 ri; + + while (1) { + if (!read_hdcp_ri_register(adapter_fd, &ri)) + printf("Ri': %04x\n", ri); + usleep(ri_time * 1000000); + } + return 0; +} diff --git a/edid-decode.1 b/edid-decode.1 index 0620cd54be3a..f0513cadaace 100644 --- a/edid-decode.1 +++ b/edid-decode.1 @@ -133,6 +133,10 @@ HDMI 1.4b: High-Definition Multimedia Interface, Version 1.4b .TP HDMI 2.1b: High-Definition Multimedia Interface, Version 2.1b .TP +HDCP 1.4: High-bandwidth Digital Content Protection System, Revision 1.4 +.TP +HDCP 2.3: High-bandwidth Digital Content Protection System, Mapping HDCP to HDMI, Revision 2.3 +.TP CTA-861-I: A DTV Profile for Uncompressed High Speed Digital Interfaces .TP CTA-861.7: Improvements to CTA-861-I @@ -287,6 +291,25 @@ The 'Made in' date appears in the Base Block. \fB\-\-version\fR Show the SHA hash and the last commit date. +.SH I2C/DDC OPTIONS +The following options read the DDC bus directly, provided the DDC bus is +exposed by linux to \fB/dev/i2c-\fR\fIX\fR as an i2c adapter device. + +This can be used to read the EDID and HDCP information directly from +the sink and parse it. +.TP +\fB\-a\fR, \fB\-\-i2c\-adapter\fR \fI<num>\fR +Use this \fB/dev/i2c-\fR\fI<num>\fR device. +.TP +\fB\-\-i2c\-edid\fR +Read and parse the EDID from the i2c adapter. +.TP +\fB\-\-i2c\-hdcp\fR +Read and parse the HDCP data from the i2c adapter. +.TP +\fB\-\-i2c\-hdcp-ri\fR \fI<t>\fR +Every \fI<t>\fR seconds read and report the HDCP Ri value from the i2c adapter. + .SH TIMING OPTIONS The following options report the timings for DMT, VIC and HDMI VIC codes and calculate the timings for CVT or GTF timings, based on the given parameters. diff --git a/edid-decode.cpp b/edid-decode.cpp index 7b9069d21eb5..b6ff5c790e8d 100644 --- a/edid-decode.cpp +++ b/edid-decode.cpp @@ -40,6 +40,7 @@ enum output_format { * That makes it easier to see which options are still free. */ enum Option { + OptI2CAdapter = 'a', OptCheck = 'c', OptCheckInline = 'C', OptFBModeTimings = 'F', @@ -62,6 +63,9 @@ enum Option { OptReplaceUniqueIDs, OptVersion, OptDiag, + OptI2CEDID, + OptI2CHDCP, + OptI2CHDCPRi, OptSTD, OptDMT, OptVIC, @@ -103,6 +107,10 @@ static struct option long_options[] = { { "fbmode", no_argument, 0, OptFBModeTimings }, { "v4l2-timings", no_argument, 0, OptV4L2Timings }, { "diagonal", required_argument, 0, OptDiag }, + { "i2c-adapter", required_argument, 0, OptI2CAdapter }, + { "i2c-edid", no_argument, 0, OptI2CEDID }, + { "i2c-hdcp", no_argument, 0, OptI2CHDCP }, + { "i2c-hdcp-ri", required_argument, 0, OptI2CHDCPRi }, { "std", required_argument, 0, OptSTD }, { "dmt", required_argument, 0, OptDMT }, { "vic", required_argument, 0, OptVIC }, @@ -156,6 +164,10 @@ static void usage(void) " -u, --utf8 Convert strings in EDIDs to UTF-8.\n" " --version Show the edid-decode version (SHA).\n" " --diagonal <inches> Set the display's diagonal in inches.\n" + " -a, --i2c-adapter <num> Use /dev/i2c-<num> to access the DDC lines.\n" + " --i2c-edid Read the EDID from the DDC lines.\n" + " --i2c-hdcp Read the HDCP from the DDC lines.\n" + " --i2c-hdcp-ri=<t> Read and print the HDCP Ri information every <t> seconds.\n" " --std <byte1>,<byte2> Show the standard timing represented by these two bytes.\n" " --dmt <dmt> Show the timings for the DMT with the given DMT ID.\n" " --vic <vic> Show the timings for this VIC.\n" @@ -2270,6 +2282,8 @@ int main(int argc, char **argv) enum output_format out_fmt = OUT_FMT_DEFAULT; gtf_parsed_data gtf_data; unsigned list_rid = 0; + int adapter_fd = -1; + double hdcp_ri_sleep = 0; std::vector<std::string> if_names; int ret; @@ -2315,6 +2329,17 @@ int main(int argc, char **argv) case OptDiag: state.diagonal = strtod(optarg, NULL); break; + case OptI2CAdapter: { + unsigned num = strtoul(optarg, NULL, 0); + + adapter_fd = request_i2c_adapter(num); + if (adapter_fd < 0) + exit(1); + break; + } + case OptI2CHDCPRi: + hdcp_ri_sleep = strtod(optarg, NULL); + break; case OptSTD: { unsigned char byte1, byte2 = 0; char *endptr; @@ -2418,10 +2443,24 @@ int main(int argc, char **argv) } if (optind == argc) { - if (options[OptInfoFrame] && !options[OptGTF]) + if (adapter_fd >= 0 && options[OptI2CEDID]) { + ret = read_edid(adapter_fd, edid); + if (ret > 0) { + state.edid_size = ret * EDID_PAGE_SIZE; + state.num_blocks = ret; + ret = 0; + } + } else if (adapter_fd >= 0) { + if (options[OptI2CHDCP]) + read_hdcp(adapter_fd); + if (options[OptI2CHDCPRi]) + read_hdcp_ri(adapter_fd, hdcp_ri_sleep); ret = 0; - else + } else if (options[OptInfoFrame] && !options[OptGTF]) { + ret = 0; + } else { ret = edid_from_file("-", stdout); + } } else { ret = edid_from_file(argv[optind], argv[optind + 1] ? stderr : stdout); } diff --git a/edid-decode.h b/edid-decode.h index bda212d6f934..c62650e1f89f 100644 --- a/edid-decode.h +++ b/edid-decode.h @@ -615,4 +615,9 @@ char *extract_string(const unsigned char *x, unsigned len, bool is_cp437); #define oneoui(c,k,n) const unsigned kOUI_##k = __LINE__<<12; #include "oui.h" +int request_i2c_adapter(unsigned adapnr); +int read_edid(int adapter_fd, unsigned char *edid); +int read_hdcp(int adapter_fd); +int read_hdcp_ri(int adapter_fd, double ri_time); + #endif diff --git a/meson.build b/meson.build index e18a7f29cc02..513ba93806e3 100644 --- a/meson.build +++ b/meson.build @@ -50,6 +50,7 @@ endif edid_decode_sources = [ 'calc-gtf-cvt.cpp', 'calc-ovt.cpp', + 'ddc.cpp', 'edid-decode.cpp', 'parse-base-block.cpp', 'parse-cta-block.cpp',