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(s: p, base: 0, res: &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(s: p, base: 0, res: &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(s: token, base: 0, res: &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(s: p, base: 0, res: &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(m: sf, s: "any," ); |
244 | else |
245 | seq_printf(m: sf, fmt: "0x%02x," , cmd); |
246 | switch (mode) { |
247 | case CEC_ERROR_INJ_MODE_ONCE: |
248 | seq_puts(m: sf, s: "once " ); |
249 | break; |
250 | case CEC_ERROR_INJ_MODE_ALWAYS: |
251 | seq_puts(m: sf, s: "always " ); |
252 | break; |
253 | case CEC_ERROR_INJ_MODE_TOGGLE: |
254 | seq_puts(m: sf, s: "toggle " ); |
255 | break; |
256 | default: |
257 | seq_puts(m: sf, s: "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(m: sf, s: "# Clear error injections:\n" ); |
268 | seq_puts(m: sf, s: "# clear clear all rx and tx error injections\n" ); |
269 | seq_puts(m: sf, s: "# rx-clear clear all rx error injections\n" ); |
270 | seq_puts(m: sf, s: "# tx-clear clear all tx error injections\n" ); |
271 | seq_puts(m: sf, s: "# <op> clear clear all rx and tx error injections for <op>\n" ); |
272 | seq_puts(m: sf, s: "# <op> rx-clear clear all rx error injections for <op>\n" ); |
273 | seq_puts(m: sf, s: "# <op> tx-clear clear all tx error injections for <op>\n" ); |
274 | seq_puts(m: sf, s: "#\n" ); |
275 | seq_puts(m: sf, s: "# RX error injection:\n" ); |
276 | seq_puts(m: sf, s: "# <op>[,<mode>] rx-nack NACK the message instead of sending an ACK\n" ); |
277 | seq_puts(m: sf, s: "# <op>[,<mode>] rx-low-drive <bit> force a low-drive condition at this bit position\n" ); |
278 | seq_puts(m: sf, s: "# <op>[,<mode>] rx-add-byte add a spurious byte to the received CEC message\n" ); |
279 | seq_puts(m: sf, s: "# <op>[,<mode>] rx-remove-byte remove the last byte from the received CEC message\n" ); |
280 | seq_puts(m: sf, s: "# any[,<mode>] rx-arb-lost [<poll>] generate a POLL message to trigger an arbitration lost\n" ); |
281 | seq_puts(m: sf, s: "#\n" ); |
282 | seq_puts(m: sf, s: "# TX error injection settings:\n" ); |
283 | seq_puts(m: sf, s: "# tx-ignore-nack-until-eom ignore early NACKs until EOM\n" ); |
284 | seq_puts(m: sf, s: "# tx-custom-low-usecs <usecs> define the 'low' time for the custom pulse\n" ); |
285 | seq_puts(m: sf, s: "# tx-custom-high-usecs <usecs> define the 'high' time for the custom pulse\n" ); |
286 | seq_puts(m: sf, s: "# tx-custom-pulse transmit the custom pulse once the bus is idle\n" ); |
287 | seq_puts(m: sf, s: "#\n" ); |
288 | seq_puts(m: sf, s: "# TX error injection:\n" ); |
289 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-no-eom don't set the EOM bit\n" ); |
290 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-early-eom set the EOM bit one byte too soon\n" ); |
291 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-add-bytes <num> append <num> (1-255) spurious bytes to the message\n" ); |
292 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-remove-byte drop the last byte from the message\n" ); |
293 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-short-bit <bit> make this bit shorter than allowed\n" ); |
294 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-long-bit <bit> make this bit longer than allowed\n" ); |
295 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-custom-bit <bit> send the custom pulse instead of this bit\n" ); |
296 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-short-start send a start pulse that's too short\n" ); |
297 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-long-start send a start pulse that's too long\n" ); |
298 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-custom-start send the custom pulse instead of the start pulse\n" ); |
299 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-last-bit <bit> stop sending after this bit\n" ); |
300 | seq_puts(m: sf, s: "# <op>[,<mode>] tx-low-drive <bit> force a low-drive condition at this bit position\n" ); |
301 | seq_puts(m: sf, s: "#\n" ); |
302 | seq_puts(m: sf, s: "# <op> CEC message opcode (0-255) or 'any'\n" ); |
303 | seq_puts(m: sf, s: "# <mode> 'once' (default), 'always', 'toggle' or 'off'\n" ); |
304 | seq_puts(m: sf, s: "# <bit> CEC message bit (0-159)\n" ); |
305 | seq_puts(m: sf, s: "# 10 bits per 'byte': bits 0-7: data, bit 8: EOM, bit 9: ACK\n" ); |
306 | seq_puts(m: sf, s: "# <poll> CEC poll message used to test arbitration lost (0x00-0xff, default 0x0f)\n" ); |
307 | seq_puts(m: sf, s: "# <usecs> microseconds (0-10000000, default 1000)\n" ); |
308 | |
309 | seq_puts(m: sf, s: "\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, cmd: i, mode); |
326 | seq_puts(m: sf, s: cmd); |
327 | if (arg_idx >= 0) |
328 | seq_printf(m: sf, fmt: " %u" , |
329 | pin->error_inj_args[i][arg_idx]); |
330 | seq_puts(m: sf, s: "\n" ); |
331 | } |
332 | } |
333 | |
334 | if (pin->tx_ignore_nack_until_eom) |
335 | seq_puts(m: sf, s: "tx-ignore-nack-until-eom\n" ); |
336 | if (pin->tx_custom_pulse) |
337 | seq_puts(m: sf, s: "tx-custom-pulse\n" ); |
338 | if (pin->tx_custom_low_usecs != CEC_TIM_CUSTOM_DEFAULT) |
339 | seq_printf(m: sf, fmt: "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(m: sf, fmt: "tx-custom-high-usecs %u\n" , |
343 | pin->tx_custom_high_usecs); |
344 | return 0; |
345 | } |
346 | |