linux/drivers/media/cec/cec-pin-error-inj.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
   4 */
   5
   6#include <linux/delay.h>
   7#include <linux/slab.h>
   8#include <linux/sched/types.h>
   9
  10#include <media/cec-pin.h>
  11#include "cec-pin-priv.h"
  12
  13struct cec_error_inj_cmd {
  14        unsigned int mode_offset;
  15        int arg_idx;
  16        const char *cmd;
  17};
  18
  19static const struct cec_error_inj_cmd cec_error_inj_cmds[] = {
  20        { CEC_ERROR_INJ_RX_NACK_OFFSET, -1, "rx-nack" },
  21        { CEC_ERROR_INJ_RX_LOW_DRIVE_OFFSET,
  22          CEC_ERROR_INJ_RX_LOW_DRIVE_ARG_IDX, "rx-low-drive" },
  23        { CEC_ERROR_INJ_RX_ADD_BYTE_OFFSET, -1, "rx-add-byte" },
  24        { CEC_ERROR_INJ_RX_REMOVE_BYTE_OFFSET, -1, "rx-remove-byte" },
  25        { CEC_ERROR_INJ_RX_ARB_LOST_OFFSET,
  26          CEC_ERROR_INJ_RX_ARB_LOST_ARG_IDX, "rx-arb-lost" },
  27
  28        { CEC_ERROR_INJ_TX_NO_EOM_OFFSET, -1, "tx-no-eom" },
  29        { CEC_ERROR_INJ_TX_EARLY_EOM_OFFSET, -1, "tx-early-eom" },
  30        { CEC_ERROR_INJ_TX_ADD_BYTES_OFFSET,
  31          CEC_ERROR_INJ_TX_ADD_BYTES_ARG_IDX, "tx-add-bytes" },
  32        { CEC_ERROR_INJ_TX_REMOVE_BYTE_OFFSET, -1, "tx-remove-byte" },
  33        { CEC_ERROR_INJ_TX_SHORT_BIT_OFFSET,
  34          CEC_ERROR_INJ_TX_SHORT_BIT_ARG_IDX, "tx-short-bit" },
  35        { CEC_ERROR_INJ_TX_LONG_BIT_OFFSET,
  36          CEC_ERROR_INJ_TX_LONG_BIT_ARG_IDX, "tx-long-bit" },
  37        { CEC_ERROR_INJ_TX_CUSTOM_BIT_OFFSET,
  38          CEC_ERROR_INJ_TX_CUSTOM_BIT_ARG_IDX, "tx-custom-bit" },
  39        { CEC_ERROR_INJ_TX_SHORT_START_OFFSET, -1, "tx-short-start" },
  40        { CEC_ERROR_INJ_TX_LONG_START_OFFSET, -1, "tx-long-start" },
  41        { CEC_ERROR_INJ_TX_CUSTOM_START_OFFSET, -1, "tx-custom-start" },
  42        { CEC_ERROR_INJ_TX_LAST_BIT_OFFSET,
  43          CEC_ERROR_INJ_TX_LAST_BIT_ARG_IDX, "tx-last-bit" },
  44        { CEC_ERROR_INJ_TX_LOW_DRIVE_OFFSET,
  45          CEC_ERROR_INJ_TX_LOW_DRIVE_ARG_IDX, "tx-low-drive" },
  46        { 0, -1, NULL }
  47};
  48
  49u16 cec_pin_rx_error_inj(struct cec_pin *pin)
  50{
  51        u16 cmd = CEC_ERROR_INJ_OP_ANY;
  52
  53        /* Only when 18 bits have been received do we have a valid cmd */
  54        if (!(pin->error_inj[cmd] & CEC_ERROR_INJ_RX_MASK) &&
  55            pin->rx_bit >= 18)
  56                cmd = pin->rx_msg.msg[1];
  57        return (pin->error_inj[cmd] & CEC_ERROR_INJ_RX_MASK) ? cmd :
  58                CEC_ERROR_INJ_OP_ANY;
  59}
  60
  61u16 cec_pin_tx_error_inj(struct cec_pin *pin)
  62{
  63        u16 cmd = CEC_ERROR_INJ_OP_ANY;
  64
  65        if (!(pin->error_inj[cmd] & CEC_ERROR_INJ_TX_MASK) &&
  66            pin->tx_msg.len > 1)
  67                cmd = pin->tx_msg.msg[1];
  68        return (pin->error_inj[cmd] & CEC_ERROR_INJ_TX_MASK) ? cmd :
  69                CEC_ERROR_INJ_OP_ANY;
  70}
  71
  72bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line)
  73{
  74        static const char *delims = " \t\r";
  75        struct cec_pin *pin = adap->pin;
  76        unsigned int i;
  77        bool has_pos = false;
  78        char *p = line;
  79        char *token;
  80        char *comma;
  81        u64 *error;
  82        u8 *args;
  83        bool has_op;
  84        u8 op;
  85        u8 mode;
  86        u8 pos;
  87
  88        p = skip_spaces(p);
  89        token = strsep(&p, delims);
  90        if (!strcmp(token, "clear")) {
  91                memset(pin->error_inj, 0, sizeof(pin->error_inj));
  92                pin->rx_toggle = pin->tx_toggle = false;
  93                pin->tx_ignore_nack_until_eom = false;
  94                pin->tx_custom_pulse = false;
  95                pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT;
  96                pin->tx_custom_high_usecs = CEC_TIM_CUSTOM_DEFAULT;
  97                return true;
  98        }
  99        if (!strcmp(token, "rx-clear")) {
 100                for (i = 0; i <= CEC_ERROR_INJ_OP_ANY; i++)
 101                        pin->error_inj[i] &= ~CEC_ERROR_INJ_RX_MASK;
 102                pin->rx_toggle = false;
 103                return true;
 104        }
 105        if (!strcmp(token, "tx-clear")) {
 106                for (i = 0; i <= CEC_ERROR_INJ_OP_ANY; i++)
 107                        pin->error_inj[i] &= ~CEC_ERROR_INJ_TX_MASK;
 108                pin->tx_toggle = false;
 109                pin->tx_ignore_nack_until_eom = false;
 110                pin->tx_custom_pulse = false;
 111                pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT;
 112                pin->tx_custom_high_usecs = CEC_TIM_CUSTOM_DEFAULT;
 113                return true;
 114        }
 115        if (!strcmp(token, "tx-ignore-nack-until-eom")) {
 116                pin->tx_ignore_nack_until_eom = true;
 117                return true;
 118        }
 119        if (!strcmp(token, "tx-custom-pulse")) {
 120                pin->tx_custom_pulse = true;
 121                cec_pin_start_timer(pin);
 122                return true;
 123        }
 124        if (!p)
 125                return false;
 126
 127        p = skip_spaces(p);
 128        if (!strcmp(token, "tx-custom-low-usecs")) {
 129                u32 usecs;
 130
 131                if (kstrtou32(p, 0, &usecs) || usecs > 10000000)
 132                        return false;
 133                pin->tx_custom_low_usecs = usecs;
 134                return true;
 135        }
 136        if (!strcmp(token, "tx-custom-high-usecs")) {
 137                u32 usecs;
 138
 139                if (kstrtou32(p, 0, &usecs) || usecs > 10000000)
 140                        return false;
 141                pin->tx_custom_high_usecs = usecs;
 142                return true;
 143        }
 144
 145        comma = strchr(token, ',');
 146        if (comma)
 147                *comma++ = '\0';
 148        if (!strcmp(token, "any")) {
 149                has_op = false;
 150                error = pin->error_inj + CEC_ERROR_INJ_OP_ANY;
 151                args = pin->error_inj_args[CEC_ERROR_INJ_OP_ANY];
 152        } else if (!kstrtou8(token, 0, &op)) {
 153                has_op = true;
 154                error = pin->error_inj + op;
 155                args = pin->error_inj_args[op];
 156        } else {
 157                return false;
 158        }
 159
 160        mode = CEC_ERROR_INJ_MODE_ONCE;
 161        if (comma) {
 162                if (!strcmp(comma, "off"))
 163                        mode = CEC_ERROR_INJ_MODE_OFF;
 164                else if (!strcmp(comma, "once"))
 165                        mode = CEC_ERROR_INJ_MODE_ONCE;
 166                else if (!strcmp(comma, "always"))
 167                        mode = CEC_ERROR_INJ_MODE_ALWAYS;
 168                else if (!strcmp(comma, "toggle"))
 169                        mode = CEC_ERROR_INJ_MODE_TOGGLE;
 170                else
 171                        return false;
 172        }
 173
 174        token = strsep(&p, delims);
 175        if (p) {
 176                p = skip_spaces(p);
 177                has_pos = !kstrtou8(p, 0, &pos);
 178        }
 179
 180        if (!strcmp(token, "clear")) {
 181                *error = 0;
 182                return true;
 183        }
 184        if (!strcmp(token, "rx-clear")) {
 185                *error &= ~CEC_ERROR_INJ_RX_MASK;
 186                return true;
 187        }
 188        if (!strcmp(token, "tx-clear")) {
 189                *error &= ~CEC_ERROR_INJ_TX_MASK;
 190                return true;
 191        }
 192
 193        for (i = 0; cec_error_inj_cmds[i].cmd; i++) {
 194                const char *cmd = cec_error_inj_cmds[i].cmd;
 195                unsigned int mode_offset;
 196                u64 mode_mask;
 197                int arg_idx;
 198                bool is_bit_pos = true;
 199
 200                if (strcmp(token, cmd))
 201                        continue;
 202
 203                mode_offset = cec_error_inj_cmds[i].mode_offset;
 204                mode_mask = CEC_ERROR_INJ_MODE_MASK << mode_offset;
 205                arg_idx = cec_error_inj_cmds[i].arg_idx;
 206
 207                if (mode_offset == CEC_ERROR_INJ_RX_ARB_LOST_OFFSET) {
 208                        if (has_op)
 209                                return false;
 210                        if (!has_pos)
 211                                pos = 0x0f;
 212                        is_bit_pos = false;
 213                } else if (mode_offset == CEC_ERROR_INJ_TX_ADD_BYTES_OFFSET) {
 214                        if (!has_pos || !pos)
 215                                return false;
 216                        is_bit_pos = false;
 217                }
 218
 219                if (arg_idx >= 0 && is_bit_pos) {
 220                        if (!has_pos || pos >= 160)
 221                                return false;
 222                        if (has_op && pos < 10 + 8)
 223                                return false;
 224                        /* Invalid bit position may not be the Ack bit */
 225                        if ((mode_offset == CEC_ERROR_INJ_TX_SHORT_BIT_OFFSET ||
 226                             mode_offset == CEC_ERROR_INJ_TX_LONG_BIT_OFFSET ||
 227                             mode_offset == CEC_ERROR_INJ_TX_CUSTOM_BIT_OFFSET) &&
 228                            (pos % 10) == 9)
 229                                return false;
 230                }
 231                *error &= ~mode_mask;
 232                *error |= (u64)mode << mode_offset;
 233                if (arg_idx >= 0)
 234                        args[arg_idx] = pos;
 235                return true;
 236        }
 237        return false;
 238}
 239
 240static void cec_pin_show_cmd(struct seq_file *sf, u32 cmd, u8 mode)
 241{
 242        if (cmd == CEC_ERROR_INJ_OP_ANY)
 243                seq_puts(sf, "any,");
 244        else
 245                seq_printf(sf, "0x%02x,", cmd);
 246        switch (mode) {
 247        case CEC_ERROR_INJ_MODE_ONCE:
 248                seq_puts(sf, "once ");
 249                break;
 250        case CEC_ERROR_INJ_MODE_ALWAYS:
 251                seq_puts(sf, "always ");
 252                break;
 253        case CEC_ERROR_INJ_MODE_TOGGLE:
 254                seq_puts(sf, "toggle ");
 255                break;
 256        default:
 257                seq_puts(sf, "off ");
 258                break;
 259        }
 260}
 261
 262int cec_pin_error_inj_show(struct cec_adapter *adap, struct seq_file *sf)
 263{
 264        struct cec_pin *pin = adap->pin;
 265        unsigned int i, j;
 266
 267        seq_puts(sf, "# Clear error injections:\n");
 268        seq_puts(sf, "#   clear          clear all rx and tx error injections\n");
 269        seq_puts(sf, "#   rx-clear       clear all rx error injections\n");
 270        seq_puts(sf, "#   tx-clear       clear all tx error injections\n");
 271        seq_puts(sf, "#   <op> clear     clear all rx and tx error injections for <op>\n");
 272        seq_puts(sf, "#   <op> rx-clear  clear all rx error injections for <op>\n");
 273        seq_puts(sf, "#   <op> tx-clear  clear all tx error injections for <op>\n");
 274        seq_puts(sf, "#\n");
 275        seq_puts(sf, "# RX error injection:\n");
 276        seq_puts(sf, "#   <op>[,<mode>] rx-nack              NACK the message instead of sending an ACK\n");
 277        seq_puts(sf, "#   <op>[,<mode>] rx-low-drive <bit>   force a low-drive condition at this bit position\n");
 278        seq_puts(sf, "#   <op>[,<mode>] rx-add-byte          add a spurious byte to the received CEC message\n");
 279        seq_puts(sf, "#   <op>[,<mode>] rx-remove-byte       remove the last byte from the received CEC message\n");
 280        seq_puts(sf, "#   <op>[,<mode>] rx-arb-lost <poll>   generate a POLL message to trigger an arbitration lost\n");
 281        seq_puts(sf, "#\n");
 282        seq_puts(sf, "# TX error injection settings:\n");
 283        seq_puts(sf, "#   tx-ignore-nack-until-eom           ignore early NACKs until EOM\n");
 284        seq_puts(sf, "#   tx-custom-low-usecs <usecs>        define the 'low' time for the custom pulse\n");
 285        seq_puts(sf, "#   tx-custom-high-usecs <usecs>       define the 'high' time for the custom pulse\n");
 286        seq_puts(sf, "#   tx-custom-pulse                    transmit the custom pulse once the bus is idle\n");
 287        seq_puts(sf, "#\n");
 288        seq_puts(sf, "# TX error injection:\n");
 289        seq_puts(sf, "#   <op>[,<mode>] tx-no-eom            don't set the EOM bit\n");
 290        seq_puts(sf, "#   <op>[,<mode>] tx-early-eom         set the EOM bit one byte too soon\n");
 291        seq_puts(sf, "#   <op>[,<mode>] tx-add-bytes <num>   append <num> (1-255) spurious bytes to the message\n");
 292        seq_puts(sf, "#   <op>[,<mode>] tx-remove-byte       drop the last byte from the message\n");
 293        seq_puts(sf, "#   <op>[,<mode>] tx-short-bit <bit>   make this bit shorter than allowed\n");
 294        seq_puts(sf, "#   <op>[,<mode>] tx-long-bit <bit>    make this bit longer than allowed\n");
 295        seq_puts(sf, "#   <op>[,<mode>] tx-custom-bit <bit>  send the custom pulse instead of this bit\n");
 296        seq_puts(sf, "#   <op>[,<mode>] tx-short-start       send a start pulse that's too short\n");
 297        seq_puts(sf, "#   <op>[,<mode>] tx-long-start        send a start pulse that's too long\n");
 298        seq_puts(sf, "#   <op>[,<mode>] tx-custom-start      send the custom pulse instead of the start pulse\n");
 299        seq_puts(sf, "#   <op>[,<mode>] tx-last-bit <bit>    stop sending after this bit\n");
 300        seq_puts(sf, "#   <op>[,<mode>] tx-low-drive <bit>   force a low-drive condition at this bit position\n");
 301        seq_puts(sf, "#\n");
 302        seq_puts(sf, "# <op>       CEC message opcode (0-255) or 'any'\n");
 303        seq_puts(sf, "# <mode>     'once' (default), 'always', 'toggle' or 'off'\n");
 304        seq_puts(sf, "# <bit>      CEC message bit (0-159)\n");
 305        seq_puts(sf, "#            10 bits per 'byte': bits 0-7: data, bit 8: EOM, bit 9: ACK\n");
 306        seq_puts(sf, "# <poll>     CEC poll message used to test arbitration lost (0x00-0xff, default 0x0f)\n");
 307        seq_puts(sf, "# <usecs>    microseconds (0-10000000, default 1000)\n");
 308
 309        seq_puts(sf, "\nclear\n");
 310
 311        for (i = 0; i < ARRAY_SIZE(pin->error_inj); i++) {
 312                u64 e = pin->error_inj[i];
 313
 314                for (j = 0; cec_error_inj_cmds[j].cmd; j++) {
 315                        const char *cmd = cec_error_inj_cmds[j].cmd;
 316                        unsigned int mode;
 317                        unsigned int mode_offset;
 318                        int arg_idx;
 319
 320                        mode_offset = cec_error_inj_cmds[j].mode_offset;
 321                        arg_idx = cec_error_inj_cmds[j].arg_idx;
 322                        mode = (e >> mode_offset) & CEC_ERROR_INJ_MODE_MASK;
 323                        if (!mode)
 324                                continue;
 325                        cec_pin_show_cmd(sf, i, mode);
 326                        seq_puts(sf, cmd);
 327                        if (arg_idx >= 0)
 328                                seq_printf(sf, " %u",
 329                                           pin->error_inj_args[i][arg_idx]);
 330                        seq_puts(sf, "\n");
 331                }
 332        }
 333
 334        if (pin->tx_ignore_nack_until_eom)
 335                seq_puts(sf, "tx-ignore-nack-until-eom\n");
 336        if (pin->tx_custom_pulse)
 337                seq_puts(sf, "tx-custom-pulse\n");
 338        if (pin->tx_custom_low_usecs != CEC_TIM_CUSTOM_DEFAULT)
 339                seq_printf(sf, "tx-custom-low-usecs %u\n",
 340                           pin->tx_custom_low_usecs);
 341        if (pin->tx_custom_high_usecs != CEC_TIM_CUSTOM_DEFAULT)
 342                seq_printf(sf, "tx-custom-high-usecs %u\n",
 343                           pin->tx_custom_high_usecs);
 344        return 0;
 345}
 346