diff mbox

[i-g-t] igt: Add intel_mst_decode utility

Message ID 1461365834-15221-1-git-send-email-jim.bride@linux.intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

jim.bride@linux.intel.com April 22, 2016, 10:57 p.m. UTC
The intel_mst_decode utility parses the i915_dp_mst_info debugfs node
(either in-place or as a captured file via a command-line argument) and
prints information about the MST topology in an easy to read, hierarchical
format.  If a file is not specified on the command-line then the utility
will read directly from debugfs, and in this case root permissions are both
required and checked for.

cc: Jani Nikula <jani.nikula@intel.com>
Signed-off-by: Jim Bride <jim.bride@linux.intel.com>
---
 tools/Makefile.sources   |   3 +
 tools/intel_mst_decode.c | 528 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 531 insertions(+)
 create mode 100644 tools/intel_mst_decode.c

Comments

Marius Vlad May 14, 2016, 3:50 p.m. UTC | #1
Could you also add a man page for it?

On Fri, Apr 22, 2016 at 03:57:14PM -0700, Jim Bride wrote:
> The intel_mst_decode utility parses the i915_dp_mst_info debugfs node
> (either in-place or as a captured file via a command-line argument) and
> prints information about the MST topology in an easy to read, hierarchical
> format.  If a file is not specified on the command-line then the utility
> will read directly from debugfs, and in this case root permissions are both
> required and checked for.
> 
> cc: Jani Nikula <jani.nikula@intel.com>
> Signed-off-by: Jim Bride <jim.bride@linux.intel.com>
> ---
>  tools/Makefile.sources   |   3 +
>  tools/intel_mst_decode.c | 528 +++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 531 insertions(+)
>  create mode 100644 tools/intel_mst_decode.c
> 
> diff --git a/tools/Makefile.sources b/tools/Makefile.sources
> index 5d5958d..19f385f 100644
> --- a/tools/Makefile.sources
> +++ b/tools/Makefile.sources
> @@ -25,6 +25,7 @@ bin_PROGRAMS = 				\
>  	intel_infoframes		\
>  	intel_l3_parity			\
>  	intel_lid			\
> +	intel_mst_decode		\
>  	intel_opregion_decode		\
>  	intel_panel_fitter		\
>  	intel_perf_counters		\
> @@ -57,3 +58,5 @@ intel_l3_parity_SOURCES =	\
>  	intel_l3_parity.h	\
>  	intel_l3_udev_listener.c
>  
> +intel_mst_decode_SOURCES =	\
> +	intel_mst_decode.c
> diff --git a/tools/intel_mst_decode.c b/tools/intel_mst_decode.c
> new file mode 100644
> index 0000000..f9dfd2d
> --- /dev/null
> +++ b/tools/intel_mst_decode.c
> @@ -0,0 +1,528 @@
> +/*
> + * i915 DisplayPort MST Topology Utility
> + * Print DP MST topology information for i915
> + *
> + * Copyright © 2016 Intel Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> + * and/or sell copies of the Software, and to permit persons to whom the
> + * Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice (including the next
> + * paragraph) shall be included in all copies or substantial portions of the
> + * Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> + * IN THE SOFTWARE.
> + *
> + * Authors:
> + *     Jim Bride <jim.bride@linux.intel.com>
> + *
> + */
> +#include "igt.h"
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <ctype.h>
> +#include <assert.h>
> +
> +/* Interesting constants and file locations */
> +#define MAX_TIMESLOTS 64
> +#define MAX_CHANNELS 3
> +#define MAX_PORTS 8
> +#define SINK_NAME_LEN 14
> +#define DPCD_RCV_CAP_SIZE 15
> +#define BRANCH_DEVID_SIZE 5
> +#define SW_REV_SIZE 8
> +#define HW_REV_SIZE SW_REV_SIZE
> +#define FAUX_MST_SIZE 2
> +#define TEXT_LABEL_SIZE 20
> +#define PREFIX_SIZE 6
> +#define LINE_BUFFER_SIZE 2048
> +#define INTEL_DP_DEBUGFS_ROOT "/sys/kernel/debug/dri/0/"
> +#define INTEL_DP_MST_INFO_FILE "i915_dp_mst_info"
> +
> +/* Command-line Parsing Stuff */
> +const char *optstring = "af:hpx";
> +static bool do_payload = false;
> +static bool do_extra = false;
> +static char *local_file = NULL;
> +
> +/* Data structures we populate from the MST info file. */
> +struct mst_port {
> +	bool is_valid;
> +	bool ddps;
> +	bool ldps;
> +	bool input;
> +	uint32_t peer_device_type;
> +	uint32_t num_sdp_streams;
> +	uint32_t num_sdp_connections;
> +};
> +
> +struct vcpi_elem {
> +	uint32_t state;
> +	uint32_t port;
> +	uint32_t start_slot;
> +	uint32_t num_slots;
> +	char sink_name[SINK_NAME_LEN];
> +};
> +
> +struct payload_data {
> +	struct vcpi_elem chan[MAX_CHANNELS]; // indexed by vcpi
> +	uint8_t pay_table[MAX_TIMESLOTS];
> +};
> +
> +struct primary_branch_info {
> +	uint8_t dpcd_recv_cap[DPCD_RCV_CAP_SIZE];
> +	uint8_t faux_mst[FAUX_MST_SIZE];
> +	uint8_t mst_ctrl;
> +	uint32_t branch_oui;
> +	char devid[BRANCH_DEVID_SIZE];
> +	char hw_rev[HW_REV_SIZE];
> +	char sw_rev[SW_REV_SIZE];
> +};
> +
> +struct mst_info {
> +	int src_port;
> +	struct mst_port port[MAX_PORTS];
> +	struct payload_data paydata;
> +	struct primary_branch_info pbi;
> +};
> +
> +/* Prototypes to keep the compiler happy */
> +
> +static FILE *open_mst_info(void);
> +static void close_mst_info(FILE *fp);
> +static void parse_port_line(char *read_buf, struct mst_info *msti);
> +static bool parse_mst_info(struct mst_info *msti);
> +static void dump_mst_info(struct mst_info *msti);
> +static void dump_extra(struct mst_info *msti);
> +static void parse_vc_info(struct mst_info *msti, char *read_buf);
> +static void parse_payload_info(struct mst_info *msti, char *read_buf);
> +static void parse_pbi_info(struct mst_info *msti, char *read_buf, FILE *fp);
> +static void parse_payload_table(struct mst_info *msti, char *read_buf);
> +
> +static FILE *open_mst_info(void)
> +{
> +	FILE *fp = NULL;
> +
> +	if (local_file) {
> +		/* open local file */
> +		fp = fopen(local_file, "r");
> +		if (fp == NULL) {
> +			fprintf(stderr, "Could not open local info file %s.\n",
> +				local_file);
> +		}
> +	} else {
> +		/* open sysfs file */
> +		fp = igt_debugfs_fopen(INTEL_DP_MST_INFO_FILE, "r");
> +		if (fp == NULL) {
> +			fprintf(stderr, "Could not open sysfs file %s%s.\n",
> +				INTEL_DP_DEBUGFS_ROOT, INTEL_DP_MST_INFO_FILE);
> +		}
> +	}
> +	return fp;
> +}
> +
> +static void close_mst_info(FILE *fp)
> +{
> +	if (!fp)
> +		return;
> +	fclose(fp);
> +}
> +
> +static inline void get_byte_val(char **buf, int sep, uint8_t *bytes, int count)
> +{
> +	int i;
> +
> +	*buf = strchr(*buf, sep);
> +	if (!*buf)
> +		return;
> +	*buf += 2; /* "<sep> " */
> +	for (i = 0; i < count; i++, *buf += 3) {
> +		sscanf(*buf, "%hhx", &(bytes[i]));
> +	}
> +
> +}
> +
> +static inline void get_int_val(char **buf, int sep, int *val)
> +{
> +	*buf = strchr(*buf, sep);
> +	*buf += 2; /* "<sep> " */
> +	sscanf(*buf, "%d", val);
> +}
> +
> +static void parse_port_line(char *read_buf, struct mst_info *msti)
> +{
> +
> +	/* port: %d: input: %d: pdt: %d, ddps: %d ldps: %d, sdp: %d/%d, %x,
> +	 * conn: %x || '(null)'
> +	 */
> +	char *idx = read_buf;
> +	int cur_port = -1, val = -1;
> +	struct mst_port *cp = NULL;
> +
> +	/* "port: %d: " */
> +	get_int_val(&idx, ':', &cur_port);
> +	idx += 2; /* ": " */
> +	assert((cur_port > -1) && (cur_port < MAX_PORTS));
> +
> +	cp = &(msti->port[cur_port]);
> +	/* "input: %d: " */
> +	get_int_val(&idx, ':', &val);
> +	cp->input = (val != 0) ? true : false;
> +	idx += 2; /* ": " */
> +
> +	/* "pdt: %d, " */
> +	get_int_val(&idx, ':', &val);
> +	cp->peer_device_type = val;
> +	idx += 2; /* ", " */
> +
> +	/* "ddps: %d ldps: %d, " */
> +	get_int_val(&idx, ':', &val);
> +	cp->ddps = val;
> +	get_int_val(&idx, ':', &val);
> +	cp->ldps = val;
> +	idx += 2; /* ", " */
> +
> +	/* "sdp: %d/%d, " */
> +	get_int_val(&idx, ':', &val);
> +	cp->num_sdp_streams = val;
> +	idx = strrchr(idx, '/');
> +	if (!idx)
> +		return;
> +	sscanf(++idx, "%d", &val);
> +	cp->num_sdp_connections = val;
> +	/* Skip rest of line for now */
> +
> +	cp->is_valid = ((cp->ddps != 0) || (cp->ldps != 0));
> +}
> +
> +static void parse_vc_info(struct mst_info *msti, char *read_buf)
> +{
> +	char *idx = read_buf;
> +	struct vcpi_elem *cvc;
> +	int val = -1;
> +	int vc_chan;
> +	char label[20];
> +
> +	/* "vcpi <index>" */
> +	sscanf(idx, "%s %d", label, &vc_chan);
> +
> +	idx = strchr(read_buf, ':');
> +	if (!idx)
> +		return;
> +	idx++; /* ':' */
> +	cvc = &(msti->paydata.chan[vc_chan]);
> +	if (strncmp(idx, "unused", 6) == 0)
> +		return;
> +
> +	idx++; /* ' ' */
> +	/* <port> <vcpi> <num_slots> "*/
> +	sscanf(idx, "%d %d %d", &(cvc->port), &val, &(cvc->num_slots));
> +
> +	/* "sink name: (<sink_name>" || "Unknown") */
> +	idx = strrchr(read_buf, ':');
> +	if (!idx)
> +		return;
> +	idx += 2; /* ": " */
> +	idx[strlen(idx) - 1] = '\0';
> +	idx = strcpy(&(cvc->sink_name[0]), idx);
Albeit redundant, but maybe strncpy()?

> +	return;
Not needed.
> +}
> +
> +static void parse_payload_info(struct mst_info *msti, char *read_buf)
> +{
> +	char *idx = read_buf;
> +	char label[TEXT_LABEL_SIZE];
> +	struct vcpi_elem *cvc;
> +	int payload;
> +
> +	/* "payload <index>: " */
> +	sscanf(idx, "%s %d", label, &payload);
> +	idx += 2; /* ": " */
> +	cvc = &(msti->paydata.chan[payload]);
> +	/* "<payload_state>, " */
> +	sscanf(idx, "%d", &(cvc->state));
> +	idx = strchr(idx, ',');
> +	idx += 2; /* ", " */
> +	/* "<start_slot>, " */
> +	sscanf(idx, "%d", &(cvc->start_slot));
> +	idx = strchr(idx, ',');
> +	idx += 2; /* ", " */
> +	/* "<num_slots>" */
> +	sscanf(idx, "%d", &(cvc->num_slots));
> +}
> +
> +static void parse_pbi_info(struct mst_info *msti, char *read_buf, FILE *fp)
> +{
> +	char *idx;
> +	char devid_label[TEXT_LABEL_SIZE];
> +	char rev_label[TEXT_LABEL_SIZE];
> +	char hw_label[TEXT_LABEL_SIZE];
> +	char sw_label[TEXT_LABEL_SIZE];
> +
> +	/* "dpcd: <15 bytes>" */
> +	idx = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +	if (!idx)
> +		return;
> +
> +	/* "dpcd: " */
> +	get_byte_val(&idx, ':', &(msti->pbi.dpcd_recv_cap[0]),
> +		     DPCD_RCV_CAP_SIZE);
> +
> +	/* "faux/mst: <two bytes>" */
> +	idx = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +	if (!idx)
> +		return;
> +	get_byte_val(&idx, ':', &(msti->pbi.faux_mst[0]), FAUX_MST_SIZE);
> +
> +	/* "mst ctrl: <byte>" */
> +	idx = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +	if (!idx)
> +		return;
> +	get_byte_val(&idx, ':', &(msti->pbi.mst_ctrl), 1);
> +
> +	/* "branch oui: <3 bytes> devid: <4 chars> "
> +	 * "revision: hw: 0xx.0xy sw: 0xx.0xy"
> +	 */
> +	idx = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +	if (!idx)
> +		return;
> +	idx = strchr(read_buf, ':');
> +	idx += 2; /* ": " */
> +	sscanf(idx, "%x %s %s %s %s %s %s %s", &(msti->pbi.branch_oui),
> +	       devid_label, msti->pbi.devid, rev_label, hw_label,
> +	       msti->pbi.hw_rev, sw_label, msti->pbi.sw_rev);
You might want to use '%*s' so you will not need all those arrays defined
locally. You have other instances of this.
> +}
> +
> +static void parse_payload_table(struct mst_info *msti, char *read_buf)
> +{
> +	char *idx = read_buf;
> +
> +	/* "payload table: <63 bytes>" */
> +	get_byte_val(&idx, ':', &(msti->paydata.pay_table[0]),
> +		     MAX_TIMESLOTS);
> +}
> +
> +static bool parse_mst_info(struct mst_info *msti)
> +{
> +	char read_buf[LINE_BUFFER_SIZE];
> +	char *s;
> +	int pay_mask, vcpi_mask, total_vc;
> +	int i;
> +	FILE *fp = open_mst_info();;
> +	bool bret = false;
> +
> +	if (!fp)
> +		return false;
> +
> +	memset(read_buf, 0, LINE_BUFFER_SIZE);
> +	memset(msti, 0, sizeof(struct mst_info));
> +
> +	/* Read which port we're gathering information about */
> +	s =  fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +	if (!s)
> +		goto out;
> +
> +	msti->src_port = tolower(read_buf[strlen(read_buf) - 2]) - 'a';
Why '-2'? -- just add a comment with the format so its clear...
> +
> +	/* Strip off and ignore pointer info about the main MST object */
> +	s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +	if (!s)
> +		goto out;
> +
> +	/*
> +	 * First parse out the port-related information.  Remember that
> +	 * these will be the ports from the perspective of the primary
> +	 * MST bridge device, and that they may or may not have anything
> +	 * connected to them.
> +	 */
> +	s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +	if (!s)
> +		goto out;
> +	/* parse the port lines until we get the first VCPI entry */
> +	while (strncmp(read_buf, "vcpi", 4) != 0) {
> +		parse_port_line(read_buf, msti);
> +		s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +		if (!s)
> +			goto out;
> +	}
> +
> +	/* We have the first line of VCPI info when we get to this point.
> +	 * This line tells us the mask to identify payloads, the mask to
> +	 * identify virtual channels, and the maximum allowed number of
> +	 * virtual channels.
> +	 */
> +	/* "vcpi: <payload_mask> <vcpi_mask>" <max_payloads> */
> +	s = strchr(read_buf, ':');
> +	if (!s)
> +		goto out;
> +	s += 2; /* strip ": " */
> +	sscanf(s, "%d %d %d", &pay_mask, &vcpi_mask, &total_vc);
> +	for (i = 0; i < total_vc; i++) {
> +		s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +		if (!s)
> +			goto out;
> +		parse_vc_info(msti, read_buf);
> +	}
> +
> +	for (i = 0; i < total_vc; i++) {
> +		s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +		if (!s)
> +			goto out;
> +		parse_payload_info(msti, read_buf);
> +	}
> +
> +	parse_pbi_info(msti, read_buf, fp);
> +
> +	s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
> +	if (s) {
> +		parse_payload_table(msti, read_buf);
> +		bret = true;
> +	}
> +
> +out:
> +	close_mst_info(fp);
> +	return bret;
> +}
> +
> +static void dump_extra(struct mst_info *msti)
> +{
> +	int i;
> +
> +	if (do_extra) {
> +		printf("Primary Branch Info: \n");
> +		printf("\tDPCD Receiver Caps: ");
> +		for (i = 0; i < DPCD_RCV_CAP_SIZE; i++)
> +			printf("%02hhx ", msti->pbi.dpcd_recv_cap[i]);
> +		printf("\n");
> +		printf("\tFaux MST: %02hhx %02hhx\n", msti->pbi.faux_mst[0],
> +		       msti->pbi.faux_mst[1]);
> +		printf("\tMST CTRL: 0x%02hhx\n", msti->pbi.mst_ctrl);
> +		printf("\tBranch HW Version: %s SW Version: %s\n",
> +		       msti->pbi.hw_rev, msti->pbi.sw_rev);
> +		printf("\n");
> +	}
> +
> +	if (do_payload) {
> +		printf("Payload Table: ");
> +		for (i = 0; i < MAX_TIMESLOTS; i++) {
> +			if (i % 8 == 0)
> +				printf("\n\t");
> +			printf("%02hhx ", msti->paydata.pay_table[i]);
> +		}
> +		printf("\n");
> +	}
> +}
> +
> +static void dump_mst_info(struct mst_info *msti)
> +{
> +	int i, j;
> +	int numBranches = 0;
> +	char prefix[PREFIX_SIZE];
> +
> +	memset(prefix, 0, sizeof(prefix));
> +	prefix[0] = '\t';
> +	printf("Source MST Port %c.\n", 'A' + msti->src_port);
> +
> +	for (i = 0; i < MAX_PORTS; i++) {
> +		if (msti->port[i].is_valid == false)
> +			continue;
> +		if ((msti->port[i].input) &&
> +		    (msti->port[i].peer_device_type == 1)) {
> +			printf("%s[Port %d] Branch Device %s (OUI %x)\n",
> +			       prefix, i, msti->pbi.devid,
> +			       msti->pbi.branch_oui);
> +			printf("%s         [[ddps: %d ldps: %d sdp: %d/%d]]",
> +			       prefix, msti->port[i].ddps, msti->port[i].ldps,
> +			       msti->port[i].num_sdp_streams,
> +			       msti->port[i].num_sdp_connections);
> +			printf("\n\n");
> +			prefix[++numBranches] = '\t';
> +		} else if ((msti->port[i].input == 0) &&
> +			   (msti->port[i].peer_device_type == 3)) {
> +			const char *spaces = "         ";
Why not use '\t\s' in printf() and remove this?
> +
> +			/* Find and dump sinks associated with this port */
> +			for (j = 0; j < MAX_CHANNELS; j++) {
> +				if (msti->paydata.chan[j].port != i)
> +					continue;
> +
> +				printf("%s[Port %d] Sink Device %s\n",
> +				       prefix, i,
> +				       msti->paydata.chan[j].sink_name);
> +				printf("%s%s((VC: %d: start: %d: slots: %d))",
> +				       prefix, spaces, j,
> +				       msti->paydata.chan[j].start_slot,
> +				       msti->paydata.chan[j].num_slots);
> +				printf("\n");
> +			}
> +			printf("%s%s[[ddps: %d ldps: %d sdp: %d/%d]]",
> +			       prefix, spaces, msti->port[i].ddps,
> +			       msti->port[i].ldps,
> +			       msti->port[i].num_sdp_streams,
> +			       msti->port[i].num_sdp_connections);
> +			printf("\n\n");
> +		}
> +	}
> +	if ((do_extra) || (do_payload))
> +		dump_extra(msti);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	int opt;
> +	struct mst_info msti;
> +	bool ret;
> +
> +	while ((opt = getopt(argc, argv, optstring)) != -1) {
> +		switch (opt) {
> +		case 'a':
> +			do_payload = true;
> +			do_extra = true;
> +			break;
> +		case 'f':
> +			local_file = strdup(optarg);
> +			break;
> +		case 'h':
> +			printf("Usage: %s [-a][-f <file>][-p][-x]\n",
> +			       argv[0]);
> +			printf("\t-a -- Print all info (-x + -p)\n");
> +			printf("\t-f <file> -- Parse <file> rather than "
> +			       "using sysfs.\n");
> +			printf("\t-h -- Print help and exit.\n");
> +			printf("\t-p -- Print payload table\n");
> +			printf("\t-x -- Print extra configuration info.\n");
> +			exit(EXIT_SUCCESS);
> +		case 'p':
> +			do_payload = true;
> +			break;
> +		case 'x':
> +			do_extra = true;
> +			break;
> +		default:
> +			fprintf(stderr, "Illegal option %c.\n", opt);
> +			exit(EXIT_FAILURE);
> +		}
> +	}
> +
> +	if ((local_file == NULL) && (geteuid() != 0)) {
> +		fprintf(stderr, "Must be root to run %s\n", argv[0]);
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	ret = parse_mst_info(&msti);
> +	if (ret)
> +		dump_mst_info(&msti);
> +	else
> +		exit(EXIT_FAILURE);
	return 0; ?
> +}
> -- 
> 2.7.4
> 
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx
diff mbox

Patch

diff --git a/tools/Makefile.sources b/tools/Makefile.sources
index 5d5958d..19f385f 100644
--- a/tools/Makefile.sources
+++ b/tools/Makefile.sources
@@ -25,6 +25,7 @@  bin_PROGRAMS = 				\
 	intel_infoframes		\
 	intel_l3_parity			\
 	intel_lid			\
+	intel_mst_decode		\
 	intel_opregion_decode		\
 	intel_panel_fitter		\
 	intel_perf_counters		\
@@ -57,3 +58,5 @@  intel_l3_parity_SOURCES =	\
 	intel_l3_parity.h	\
 	intel_l3_udev_listener.c
 
+intel_mst_decode_SOURCES =	\
+	intel_mst_decode.c
diff --git a/tools/intel_mst_decode.c b/tools/intel_mst_decode.c
new file mode 100644
index 0000000..f9dfd2d
--- /dev/null
+++ b/tools/intel_mst_decode.c
@@ -0,0 +1,528 @@ 
+/*
+ * i915 DisplayPort MST Topology Utility
+ * Print DP MST topology information for i915
+ *
+ * Copyright © 2016 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ *     Jim Bride <jim.bride@linux.intel.com>
+ *
+ */
+#include "igt.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+/* Interesting constants and file locations */
+#define MAX_TIMESLOTS 64
+#define MAX_CHANNELS 3
+#define MAX_PORTS 8
+#define SINK_NAME_LEN 14
+#define DPCD_RCV_CAP_SIZE 15
+#define BRANCH_DEVID_SIZE 5
+#define SW_REV_SIZE 8
+#define HW_REV_SIZE SW_REV_SIZE
+#define FAUX_MST_SIZE 2
+#define TEXT_LABEL_SIZE 20
+#define PREFIX_SIZE 6
+#define LINE_BUFFER_SIZE 2048
+#define INTEL_DP_DEBUGFS_ROOT "/sys/kernel/debug/dri/0/"
+#define INTEL_DP_MST_INFO_FILE "i915_dp_mst_info"
+
+/* Command-line Parsing Stuff */
+const char *optstring = "af:hpx";
+static bool do_payload = false;
+static bool do_extra = false;
+static char *local_file = NULL;
+
+/* Data structures we populate from the MST info file. */
+struct mst_port {
+	bool is_valid;
+	bool ddps;
+	bool ldps;
+	bool input;
+	uint32_t peer_device_type;
+	uint32_t num_sdp_streams;
+	uint32_t num_sdp_connections;
+};
+
+struct vcpi_elem {
+	uint32_t state;
+	uint32_t port;
+	uint32_t start_slot;
+	uint32_t num_slots;
+	char sink_name[SINK_NAME_LEN];
+};
+
+struct payload_data {
+	struct vcpi_elem chan[MAX_CHANNELS]; // indexed by vcpi
+	uint8_t pay_table[MAX_TIMESLOTS];
+};
+
+struct primary_branch_info {
+	uint8_t dpcd_recv_cap[DPCD_RCV_CAP_SIZE];
+	uint8_t faux_mst[FAUX_MST_SIZE];
+	uint8_t mst_ctrl;
+	uint32_t branch_oui;
+	char devid[BRANCH_DEVID_SIZE];
+	char hw_rev[HW_REV_SIZE];
+	char sw_rev[SW_REV_SIZE];
+};
+
+struct mst_info {
+	int src_port;
+	struct mst_port port[MAX_PORTS];
+	struct payload_data paydata;
+	struct primary_branch_info pbi;
+};
+
+/* Prototypes to keep the compiler happy */
+
+static FILE *open_mst_info(void);
+static void close_mst_info(FILE *fp);
+static void parse_port_line(char *read_buf, struct mst_info *msti);
+static bool parse_mst_info(struct mst_info *msti);
+static void dump_mst_info(struct mst_info *msti);
+static void dump_extra(struct mst_info *msti);
+static void parse_vc_info(struct mst_info *msti, char *read_buf);
+static void parse_payload_info(struct mst_info *msti, char *read_buf);
+static void parse_pbi_info(struct mst_info *msti, char *read_buf, FILE *fp);
+static void parse_payload_table(struct mst_info *msti, char *read_buf);
+
+static FILE *open_mst_info(void)
+{
+	FILE *fp = NULL;
+
+	if (local_file) {
+		/* open local file */
+		fp = fopen(local_file, "r");
+		if (fp == NULL) {
+			fprintf(stderr, "Could not open local info file %s.\n",
+				local_file);
+		}
+	} else {
+		/* open sysfs file */
+		fp = igt_debugfs_fopen(INTEL_DP_MST_INFO_FILE, "r");
+		if (fp == NULL) {
+			fprintf(stderr, "Could not open sysfs file %s%s.\n",
+				INTEL_DP_DEBUGFS_ROOT, INTEL_DP_MST_INFO_FILE);
+		}
+	}
+	return fp;
+}
+
+static void close_mst_info(FILE *fp)
+{
+	if (!fp)
+		return;
+	fclose(fp);
+}
+
+static inline void get_byte_val(char **buf, int sep, uint8_t *bytes, int count)
+{
+	int i;
+
+	*buf = strchr(*buf, sep);
+	if (!*buf)
+		return;
+	*buf += 2; /* "<sep> " */
+	for (i = 0; i < count; i++, *buf += 3) {
+		sscanf(*buf, "%hhx", &(bytes[i]));
+	}
+
+}
+
+static inline void get_int_val(char **buf, int sep, int *val)
+{
+	*buf = strchr(*buf, sep);
+	*buf += 2; /* "<sep> " */
+	sscanf(*buf, "%d", val);
+}
+
+static void parse_port_line(char *read_buf, struct mst_info *msti)
+{
+
+	/* port: %d: input: %d: pdt: %d, ddps: %d ldps: %d, sdp: %d/%d, %x,
+	 * conn: %x || '(null)'
+	 */
+	char *idx = read_buf;
+	int cur_port = -1, val = -1;
+	struct mst_port *cp = NULL;
+
+	/* "port: %d: " */
+	get_int_val(&idx, ':', &cur_port);
+	idx += 2; /* ": " */
+	assert((cur_port > -1) && (cur_port < MAX_PORTS));
+
+	cp = &(msti->port[cur_port]);
+	/* "input: %d: " */
+	get_int_val(&idx, ':', &val);
+	cp->input = (val != 0) ? true : false;
+	idx += 2; /* ": " */
+
+	/* "pdt: %d, " */
+	get_int_val(&idx, ':', &val);
+	cp->peer_device_type = val;
+	idx += 2; /* ", " */
+
+	/* "ddps: %d ldps: %d, " */
+	get_int_val(&idx, ':', &val);
+	cp->ddps = val;
+	get_int_val(&idx, ':', &val);
+	cp->ldps = val;
+	idx += 2; /* ", " */
+
+	/* "sdp: %d/%d, " */
+	get_int_val(&idx, ':', &val);
+	cp->num_sdp_streams = val;
+	idx = strrchr(idx, '/');
+	if (!idx)
+		return;
+	sscanf(++idx, "%d", &val);
+	cp->num_sdp_connections = val;
+	/* Skip rest of line for now */
+
+	cp->is_valid = ((cp->ddps != 0) || (cp->ldps != 0));
+}
+
+static void parse_vc_info(struct mst_info *msti, char *read_buf)
+{
+	char *idx = read_buf;
+	struct vcpi_elem *cvc;
+	int val = -1;
+	int vc_chan;
+	char label[20];
+
+	/* "vcpi <index>" */
+	sscanf(idx, "%s %d", label, &vc_chan);
+
+	idx = strchr(read_buf, ':');
+	if (!idx)
+		return;
+	idx++; /* ':' */
+	cvc = &(msti->paydata.chan[vc_chan]);
+	if (strncmp(idx, "unused", 6) == 0)
+		return;
+
+	idx++; /* ' ' */
+	/* <port> <vcpi> <num_slots> "*/
+	sscanf(idx, "%d %d %d", &(cvc->port), &val, &(cvc->num_slots));
+
+	/* "sink name: (<sink_name>" || "Unknown") */
+	idx = strrchr(read_buf, ':');
+	if (!idx)
+		return;
+	idx += 2; /* ": " */
+	idx[strlen(idx) - 1] = '\0';
+	idx = strcpy(&(cvc->sink_name[0]), idx);
+	return;
+}
+
+static void parse_payload_info(struct mst_info *msti, char *read_buf)
+{
+	char *idx = read_buf;
+	char label[TEXT_LABEL_SIZE];
+	struct vcpi_elem *cvc;
+	int payload;
+
+	/* "payload <index>: " */
+	sscanf(idx, "%s %d", label, &payload);
+	idx += 2; /* ": " */
+	cvc = &(msti->paydata.chan[payload]);
+	/* "<payload_state>, " */
+	sscanf(idx, "%d", &(cvc->state));
+	idx = strchr(idx, ',');
+	idx += 2; /* ", " */
+	/* "<start_slot>, " */
+	sscanf(idx, "%d", &(cvc->start_slot));
+	idx = strchr(idx, ',');
+	idx += 2; /* ", " */
+	/* "<num_slots>" */
+	sscanf(idx, "%d", &(cvc->num_slots));
+}
+
+static void parse_pbi_info(struct mst_info *msti, char *read_buf, FILE *fp)
+{
+	char *idx;
+	char devid_label[TEXT_LABEL_SIZE];
+	char rev_label[TEXT_LABEL_SIZE];
+	char hw_label[TEXT_LABEL_SIZE];
+	char sw_label[TEXT_LABEL_SIZE];
+
+	/* "dpcd: <15 bytes>" */
+	idx = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+	if (!idx)
+		return;
+
+	/* "dpcd: " */
+	get_byte_val(&idx, ':', &(msti->pbi.dpcd_recv_cap[0]),
+		     DPCD_RCV_CAP_SIZE);
+
+	/* "faux/mst: <two bytes>" */
+	idx = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+	if (!idx)
+		return;
+	get_byte_val(&idx, ':', &(msti->pbi.faux_mst[0]), FAUX_MST_SIZE);
+
+	/* "mst ctrl: <byte>" */
+	idx = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+	if (!idx)
+		return;
+	get_byte_val(&idx, ':', &(msti->pbi.mst_ctrl), 1);
+
+	/* "branch oui: <3 bytes> devid: <4 chars> "
+	 * "revision: hw: 0xx.0xy sw: 0xx.0xy"
+	 */
+	idx = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+	if (!idx)
+		return;
+	idx = strchr(read_buf, ':');
+	idx += 2; /* ": " */
+	sscanf(idx, "%x %s %s %s %s %s %s %s", &(msti->pbi.branch_oui),
+	       devid_label, msti->pbi.devid, rev_label, hw_label,
+	       msti->pbi.hw_rev, sw_label, msti->pbi.sw_rev);
+}
+
+static void parse_payload_table(struct mst_info *msti, char *read_buf)
+{
+	char *idx = read_buf;
+
+	/* "payload table: <63 bytes>" */
+	get_byte_val(&idx, ':', &(msti->paydata.pay_table[0]),
+		     MAX_TIMESLOTS);
+}
+
+static bool parse_mst_info(struct mst_info *msti)
+{
+	char read_buf[LINE_BUFFER_SIZE];
+	char *s;
+	int pay_mask, vcpi_mask, total_vc;
+	int i;
+	FILE *fp = open_mst_info();;
+	bool bret = false;
+
+	if (!fp)
+		return false;
+
+	memset(read_buf, 0, LINE_BUFFER_SIZE);
+	memset(msti, 0, sizeof(struct mst_info));
+
+	/* Read which port we're gathering information about */
+	s =  fgets(read_buf, LINE_BUFFER_SIZE, fp);
+	if (!s)
+		goto out;
+
+	msti->src_port = tolower(read_buf[strlen(read_buf) - 2]) - 'a';
+
+	/* Strip off and ignore pointer info about the main MST object */
+	s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+	if (!s)
+		goto out;
+
+	/*
+	 * First parse out the port-related information.  Remember that
+	 * these will be the ports from the perspective of the primary
+	 * MST bridge device, and that they may or may not have anything
+	 * connected to them.
+	 */
+	s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+	if (!s)
+		goto out;
+	/* parse the port lines until we get the first VCPI entry */
+	while (strncmp(read_buf, "vcpi", 4) != 0) {
+		parse_port_line(read_buf, msti);
+		s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+		if (!s)
+			goto out;
+	}
+
+	/* We have the first line of VCPI info when we get to this point.
+	 * This line tells us the mask to identify payloads, the mask to
+	 * identify virtual channels, and the maximum allowed number of
+	 * virtual channels.
+	 */
+	/* "vcpi: <payload_mask> <vcpi_mask>" <max_payloads> */
+	s = strchr(read_buf, ':');
+	if (!s)
+		goto out;
+	s += 2; /* strip ": " */
+	sscanf(s, "%d %d %d", &pay_mask, &vcpi_mask, &total_vc);
+	for (i = 0; i < total_vc; i++) {
+		s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+		if (!s)
+			goto out;
+		parse_vc_info(msti, read_buf);
+	}
+
+	for (i = 0; i < total_vc; i++) {
+		s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+		if (!s)
+			goto out;
+		parse_payload_info(msti, read_buf);
+	}
+
+	parse_pbi_info(msti, read_buf, fp);
+
+	s = fgets(read_buf, LINE_BUFFER_SIZE, fp);
+	if (s) {
+		parse_payload_table(msti, read_buf);
+		bret = true;
+	}
+
+out:
+	close_mst_info(fp);
+	return bret;
+}
+
+static void dump_extra(struct mst_info *msti)
+{
+	int i;
+
+	if (do_extra) {
+		printf("Primary Branch Info: \n");
+		printf("\tDPCD Receiver Caps: ");
+		for (i = 0; i < DPCD_RCV_CAP_SIZE; i++)
+			printf("%02hhx ", msti->pbi.dpcd_recv_cap[i]);
+		printf("\n");
+		printf("\tFaux MST: %02hhx %02hhx\n", msti->pbi.faux_mst[0],
+		       msti->pbi.faux_mst[1]);
+		printf("\tMST CTRL: 0x%02hhx\n", msti->pbi.mst_ctrl);
+		printf("\tBranch HW Version: %s SW Version: %s\n",
+		       msti->pbi.hw_rev, msti->pbi.sw_rev);
+		printf("\n");
+	}
+
+	if (do_payload) {
+		printf("Payload Table: ");
+		for (i = 0; i < MAX_TIMESLOTS; i++) {
+			if (i % 8 == 0)
+				printf("\n\t");
+			printf("%02hhx ", msti->paydata.pay_table[i]);
+		}
+		printf("\n");
+	}
+}
+
+static void dump_mst_info(struct mst_info *msti)
+{
+	int i, j;
+	int numBranches = 0;
+	char prefix[PREFIX_SIZE];
+
+	memset(prefix, 0, sizeof(prefix));
+	prefix[0] = '\t';
+	printf("Source MST Port %c.\n", 'A' + msti->src_port);
+
+	for (i = 0; i < MAX_PORTS; i++) {
+		if (msti->port[i].is_valid == false)
+			continue;
+		if ((msti->port[i].input) &&
+		    (msti->port[i].peer_device_type == 1)) {
+			printf("%s[Port %d] Branch Device %s (OUI %x)\n",
+			       prefix, i, msti->pbi.devid,
+			       msti->pbi.branch_oui);
+			printf("%s         [[ddps: %d ldps: %d sdp: %d/%d]]",
+			       prefix, msti->port[i].ddps, msti->port[i].ldps,
+			       msti->port[i].num_sdp_streams,
+			       msti->port[i].num_sdp_connections);
+			printf("\n\n");
+			prefix[++numBranches] = '\t';
+		} else if ((msti->port[i].input == 0) &&
+			   (msti->port[i].peer_device_type == 3)) {
+			const char *spaces = "         ";
+
+			/* Find and dump sinks associated with this port */
+			for (j = 0; j < MAX_CHANNELS; j++) {
+				if (msti->paydata.chan[j].port != i)
+					continue;
+
+				printf("%s[Port %d] Sink Device %s\n",
+				       prefix, i,
+				       msti->paydata.chan[j].sink_name);
+				printf("%s%s((VC: %d: start: %d: slots: %d))",
+				       prefix, spaces, j,
+				       msti->paydata.chan[j].start_slot,
+				       msti->paydata.chan[j].num_slots);
+				printf("\n");
+			}
+			printf("%s%s[[ddps: %d ldps: %d sdp: %d/%d]]",
+			       prefix, spaces, msti->port[i].ddps,
+			       msti->port[i].ldps,
+			       msti->port[i].num_sdp_streams,
+			       msti->port[i].num_sdp_connections);
+			printf("\n\n");
+		}
+	}
+	if ((do_extra) || (do_payload))
+		dump_extra(msti);
+}
+
+int main(int argc, char *argv[])
+{
+	int opt;
+	struct mst_info msti;
+	bool ret;
+
+	while ((opt = getopt(argc, argv, optstring)) != -1) {
+		switch (opt) {
+		case 'a':
+			do_payload = true;
+			do_extra = true;
+			break;
+		case 'f':
+			local_file = strdup(optarg);
+			break;
+		case 'h':
+			printf("Usage: %s [-a][-f <file>][-p][-x]\n",
+			       argv[0]);
+			printf("\t-a -- Print all info (-x + -p)\n");
+			printf("\t-f <file> -- Parse <file> rather than "
+			       "using sysfs.\n");
+			printf("\t-h -- Print help and exit.\n");
+			printf("\t-p -- Print payload table\n");
+			printf("\t-x -- Print extra configuration info.\n");
+			exit(EXIT_SUCCESS);
+		case 'p':
+			do_payload = true;
+			break;
+		case 'x':
+			do_extra = true;
+			break;
+		default:
+			fprintf(stderr, "Illegal option %c.\n", opt);
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	if ((local_file == NULL) && (geteuid() != 0)) {
+		fprintf(stderr, "Must be root to run %s\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	ret = parse_mst_info(&msti);
+	if (ret)
+		dump_mst_info(&msti);
+	else
+		exit(EXIT_FAILURE);
+}