GNU Linux-libre 4.19.207-gnu1
[releases.git] / drivers / media / cec / cec-pin-error-inj.c
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
13 struct cec_error_inj_cmd {
14         unsigned int mode_offset;
15         int arg_idx;
16         const char *cmd;
17 };
18
19 static 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
49 u16 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
61 u16 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
72 bool 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
240 static 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
262 int 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 }