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',

Reply via email to