1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (C) 2019 Eugeniu Rosca <rosca.eugeniu@gmail.com>
4 *
5 * Command to read/modify/write Android BCB fields
6 */
7
8#include <android_bootloader_message.h>
9#include <bcb.h>
10#include <command.h>
11#include <common.h>
12#include <display_options.h>
13#include <log.h>
14#include <part.h>
15#include <malloc.h>
16#include <memalign.h>
17#include <linux/err.h>
18
19enum bcb_cmd {
20	BCB_CMD_LOAD,
21	BCB_CMD_FIELD_SET,
22	BCB_CMD_FIELD_CLEAR,
23	BCB_CMD_FIELD_TEST,
24	BCB_CMD_FIELD_DUMP,
25	BCB_CMD_STORE,
26};
27
28static const char * const fields[] = {
29	"command",
30	"status",
31	"recovery",
32	"stage"
33};
34
35static struct bootloader_message bcb __aligned(ARCH_DMA_MINALIGN) = { { 0 } };
36static struct disk_partition partition_data;
37
38static struct blk_desc *block;
39static struct disk_partition *partition = &partition_data;
40
41static int bcb_cmd_get(char *cmd)
42{
43	if (!strcmp(cmd, "load"))
44		return BCB_CMD_LOAD;
45	if (!strcmp(cmd, "set"))
46		return BCB_CMD_FIELD_SET;
47	if (!strcmp(cmd, "clear"))
48		return BCB_CMD_FIELD_CLEAR;
49	if (!strcmp(cmd, "test"))
50		return BCB_CMD_FIELD_TEST;
51	if (!strcmp(cmd, "store"))
52		return BCB_CMD_STORE;
53	if (!strcmp(cmd, "dump"))
54		return BCB_CMD_FIELD_DUMP;
55	else
56		return -1;
57}
58
59static int bcb_is_misused(int argc, char *const argv[])
60{
61	int cmd = bcb_cmd_get(argv[0]);
62
63	switch (cmd) {
64	case BCB_CMD_LOAD:
65		if (argc != 3 && argc != 4)
66			goto err;
67		break;
68	case BCB_CMD_FIELD_SET:
69		if (argc != 3)
70			goto err;
71		break;
72	case BCB_CMD_FIELD_TEST:
73		if (argc != 4)
74			goto err;
75		break;
76	case BCB_CMD_FIELD_CLEAR:
77		if (argc != 1 && argc != 2)
78			goto err;
79		break;
80	case BCB_CMD_STORE:
81		if (argc != 1)
82			goto err;
83		break;
84	case BCB_CMD_FIELD_DUMP:
85		if (argc != 2)
86			goto err;
87		break;
88	default:
89		printf("Error: 'bcb %s' not supported\n", argv[0]);
90		return -1;
91	}
92
93	if (cmd != BCB_CMD_LOAD && !block) {
94		printf("Error: Please, load BCB first!\n");
95		return -1;
96	}
97
98	return 0;
99err:
100	printf("Error: Bad usage of 'bcb %s'\n", argv[0]);
101
102	return -1;
103}
104
105static int bcb_field_get(const char *name, char **fieldp, int *sizep)
106{
107	if (!strcmp(name, "command")) {
108		*fieldp = bcb.command;
109		*sizep = sizeof(bcb.command);
110	} else if (!strcmp(name, "status")) {
111		*fieldp = bcb.status;
112		*sizep = sizeof(bcb.status);
113	} else if (!strcmp(name, "recovery")) {
114		*fieldp = bcb.recovery;
115		*sizep = sizeof(bcb.recovery);
116	} else if (!strcmp(name, "stage")) {
117		*fieldp = bcb.stage;
118		*sizep = sizeof(bcb.stage);
119	} else if (!strcmp(name, "reserved")) {
120		*fieldp = bcb.reserved;
121		*sizep = sizeof(bcb.reserved);
122	} else {
123		printf("Error: Unknown bcb field '%s'\n", name);
124		return -1;
125	}
126
127	return 0;
128}
129
130static void __bcb_reset(void)
131{
132	block = NULL;
133	partition = &partition_data;
134	memset(&partition_data, 0, sizeof(struct disk_partition));
135	memset(&bcb, 0, sizeof(struct bootloader_message));
136}
137
138static int __bcb_initialize(const char *iface, int devnum, const char *partp)
139{
140	char *endp;
141	int part, ret;
142
143	block = blk_get_dev(iface, devnum);
144	if (!block) {
145		ret = -ENODEV;
146		goto err_read_fail;
147	}
148
149	/*
150	 * always select the first hwpart in case another
151	 * blk operation selected a different hwpart
152	 */
153	ret = blk_dselect_hwpart(block, 0);
154	if (IS_ERR_VALUE(ret)) {
155		ret = -ENODEV;
156		goto err_read_fail;
157	}
158
159	part = simple_strtoul(partp, &endp, 0);
160	if (*endp == '\0') {
161		ret = part_get_info(block, part, partition);
162		if (ret)
163			goto err_read_fail;
164	} else {
165		part = part_get_info_by_name(block, partp, partition);
166		if (part < 0) {
167			ret = part;
168			goto err_read_fail;
169		}
170	}
171
172	return CMD_RET_SUCCESS;
173
174err_read_fail:
175	printf("Error: %d %d:%s read failed (%d)\n", block->uclass_id,
176	       block->devnum, partition->name, ret);
177	__bcb_reset();
178	return CMD_RET_FAILURE;
179}
180
181static int __bcb_load(void)
182{
183	u64 cnt;
184	int ret;
185
186	cnt = DIV_ROUND_UP(sizeof(struct bootloader_message), partition->blksz);
187	if (cnt > partition->size)
188		goto err_too_small;
189
190	if (blk_dread(block, partition->start, cnt, &bcb) != cnt) {
191		ret = -EIO;
192		goto err_read_fail;
193	}
194
195	debug("%s: Loaded from %d %d:%s\n", __func__, block->uclass_id,
196	      block->devnum, partition->name);
197
198	return CMD_RET_SUCCESS;
199err_read_fail:
200	printf("Error: %d %d:%s read failed (%d)\n", block->uclass_id,
201	       block->devnum, partition->name, ret);
202	goto err;
203err_too_small:
204	printf("Error: %d %d:%s too small!", block->uclass_id,
205	       block->devnum, partition->name);
206err:
207	__bcb_reset();
208	return CMD_RET_FAILURE;
209}
210
211static int do_bcb_load(struct cmd_tbl *cmdtp, int flag, int argc,
212		       char * const argv[])
213{
214	int ret;
215	int devnum;
216	char *endp;
217	char *iface = "mmc";
218
219	if (argc == 4) {
220		iface = argv[1];
221		argc--;
222		argv++;
223	}
224
225	devnum = simple_strtoul(argv[1], &endp, 0);
226	if (*endp != '\0') {
227		printf("Error: Device id '%s' not a number\n", argv[1]);
228		return CMD_RET_FAILURE;
229	}
230
231	ret = __bcb_initialize(iface, devnum, argv[2]);
232	if (ret != CMD_RET_SUCCESS)
233		return ret;
234
235	return __bcb_load();
236}
237
238static int __bcb_set(const char *fieldp, const char *valp)
239{
240	int size, len;
241	char *field, *str, *found, *tmp;
242
243	if (bcb_field_get(fieldp, &field, &size))
244		return CMD_RET_FAILURE;
245
246	len = strlen(valp);
247	if (len >= size) {
248		printf("Error: sizeof('%s') = %d >= %d = sizeof(bcb.%s)\n",
249		       valp, len, size, fieldp);
250		return CMD_RET_FAILURE;
251	}
252	str = strdup(valp);
253	if (!str) {
254		printf("Error: Out of memory while strdup\n");
255		return CMD_RET_FAILURE;
256	}
257
258	tmp = str;
259	field[0] = '\0';
260	while ((found = strsep(&tmp, ":"))) {
261		if (field[0] != '\0')
262			strcat(field, "\n");
263		strcat(field, found);
264	}
265	free(str);
266
267	return CMD_RET_SUCCESS;
268}
269
270static int do_bcb_set(struct cmd_tbl *cmdtp, int flag, int argc,
271		      char * const argv[])
272{
273	return __bcb_set(argv[1], argv[2]);
274}
275
276static int do_bcb_clear(struct cmd_tbl *cmdtp, int flag, int argc,
277			char *const argv[])
278{
279	int size;
280	char *field;
281
282	if (argc == 1) {
283		memset(&bcb, 0, sizeof(bcb));
284		return CMD_RET_SUCCESS;
285	}
286
287	if (bcb_field_get(argv[1], &field, &size))
288		return CMD_RET_FAILURE;
289
290	memset(field, 0, size);
291
292	return CMD_RET_SUCCESS;
293}
294
295static int do_bcb_test(struct cmd_tbl *cmdtp, int flag, int argc,
296		       char *const argv[])
297{
298	int size;
299	char *field;
300	char *op = argv[2];
301
302	if (bcb_field_get(argv[1], &field, &size))
303		return CMD_RET_FAILURE;
304
305	if (*op == '=' && *(op + 1) == '\0') {
306		if (!strncmp(argv[3], field, size))
307			return CMD_RET_SUCCESS;
308		else
309			return CMD_RET_FAILURE;
310	} else if (*op == '~' && *(op + 1) == '\0') {
311		if (!strstr(field, argv[3]))
312			return CMD_RET_FAILURE;
313		else
314			return CMD_RET_SUCCESS;
315	} else {
316		printf("Error: Unknown operator '%s'\n", op);
317	}
318
319	return CMD_RET_FAILURE;
320}
321
322static int do_bcb_dump(struct cmd_tbl *cmdtp, int flag, int argc,
323		       char *const argv[])
324{
325	int size;
326	char *field;
327
328	if (bcb_field_get(argv[1], &field, &size))
329		return CMD_RET_FAILURE;
330
331	print_buffer((ulong)field - (ulong)&bcb, (void *)field, 1, size, 16);
332
333	return CMD_RET_SUCCESS;
334}
335
336static int __bcb_store(void)
337{
338	u64 cnt;
339	int ret;
340
341	cnt = DIV_ROUND_UP(sizeof(struct bootloader_message), partition->blksz);
342
343	if (blk_dwrite(block, partition->start, cnt, &bcb) != cnt) {
344		ret = -EIO;
345		goto err;
346	}
347
348	return CMD_RET_SUCCESS;
349err:
350	printf("Error: %d %d:%s write failed (%d)\n", block->uclass_id,
351	       block->devnum, partition->name, ret);
352
353	return CMD_RET_FAILURE;
354}
355
356static int do_bcb_store(struct cmd_tbl *cmdtp, int flag, int argc,
357			char * const argv[])
358{
359	return __bcb_store();
360}
361
362int bcb_find_partition_and_load(const char *iface, int devnum, char *partp)
363{
364	int ret;
365
366	__bcb_reset();
367
368	ret = __bcb_initialize(iface, devnum, partp);
369	if (ret != CMD_RET_SUCCESS)
370		return ret;
371
372	return __bcb_load();
373}
374
375int bcb_load(struct blk_desc *block_description, struct disk_partition *disk_partition)
376{
377	__bcb_reset();
378
379	block = block_description;
380	partition = disk_partition;
381
382	return __bcb_load();
383}
384
385int bcb_set(enum bcb_field field, const char *value)
386{
387	if (field > BCB_FIELD_STAGE)
388		return CMD_RET_FAILURE;
389	return __bcb_set(fields[field], value);
390}
391
392int bcb_get(enum bcb_field field, char *value_out, size_t value_size)
393{
394	int size;
395	char *field_value;
396
397	if (field > BCB_FIELD_STAGE)
398		return CMD_RET_FAILURE;
399	if (bcb_field_get(fields[field], &field_value, &size))
400		return CMD_RET_FAILURE;
401
402	strlcpy(value_out, field_value, value_size);
403
404	return CMD_RET_SUCCESS;
405}
406
407int bcb_store(void)
408{
409	return __bcb_store();
410}
411
412void bcb_reset(void)
413{
414	__bcb_reset();
415}
416
417static struct cmd_tbl cmd_bcb_sub[] = {
418	U_BOOT_CMD_MKENT(load, CONFIG_SYS_MAXARGS, 1, do_bcb_load, "", ""),
419	U_BOOT_CMD_MKENT(set, CONFIG_SYS_MAXARGS, 1, do_bcb_set, "", ""),
420	U_BOOT_CMD_MKENT(clear, CONFIG_SYS_MAXARGS, 1, do_bcb_clear, "", ""),
421	U_BOOT_CMD_MKENT(test, CONFIG_SYS_MAXARGS, 1, do_bcb_test, "", ""),
422	U_BOOT_CMD_MKENT(dump, CONFIG_SYS_MAXARGS, 1, do_bcb_dump, "", ""),
423	U_BOOT_CMD_MKENT(store, CONFIG_SYS_MAXARGS, 1, do_bcb_store, "", ""),
424};
425
426static int do_bcb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
427{
428	struct cmd_tbl *c;
429
430	if (argc < 2)
431		return CMD_RET_USAGE;
432
433	argc--;
434	argv++;
435
436	c = find_cmd_tbl(argv[0], cmd_bcb_sub, ARRAY_SIZE(cmd_bcb_sub));
437	if (!c)
438		return CMD_RET_USAGE;
439
440	if (bcb_is_misused(argc, argv)) {
441		/*
442		 * We try to improve the user experience by reporting the
443		 * root-cause of misusage, so don't return CMD_RET_USAGE,
444		 * since the latter prints out the full-blown help text
445		 */
446		return CMD_RET_FAILURE;
447	}
448
449	return c->cmd(cmdtp, flag, argc, argv);
450}
451
452U_BOOT_CMD(
453	bcb, CONFIG_SYS_MAXARGS, 1, do_bcb,
454	"Load/set/clear/test/dump/store Android BCB fields",
455	"load <interface> <dev> <part>  - load  BCB from <interface> <dev>:<part>\n"
456	"load <dev> <part>              - load  BCB from mmc <dev>:<part>\n"
457	"bcb set   <field> <val>        - set   BCB <field> to <val>\n"
458	"bcb clear [<field>]            - clear BCB <field> or all fields\n"
459	"bcb test  <field> <op> <val>   - test  BCB <field> against <val>\n"
460	"bcb dump  <field>              - dump  BCB <field>\n"
461	"bcb store                      - store BCB back to <interface>\n"
462	"\n"
463	"Legend:\n"
464	"<interface> - storage device interface (virtio, mmc, etc)\n"
465	"<dev>       - storage device index containing the BCB partition\n"
466	"<part>      - partition index or name containing the BCB\n"
467	"<field>     - one of {command,status,recovery,stage,reserved}\n"
468	"<op>        - the binary operator used in 'bcb test':\n"
469	"              '=' returns true if <val> matches the string stored in <field>\n"
470	"              '~' returns true if <val> matches a subset of <field>'s string\n"
471	"<val>       - string/text provided as input to bcb {set,test}\n"
472	"              NOTE: any ':' character in <val> will be replaced by line feed\n"
473	"              during 'bcb set' and used as separator by upper layers\n"
474);
475