1 /*
2  * Copyright 2023 Google LLC
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdio.h>
8 #include <zephyr/input/input.h>
9 #ifdef CONFIG_INPUT_SHELL_KBD_MATRIX_STATE
10 #include <zephyr/input/input_kbd_matrix.h>
11 #endif
12 #include <zephyr/logging/log.h>
13 #include <zephyr/shell/shell.h>
14 #include <zephyr/sys/atomic.h>
15 
16 LOG_MODULE_DECLARE(input);
17 
18 #ifdef CONFIG_INPUT_EVENT_DUMP
19 #ifdef CONFIG_INPUT_SHELL
20 static atomic_t dump_enable;
21 
input_dump_enabled(void)22 static bool input_dump_enabled(void)
23 {
24 	return atomic_get(&dump_enable);
25 }
26 
input_cmd_dump(const struct shell * sh,size_t argc,char * argv[])27 static int input_cmd_dump(const struct shell *sh, size_t argc, char *argv[])
28 {
29 	bool enabled;
30 	int err = 0;
31 
32 	enabled = shell_strtobool(argv[1], 0, &err);
33 	if (err) {
34 		shell_error(sh, "Invalid argument: %s", argv[1]);
35 		return err;
36 	}
37 
38 	if (enabled) {
39 		shell_info(sh, "Input event dumping enabled");
40 		atomic_set(&dump_enable, 1);
41 	} else {
42 		atomic_set(&dump_enable, 0);
43 	}
44 
45 	return 0;
46 }
47 #else
input_dump_enabled(void)48 static bool input_dump_enabled(void)
49 {
50 	return true;
51 }
52 #endif /* CONFIG_INPUT_SHELL */
53 
input_dump_cb(struct input_event * evt,void * user_data)54 static void input_dump_cb(struct input_event *evt, void *user_data)
55 {
56 	ARG_UNUSED(user_data);
57 
58 	if (!input_dump_enabled()) {
59 		return;
60 	}
61 
62 	LOG_INF("input event: dev=%-16s %3s type=%2x code=%3d value=%d",
63 		evt->dev ? evt->dev->name : "NULL",
64 		evt->sync ? "SYN" : "",
65 		evt->type,
66 		evt->code,
67 		evt->value);
68 }
69 INPUT_CALLBACK_DEFINE(NULL, input_dump_cb, NULL);
70 #endif /* CONFIG_INPUT_EVENT_DUMP */
71 
72 #ifdef CONFIG_INPUT_SHELL
input_cmd_report(const struct shell * sh,size_t argc,char * argv[])73 static int input_cmd_report(const struct shell *sh, size_t argc, char *argv[])
74 {
75 	bool sync;
76 	int err = 0;
77 	uint32_t type, code, value;
78 
79 	if (argc == 5) {
80 		sync = shell_strtobool(argv[4], 0, &err);
81 		if (err) {
82 			shell_error(sh, "Invalid argument: %s", argv[4]);
83 			return err;
84 		}
85 	} else {
86 		sync = true;
87 	}
88 
89 	type = shell_strtoul(argv[1], 0, &err);
90 	if (err) {
91 		shell_error(sh, "Invalid argument: %s", argv[1]);
92 		return err;
93 	}
94 	if (type > UINT8_MAX) {
95 		shell_error(sh, "Out of range: %s", argv[1]);
96 		return -EINVAL;
97 	}
98 
99 	code = shell_strtoul(argv[2], 0, &err);
100 	if (err) {
101 		shell_error(sh, "Invalid argument: %s", argv[2]);
102 		return err;
103 	}
104 	if (code > UINT16_MAX) {
105 		shell_error(sh, "Out of range: %s", argv[2]);
106 		return -EINVAL;
107 	}
108 
109 	value = shell_strtoul(argv[3], 0, &err);
110 	if (err) {
111 		shell_error(sh, "Invalid argument: %s", argv[3]);
112 		return err;
113 	}
114 
115 	input_report(NULL, type, code, value, sync, K_FOREVER);
116 
117 	return 0;
118 }
119 
120 #ifdef CONFIG_INPUT_SHELL_KBD_MATRIX_STATE
121 static const struct device *kbd_matrix_state_dev;
122 static kbd_row_t kbd_matrix_state[CONFIG_INPUT_SHELL_KBD_MATRIX_STATE_MAX_COLS];
123 static kbd_row_t kbd_matrix_key_mask[CONFIG_INPUT_SHELL_KBD_MATRIX_STATE_MAX_COLS];
124 
125 /* Keep space for each column value, 2 char per byte + space. */
126 #define KEY_MATRIX_ENTRY_LEN (sizeof(kbd_row_t) * 2 + 1)
127 #define KEY_MATRIX_BUF_SZ (CONFIG_INPUT_SHELL_KBD_MATRIX_STATE_MAX_COLS * \
128 			   KEY_MATRIX_ENTRY_LEN)
129 static char kbd_matrix_buf[KEY_MATRIX_BUF_SZ];
130 
kbd_matrix_state_log_entry(char * header,kbd_row_t * data)131 static void kbd_matrix_state_log_entry(char *header, kbd_row_t *data)
132 {
133 	const struct input_kbd_matrix_common_config *cfg = kbd_matrix_state_dev->config;
134 	char *buf = kbd_matrix_buf;
135 	int size = sizeof(kbd_matrix_buf);
136 	int ret;
137 	char blank[KEY_MATRIX_ENTRY_LEN];
138 	int count = 0;
139 
140 	memset(blank, '-', sizeof(blank) - 1);
141 	blank[sizeof(blank) - 1] = '\0';
142 
143 	for (int i = 0; i < cfg->col_size; i++) {
144 		char *sep = (i + 1) < cfg->col_size ? " " : "";
145 
146 		if (data[i] != 0) {
147 			ret = snprintf(buf, size, "%" PRIkbdrow "%s", data[i], sep);
148 		} else {
149 			ret = snprintf(buf, size, "%s%s", blank, sep);
150 		}
151 		size -= ret;
152 		buf += ret;
153 
154 		count += POPCOUNT(data[i]);
155 
156 		/* Last byte is for the string termination */
157 		if (size < 1) {
158 			LOG_ERR("kbd_matrix_buf too small");
159 			return;
160 		}
161 	}
162 
163 	LOG_INF("%s %s [%s] (%d)",
164 		kbd_matrix_state_dev->name, header, kbd_matrix_buf, count);
165 }
166 
kbd_matrix_state_log(struct input_event * evt,void * user_data)167 static void kbd_matrix_state_log(struct input_event *evt, void *user_data)
168 {
169 	const struct input_kbd_matrix_common_config *cfg;
170 	static uint32_t row, col;
171 	static bool val;
172 
173 	ARG_UNUSED(user_data);
174 
175 	if (kbd_matrix_state_dev == NULL || kbd_matrix_state_dev != evt->dev) {
176 		return;
177 	}
178 
179 	cfg = kbd_matrix_state_dev->config;
180 
181 	switch (evt->code) {
182 	case INPUT_ABS_X:
183 		col = evt->value;
184 		break;
185 	case INPUT_ABS_Y:
186 		row = evt->value;
187 		break;
188 	case INPUT_BTN_TOUCH:
189 		val = evt->value;
190 		break;
191 	}
192 
193 	if (!evt->sync) {
194 		return;
195 	}
196 
197 	if (col > (CONFIG_INPUT_SHELL_KBD_MATRIX_STATE_MAX_COLS - 1)) {
198 		LOG_ERR("column index too large for the state buffer: %d", col);
199 		return;
200 	}
201 
202 	if (col > (cfg->col_size - 1)) {
203 		LOG_ERR("invalid column index: %d", col);
204 		return;
205 	}
206 
207 	if (row > (cfg->row_size - 1)) {
208 		LOG_ERR("invalid row index: %d", row);
209 		return;
210 	}
211 
212 	WRITE_BIT(kbd_matrix_state[col], row, val);
213 	if (val != 0) {
214 		WRITE_BIT(kbd_matrix_key_mask[col], row, 1);
215 	}
216 
217 	kbd_matrix_state_log_entry("state", kbd_matrix_state);
218 }
219 INPUT_CALLBACK_DEFINE(NULL, kbd_matrix_state_log, NULL);
220 
input_cmd_kbd_matrix_state_dump(const struct shell * sh,size_t argc,char * argv[])221 static int input_cmd_kbd_matrix_state_dump(const struct shell *sh,
222 					   size_t argc, char *argv[])
223 {
224 	const struct device *dev;
225 
226 	if (!strcmp(argv[1], "off")) {
227 		if (kbd_matrix_state_dev != NULL) {
228 			kbd_matrix_state_log_entry("key-mask",
229 						   kbd_matrix_key_mask);
230 		}
231 
232 		kbd_matrix_state_dev = NULL;
233 		shell_info(sh, "Keyboard state logging disabled");
234 		return 0;
235 	}
236 
237 	dev = device_get_binding(argv[1]);
238 	if (dev == NULL) {
239 		shell_error(sh, "Invalid device: %s", argv[1]);
240 		return -ENODEV;
241 	}
242 
243 	if (kbd_matrix_state_dev != NULL && kbd_matrix_state_dev != dev) {
244 		shell_error(sh, "Already logging for %s, disable logging first",
245 			    kbd_matrix_state_dev->name);
246 		return -EINVAL;
247 	}
248 
249 	memset(kbd_matrix_state, 0, sizeof(kbd_matrix_state));
250 	memset(kbd_matrix_key_mask, 0, sizeof(kbd_matrix_state));
251 	kbd_matrix_state_dev = dev;
252 
253 	shell_info(sh, "Keyboard state logging enabled for %s", dev->name);
254 
255 	return 0;
256 }
257 
device_name_get(size_t idx,struct shell_static_entry * entry)258 static void device_name_get(size_t idx, struct shell_static_entry *entry)
259 {
260 	const struct device *dev = shell_device_lookup(idx, NULL);
261 
262 	entry->syntax = (dev != NULL) ? dev->name : NULL;
263 	entry->handler = NULL;
264 	entry->help = NULL;
265 	entry->subcmd = NULL;
266 }
267 
268 SHELL_DYNAMIC_CMD_CREATE(dsub_device_name, device_name_get);
269 #endif /* CONFIG_INPUT_SHELL_KBD_MATRIX_STATE */
270 
271 SHELL_STATIC_SUBCMD_SET_CREATE(
272 	sub_input_cmds,
273 #ifdef CONFIG_INPUT_EVENT_DUMP
274 	SHELL_CMD_ARG(dump, NULL,
275 		      "Enable event dumping\n"
276 		      "usage: dump <on|off>",
277 		      input_cmd_dump, 2, 0),
278 #endif /* CONFIG_INPUT_EVENT_DUMP */
279 #ifdef CONFIG_INPUT_SHELL_KBD_MATRIX_STATE
280 	SHELL_CMD_ARG(kbd_matrix_state_dump, &dsub_device_name,
281 		      "Print the state of a keyboard matrix device each time a "
282 		      "key is pressed or released\n"
283 		      "usage: kbd_matrix_state_dump <device>|off",
284 		      input_cmd_kbd_matrix_state_dump, 2, 0),
285 #endif /* CONFIG_INPUT_SHELL_KBD_MATRIX_STATE */
286 	SHELL_CMD_ARG(report, NULL,
287 		      "Trigger an input report event\n"
288 		      "usage: report <type> <code> <value> [<sync>]",
289 		      input_cmd_report, 4, 1),
290 	SHELL_SUBCMD_SET_END);
291 
292 SHELL_CMD_REGISTER(input, &sub_input_cmds, "Input commands", NULL);
293 #endif /* CONFIG_INPUT_SHELL */
294