[RFC PATCH] cec-*: monitor pin support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Support the new pin monitoring events API in the CEC utilities.

It needs a bit more cleanup before it is ready to be merged, and I am sure that
more work can be done to refine the analysis code. But this is a good first start.

Signed-off-by: Hans Verkuil <hans.verkuil@xxxxxxxxx>
---
 include/linux/cec.h                     |   8 +-
 utils/cec-compliance/cec-compliance.cpp |   2 +
 utils/cec-ctl/cec-ctl.cpp               | 311 +++++++++++++++++++++++++++++++-
 utils/cec-follower/cec-follower.cpp     |   2 +
 utils/cec-follower/cec-processing.cpp   |   2 +
 5 files changed, 316 insertions(+), 9 deletions(-)

diff --git a/include/linux/cec.h b/include/linux/cec.h
index 44579a24..d87a67b0 100644
--- a/include/linux/cec.h
+++ b/include/linux/cec.h
@@ -318,6 +318,7 @@ static inline int cec_is_unconfigured(__u16 log_addr_mask)
 #define CEC_MODE_FOLLOWER		(0x1 << 4)
 #define CEC_MODE_EXCL_FOLLOWER		(0x2 << 4)
 #define CEC_MODE_EXCL_FOLLOWER_PASSTHRU	(0x3 << 4)
+#define CEC_MODE_MONITOR_PIN		(0xd << 4)
 #define CEC_MODE_MONITOR		(0xe << 4)
 #define CEC_MODE_MONITOR_ALL		(0xf << 4)
 #define CEC_MODE_FOLLOWER_MSK		0xf0
@@ -338,6 +339,8 @@ static inline int cec_is_unconfigured(__u16 log_addr_mask)
 #define CEC_CAP_MONITOR_ALL	(1 << 5)
 /* Hardware can use CEC only if the HDMI HPD pin is high. */
 #define CEC_CAP_NEEDS_HPD	(1 << 6)
+/* Hardware can monitor CEC pin transitions */
+#define CEC_CAP_MONITOR_PIN	(1 << 7)

 /**
  * struct cec_caps - CEC capabilities structure.
@@ -405,8 +408,11 @@ struct cec_log_addrs {
  * didn't empty the message queue in time
  */
 #define CEC_EVENT_LOST_MSGS		2
+#define CEC_EVENT_PIN_LOW		3
+#define CEC_EVENT_PIN_HIGH		4

 #define CEC_EVENT_FL_INITIAL_STATE	(1 << 0)
+#define CEC_EVENT_FL_DROPPED_EVENTS	(1 << 1)

 /**
  * struct cec_event_state_change - used when the CEC adapter changes state.
@@ -419,7 +425,7 @@ struct cec_event_state_change {
 };

 /**
- * struct cec_event_lost_msgs - tells you how many messages were lost due.
+ * struct cec_event_lost_msgs - tells you how many messages were lost.
  * @lost_msgs: how many messages were lost.
  */
 struct cec_event_lost_msgs {
diff --git a/utils/cec-compliance/cec-compliance.cpp b/utils/cec-compliance/cec-compliance.cpp
index 0dd2a54f..1badf8be 100644
--- a/utils/cec-compliance/cec-compliance.cpp
+++ b/utils/cec-compliance/cec-compliance.cpp
@@ -261,6 +261,8 @@ static std::string caps2s(unsigned caps)
 		s += "\t\tMonitor All\n";
 	if (caps & CEC_CAP_NEEDS_HPD)
 		s += "\t\tNeeds HPD\n";
+	if (caps & CEC_CAP_MONITOR_PIN)
+		s += "\t\tMonitor Pin\n";
 	return s;
 }

diff --git a/utils/cec-ctl/cec-ctl.cpp b/utils/cec-ctl/cec-ctl.cpp
index 80d4014a..7b23ab7e 100644
--- a/utils/cec-ctl/cec-ctl.cpp
+++ b/utils/cec-ctl/cec-ctl.cpp
@@ -668,6 +668,7 @@ enum Option {
 	OptReplyToFollowers,
 	OptTimeout,
 	OptMonitorTime,
+	OptMonitorPin,
 	OptListUICommands,
 	OptRcTVProfile1,
 	OptRcTVProfile2,
@@ -730,6 +731,7 @@ static struct option long_options[] = {
 	{ "clear", no_argument, 0, OptClear },
 	{ "monitor", no_argument, 0, OptMonitor },
 	{ "monitor-all", no_argument, 0, OptMonitorAll },
+	{ "monitor-pin", no_argument, 0, OptMonitorPin },
 	{ "monitor-time", required_argument, 0, OptMonitorTime },
 	{ "no-reply", no_argument, 0, OptNoReply },
 	{ "to", required_argument, 0, OptTo },
@@ -786,6 +788,7 @@ static void usage(void)
 	       "  -C, --clear              Clear all logical addresses\n"
 	       "  -m, --monitor            Monitor CEC traffic\n"
 	       "  -M, --monitor-all        Monitor all CEC traffic\n"
+	       "  --monitor-pin            Monitor low-level CEC pin\n"
 	       "  --monitor-time=<secs>    Monitor for <secs> seconds (default is forever)\n"
 	       "  -n, --no-reply           Don't wait for a reply\n"
 	       "  -t, --to=<la>            Send message to the given logical address\n"
@@ -857,6 +860,8 @@ static std::string caps2s(unsigned caps)
 		s += "\t\tMonitor All\n";
 	if (caps & CEC_CAP_NEEDS_HPD)
 		s += "\t\tNeeds HPD\n";
+	if (caps & CEC_CAP_MONITOR_PIN)
+		s += "\t\tMonitor Pin\n";
 	return s;
 }

@@ -1224,11 +1229,231 @@ static void log_unknown_msg(const struct cec_msg *msg)
 	}
 }

+enum cec_state {
+	CEC_ST_IDLE,
+	CEC_ST_RECEIVE_START_BIT,
+	CEC_ST_RECEIVING_DATA,
+};
+
+/* All timings are in microseconds */
+#define CEC_TIM_MARGIN			100
+
+#define CEC_TIM_START_BIT_LOW		3700
+#define CEC_TIM_START_BIT_LOW_MIN	3500
+#define CEC_TIM_START_BIT_LOW_MAX	3900
+#define CEC_TIM_START_BIT_TOTAL		4500
+#define CEC_TIM_START_BIT_TOTAL_MIN	4300
+#define CEC_TIM_START_BIT_TOTAL_MAX	4700
+
+#define CEC_TIM_DATA_BIT_0_LOW		1500
+#define CEC_TIM_DATA_BIT_0_LOW_MIN	1300
+#define CEC_TIM_DATA_BIT_0_LOW_MAX	1700
+#define CEC_TIM_DATA_BIT_1_LOW		600
+#define CEC_TIM_DATA_BIT_1_LOW_MIN	400
+#define CEC_TIM_DATA_BIT_1_LOW_MAX	800
+#define CEC_TIM_DATA_BIT_TOTAL		2400
+#define CEC_TIM_DATA_BIT_TOTAL_MIN	2050
+#define CEC_TIM_DATA_BIT_TOTAL_MAX	2750
+#define CEC_TIM_DATA_BIT_SAMPLE		1050
+#define CEC_TIM_DATA_BIT_SAMPLE_MIN	850
+#define CEC_TIM_DATA_BIT_SAMPLE_MAX	1250
+
+#define CEC_TIM_IDLE_SAMPLE		1000
+#define CEC_TIM_IDLE_SAMPLE_MIN		500
+#define CEC_TIM_IDLE_SAMPLE_MAX		1500
+#define CEC_TIM_START_BIT_SAMPLE	500
+#define CEC_TIM_START_BIT_SAMPLE_MIN	300
+#define CEC_TIM_START_BIT_SAMPLE_MAX	700
+
+#define CEC_TIM_LOW_DRIVE_ERROR         (1.5 * CEC_TIM_DATA_BIT_TOTAL)
+#define CEC_TIM_LOW_DRIVE_ERROR_MIN     (1.4 * CEC_TIM_DATA_BIT_TOTAL)
+#define CEC_TIM_LOW_DRIVE_ERROR_MAX     (1.6 * CEC_TIM_DATA_BIT_TOTAL)
+
+static __u64 eob_ts;
+static __u64 eob_ts_max;
+
+static void cec_pin_debug(__u64 ev_ts, __u64 usecs, bool was_high, bool is_high)
+{
+	static enum cec_state state;
+	static __u64 ts;
+	static __u64 low_usecs;
+	static unsigned int rx_bit;
+	static __u8 byte;
+	static bool eom;
+	static bool first_byte;
+	static bool bcast;
+	__u64 usecs_min = usecs > CEC_TIM_MARGIN ? usecs - CEC_TIM_MARGIN : 0;
+
+	ts += usecs;
+
+	switch (state) {
+	case CEC_ST_RECEIVE_START_BIT:
+		if (was_high) {
+			if (low_usecs + usecs_min > CEC_TIM_START_BIT_TOTAL_MAX) {
+				state = CEC_ST_IDLE;
+				printf("%10.2f: total start bit time too long (%.2f > %.2f ms)\n",
+					ts / 1000.0, (low_usecs + usecs_min) / 1000.0,
+					CEC_TIM_START_BIT_TOTAL_MAX / 1000.0);
+				break;
+			}
+			if (low_usecs + usecs < CEC_TIM_START_BIT_TOTAL_MIN - CEC_TIM_MARGIN) {
+				state = CEC_ST_IDLE;
+				printf("%10.2f: total start bit time too short (%.2f < %.2f ms)\n",
+					ts / 1000.0, (low_usecs + usecs) / 1000.0,
+					CEC_TIM_START_BIT_TOTAL_MIN / 1000.0);
+				break;
+			}
+			state = CEC_ST_RECEIVING_DATA;
+			rx_bit = 0;
+			byte = 0;
+			eom = false;
+			first_byte = true;
+			bcast = false;
+		} else {
+			if (usecs_min > CEC_TIM_START_BIT_LOW_MAX) {
+				state = CEC_ST_IDLE;
+				printf("%10.2f: start bit low too long (%.2f > %.2f ms)\n",
+					ts / 1000.0, usecs_min / 1000.0,
+					CEC_TIM_START_BIT_LOW_MAX / 1000.0);
+				break;
+			}
+			if (usecs < CEC_TIM_START_BIT_LOW_MIN - CEC_TIM_MARGIN) {
+				state = CEC_ST_IDLE;
+				printf("%10.2f: start bit low too short (%.2f < %.2f ms)\n",
+					ts / 1000.0, usecs / 1000.0,
+					CEC_TIM_START_BIT_LOW_MIN / 1000.0);
+				break;
+			}
+			low_usecs = usecs;
+			eob_ts = ev_ts + 1000 * (CEC_TIM_START_BIT_TOTAL - low_usecs);
+			eob_ts_max = ev_ts + 1000 * (CEC_TIM_START_BIT_TOTAL_MAX - low_usecs);
+		}
+		break;
+	case CEC_ST_RECEIVING_DATA:
+		if (was_high) {
+			bool bit;
+			bool ack = false;
+
+			if (rx_bit < 9 && low_usecs + usecs_min > CEC_TIM_DATA_BIT_TOTAL_MAX) {
+				printf("%10.2f: data bit %d total time too long (%.2f ms)\n",
+					ts / 1000.0, rx_bit, (low_usecs + usecs_min) / 1000.0);
+			}
+			if (low_usecs + usecs < CEC_TIM_DATA_BIT_TOTAL_MIN - CEC_TIM_MARGIN) {
+				printf("%10.2f: data bit %d total time too short (%.2f ms)\n\n",
+					ts / 1000.0, rx_bit, (low_usecs + usecs) / 1000.0);
+			}
+			bit = low_usecs < CEC_TIM_DATA_BIT_1_LOW_MAX + CEC_TIM_MARGIN;
+			if (rx_bit <= 7) {
+				byte |= bit << (7 - rx_bit);
+			} else if (rx_bit == 8) {
+				eom = bit;
+			} else {
+				if (first_byte) {
+					first_byte = false;
+					bcast = (byte & 0xf) == 0xf;
+				}
+				printf("%10.2f: rx 0x%02x%s%s%s (%s)\n",
+				       (ts - usecs) / 1000.0, byte,
+				       eom ? " EOM" : "", (bcast ^ bit) ? " NACK" : " ACK",
+				       bcast ? " (broadcast)" : "",
+				       ts2s(ev_ts - usecs * 1000).c_str());
+				if (show_info)
+					printf("\n");
+				ack = !(bcast ^ bit);
+			}
+			rx_bit++;
+			if (rx_bit == 10) {
+				if ((!eom && ack) && low_usecs + usecs_min > CEC_TIM_DATA_BIT_TOTAL_MAX)
+					printf("%10.2f: data bit %d total time too long (%.2f ms)\n",
+						ts / 1000.0, rx_bit - 1, (low_usecs + usecs_min) / 1000.0);
+				if (eom || is_high)
+					state = is_high ? CEC_ST_IDLE : CEC_ST_RECEIVE_START_BIT;
+				if (state == CEC_ST_IDLE)
+					printf("\n");
+				rx_bit = 0;
+				byte = 0;
+				eom = false;
+			}
+			break;
+		} else {
+			low_usecs = usecs;
+			if (usecs >= CEC_TIM_LOW_DRIVE_ERROR_MIN - CEC_TIM_MARGIN &&
+			    usecs <= CEC_TIM_LOW_DRIVE_ERROR_MAX + CEC_TIM_MARGIN) {
+				state = CEC_ST_IDLE;
+				printf("%10.2f: low drive (%.2f ms) detected\n\n",
+				       ts / 1000.0, usecs / 1000.0);
+				break;
+			}
+			if (usecs_min >= CEC_TIM_LOW_DRIVE_ERROR_MAX) {
+				state = CEC_ST_IDLE;
+				printf("%10.2f: low drive too long (%.2f > %.2f ms)\n\n",
+				       ts / 1000.0, usecs_min / 1000.0,
+				       CEC_TIM_LOW_DRIVE_ERROR_MAX / 1000.0);
+				break;
+			}
+
+			if (rx_bit == 0 && !first_byte) {
+				if (!rx_bit && usecs_min > CEC_TIM_START_BIT_LOW_MAX) {
+					state = CEC_ST_IDLE;
+					printf("%10.2f: start bit low too long (%.2f > %.2f ms)\n\n",
+						ts / 1000.0, usecs_min / 1000.0,
+						CEC_TIM_START_BIT_LOW_MAX / 1000.0);
+					break;
+				}
+				if (usecs >= CEC_TIM_START_BIT_LOW_MIN - CEC_TIM_MARGIN) {
+					state = CEC_ST_RECEIVE_START_BIT;
+					break;
+				}
+			}
+			if (usecs_min > CEC_TIM_DATA_BIT_0_LOW_MAX) {
+				printf("%10.2f: data bit %d low too long (%.2f ms)\n",
+					ts / 1000.0, rx_bit, usecs_min / 1000.0);
+				if (usecs_min > CEC_TIM_DATA_BIT_TOTAL_MAX) {
+					state = CEC_ST_IDLE;
+					printf("\n");
+				}
+				break;
+			}
+			if (usecs_min > CEC_TIM_DATA_BIT_1_LOW_MAX &&
+			    usecs < CEC_TIM_DATA_BIT_0_LOW_MIN - CEC_TIM_MARGIN) {
+				state = CEC_ST_IDLE;
+				printf("%10.2f: data bit %d invalid 0->1 transition (%.2f ms)\n\n",
+					ts / 1000.0, rx_bit, usecs / 1000.0);
+				break;
+			}
+			if (usecs < CEC_TIM_DATA_BIT_1_LOW_MIN - CEC_TIM_MARGIN) {
+				state = CEC_ST_IDLE;
+				printf("%10.2f: data bit %d low too short (%.2f ms)\n\n",
+					ts / 1000.0, rx_bit, usecs / 1000.0);
+				break;
+			}
+
+			eob_ts = ev_ts + 1000 * (CEC_TIM_DATA_BIT_TOTAL - low_usecs);
+			eob_ts_max = ev_ts + 1000 * (CEC_TIM_DATA_BIT_TOTAL_MAX - low_usecs);
+		}
+		break;
+	case CEC_ST_IDLE:
+		if (!is_high)
+			state = CEC_ST_RECEIVE_START_BIT;
+		break;
+	}
+	if (was_high && is_high)
+		state = CEC_ST_IDLE;
+}
+
 static void log_event(struct cec_event &ev)
 {
+	static __u64 last_ts;
+	static __u64 last_change_ts;
+	static __u64 last_1_to_0_ts;
+	static bool was_high = true;
+	bool is_high = ev.event == CEC_EVENT_PIN_HIGH;
 	__u16 pa;

-	printf("\n");
+	if (ev.event != CEC_EVENT_PIN_LOW && ev.event != CEC_EVENT_PIN_HIGH)
+		printf("\n");
+	if (ev.flags & CEC_EVENT_FL_DROPPED_EVENTS)
+		printf("(Note: events were lost)\n");
 	if (ev.flags & CEC_EVENT_FL_INITIAL_STATE)
 		printf("Initial ");
 	switch (ev.event) {
@@ -1242,6 +1467,52 @@ static void log_event(struct cec_event &ev)
 	case CEC_EVENT_LOST_MSGS:
 		printf("Event: Lost Messages\n");
 		break;
+	case CEC_EVENT_PIN_LOW:
+	case CEC_EVENT_PIN_HIGH:
+		if (ev.flags & CEC_EVENT_FL_INITIAL_STATE)
+			printf("Event: CEC Pin %s\n", is_high ? "High" : "Low");
+
+		eob_ts = eob_ts_max = 0;
+
+		if (last_change_ts == 0) {
+			last_ts = last_change_ts = last_1_to_0_ts = ev.ts - CEC_TIM_DATA_BIT_TOTAL * 16000;
+			if (is_high)
+				break;
+		}
+		if (show_info) {
+			if (last_change_ts)
+				printf("%10.2f ms ", (ev.ts - last_change_ts) / 1000000.0);
+			else
+				printf("%10.2f ms ", 0.0);
+			if (last_change_ts && is_high && was_high)
+				printf("1 -> 1 (%.2f ms, %s)\n",
+				       (ev.ts - last_1_to_0_ts) / 1000000.0,
+				       ts2s(ev.ts).c_str());
+			else if (was_high)
+				printf("1 -> 0 (%.2f ms, %s)\n",
+				       (ev.ts - last_1_to_0_ts) / 1000000.0,
+				       ts2s(ev.ts).c_str());
+			else if (last_change_ts)
+				printf("0 -> 1 (%d, %s)\n",
+				       (ev.ts - last_change_ts) / 1000 < CEC_TIM_DATA_BIT_1_LOW_MAX + CEC_TIM_MARGIN,
+				       ts2s(ev.ts).c_str());
+			else
+				printf("0 -> 1\n");
+			last_change_ts = ev.ts;
+			if (ev.event == CEC_EVENT_PIN_LOW)
+				last_1_to_0_ts = ev.ts;
+		}
+		cec_pin_debug(ev.ts, (ev.ts - last_ts) / 1000, was_high, is_high);
+		if (!is_high) {
+			float usecs = (ev.ts - last_ts) / 1000;
+			unsigned periods = usecs / CEC_TIM_DATA_BIT_TOTAL;
+
+			if (periods > 1 && periods < 10)
+				printf("free signal time = %.1f bit periods\n", usecs / CEC_TIM_DATA_BIT_TOTAL);
+		}
+		last_ts = ev.ts;
+		was_high = is_high;
+		return;
 	default:
 		printf("Event: Unknown (0x%x)\n", ev.event);
 		break;
@@ -2087,7 +2358,7 @@ int main(int argc, char **argv)
 		}
 	}
 	if (node.num_log_addrs == 0) {
-		if (options[OptMonitor] || options[OptMonitorAll])
+		if (options[OptMonitor] || options[OptMonitorAll] || options[OptMonitorPin])
 			goto skip_la;
 		return 0;
 	}
@@ -2137,9 +2408,10 @@ int main(int argc, char **argv)
 	fflush(stdout);

 skip_la:
-	if (options[OptMonitor] || options[OptMonitorAll]) {
+	if (options[OptMonitor] || options[OptMonitorAll] || options[OptMonitorPin]) {
 		__u32 monitor = options[OptMonitorAll] ?
-			CEC_MODE_MONITOR_ALL : CEC_MODE_MONITOR;
+			CEC_MODE_MONITOR_ALL : (options[OptMonitorPin] ? CEC_MODE_MONITOR_PIN :
+						CEC_MODE_MONITOR);
 		fd_set rd_fds;
 		fd_set ex_fds;
 		int fd = node.fd;
@@ -2151,6 +2423,11 @@ skip_la:
 			printf("Monitor All mode is not supported, falling back to regular monitoring\n");
 			monitor = CEC_MODE_MONITOR;
 		}
+		if (!(node.caps & CEC_CAP_MONITOR_PIN) &&
+		    monitor == CEC_MODE_MONITOR_PIN) {
+			printf("Monitor Pin mode is not supported, falling back to regular monitoring\n");
+			monitor = CEC_MODE_MONITOR;
+		}
 		if (doioctl(&node, CEC_S_MODE, &monitor)) {
 			printf("Selecting monitor mode failed, you may have to run this as root.\n");
 			goto skip_mon;
@@ -2158,8 +2435,9 @@ skip_la:

 		fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
 		t = time(NULL) + monitor_time;
-		while (1) {
-			struct timeval tv = { t - time(NULL), 0 };
+		while (!monitor_time || time(NULL) < t) {
+			struct timeval tv = { 1, 0 };
+			bool pin_event = false;
 			int res;

 			fflush(stdout);
@@ -2167,8 +2445,8 @@ skip_la:
 			FD_ZERO(&ex_fds);
 			FD_SET(fd, &rd_fds);
 			FD_SET(fd, &ex_fds);
-			res = select(fd + 1, &rd_fds, NULL, &ex_fds, monitor_time ? &tv : NULL);
-			if (res <= 0)
+			res = select(fd + 1, &rd_fds, NULL, &ex_fds, &tv);
+			if (res < 0)
 				break;
 			if (FD_ISSET(fd, &rd_fds)) {
 				struct cec_msg msg = { };
@@ -2203,8 +2481,25 @@ skip_la:

 				if (doioctl(&node, CEC_DQEVENT, &ev))
 					continue;
+				if (ev.event == CEC_EVENT_PIN_LOW ||
+				    ev.event == CEC_EVENT_PIN_HIGH)
+					pin_event = true;
 				log_event(ev);
 			}
+			if (!pin_event && eob_ts) {
+				struct timespec ts;
+				__u64 ts64;
+
+				clock_gettime(CLOCK_MONOTONIC, &ts);
+				ts64 = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+				if (ts64 >= eob_ts_max) {
+					struct cec_event ev = {
+						.ts = eob_ts,
+						.event = CEC_EVENT_PIN_HIGH,
+					};
+					log_event(ev);
+				}
+			}
 		}
 	}
 	fflush(stdout);
diff --git a/utils/cec-follower/cec-follower.cpp b/utils/cec-follower/cec-follower.cpp
index 70ff7e74..fafad84a 100644
--- a/utils/cec-follower/cec-follower.cpp
+++ b/utils/cec-follower/cec-follower.cpp
@@ -123,6 +123,8 @@ static std::string caps2s(unsigned caps)
 		s += "\t\tMonitor All\n";
 	if (caps & CEC_CAP_NEEDS_HPD)
 		s += "\t\tNeeds HPD\n";
+	if (caps & CEC_CAP_MONITOR_PIN)
+		s += "\t\tMonitor Pin\n";
 	return s;
 }

diff --git a/utils/cec-follower/cec-processing.cpp b/utils/cec-follower/cec-processing.cpp
index 1e4527bb..88ab8286 100644
--- a/utils/cec-follower/cec-processing.cpp
+++ b/utils/cec-follower/cec-processing.cpp
@@ -176,6 +176,8 @@ static void log_event(struct cec_event &ev)
 {
 	__u16 pa;

+	if (ev.flags & CEC_EVENT_FL_DROPPED_EVENTS)
+		printf("(Note: events were lost)\n");
 	if (ev.flags & CEC_EVENT_FL_INITIAL_STATE)
 		printf("Initial ");
 	switch (ev.event) {
-- 
2.11.0




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux