summaryrefslogtreecommitdiffstats
path: root/drivers/media/cec
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/cec')
-rw-r--r--drivers/media/cec/Kconfig6
-rw-r--r--drivers/media/cec/Makefile4
-rw-r--r--drivers/media/cec/cec-adap.c54
-rw-r--r--drivers/media/cec/cec-api.c14
-rw-r--r--drivers/media/cec/cec-core.c72
-rw-r--r--drivers/media/cec/cec-edid.c14
-rw-r--r--drivers/media/cec/cec-notifier.c14
-rw-r--r--drivers/media/cec/cec-pin-error-inj.c342
-rw-r--r--drivers/media/cec/cec-pin-priv.h148
-rw-r--r--drivers/media/cec/cec-pin.c678
-rw-r--r--drivers/media/cec/cec-priv.h14
11 files changed, 1157 insertions, 203 deletions
diff --git a/drivers/media/cec/Kconfig b/drivers/media/cec/Kconfig
index 43428cec..9c2b108 100644
--- a/drivers/media/cec/Kconfig
+++ b/drivers/media/cec/Kconfig
@@ -4,3 +4,9 @@ config MEDIA_CEC_RC
depends on CEC_CORE=m || RC_CORE=y
---help---
Pass on CEC remote control messages to the RC framework.
+
+config CEC_PIN_ERROR_INJ
+ bool "Enable CEC error injection support"
+ depends on CEC_PIN && DEBUG_FS
+ ---help---
+ This option enables CEC error injection using debugfs.
diff --git a/drivers/media/cec/Makefile b/drivers/media/cec/Makefile
index 41ee332..29a2ab9 100644
--- a/drivers/media/cec/Makefile
+++ b/drivers/media/cec/Makefile
@@ -9,4 +9,8 @@ ifeq ($(CONFIG_CEC_PIN),y)
cec-objs += cec-pin.o
endif
+ifeq ($(CONFIG_CEC_PIN_ERROR_INJ),y)
+ cec-objs += cec-pin-error-inj.o
+endif
+
obj-$(CONFIG_CEC_CORE) += cec.o
diff --git a/drivers/media/cec/cec-adap.c b/drivers/media/cec/cec-adap.c
index 2b1e540..002ed4c 100644
--- a/drivers/media/cec/cec-adap.c
+++ b/drivers/media/cec/cec-adap.c
@@ -1,20 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* cec-adap.c - HDMI Consumer Electronics Control framework - CEC adapter
*
* Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
- *
- * This program is free software; you may redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * 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.
*/
#include <linux/errno.h>
@@ -85,8 +73,8 @@ static unsigned int cec_log_addr2dev(const struct cec_adapter *adap, u8 log_addr
void cec_queue_event_fh(struct cec_fh *fh,
const struct cec_event *new_ev, u64 ts)
{
- static const u8 max_events[CEC_NUM_EVENTS] = {
- 1, 1, 64, 64, 8, 8,
+ static const u16 max_events[CEC_NUM_EVENTS] = {
+ 1, 1, 800, 800, 8, 8,
};
struct cec_event_entry *entry;
unsigned int ev_idx = new_ev->event - 1;
@@ -154,11 +142,13 @@ static void cec_queue_event(struct cec_adapter *adap,
}
/* Notify userspace that the CEC pin changed state at the given time. */
-void cec_queue_pin_cec_event(struct cec_adapter *adap, bool is_high, ktime_t ts)
+void cec_queue_pin_cec_event(struct cec_adapter *adap, bool is_high,
+ bool dropped_events, ktime_t ts)
{
struct cec_event ev = {
.event = is_high ? CEC_EVENT_PIN_CEC_HIGH :
CEC_EVENT_PIN_CEC_LOW,
+ .flags = dropped_events ? CEC_EVENT_FL_DROPPED_EVENTS : 0,
};
struct cec_fh *fh;
@@ -711,16 +701,31 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
else
msg->flags = 0;
+ if (msg->len > 1 && msg->msg[1] == CEC_MSG_CDC_MESSAGE) {
+ msg->msg[2] = adap->phys_addr >> 8;
+ msg->msg[3] = adap->phys_addr & 0xff;
+ }
+
/* Sanity checks */
if (msg->len == 0 || msg->len > CEC_MAX_MSG_SIZE) {
dprintk(1, "%s: invalid length %d\n", __func__, msg->len);
return -EINVAL;
}
+
+ memset(msg->msg + msg->len, 0, sizeof(msg->msg) - msg->len);
+
+ if (msg->timeout)
+ dprintk(2, "%s: %*ph (wait for 0x%02x%s)\n",
+ __func__, msg->len, msg->msg, msg->reply,
+ !block ? ", nb" : "");
+ else
+ dprintk(2, "%s: %*ph%s\n",
+ __func__, msg->len, msg->msg, !block ? " (nb)" : "");
+
if (msg->timeout && msg->len == 1) {
- dprintk(1, "%s: can't reply for poll msg\n", __func__);
+ dprintk(1, "%s: can't reply to poll msg\n", __func__);
return -EINVAL;
}
- memset(msg->msg + msg->len, 0, sizeof(msg->msg) - msg->len);
if (msg->len == 1) {
if (cec_msg_destination(msg) == 0xf) {
dprintk(1, "%s: invalid poll message\n", __func__);
@@ -780,19 +785,6 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
if (!msg->sequence)
msg->sequence = ++adap->sequence;
- if (msg->len > 1 && msg->msg[1] == CEC_MSG_CDC_MESSAGE) {
- msg->msg[2] = adap->phys_addr >> 8;
- msg->msg[3] = adap->phys_addr & 0xff;
- }
-
- if (msg->timeout)
- dprintk(2, "%s: %*ph (wait for 0x%02x%s)\n",
- __func__, msg->len, msg->msg, msg->reply,
- !block ? ", nb" : "");
- else
- dprintk(2, "%s: %*ph%s\n",
- __func__, msg->len, msg->msg, !block ? " (nb)" : "");
-
data->msg = *msg;
data->fh = fh;
data->adap = adap;
diff --git a/drivers/media/cec/cec-api.c b/drivers/media/cec/cec-api.c
index 492db12..10b67fc 100644
--- a/drivers/media/cec/cec-api.c
+++ b/drivers/media/cec/cec-api.c
@@ -1,20 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* cec-api.c - HDMI Consumer Electronics Control framework - API
*
* Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
- *
- * This program is free software; you may redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * 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.
*/
#include <linux/errno.h>
diff --git a/drivers/media/cec/cec-core.c b/drivers/media/cec/cec-core.c
index a9f9525..b0c87f9 100644
--- a/drivers/media/cec/cec-core.c
+++ b/drivers/media/cec/cec-core.c
@@ -1,20 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* cec-core.c - HDMI Consumer Electronics Control framework - Core
*
* Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
- *
- * This program is free software; you may redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * 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.
*/
#include <linux/errno.h>
@@ -207,6 +195,55 @@ void cec_register_cec_notifier(struct cec_adapter *adap,
EXPORT_SYMBOL_GPL(cec_register_cec_notifier);
#endif
+#ifdef CONFIG_DEBUG_FS
+static ssize_t cec_error_inj_write(struct file *file,
+ const char __user *ubuf, size_t count, loff_t *ppos)
+{
+ struct seq_file *sf = file->private_data;
+ struct cec_adapter *adap = sf->private;
+ char *buf;
+ char *line;
+ char *p;
+
+ buf = memdup_user_nul(ubuf, min_t(size_t, PAGE_SIZE, count));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+ p = buf;
+ while (p && *p) {
+ p = skip_spaces(p);
+ line = strsep(&p, "\n");
+ if (!*line || *line == '#')
+ continue;
+ if (!adap->ops->error_inj_parse_line(adap, line)) {
+ kfree(buf);
+ return -EINVAL;
+ }
+ }
+ kfree(buf);
+ return count;
+}
+
+static int cec_error_inj_show(struct seq_file *sf, void *unused)
+{
+ struct cec_adapter *adap = sf->private;
+
+ return adap->ops->error_inj_show(adap, sf);
+}
+
+static int cec_error_inj_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, cec_error_inj_show, inode->i_private);
+}
+
+static const struct file_operations cec_error_inj_fops = {
+ .open = cec_error_inj_open,
+ .write = cec_error_inj_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+#endif
+
struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops,
void *priv, const char *name, u32 caps,
u8 available_las)
@@ -346,7 +383,16 @@ int cec_register_adapter(struct cec_adapter *adap,
pr_warn("cec-%s: Failed to create status file\n", adap->name);
debugfs_remove_recursive(adap->cec_dir);
adap->cec_dir = NULL;
+ return 0;
}
+ if (!adap->ops->error_inj_show || !adap->ops->error_inj_parse_line)
+ return 0;
+ adap->error_inj_file = debugfs_create_file("error-inj", 0644,
+ adap->cec_dir, adap,
+ &cec_error_inj_fops);
+ if (IS_ERR_OR_NULL(adap->error_inj_file))
+ pr_warn("cec-%s: Failed to create error-inj file\n",
+ adap->name);
#endif
return 0;
}
diff --git a/drivers/media/cec/cec-edid.c b/drivers/media/cec/cec-edid.c
index 38e3fec..ec72ac1 100644
--- a/drivers/media/cec/cec-edid.c
+++ b/drivers/media/cec/cec-edid.c
@@ -1,20 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* cec-edid - HDMI Consumer Electronics Control EDID & CEC helper functions
*
* Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
- *
- * This program is free software; you may redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * 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.
*/
#include <linux/module.h>
diff --git a/drivers/media/cec/cec-notifier.c b/drivers/media/cec/cec-notifier.c
index 08b619d..16dffa0 100644
--- a/drivers/media/cec/cec-notifier.c
+++ b/drivers/media/cec/cec-notifier.c
@@ -1,21 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* cec-notifier.c - notify CEC drivers of physical address changes
*
* Copyright 2016 Russell King <rmk+kernel@arm.linux.org.uk>
* Copyright 2016-2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
- *
- * This program is free software; you may redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * 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.
*/
#include <linux/export.h>
diff --git a/drivers/media/cec/cec-pin-error-inj.c b/drivers/media/cec/cec-pin-error-inj.c
new file mode 100644
index 0000000..aaa899a
--- /dev/null
+++ b/drivers/media/cec/cec-pin-error-inj.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ */
+
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched/types.h>
+
+#include <media/cec-pin.h>
+#include "cec-pin-priv.h"
+
+struct cec_error_inj_cmd {
+ unsigned int mode_offset;
+ int arg_idx;
+ const char *cmd;
+};
+
+static const struct cec_error_inj_cmd cec_error_inj_cmds[] = {
+ { CEC_ERROR_INJ_RX_NACK_OFFSET, -1, "rx-nack" },
+ { CEC_ERROR_INJ_RX_LOW_DRIVE_OFFSET,
+ CEC_ERROR_INJ_RX_LOW_DRIVE_ARG_IDX, "rx-low-drive" },
+ { CEC_ERROR_INJ_RX_ADD_BYTE_OFFSET, -1, "rx-add-byte" },
+ { CEC_ERROR_INJ_RX_REMOVE_BYTE_OFFSET, -1, "rx-remove-byte" },
+ { CEC_ERROR_INJ_RX_ARB_LOST_OFFSET,
+ CEC_ERROR_INJ_RX_ARB_LOST_ARG_IDX, "rx-arb-lost" },
+
+ { CEC_ERROR_INJ_TX_NO_EOM_OFFSET, -1, "tx-no-eom" },
+ { CEC_ERROR_INJ_TX_EARLY_EOM_OFFSET, -1, "tx-early-eom" },
+ { CEC_ERROR_INJ_TX_ADD_BYTES_OFFSET,
+ CEC_ERROR_INJ_TX_ADD_BYTES_ARG_IDX, "tx-add-bytes" },
+ { CEC_ERROR_INJ_TX_REMOVE_BYTE_OFFSET, -1, "tx-remove-byte" },
+ { CEC_ERROR_INJ_TX_SHORT_BIT_OFFSET,
+ CEC_ERROR_INJ_TX_SHORT_BIT_ARG_IDX, "tx-short-bit" },
+ { CEC_ERROR_INJ_TX_LONG_BIT_OFFSET,
+ CEC_ERROR_INJ_TX_LONG_BIT_ARG_IDX, "tx-long-bit" },
+ { CEC_ERROR_INJ_TX_CUSTOM_BIT_OFFSET,
+ CEC_ERROR_INJ_TX_CUSTOM_BIT_ARG_IDX, "tx-custom-bit" },
+ { CEC_ERROR_INJ_TX_SHORT_START_OFFSET, -1, "tx-short-start" },
+ { CEC_ERROR_INJ_TX_LONG_START_OFFSET, -1, "tx-long-start" },
+ { CEC_ERROR_INJ_TX_CUSTOM_START_OFFSET, -1, "tx-custom-start" },
+ { CEC_ERROR_INJ_TX_LAST_BIT_OFFSET,
+ CEC_ERROR_INJ_TX_LAST_BIT_ARG_IDX, "tx-last-bit" },
+ { CEC_ERROR_INJ_TX_LOW_DRIVE_OFFSET,
+ CEC_ERROR_INJ_TX_LOW_DRIVE_ARG_IDX, "tx-low-drive" },
+ { 0, -1, NULL }
+};
+
+u16 cec_pin_rx_error_inj(struct cec_pin *pin)
+{
+ u16 cmd = CEC_ERROR_INJ_OP_ANY;
+
+ /* Only when 18 bits have been received do we have a valid cmd */
+ if (!(pin->error_inj[cmd] & CEC_ERROR_INJ_RX_MASK) &&
+ pin->rx_bit >= 18)
+ cmd = pin->rx_msg.msg[1];
+ return (pin->error_inj[cmd] & CEC_ERROR_INJ_RX_MASK) ? cmd :
+ CEC_ERROR_INJ_OP_ANY;
+}
+
+u16 cec_pin_tx_error_inj(struct cec_pin *pin)
+{
+ u16 cmd = CEC_ERROR_INJ_OP_ANY;
+
+ if (!(pin->error_inj[cmd] & CEC_ERROR_INJ_TX_MASK) &&
+ pin->tx_msg.len > 1)
+ cmd = pin->tx_msg.msg[1];
+ return (pin->error_inj[cmd] & CEC_ERROR_INJ_TX_MASK) ? cmd :
+ CEC_ERROR_INJ_OP_ANY;
+}
+
+bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line)
+{
+ static const char *delims = " \t\r";
+ struct cec_pin *pin = adap->pin;
+ unsigned int i;
+ bool has_pos = false;
+ char *p = line;
+ char *token;
+ char *comma;
+ u64 *error;
+ u8 *args;
+ bool has_op;
+ u32 op;
+ u8 mode;
+ u8 pos;
+ u8 v;
+
+ p = skip_spaces(p);
+ token = strsep(&p, delims);
+ if (!strcmp(token, "clear")) {
+ memset(pin->error_inj, 0, sizeof(pin->error_inj));
+ pin->rx_toggle = pin->tx_toggle = false;
+ pin->tx_ignore_nack_until_eom = false;
+ pin->tx_custom_pulse = false;
+ pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT;
+ pin->tx_custom_high_usecs = CEC_TIM_CUSTOM_DEFAULT;
+ return true;
+ }
+ if (!strcmp(token, "rx-clear")) {
+ for (i = 0; i <= CEC_ERROR_INJ_OP_ANY; i++)
+ pin->error_inj[i] &= ~CEC_ERROR_INJ_RX_MASK;
+ pin->rx_toggle = false;
+ return true;
+ }
+ if (!strcmp(token, "tx-clear")) {
+ for (i = 0; i <= CEC_ERROR_INJ_OP_ANY; i++)
+ pin->error_inj[i] &= ~CEC_ERROR_INJ_TX_MASK;
+ pin->tx_toggle = false;
+ pin->tx_ignore_nack_until_eom = false;
+ pin->tx_custom_pulse = false;
+ pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT;
+ pin->tx_custom_high_usecs = CEC_TIM_CUSTOM_DEFAULT;
+ return true;
+ }
+ if (!strcmp(token, "tx-ignore-nack-until-eom")) {
+ pin->tx_ignore_nack_until_eom = true;
+ return true;
+ }
+ if (!strcmp(token, "tx-custom-pulse")) {
+ pin->tx_custom_pulse = true;
+ cec_pin_start_timer(pin);
+ return true;
+ }
+ if (!p)
+ return false;
+
+ p = skip_spaces(p);
+ if (!strcmp(token, "tx-custom-low-usecs")) {
+ u32 usecs;
+
+ if (kstrtou32(p, 0, &usecs) || usecs > 10000000)
+ return false;
+ pin->tx_custom_low_usecs = usecs;
+ return true;
+ }
+ if (!strcmp(token, "tx-custom-high-usecs")) {
+ u32 usecs;
+
+ if (kstrtou32(p, 0, &usecs) || usecs > 10000000)
+ return false;
+ pin->tx_custom_high_usecs = usecs;
+ return true;
+ }
+
+ comma = strchr(token, ',');
+ if (comma)
+ *comma++ = '\0';
+ if (!strcmp(token, "any"))
+ op = CEC_ERROR_INJ_OP_ANY;
+ else if (!kstrtou8(token, 0, &v))
+ op = v;
+ else
+ return false;
+ mode = CEC_ERROR_INJ_MODE_ONCE;
+ if (comma) {
+ if (!strcmp(comma, "off"))
+ mode = CEC_ERROR_INJ_MODE_OFF;
+ else if (!strcmp(comma, "once"))
+ mode = CEC_ERROR_INJ_MODE_ONCE;
+ else if (!strcmp(comma, "always"))
+ mode = CEC_ERROR_INJ_MODE_ALWAYS;
+ else if (!strcmp(comma, "toggle"))
+ mode = CEC_ERROR_INJ_MODE_TOGGLE;
+ else
+ return false;
+ }
+
+ error = pin->error_inj + op;
+ args = pin->error_inj_args[op];
+ has_op = op <= 0xff;
+
+ token = strsep(&p, delims);
+ if (p) {
+ p = skip_spaces(p);
+ has_pos = !kstrtou8(p, 0, &pos);
+ }
+
+ if (!strcmp(token, "clear")) {
+ *error = 0;
+ return true;
+ }
+ if (!strcmp(token, "rx-clear")) {
+ *error &= ~CEC_ERROR_INJ_RX_MASK;
+ return true;
+ }
+ if (!strcmp(token, "tx-clear")) {
+ *error &= ~CEC_ERROR_INJ_TX_MASK;
+ return true;
+ }
+
+ for (i = 0; cec_error_inj_cmds[i].cmd; i++) {
+ const char *cmd = cec_error_inj_cmds[i].cmd;
+ unsigned int mode_offset;
+ u64 mode_mask;
+ int arg_idx;
+ bool is_bit_pos = true;
+
+ if (strcmp(token, cmd))
+ continue;
+
+ mode_offset = cec_error_inj_cmds[i].mode_offset;
+ mode_mask = CEC_ERROR_INJ_MODE_MASK << mode_offset;
+ arg_idx = cec_error_inj_cmds[i].arg_idx;
+
+ if (mode_offset == CEC_ERROR_INJ_RX_ARB_LOST_OFFSET ||
+ mode_offset == CEC_ERROR_INJ_TX_ADD_BYTES_OFFSET)
+ is_bit_pos = false;
+
+ if (mode_offset == CEC_ERROR_INJ_RX_ARB_LOST_OFFSET) {
+ if (has_op)
+ return false;
+ if (!has_pos)
+ pos = 0x0f;
+ }
+ if (arg_idx >= 0 && is_bit_pos) {
+ if (!has_pos || pos >= 160)
+ return false;
+ if (has_op && pos < 10 + 8)
+ return false;
+ /* Invalid bit position may not be the Ack bit */
+ if ((mode_offset == CEC_ERROR_INJ_TX_SHORT_BIT_OFFSET ||
+ mode_offset == CEC_ERROR_INJ_TX_LONG_BIT_OFFSET ||
+ mode_offset == CEC_ERROR_INJ_TX_CUSTOM_BIT_OFFSET) &&
+ (pos % 10) == 9)
+ return false;
+ }
+ *error &= ~mode_mask;
+ *error |= (u64)mode << mode_offset;
+ if (arg_idx >= 0)
+ args[arg_idx] = pos;
+ return true;
+ }
+ return false;
+}
+
+static void cec_pin_show_cmd(struct seq_file *sf, u32 cmd, u8 mode)
+{
+ if (cmd == CEC_ERROR_INJ_OP_ANY)
+ seq_puts(sf, "any,");
+ else
+ seq_printf(sf, "0x%02x,", cmd);
+ switch (mode) {
+ case CEC_ERROR_INJ_MODE_ONCE:
+ seq_puts(sf, "once ");
+ break;
+ case CEC_ERROR_INJ_MODE_ALWAYS:
+ seq_puts(sf, "always ");
+ break;
+ case CEC_ERROR_INJ_MODE_TOGGLE:
+ seq_puts(sf, "toggle ");
+ break;
+ default:
+ seq_puts(sf, "off ");
+ break;
+ }
+}
+
+int cec_pin_error_inj_show(struct cec_adapter *adap, struct seq_file *sf)
+{
+ struct cec_pin *pin = adap->pin;
+ unsigned int i, j;
+
+ seq_puts(sf, "# Clear error injections:\n");
+ seq_puts(sf, "# clear clear all rx and tx error injections\n");
+ seq_puts(sf, "# rx-clear clear all rx error injections\n");
+ seq_puts(sf, "# tx-clear clear all tx error injections\n");
+ seq_puts(sf, "# <op> clear clear all rx and tx error injections for <op>\n");
+ seq_puts(sf, "# <op> rx-clear clear all rx error injections for <op>\n");
+ seq_puts(sf, "# <op> tx-clear clear all tx error injections for <op>\n");
+ seq_puts(sf, "#\n");
+ seq_puts(sf, "# RX error injection:\n");
+ seq_puts(sf, "# <op>[,<mode>] rx-nack NACK the message instead of sending an ACK\n");
+ seq_puts(sf, "# <op>[,<mode>] rx-low-drive <bit> force a low-drive condition at this bit position\n");
+ seq_puts(sf, "# <op>[,<mode>] rx-add-byte add a spurious byte to the received CEC message\n");
+ seq_puts(sf, "# <op>[,<mode>] rx-remove-byte remove the last byte from the received CEC message\n");
+ seq_puts(sf, "# <op>[,<mode>] rx-arb-lost <poll> generate a POLL message to trigger an arbitration lost\n");
+ seq_puts(sf, "#\n");
+ seq_puts(sf, "# TX error injection settings:\n");
+ seq_puts(sf, "# tx-ignore-nack-until-eom ignore early NACKs until EOM\n");
+ seq_puts(sf, "# tx-custom-low-usecs <usecs> define the 'low' time for the custom pulse\n");
+ seq_puts(sf, "# tx-custom-high-usecs <usecs> define the 'high' time for the custom pulse\n");
+ seq_puts(sf, "# tx-custom-pulse transmit the custom pulse once the bus is idle\n");
+ seq_puts(sf, "#\n");
+ seq_puts(sf, "# TX error injection:\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-no-eom don't set the EOM bit\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-early-eom set the EOM bit one byte too soon\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-add-bytes <num> append <num> (1-255) spurious bytes to the message\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-remove-byte drop the last byte from the message\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-short-bit <bit> make this bit shorter than allowed\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-long-bit <bit> make this bit longer than allowed\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-custom-bit <bit> send the custom pulse instead of this bit\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-short-start send a start pulse that's too short\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-long-start send a start pulse that's too long\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-custom-start send the custom pulse instead of the start pulse\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-last-bit <bit> stop sending after this bit\n");
+ seq_puts(sf, "# <op>[,<mode>] tx-low-drive <bit> force a low-drive condition at this bit position\n");
+ seq_puts(sf, "#\n");
+ seq_puts(sf, "# <op> CEC message opcode (0-255) or 'any'\n");
+ seq_puts(sf, "# <mode> 'once' (default), 'always', 'toggle' or 'off'\n");
+ seq_puts(sf, "# <bit> CEC message bit (0-159)\n");
+ seq_puts(sf, "# 10 bits per 'byte': bits 0-7: data, bit 8: EOM, bit 9: ACK\n");
+ seq_puts(sf, "# <poll> CEC poll message used to test arbitration lost (0x00-0xff, default 0x0f)\n");
+ seq_puts(sf, "# <usecs> microseconds (0-10000000, default 1000)\n");
+
+ seq_puts(sf, "\nclear\n");
+
+ for (i = 0; i < ARRAY_SIZE(pin->error_inj); i++) {
+ u64 e = pin->error_inj[i];
+
+ for (j = 0; cec_error_inj_cmds[j].cmd; j++) {
+ const char *cmd = cec_error_inj_cmds[j].cmd;
+ unsigned int mode;
+ unsigned int mode_offset;
+ int arg_idx;
+
+ mode_offset = cec_error_inj_cmds[j].mode_offset;
+ arg_idx = cec_error_inj_cmds[j].arg_idx;
+ mode = (e >> mode_offset) & CEC_ERROR_INJ_MODE_MASK;
+ if (!mode)
+ continue;
+ cec_pin_show_cmd(sf, i, mode);
+ seq_puts(sf, cmd);
+ if (arg_idx >= 0)
+ seq_printf(sf, " %u",
+ pin->error_inj_args[i][arg_idx]);
+ seq_puts(sf, "\n");
+ }
+ }
+
+ if (pin->tx_ignore_nack_until_eom)
+ seq_puts(sf, "tx-ignore-nack-until-eom\n");
+ if (pin->tx_custom_pulse)
+ seq_puts(sf, "tx-custom-pulse\n");
+ if (pin->tx_custom_low_usecs != CEC_TIM_CUSTOM_DEFAULT)
+ seq_printf(sf, "tx-custom-low-usecs %u\n",
+ pin->tx_custom_low_usecs);
+ if (pin->tx_custom_high_usecs != CEC_TIM_CUSTOM_DEFAULT)
+ seq_printf(sf, "tx-custom-high-usecs %u\n",
+ pin->tx_custom_high_usecs);
+ return 0;
+}
diff --git a/drivers/media/cec/cec-pin-priv.h b/drivers/media/cec/cec-pin-priv.h
index 7d0def1..f423db8 100644
--- a/drivers/media/cec/cec-pin-priv.h
+++ b/drivers/media/cec/cec-pin-priv.h
@@ -1,20 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
/*
* cec-pin-priv.h - internal cec-pin header
*
* Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
- *
- * This program is free software; you may redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * 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.
*/
#ifndef LINUX_CEC_PIN_PRIV_H
@@ -40,14 +28,30 @@ enum cec_pin_state {
CEC_ST_TX_START_BIT_LOW,
/* Drive CEC high for the start bit */
CEC_ST_TX_START_BIT_HIGH,
+ /* Generate a start bit period that is too short */
+ CEC_ST_TX_START_BIT_HIGH_SHORT,
+ /* Generate a start bit period that is too long */
+ CEC_ST_TX_START_BIT_HIGH_LONG,
+ /* Drive CEC low for the start bit using the custom timing */
+ CEC_ST_TX_START_BIT_LOW_CUSTOM,
+ /* Drive CEC high for the start bit using the custom timing */
+ CEC_ST_TX_START_BIT_HIGH_CUSTOM,
/* Drive CEC low for the 0 bit */
CEC_ST_TX_DATA_BIT_0_LOW,
/* Drive CEC high for the 0 bit */
CEC_ST_TX_DATA_BIT_0_HIGH,
+ /* Generate a bit period that is too short */
+ CEC_ST_TX_DATA_BIT_0_HIGH_SHORT,
+ /* Generate a bit period that is too long */
+ CEC_ST_TX_DATA_BIT_0_HIGH_LONG,
/* Drive CEC low for the 1 bit */
CEC_ST_TX_DATA_BIT_1_LOW,
/* Drive CEC high for the 1 bit */
CEC_ST_TX_DATA_BIT_1_HIGH,
+ /* Generate a bit period that is too short */
+ CEC_ST_TX_DATA_BIT_1_HIGH_SHORT,
+ /* Generate a bit period that is too long */
+ CEC_ST_TX_DATA_BIT_1_HIGH_LONG,
/*
* Wait for start of sample time to check for Ack bit or first
* four initiator bits to check for Arbitration Lost.
@@ -55,6 +59,20 @@ enum cec_pin_state {
CEC_ST_TX_DATA_BIT_1_HIGH_PRE_SAMPLE,
/* Wait for end of bit period after sampling */
CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE,
+ /* Generate a bit period that is too short */
+ CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE_SHORT,
+ /* Generate a bit period that is too long */
+ CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE_LONG,
+ /* Drive CEC low for a data bit using the custom timing */
+ CEC_ST_TX_DATA_BIT_LOW_CUSTOM,
+ /* Drive CEC high for a data bit using the custom timing */
+ CEC_ST_TX_DATA_BIT_HIGH_CUSTOM,
+ /* Drive CEC low for a standalone pulse using the custom timing */
+ CEC_ST_TX_PULSE_LOW_CUSTOM,
+ /* Drive CEC high for a standalone pulse using the custom timing */
+ CEC_ST_TX_PULSE_HIGH_CUSTOM,
+ /* Start low drive */
+ CEC_ST_TX_LOW_DRIVE,
/* Rx states */
@@ -66,8 +84,8 @@ enum cec_pin_state {
CEC_ST_RX_DATA_SAMPLE,
/* Wait for earliest end of bit period after sampling */
CEC_ST_RX_DATA_POST_SAMPLE,
- /* Wait for CEC to go high (i.e. end of bit period */
- CEC_ST_RX_DATA_HIGH,
+ /* Wait for CEC to go low (i.e. end of bit period) */
+ CEC_ST_RX_DATA_WAIT_FOR_LOW,
/* Drive CEC low to send 0 Ack bit */
CEC_ST_RX_ACK_LOW,
/* End of 0 Ack time, wait for earliest end of bit period */
@@ -76,9 +94,9 @@ enum cec_pin_state {
CEC_ST_RX_ACK_HIGH_POST,
/* Wait for earliest end of bit period and end of message */
CEC_ST_RX_ACK_FINISH,
-
/* Start low drive */
- CEC_ST_LOW_DRIVE,
+ CEC_ST_RX_LOW_DRIVE,
+
/* Monitor pin using interrupts */
CEC_ST_RX_IRQ,
@@ -86,7 +104,58 @@ enum cec_pin_state {
CEC_PIN_STATES
};
-#define CEC_NUM_PIN_EVENTS 128
+/* Error Injection */
+
+/* Error injection modes */
+#define CEC_ERROR_INJ_MODE_OFF 0
+#define CEC_ERROR_INJ_MODE_ONCE 1
+#define CEC_ERROR_INJ_MODE_ALWAYS 2
+#define CEC_ERROR_INJ_MODE_TOGGLE 3
+#define CEC_ERROR_INJ_MODE_MASK 3ULL
+
+/* Receive error injection options */
+#define CEC_ERROR_INJ_RX_NACK_OFFSET 0
+#define CEC_ERROR_INJ_RX_LOW_DRIVE_OFFSET 2
+#define CEC_ERROR_INJ_RX_ADD_BYTE_OFFSET 4
+#define CEC_ERROR_INJ_RX_REMOVE_BYTE_OFFSET 6
+#define CEC_ERROR_INJ_RX_ARB_LOST_OFFSET 8
+#define CEC_ERROR_INJ_RX_MASK 0xffffULL
+
+/* Transmit error injection options */
+#define CEC_ERROR_INJ_TX_NO_EOM_OFFSET 16
+#define CEC_ERROR_INJ_TX_EARLY_EOM_OFFSET 18
+#define CEC_ERROR_INJ_TX_SHORT_BIT_OFFSET 20
+#define CEC_ERROR_INJ_TX_LONG_BIT_OFFSET 22
+#define CEC_ERROR_INJ_TX_CUSTOM_BIT_OFFSET 24
+#define CEC_ERROR_INJ_TX_SHORT_START_OFFSET 26
+#define CEC_ERROR_INJ_TX_LONG_START_OFFSET 28
+#define CEC_ERROR_INJ_TX_CUSTOM_START_OFFSET 30
+#define CEC_ERROR_INJ_TX_LAST_BIT_OFFSET 32
+#define CEC_ERROR_INJ_TX_ADD_BYTES_OFFSET 34
+#define CEC_ERROR_INJ_TX_REMOVE_BYTE_OFFSET 36
+#define CEC_ERROR_INJ_TX_LOW_DRIVE_OFFSET 38
+#define CEC_ERROR_INJ_TX_MASK 0xffffffffffff0000ULL
+
+#define CEC_ERROR_INJ_RX_LOW_DRIVE_ARG_IDX 0
+#define CEC_ERROR_INJ_RX_ARB_LOST_ARG_IDX 1
+
+#define CEC_ERROR_INJ_TX_ADD_BYTES_ARG_IDX 2
+#define CEC_ERROR_INJ_TX_SHORT_BIT_ARG_IDX 3
+#define CEC_ERROR_INJ_TX_LONG_BIT_ARG_IDX 4
+#define CEC_ERROR_INJ_TX_CUSTOM_BIT_ARG_IDX 5
+#define CEC_ERROR_INJ_TX_LAST_BIT_ARG_IDX 6
+#define CEC_ERROR_INJ_TX_LOW_DRIVE_ARG_IDX 7
+#define CEC_ERROR_INJ_NUM_ARGS 8
+
+/* Special CEC op values */
+#define CEC_ERROR_INJ_OP_ANY 0x00000100
+
+/* The default for the low/high time of the custom pulse */
+#define CEC_TIM_CUSTOM_DEFAULT 1000
+
+#define CEC_NUM_PIN_EVENTS 128
+#define CEC_PIN_EVENT_FL_IS_HIGH (1 << 0)
+#define CEC_PIN_EVENT_FL_DROPPED (1 << 1)
#define CEC_PIN_IRQ_UNCHANGED 0
#define CEC_PIN_IRQ_DISABLE 1
@@ -110,24 +179,63 @@ struct cec_pin {
u32 tx_bit;
bool tx_nacked;
u32 tx_signal_free_time;
+ bool tx_toggle;
struct cec_msg rx_msg;
u32 rx_bit;
+ bool rx_toggle;
+ u32 rx_start_bit_low_too_short_cnt;
+ u64 rx_start_bit_low_too_short_ts;
+ u32 rx_start_bit_low_too_short_delta;
+ u32 rx_start_bit_too_short_cnt;
+ u64 rx_start_bit_too_short_ts;
+ u32 rx_start_bit_too_short_delta;
+ u32 rx_start_bit_too_long_cnt;
+ u32 rx_data_bit_too_short_cnt;
+ u64 rx_data_bit_too_short_ts;
+ u32 rx_data_bit_too_short_delta;
+ u32 rx_data_bit_too_long_cnt;
+ u32 rx_low_drive_cnt;
struct cec_msg work_rx_msg;
u8 work_tx_status;
ktime_t work_tx_ts;
atomic_t work_irq_change;
- atomic_t work_pin_events;
+ atomic_t work_pin_num_events;
unsigned int work_pin_events_wr;
unsigned int work_pin_events_rd;
ktime_t work_pin_ts[CEC_NUM_PIN_EVENTS];
- bool work_pin_is_high[CEC_NUM_PIN_EVENTS];
+ u8 work_pin_events[CEC_NUM_PIN_EVENTS];
+ bool work_pin_events_dropped;
+ u32 work_pin_events_dropped_cnt;
ktime_t timer_ts;
u32 timer_cnt;
u32 timer_100ms_overruns;
u32 timer_300ms_overruns;
u32 timer_max_overrun;
u32 timer_sum_overrun;
+
+ u32 tx_custom_low_usecs;
+ u32 tx_custom_high_usecs;
+ bool tx_ignore_nack_until_eom;
+ bool tx_custom_pulse;
+ bool tx_generated_poll;
+ bool tx_post_eom;
+ u8 tx_extra_bytes;
+ u32 tx_low_drive_cnt;
+#ifdef CONFIG_CEC_PIN_ERROR_INJ
+ u64 error_inj[CEC_ERROR_INJ_OP_ANY + 1];
+ u8 error_inj_args[CEC_ERROR_INJ_OP_ANY + 1][CEC_ERROR_INJ_NUM_ARGS];
+#endif
};
+void cec_pin_start_timer(struct cec_pin *pin);
+
+#ifdef CONFIG_CEC_PIN_ERROR_INJ
+bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line);
+int cec_pin_error_inj_show(struct cec_adapter *adap, struct seq_file *sf);
+
+u16 cec_pin_rx_error_inj(struct cec_pin *pin);
+u16 cec_pin_tx_error_inj(struct cec_pin *pin);
+#endif
+
#endif
diff --git a/drivers/media/cec/cec-pin.c b/drivers/media/cec/cec-pin.c
index b48dfe8..fafe1eb 100644
--- a/drivers/media/cec/cec-pin.c
+++ b/drivers/media/cec/cec-pin.c
@@ -1,18 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
- *
- * This program is free software; you may redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * 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.
*/
#include <linux/delay.h>
@@ -51,11 +39,29 @@
#define CEC_TIM_IDLE_SAMPLE 1000
/* when processing the start bit, sample twice per millisecond */
#define CEC_TIM_START_BIT_SAMPLE 500
-/* when polling for a state change, sample once every 50 micoseconds */
+/* when polling for a state change, sample once every 50 microseconds */
#define CEC_TIM_SAMPLE 50
#define CEC_TIM_LOW_DRIVE_ERROR (1.5 * CEC_TIM_DATA_BIT_TOTAL)
+/*
+ * Total data bit time that is too short/long for a valid bit,
+ * used for error injection.
+ */
+#define CEC_TIM_DATA_BIT_TOTAL_SHORT 1800
+#define CEC_TIM_DATA_BIT_TOTAL_LONG 2900
+
+/*
+ * Total start bit time that is too short/long for a valid bit,
+ * used for error injection.
+ */
+#define CEC_TIM_START_BIT_TOTAL_SHORT 4100
+#define CEC_TIM_START_BIT_TOTAL_LONG 5000
+
+/* Data bits are 0-7, EOM is bit 8 and ACK is bit 9 */
+#define EOM_BIT 8
+#define ACK_BIT 9
+
struct cec_state {
const char * const name;
unsigned int usecs;
@@ -68,17 +74,32 @@ static const struct cec_state states[CEC_PIN_STATES] = {
{ "Tx Wait for High", CEC_TIM_IDLE_SAMPLE },
{ "Tx Start Bit Low", CEC_TIM_START_BIT_LOW },
{ "Tx Start Bit High", CEC_TIM_START_BIT_TOTAL - CEC_TIM_START_BIT_LOW },
+ { "Tx Start Bit High Short", CEC_TIM_START_BIT_TOTAL_SHORT - CEC_TIM_START_BIT_LOW },
+ { "Tx Start Bit High Long", CEC_TIM_START_BIT_TOTAL_LONG - CEC_TIM_START_BIT_LOW },
+ { "Tx Start Bit Low Custom", 0 },
+ { "Tx Start Bit High Custom", 0 },
{ "Tx Data 0 Low", CEC_TIM_DATA_BIT_0_LOW },
{ "Tx Data 0 High", CEC_TIM_DATA_BIT_TOTAL - CEC_TIM_DATA_BIT_0_LOW },
+ { "Tx Data 0 High Short", CEC_TIM_DATA_BIT_TOTAL_SHORT - CEC_TIM_DATA_BIT_0_LOW },
+ { "Tx Data 0 High Long", CEC_TIM_DATA_BIT_TOTAL_LONG - CEC_TIM_DATA_BIT_0_LOW },
{ "Tx Data 1 Low", CEC_TIM_DATA_BIT_1_LOW },
{ "Tx Data 1 High", CEC_TIM_DATA_BIT_TOTAL - CEC_TIM_DATA_BIT_1_LOW },
- { "Tx Data 1 Pre Sample", CEC_TIM_DATA_BIT_SAMPLE - CEC_TIM_DATA_BIT_1_LOW },
- { "Tx Data 1 Post Sample", CEC_TIM_DATA_BIT_TOTAL - CEC_TIM_DATA_BIT_SAMPLE },
+ { "Tx Data 1 High Short", CEC_TIM_DATA_BIT_TOTAL_SHORT - CEC_TIM_DATA_BIT_1_LOW },
+ { "Tx Data 1 High Long", CEC_TIM_DATA_BIT_TOTAL_LONG - CEC_TIM_DATA_BIT_1_LOW },
+ { "Tx Data 1 High Pre Sample", CEC_TIM_DATA_BIT_SAMPLE - CEC_TIM_DATA_BIT_1_LOW },
+ { "Tx Data 1 High Post Sample", CEC_TIM_DATA_BIT_TOTAL - CEC_TIM_DATA_BIT_SAMPLE },
+ { "Tx Data 1 High Post Sample Short", CEC_TIM_DATA_BIT_TOTAL_SHORT - CEC_TIM_DATA_BIT_SAMPLE },
+ { "Tx Data 1 High Post Sample Long", CEC_TIM_DATA_BIT_TOTAL_LONG - CEC_TIM_DATA_BIT_SAMPLE },
+ { "Tx Data Bit Low Custom", 0 },
+ { "Tx Data Bit High Custom", 0 },
+ { "Tx Pulse Low Custom", 0 },
+ { "Tx Pulse High Custom", 0 },
+ { "Tx Low Drive", CEC_TIM_LOW_DRIVE_ERROR },
{ "Rx Start Bit Low", CEC_TIM_SAMPLE },
{ "Rx Start Bit High", CEC_TIM_SAMPLE },
{ "Rx Data Sample", CEC_TIM_DATA_BIT_SAMPLE },
{ "Rx Data Post Sample", CEC_TIM_DATA_BIT_HIGH - CEC_TIM_DATA_BIT_SAMPLE },
- { "Rx Data High", CEC_TIM_SAMPLE },
+ { "Rx Data Wait for Low", CEC_TIM_SAMPLE },
{ "Rx Ack Low", CEC_TIM_DATA_BIT_0_LOW },
{ "Rx Ack Low Post", CEC_TIM_DATA_BIT_HIGH - CEC_TIM_DATA_BIT_0_LOW },
{ "Rx Ack High Post", CEC_TIM_DATA_BIT_HIGH },
@@ -93,12 +114,21 @@ static void cec_pin_update(struct cec_pin *pin, bool v, bool force)
return;
pin->adap->cec_pin_is_high = v;
- if (atomic_read(&pin->work_pin_events) < CEC_NUM_PIN_EVENTS) {
- pin->work_pin_is_high[pin->work_pin_events_wr] = v;
+ if (atomic_read(&pin->work_pin_num_events) < CEC_NUM_PIN_EVENTS) {
+ u8 ev = v;
+
+ if (pin->work_pin_events_dropped) {
+ pin->work_pin_events_dropped = false;
+ v |= CEC_PIN_EVENT_FL_DROPPED;
+ }
+ pin->work_pin_events[pin->work_pin_events_wr] = ev;
pin->work_pin_ts[pin->work_pin_events_wr] = ktime_get();
pin->work_pin_events_wr =
(pin->work_pin_events_wr + 1) % CEC_NUM_PIN_EVENTS;
- atomic_inc(&pin->work_pin_events);
+ atomic_inc(&pin->work_pin_num_events);
+ } else {
+ pin->work_pin_events_dropped = true;
+ pin->work_pin_events_dropped_cnt++;
}
wake_up_interruptible(&pin->kthread_waitq);
}
@@ -123,6 +153,173 @@ static bool cec_pin_high(struct cec_pin *pin)
return cec_pin_read(pin);
}
+static bool rx_error_inj(struct cec_pin *pin, unsigned int mode_offset,
+ int arg_idx, u8 *arg)
+{
+#ifdef CONFIG_CEC_PIN_ERROR_INJ
+ u16 cmd = cec_pin_rx_error_inj(pin);
+ u64 e = pin->error_inj[cmd];
+ unsigned int mode = (e >> mode_offset) & CEC_ERROR_INJ_MODE_MASK;
+
+ if (arg_idx >= 0) {
+ u8 pos = pin->error_inj_args[cmd][arg_idx];
+
+ if (arg)
+ *arg = pos;
+ else if (pos != pin->rx_bit)
+ return false;
+ }
+
+ switch (mode) {
+ case CEC_ERROR_INJ_MODE_ONCE:
+ pin->error_inj[cmd] &=
+ ~(CEC_ERROR_INJ_MODE_MASK << mode_offset);
+ return true;
+ case CEC_ERROR_INJ_MODE_ALWAYS:
+ return true;
+ case CEC_ERROR_INJ_MODE_TOGGLE:
+ return pin->rx_toggle;
+ default:
+ return false;
+ }
+#else
+ return false;
+#endif
+}
+
+static bool rx_nack(struct cec_pin *pin)
+{
+ return rx_error_inj(pin, CEC_ERROR_INJ_RX_NACK_OFFSET, -1, NULL);
+}
+
+static bool rx_low_drive(struct cec_pin *pin)
+{
+ return rx_error_inj(pin, CEC_ERROR_INJ_RX_LOW_DRIVE_OFFSET,
+ CEC_ERROR_INJ_RX_LOW_DRIVE_ARG_IDX, NULL);
+}
+
+static bool rx_add_byte(struct cec_pin *pin)
+{
+ return rx_error_inj(pin, CEC_ERROR_INJ_RX_ADD_BYTE_OFFSET, -1, NULL);
+}
+
+static bool rx_remove_byte(struct cec_pin *pin)
+{
+ return rx_error_inj(pin, CEC_ERROR_INJ_RX_REMOVE_BYTE_OFFSET, -1, NULL);
+}
+
+static bool rx_arb_lost(struct cec_pin *pin, u8 *poll)
+{
+ return pin->tx_msg.len == 0 &&
+ rx_error_inj(pin, CEC_ERROR_INJ_RX_ARB_LOST_OFFSET,
+ CEC_ERROR_INJ_RX_ARB_LOST_ARG_IDX, poll);
+}
+
+static bool tx_error_inj(struct cec_pin *pin, unsigned int mode_offset,
+ int arg_idx, u8 *arg)
+{
+#ifdef CONFIG_CEC_PIN_ERROR_INJ
+ u16 cmd = cec_pin_tx_error_inj(pin);
+ u64 e = pin->error_inj[cmd];
+ unsigned int mode = (e >> mode_offset) & CEC_ERROR_INJ_MODE_MASK;
+
+ if (arg_idx >= 0) {
+ u8 pos = pin->error_inj_args[cmd][arg_idx];
+
+ if (arg)
+ *arg = pos;
+ else if (pos != pin->tx_bit)
+ return false;
+ }
+
+ switch (mode) {
+ case CEC_ERROR_INJ_MODE_ONCE:
+ pin->error_inj[cmd] &=
+ ~(CEC_ERROR_INJ_MODE_MASK << mode_offset);
+ return true;
+ case CEC_ERROR_INJ_MODE_ALWAYS:
+ return true;
+ case CEC_ERROR_INJ_MODE_TOGGLE:
+ return pin->tx_toggle;
+ default:
+ return false;
+ }
+#else
+ return false;
+#endif
+}
+
+static bool tx_no_eom(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_NO_EOM_OFFSET, -1, NULL);
+}
+
+static bool tx_early_eom(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_EARLY_EOM_OFFSET, -1, NULL);
+}
+
+static bool tx_short_bit(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_SHORT_BIT_OFFSET,
+ CEC_ERROR_INJ_TX_SHORT_BIT_ARG_IDX, NULL);
+}
+
+static bool tx_long_bit(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_LONG_BIT_OFFSET,
+ CEC_ERROR_INJ_TX_LONG_BIT_ARG_IDX, NULL);
+}
+
+static bool tx_custom_bit(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_CUSTOM_BIT_OFFSET,
+ CEC_ERROR_INJ_TX_CUSTOM_BIT_ARG_IDX, NULL);
+}
+
+static bool tx_short_start(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_SHORT_START_OFFSET, -1, NULL);
+}
+
+static bool tx_long_start(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_LONG_START_OFFSET, -1, NULL);
+}
+
+static bool tx_custom_start(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_CUSTOM_START_OFFSET,
+ -1, NULL);
+}
+
+static bool tx_last_bit(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_LAST_BIT_OFFSET,
+ CEC_ERROR_INJ_TX_LAST_BIT_ARG_IDX, NULL);
+}
+
+static u8 tx_add_bytes(struct cec_pin *pin)
+{
+ u8 bytes;
+
+ if (tx_error_inj(pin, CEC_ERROR_INJ_TX_ADD_BYTES_OFFSET,
+ CEC_ERROR_INJ_TX_ADD_BYTES_ARG_IDX, &bytes))
+ return bytes;
+ return 0;
+}
+
+static bool tx_remove_byte(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_REMOVE_BYTE_OFFSET, -1, NULL);
+}
+
+static bool tx_low_drive(struct cec_pin *pin)
+{
+ return tx_error_inj(pin, CEC_ERROR_INJ_TX_LOW_DRIVE_OFFSET,
+ CEC_ERROR_INJ_TX_LOW_DRIVE_ARG_IDX, NULL);
+}
+
static void cec_pin_to_idle(struct cec_pin *pin)
{
/*
@@ -132,8 +329,16 @@ static void cec_pin_to_idle(struct cec_pin *pin)
pin->rx_bit = pin->tx_bit = 0;
pin->rx_msg.len = 0;
memset(pin->rx_msg.msg, 0, sizeof(pin->rx_msg.msg));
- pin->state = CEC_ST_IDLE;
pin->ts = ns_to_ktime(0);
+ pin->tx_generated_poll = false;
+ pin->tx_post_eom = false;
+ if (pin->state >= CEC_ST_TX_WAIT &&
+ pin->state <= CEC_ST_TX_LOW_DRIVE)
+ pin->tx_toggle ^= 1;
+ if (pin->state >= CEC_ST_RX_START_BIT_LOW &&
+ pin->state <= CEC_ST_RX_LOW_DRIVE)
+ pin->rx_toggle ^= 1;
+ pin->state = CEC_ST_IDLE;
}
/*
@@ -174,42 +379,109 @@ static void cec_pin_tx_states(struct cec_pin *pin, ktime_t ts)
break;
case CEC_ST_TX_START_BIT_LOW:
- pin->state = CEC_ST_TX_START_BIT_HIGH;
+ if (tx_short_start(pin)) {
+ /*
+ * Error Injection: send an invalid (too short)
+ * start pulse.
+ */
+ pin->state = CEC_ST_TX_START_BIT_HIGH_SHORT;
+ } else if (tx_long_start(pin)) {
+ /*
+ * Error Injection: send an invalid (too long)
+ * start pulse.
+ */
+ pin->state = CEC_ST_TX_START_BIT_HIGH_LONG;
+ } else {
+ pin->state = CEC_ST_TX_START_BIT_HIGH;
+ }
+ /* Generate start bit */
+ cec_pin_high(pin);
+ break;
+
+ case CEC_ST_TX_START_BIT_LOW_CUSTOM:
+ pin->state = CEC_ST_TX_START_BIT_HIGH_CUSTOM;
/* Generate start bit */
cec_pin_high(pin);
break;
case CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE:
- /* If the read value is 1, then all is OK */
- if (!cec_pin_read(pin)) {
+ case CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE_SHORT:
+ case CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE_LONG:
+ if (pin->tx_nacked) {
+ cec_pin_to_idle(pin);
+ pin->tx_msg.len = 0;
+ if (pin->tx_generated_poll)
+ break;
+ pin->work_tx_ts = ts;
+ pin->work_tx_status = CEC_TX_STATUS_NACK;
+ wake_up_interruptible(&pin->kthread_waitq);
+ break;
+ }
+ /* fall through */
+ case CEC_ST_TX_DATA_BIT_0_HIGH:
+ case CEC_ST_TX_DATA_BIT_0_HIGH_SHORT:
+ case CEC_ST_TX_DATA_BIT_0_HIGH_LONG:
+ case CEC_ST_TX_DATA_BIT_1_HIGH:
+ case CEC_ST_TX_DATA_BIT_1_HIGH_SHORT:
+ case CEC_ST_TX_DATA_BIT_1_HIGH_LONG:
+ /*
+ * If the read value is 1, then all is OK, otherwise we have a
+ * low drive condition.
+ *
+ * Special case: when we generate a poll message due to an
+ * Arbitration Lost error injection, then ignore this since
+ * the pin can actually be low in that case.
+ */
+ if (!cec_pin_read(pin) && !pin->tx_generated_poll) {
/*
* It's 0, so someone detected an error and pulled the
* line low for 1.5 times the nominal bit period.
*/
pin->tx_msg.len = 0;
+ pin->state = CEC_ST_TX_WAIT_FOR_HIGH;
pin->work_tx_ts = ts;
pin->work_tx_status = CEC_TX_STATUS_LOW_DRIVE;
- pin->state = CEC_ST_TX_WAIT_FOR_HIGH;
+ pin->tx_low_drive_cnt++;
wake_up_interruptible(&pin->kthread_waitq);
break;
}
- if (pin->tx_nacked) {
+ /* fall through */
+ case CEC_ST_TX_DATA_BIT_HIGH_CUSTOM:
+ if (tx_last_bit(pin)) {
+ /* Error Injection: just stop sending after this bit */
cec_pin_to_idle(pin);
pin->tx_msg.len = 0;
+ if (pin->tx_generated_poll)
+ break;
pin->work_tx_ts = ts;
- pin->work_tx_status = CEC_TX_STATUS_NACK;
+ pin->work_tx_status = CEC_TX_STATUS_OK;
wake_up_interruptible(&pin->kthread_waitq);
break;
}
- /* fall through */
- case CEC_ST_TX_DATA_BIT_0_HIGH:
- case CEC_ST_TX_DATA_BIT_1_HIGH:
pin->tx_bit++;
/* fall through */
case CEC_ST_TX_START_BIT_HIGH:
- if (pin->tx_bit / 10 >= pin->tx_msg.len) {
+ case CEC_ST_TX_START_BIT_HIGH_SHORT:
+ case CEC_ST_TX_START_BIT_HIGH_LONG:
+ case CEC_ST_TX_START_BIT_HIGH_CUSTOM:
+ if (tx_low_drive(pin)) {
+ /* Error injection: go to low drive */
+ cec_pin_low(pin);
+ pin->state = CEC_ST_TX_LOW_DRIVE;
+ pin->tx_msg.len = 0;
+ if (pin->tx_generated_poll)
+ break;
+ pin->work_tx_ts = ts;
+ pin->work_tx_status = CEC_TX_STATUS_LOW_DRIVE;
+ pin->tx_low_drive_cnt++;
+ wake_up_interruptible(&pin->kthread_waitq);
+ break;
+ }
+ if (pin->tx_bit / 10 >= pin->tx_msg.len + pin->tx_extra_bytes) {
cec_pin_to_idle(pin);
pin->tx_msg.len = 0;
+ if (pin->tx_generated_poll)
+ break;
pin->work_tx_ts = ts;
pin->work_tx_status = CEC_TX_STATUS_OK;
wake_up_interruptible(&pin->kthread_waitq);
@@ -217,39 +489,82 @@ static void cec_pin_tx_states(struct cec_pin *pin, ktime_t ts)
}
switch (pin->tx_bit % 10) {
- default:
- v = pin->tx_msg.msg[pin->tx_bit / 10] &
- (1 << (7 - (pin->tx_bit % 10)));
+ default: {
+ /*
+ * In the CEC_ERROR_INJ_TX_ADD_BYTES case we transmit
+ * extra bytes, so pin->tx_bit / 10 can become >= 16.
+ * Generate bit values for those extra bytes instead
+ * of reading them from the transmit buffer.
+ */
+ unsigned int idx = (pin->tx_bit / 10);
+ u8 val = idx;
+
+ if (idx < pin->tx_msg.len)
+ val = pin->tx_msg.msg[idx];
+ v = val & (1 << (7 - (pin->tx_bit % 10)));
+
pin->state = v ? CEC_ST_TX_DATA_BIT_1_LOW :
- CEC_ST_TX_DATA_BIT_0_LOW;
+ CEC_ST_TX_DATA_BIT_0_LOW;
break;
- case 8:
- v = pin->tx_bit / 10 == pin->tx_msg.len - 1;
+ }
+ case EOM_BIT: {
+ unsigned int tot_len = pin->tx_msg.len +
+ pin->tx_extra_bytes;
+ unsigned int tx_byte_idx = pin->tx_bit / 10;
+
+ v = !pin->tx_post_eom && tx_byte_idx == tot_len - 1;
+ if (tot_len > 1 && tx_byte_idx == tot_len - 2 &&
+ tx_early_eom(pin)) {
+ /* Error injection: set EOM one byte early */
+ v = true;
+ pin->tx_post_eom = true;
+ } else if (v && tx_no_eom(pin)) {
+ /* Error injection: no EOM */
+ v = false;
+ }
pin->state = v ? CEC_ST_TX_DATA_BIT_1_LOW :
- CEC_ST_TX_DATA_BIT_0_LOW;
+ CEC_ST_TX_DATA_BIT_0_LOW;
break;
- case 9:
+ }
+ case ACK_BIT:
pin->state = CEC_ST_TX_DATA_BIT_1_LOW;
break;
}
+ if (tx_custom_bit(pin))
+ pin->state = CEC_ST_TX_DATA_BIT_LOW_CUSTOM;
cec_pin_low(pin);
break;
case CEC_ST_TX_DATA_BIT_0_LOW:
case CEC_ST_TX_DATA_BIT_1_LOW:
v = pin->state == CEC_ST_TX_DATA_BIT_1_LOW;
- pin->state = v ? CEC_ST_TX_DATA_BIT_1_HIGH :
- CEC_ST_TX_DATA_BIT_0_HIGH;
- is_ack_bit = pin->tx_bit % 10 == 9;
- if (v && (pin->tx_bit < 4 || is_ack_bit))
+ is_ack_bit = pin->tx_bit % 10 == ACK_BIT;
+ if (v && (pin->tx_bit < 4 || is_ack_bit)) {
pin->state = CEC_ST_TX_DATA_BIT_1_HIGH_PRE_SAMPLE;
+ } else if (!is_ack_bit && tx_short_bit(pin)) {
+ /* Error Injection: send an invalid (too short) bit */
+ pin->state = v ? CEC_ST_TX_DATA_BIT_1_HIGH_SHORT :
+ CEC_ST_TX_DATA_BIT_0_HIGH_SHORT;
+ } else if (!is_ack_bit && tx_long_bit(pin)) {
+ /* Error Injection: send an invalid (too long) bit */
+ pin->state = v ? CEC_ST_TX_DATA_BIT_1_HIGH_LONG :
+ CEC_ST_TX_DATA_BIT_0_HIGH_LONG;
+ } else {
+ pin->state = v ? CEC_ST_TX_DATA_BIT_1_HIGH :
+ CEC_ST_TX_DATA_BIT_0_HIGH;
+ }
+ cec_pin_high(pin);
+ break;
+
+ case CEC_ST_TX_DATA_BIT_LOW_CUSTOM:
+ pin->state = CEC_ST_TX_DATA_BIT_HIGH_CUSTOM;
cec_pin_high(pin);
break;
case CEC_ST_TX_DATA_BIT_1_HIGH_PRE_SAMPLE:
/* Read the CEC value at the sample time */
v = cec_pin_read(pin);
- is_ack_bit = pin->tx_bit % 10 == 9;
+ is_ack_bit = pin->tx_bit % 10 == ACK_BIT;
/*
* If v == 0 and we're within the first 4 bits
* of the initiator, then someone else started
@@ -258,7 +573,7 @@ static void cec_pin_tx_states(struct cec_pin *pin, ktime_t ts)
* transmitter has more leading 0 bits in the
* initiator).
*/
- if (!v && !is_ack_bit) {
+ if (!v && !is_ack_bit && !pin->tx_generated_poll) {
pin->tx_msg.len = 0;
pin->work_tx_ts = ts;
pin->work_tx_status = CEC_TX_STATUS_ARB_LOST;
@@ -267,18 +582,27 @@ static void cec_pin_tx_states(struct cec_pin *pin, ktime_t ts)
pin->tx_bit = 0;
memset(pin->rx_msg.msg, 0, sizeof(pin->rx_msg.msg));
pin->rx_msg.msg[0] = pin->tx_msg.msg[0];
- pin->rx_msg.msg[0] &= ~(1 << (7 - pin->rx_bit));
+ pin->rx_msg.msg[0] &= (0xff << (8 - pin->rx_bit));
pin->rx_msg.len = 0;
+ pin->ts = ktime_sub_us(ts, CEC_TIM_DATA_BIT_SAMPLE);
pin->state = CEC_ST_RX_DATA_POST_SAMPLE;
pin->rx_bit++;
break;
}
pin->state = CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE;
+ if (!is_ack_bit && tx_short_bit(pin)) {
+ /* Error Injection: send an invalid (too short) bit */
+ pin->state = CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE_SHORT;
+ } else if (!is_ack_bit && tx_long_bit(pin)) {
+ /* Error Injection: send an invalid (too long) bit */
+ pin->state = CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE_LONG;
+ }
if (!is_ack_bit)
break;
/* Was the message ACKed? */
ack = cec_msg_is_broadcast(&pin->tx_msg) ? v : !v;
- if (!ack) {
+ if (!ack && !pin->tx_ignore_nack_until_eom &&
+ pin->tx_bit / 10 < pin->tx_msg.len && !pin->tx_post_eom) {
/*
* Note: the CEC spec is ambiguous regarding
* what action to take when a NACK appears
@@ -295,6 +619,15 @@ static void cec_pin_tx_states(struct cec_pin *pin, ktime_t ts)
}
break;
+ case CEC_ST_TX_PULSE_LOW_CUSTOM:
+ cec_pin_high(pin);
+ pin->state = CEC_ST_TX_PULSE_HIGH_CUSTOM;
+ break;
+
+ case CEC_ST_TX_PULSE_HIGH_CUSTOM:
+ cec_pin_to_idle(pin);
+ break;
+
default:
break;
}
@@ -322,6 +655,7 @@ static void cec_pin_rx_states(struct cec_pin *pin, ktime_t ts)
bool ack;
bool bcast, for_us;
u8 dest;
+ u8 poll;
switch (pin->state) {
/* Receive states */
@@ -331,24 +665,54 @@ static void cec_pin_rx_states(struct cec_pin *pin, ktime_t ts)
break;
pin->state = CEC_ST_RX_START_BIT_HIGH;
delta = ktime_us_delta(ts, pin->ts);
- pin->ts = ts;
/* Start bit low is too short, go back to idle */
- if (delta < CEC_TIM_START_BIT_LOW_MIN -
- CEC_TIM_IDLE_SAMPLE) {
+ if (delta < CEC_TIM_START_BIT_LOW_MIN - CEC_TIM_IDLE_SAMPLE) {
+ if (!pin->rx_start_bit_low_too_short_cnt++) {
+ pin->rx_start_bit_low_too_short_ts = pin->ts;
+ pin->rx_start_bit_low_too_short_delta = delta;
+ }
cec_pin_to_idle(pin);
+ break;
+ }
+ if (rx_arb_lost(pin, &poll)) {
+ cec_msg_init(&pin->tx_msg, poll >> 4, poll & 0xf);
+ pin->tx_generated_poll = true;
+ pin->tx_extra_bytes = 0;
+ pin->state = CEC_ST_TX_START_BIT_HIGH;
+ pin->ts = ts;
}
break;
case CEC_ST_RX_START_BIT_HIGH:
v = cec_pin_read(pin);
delta = ktime_us_delta(ts, pin->ts);
- if (v && delta > CEC_TIM_START_BIT_TOTAL_MAX -
- CEC_TIM_START_BIT_LOW_MIN) {
+ /*
+ * Unfortunately the spec does not specify when to give up
+ * and go to idle. We just pick TOTAL_LONG.
+ */
+ if (v && delta > CEC_TIM_START_BIT_TOTAL_LONG) {
+ pin->rx_start_bit_too_long_cnt++;
cec_pin_to_idle(pin);
break;
}
if (v)
break;
+ /* Start bit is too short, go back to idle */
+ if (delta < CEC_TIM_START_BIT_TOTAL_MIN - CEC_TIM_IDLE_SAMPLE) {
+ if (!pin->rx_start_bit_too_short_cnt++) {
+ pin->rx_start_bit_too_short_ts = pin->ts;
+ pin->rx_start_bit_too_short_delta = delta;
+ }
+ cec_pin_to_idle(pin);
+ break;
+ }
+ if (rx_low_drive(pin)) {
+ /* Error injection: go to low drive */
+ cec_pin_low(pin);
+ pin->state = CEC_ST_RX_LOW_DRIVE;
+ pin->rx_low_drive_cnt++;
+ break;
+ }
pin->state = CEC_ST_RX_DATA_SAMPLE;
pin->ts = ts;
pin->rx_eom = false;
@@ -363,36 +727,55 @@ static void cec_pin_rx_states(struct cec_pin *pin, ktime_t ts)
pin->rx_msg.msg[pin->rx_bit / 10] |=
v << (7 - (pin->rx_bit % 10));
break;
- case 8:
+ case EOM_BIT:
pin->rx_eom = v;
pin->rx_msg.len = pin->rx_bit / 10 + 1;
break;
- case 9:
+ case ACK_BIT:
break;
}
pin->rx_bit++;
break;
case CEC_ST_RX_DATA_POST_SAMPLE:
- pin->state = CEC_ST_RX_DATA_HIGH;
+ pin->state = CEC_ST_RX_DATA_WAIT_FOR_LOW;
break;
- case CEC_ST_RX_DATA_HIGH:
+ case CEC_ST_RX_DATA_WAIT_FOR_LOW:
v = cec_pin_read(pin);
delta = ktime_us_delta(ts, pin->ts);
- if (v && delta > CEC_TIM_DATA_BIT_TOTAL_MAX) {
+ /*
+ * Unfortunately the spec does not specify when to give up
+ * and go to idle. We just pick TOTAL_LONG.
+ */
+ if (v && delta > CEC_TIM_DATA_BIT_TOTAL_LONG) {
+ pin->rx_data_bit_too_long_cnt++;
cec_pin_to_idle(pin);
break;
}
if (v)
break;
+
+ if (rx_low_drive(pin)) {
+ /* Error injection: go to low drive */
+ cec_pin_low(pin);
+ pin->state = CEC_ST_RX_LOW_DRIVE;
+ pin->rx_low_drive_cnt++;
+ break;
+ }
+
/*
* Go to low drive state when the total bit time is
* too short.
*/
if (delta < CEC_TIM_DATA_BIT_TOTAL_MIN) {
+ if (!pin->rx_data_bit_too_short_cnt++) {
+ pin->rx_data_bit_too_short_ts = pin->ts;
+ pin->rx_data_bit_too_short_delta = delta;
+ }
cec_pin_low(pin);
- pin->state = CEC_ST_LOW_DRIVE;
+ pin->state = CEC_ST_RX_LOW_DRIVE;
+ pin->rx_low_drive_cnt++;
break;
}
pin->ts = ts;
@@ -408,6 +791,11 @@ static void cec_pin_rx_states(struct cec_pin *pin, ktime_t ts)
/* ACK bit value */
ack = bcast ? 1 : !for_us;
+ if (for_us && rx_nack(pin)) {
+ /* Error injection: toggle the ACK bit */
+ ack = !ack;
+ }
+
if (ack) {
/* No need to write to the bus, just wait */
pin->state = CEC_ST_RX_ACK_HIGH_POST;
@@ -434,7 +822,7 @@ static void cec_pin_rx_states(struct cec_pin *pin, ktime_t ts)
break;
}
pin->rx_bit++;
- pin->state = CEC_ST_RX_DATA_HIGH;
+ pin->state = CEC_ST_RX_DATA_WAIT_FOR_LOW;
break;
case CEC_ST_RX_ACK_FINISH:
@@ -456,6 +844,7 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
struct cec_adapter *adap = pin->adap;
ktime_t ts;
s32 delta;
+ u32 usecs;
ts = ktime_get();
if (ktime_to_ns(pin->timer_ts)) {
@@ -503,13 +892,27 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
/* Transmit states */
case CEC_ST_TX_WAIT_FOR_HIGH:
case CEC_ST_TX_START_BIT_LOW:
- case CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE:
- case CEC_ST_TX_DATA_BIT_0_HIGH:
- case CEC_ST_TX_DATA_BIT_1_HIGH:
case CEC_ST_TX_START_BIT_HIGH:
+ case CEC_ST_TX_START_BIT_HIGH_SHORT:
+ case CEC_ST_TX_START_BIT_HIGH_LONG:
+ case CEC_ST_TX_START_BIT_LOW_CUSTOM:
+ case CEC_ST_TX_START_BIT_HIGH_CUSTOM:
case CEC_ST_TX_DATA_BIT_0_LOW:
+ case CEC_ST_TX_DATA_BIT_0_HIGH:
+ case CEC_ST_TX_DATA_BIT_0_HIGH_SHORT:
+ case CEC_ST_TX_DATA_BIT_0_HIGH_LONG:
case CEC_ST_TX_DATA_BIT_1_LOW:
+ case CEC_ST_TX_DATA_BIT_1_HIGH:
+ case CEC_ST_TX_DATA_BIT_1_HIGH_SHORT:
+ case CEC_ST_TX_DATA_BIT_1_HIGH_LONG:
case CEC_ST_TX_DATA_BIT_1_HIGH_PRE_SAMPLE:
+ case CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE:
+ case CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE_SHORT:
+ case CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE_LONG:
+ case CEC_ST_TX_DATA_BIT_LOW_CUSTOM:
+ case CEC_ST_TX_DATA_BIT_HIGH_CUSTOM:
+ case CEC_ST_TX_PULSE_LOW_CUSTOM:
+ case CEC_ST_TX_PULSE_HIGH_CUSTOM:
cec_pin_tx_states(pin, ts);
break;
@@ -518,7 +921,7 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
case CEC_ST_RX_START_BIT_HIGH:
case CEC_ST_RX_DATA_SAMPLE:
case CEC_ST_RX_DATA_POST_SAMPLE:
- case CEC_ST_RX_DATA_HIGH:
+ case CEC_ST_RX_DATA_WAIT_FOR_LOW:
case CEC_ST_RX_ACK_LOW:
case CEC_ST_RX_ACK_LOW_POST:
case CEC_ST_RX_ACK_HIGH_POST:
@@ -545,7 +948,10 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
if (delta / CEC_TIM_DATA_BIT_TOTAL >
pin->tx_signal_free_time) {
pin->tx_nacked = false;
- pin->state = CEC_ST_TX_START_BIT_LOW;
+ if (tx_custom_start(pin))
+ pin->state = CEC_ST_TX_START_BIT_LOW_CUSTOM;
+ else
+ pin->state = CEC_ST_TX_START_BIT_LOW;
/* Generate start bit */
cec_pin_low(pin);
break;
@@ -555,6 +961,13 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
pin->state = CEC_ST_TX_WAIT;
break;
}
+ if (pin->tx_custom_pulse && pin->state == CEC_ST_IDLE) {
+ pin->tx_custom_pulse = false;
+ /* Generate custom pulse */
+ cec_pin_low(pin);
+ pin->state = CEC_ST_TX_PULSE_LOW_CUSTOM;
+ break;
+ }
if (pin->state != CEC_ST_IDLE || pin->ops->enable_irq == NULL ||
pin->enable_irq_failed || adap->is_configuring ||
adap->is_configured || adap->monitor_all_cnt)
@@ -565,21 +978,40 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
wake_up_interruptible(&pin->kthread_waitq);
return HRTIMER_NORESTART;
- case CEC_ST_LOW_DRIVE:
+ case CEC_ST_TX_LOW_DRIVE:
+ case CEC_ST_RX_LOW_DRIVE:
+ cec_pin_high(pin);
cec_pin_to_idle(pin);
break;
default:
break;
}
- if (!adap->monitor_pin_cnt || states[pin->state].usecs <= 150) {
+
+ switch (pin->state) {
+ case CEC_ST_TX_START_BIT_LOW_CUSTOM:
+ case CEC_ST_TX_DATA_BIT_LOW_CUSTOM:
+ case CEC_ST_TX_PULSE_LOW_CUSTOM:
+ usecs = pin->tx_custom_low_usecs;
+ break;
+ case CEC_ST_TX_START_BIT_HIGH_CUSTOM:
+ case CEC_ST_TX_DATA_BIT_HIGH_CUSTOM:
+ case CEC_ST_TX_PULSE_HIGH_CUSTOM:
+ usecs = pin->tx_custom_high_usecs;
+ break;
+ default:
+ usecs = states[pin->state].usecs;
+ break;
+ }
+
+ if (!adap->monitor_pin_cnt || usecs <= 150) {
pin->wait_usecs = 0;
- pin->timer_ts = ktime_add_us(ts, states[pin->state].usecs);
+ pin->timer_ts = ktime_add_us(ts, usecs);
hrtimer_forward_now(timer,
- ns_to_ktime(states[pin->state].usecs * 1000));
+ ns_to_ktime(usecs * 1000));
return HRTIMER_RESTART;
}
- pin->wait_usecs = states[pin->state].usecs - 100;
+ pin->wait_usecs = usecs - 100;
pin->timer_ts = ktime_add_us(ts, 100);
hrtimer_forward_now(timer, ns_to_ktime(100000));
return HRTIMER_RESTART;
@@ -596,12 +1028,25 @@ static int cec_pin_thread_func(void *_adap)
pin->work_rx_msg.len ||
pin->work_tx_status ||
atomic_read(&pin->work_irq_change) ||
- atomic_read(&pin->work_pin_events));
+ atomic_read(&pin->work_pin_num_events));
if (pin->work_rx_msg.len) {
- cec_received_msg_ts(adap, &pin->work_rx_msg,
+ struct cec_msg *msg = &pin->work_rx_msg;
+
+ if (msg->len > 1 && msg->len < CEC_MAX_MSG_SIZE &&
+ rx_add_byte(pin)) {
+ /* Error injection: add byte to the message */
+ msg->msg[msg->len++] = 0x55;
+ }
+ if (msg->len > 2 && rx_remove_byte(pin)) {
+ /* Error injection: remove byte from message */
+ msg->len--;
+ }
+ if (msg->len > CEC_MAX_MSG_SIZE)
+ msg->len = CEC_MAX_MSG_SIZE;
+ cec_received_msg_ts(adap, msg,
ns_to_ktime(pin->work_rx_msg.rx_ts));
- pin->work_rx_msg.len = 0;
+ msg->len = 0;
}
if (pin->work_tx_status) {
unsigned int tx_status = pin->work_tx_status;
@@ -611,14 +1056,16 @@ static int cec_pin_thread_func(void *_adap)
pin->work_tx_ts);
}
- while (atomic_read(&pin->work_pin_events)) {
+ while (atomic_read(&pin->work_pin_num_events)) {
unsigned int idx = pin->work_pin_events_rd;
+ u8 v = pin->work_pin_events[idx];
cec_queue_pin_cec_event(adap,
- pin->work_pin_is_high[idx],
+ v & CEC_PIN_EVENT_FL_IS_HIGH,
+ v & CEC_PIN_EVENT_FL_DROPPED,
pin->work_pin_ts[idx]);
pin->work_pin_events_rd = (idx + 1) % CEC_NUM_PIN_EVENTS;
- atomic_dec(&pin->work_pin_events);
+ atomic_dec(&pin->work_pin_num_events);
}
switch (atomic_xchg(&pin->work_irq_change,
@@ -654,8 +1101,9 @@ static int cec_pin_adap_enable(struct cec_adapter *adap, bool enable)
pin->enabled = enable;
if (enable) {
- atomic_set(&pin->work_pin_events, 0);
+ atomic_set(&pin->work_pin_num_events, 0);
pin->work_pin_events_rd = pin->work_pin_events_wr = 0;
+ pin->work_pin_events_dropped = false;
cec_pin_read(pin);
cec_pin_to_idle(pin);
pin->tx_msg.len = 0;
@@ -692,23 +1140,37 @@ static int cec_pin_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
return 0;
}
+void cec_pin_start_timer(struct cec_pin *pin)
+{
+ if (pin->state != CEC_ST_RX_IRQ)
+ return;
+
+ atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_UNCHANGED);
+ pin->ops->disable_irq(pin->adap);
+ cec_pin_high(pin);
+ cec_pin_to_idle(pin);
+ hrtimer_start(&pin->timer, ns_to_ktime(0), HRTIMER_MODE_REL);
+}
+
static int cec_pin_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct cec_pin *pin = adap->pin;
pin->tx_signal_free_time = signal_free_time;
+ pin->tx_extra_bytes = 0;
pin->tx_msg = *msg;
+ if (msg->len > 1) {
+ /* Error injection: add byte to the message */
+ pin->tx_extra_bytes = tx_add_bytes(pin);
+ }
+ if (msg->len > 2 && tx_remove_byte(pin)) {
+ /* Error injection: remove byte from the message */
+ pin->tx_msg.len--;
+ }
pin->work_tx_status = 0;
pin->tx_bit = 0;
- if (pin->state == CEC_ST_RX_IRQ) {
- atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_UNCHANGED);
- pin->ops->disable_irq(adap);
- cec_pin_high(pin);
- cec_pin_to_idle(pin);
- hrtimer_start(&pin->timer, ns_to_ktime(0),
- HRTIMER_MODE_REL);
- }
+ cec_pin_start_timer(pin);
return 0;
}
@@ -717,10 +1179,12 @@ static void cec_pin_adap_status(struct cec_adapter *adap,
{
struct cec_pin *pin = adap->pin;
- seq_printf(file, "state: %s\n", states[pin->state].name);
- seq_printf(file, "tx_bit: %d\n", pin->tx_bit);
- seq_printf(file, "rx_bit: %d\n", pin->rx_bit);
+ seq_printf(file, "state: %s\n", states[pin->state].name);
+ seq_printf(file, "tx_bit: %d\n", pin->tx_bit);
+ seq_printf(file, "rx_bit: %d\n", pin->rx_bit);
seq_printf(file, "cec pin: %d\n", pin->ops->read(adap));
+ seq_printf(file, "cec pin events dropped: %u\n",
+ pin->work_pin_events_dropped_cnt);
seq_printf(file, "irq failed: %d\n", pin->enable_irq_failed);
if (pin->timer_100ms_overruns) {
seq_printf(file, "timer overruns > 100ms: %u of %u\n",
@@ -732,11 +1196,45 @@ static void cec_pin_adap_status(struct cec_adapter *adap,
seq_printf(file, "avg timer overrun: %u usecs\n",
pin->timer_sum_overrun / pin->timer_100ms_overruns);
}
+ if (pin->rx_start_bit_low_too_short_cnt)
+ seq_printf(file,
+ "rx start bit low too short: %u (delta %u, ts %llu)\n",
+ pin->rx_start_bit_low_too_short_cnt,
+ pin->rx_start_bit_low_too_short_delta,
+ pin->rx_start_bit_low_too_short_ts);
+ if (pin->rx_start_bit_too_short_cnt)
+ seq_printf(file,
+ "rx start bit too short: %u (delta %u, ts %llu)\n",
+ pin->rx_start_bit_too_short_cnt,
+ pin->rx_start_bit_too_short_delta,
+ pin->rx_start_bit_too_short_ts);
+ if (pin->rx_start_bit_too_long_cnt)
+ seq_printf(file, "rx start bit too long: %u\n",
+ pin->rx_start_bit_too_long_cnt);
+ if (pin->rx_data_bit_too_short_cnt)
+ seq_printf(file,
+ "rx data bit too short: %u (delta %u, ts %llu)\n",
+ pin->rx_data_bit_too_short_cnt,
+ pin->rx_data_bit_too_short_delta,
+ pin->rx_data_bit_too_short_ts);
+ if (pin->rx_data_bit_too_long_cnt)
+ seq_printf(file, "rx data bit too long: %u\n",
+ pin->rx_data_bit_too_long_cnt);
+ seq_printf(file, "rx initiated low drive: %u\n", pin->rx_low_drive_cnt);
+ seq_printf(file, "tx detected low drive: %u\n", pin->tx_low_drive_cnt);
+ pin->work_pin_events_dropped_cnt = 0;
pin->timer_cnt = 0;
pin->timer_100ms_overruns = 0;
pin->timer_300ms_overruns = 0;
pin->timer_max_overrun = 0;
pin->timer_sum_overrun = 0;
+ pin->rx_start_bit_low_too_short_cnt = 0;
+ pin->rx_start_bit_too_short_cnt = 0;
+ pin->rx_start_bit_too_long_cnt = 0;
+ pin->rx_data_bit_too_short_cnt = 0;
+ pin->rx_data_bit_too_long_cnt = 0;
+ pin->rx_low_drive_cnt = 0;
+ pin->tx_low_drive_cnt = 0;
if (pin->ops->status)
pin->ops->status(adap, file);
}
@@ -778,6 +1276,10 @@ static const struct cec_adap_ops cec_pin_adap_ops = {
.adap_transmit = cec_pin_adap_transmit,
.adap_status = cec_pin_adap_status,
.adap_free = cec_pin_adap_free,
+#ifdef CONFIG_CEC_PIN_ERROR_INJ
+ .error_inj_parse_line = cec_pin_error_inj_parse_line,
+ .error_inj_show = cec_pin_error_inj_show,
+#endif
};
struct cec_adapter *cec_pin_allocate_adapter(const struct cec_pin_ops *pin_ops,
@@ -792,6 +1294,8 @@ struct cec_adapter *cec_pin_allocate_adapter(const struct cec_pin_ops *pin_ops,
hrtimer_init(&pin->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pin->timer.function = cec_pin_timer;
init_waitqueue_head(&pin->kthread_waitq);
+ pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT;
+ pin->tx_custom_high_usecs = CEC_TIM_CUSTOM_DEFAULT;
adap = cec_allocate_adapter(&cec_pin_adap_ops, priv, name,
caps | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN,
diff --git a/drivers/media/cec/cec-priv.h b/drivers/media/cec/cec-priv.h
index daf5976..804e38f 100644
--- a/drivers/media/cec/cec-priv.h
+++ b/drivers/media/cec/cec-priv.h
@@ -1,20 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
/*
* cec-priv.h - HDMI Consumer Electronics Control internal header
*
* Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
- *
- * This program is free software; you may redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * 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.
*/
#ifndef _CEC_PRIV_H
OpenPOWER on IntegriCloud