1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright 2023 Linus Walleij <linus.walleij@linaro.org>
4  * Support for the "SEAttle iMAge" SEAMA NAND image format
5  */
6 
7 #include <command.h>
8 #include <env.h>
9 #include <nand.h>
10 
11 /*
12  * All SEAMA data is stored in the flash in "network endianness"
13  * i.e. big endian, which means that it needs to be byte-swapped
14  * on all little endian platforms.
15  *
16  * structure for a SEAMA entity in NAND flash:
17  *
18  * 32 bit SEAMA magic 0x5EA3A417
19  * 16 bit reserved
20  * 16 bit metadata size (following the header)
21  * 32 bit image size
22  * 16 bytes MD5 digest of the image
23  * meta data
24  * ... image data ...
25  *
26  * Then if a new SEAMA magic follows, that is the next image.
27  */
28 
29 #define SEAMA_MAGIC		0x5EA3A417
30 #define SEAMA_HDR_NO_META_SZ	28
31 #define SEAMA_MAX_META_SZ	(1024 - SEAMA_HDR_NO_META_SZ)
32 
33 struct seama_header {
34 	u32 magic;
35 	u32 meta_size;
36 	u32 image_size;
37 	u8 md5[16];
38 	u8 metadata[SEAMA_MAX_META_SZ];
39 };
40 
41 static struct seama_header shdr;
42 
env_set_val(const char * varname,ulong val)43 static int env_set_val(const char *varname, ulong val)
44 {
45 	int ret;
46 
47 	ret = env_set_hex(varname, val);
48 	if (ret)
49 		printf("Failed to %s env var\n", varname);
50 
51 	return ret;
52 }
53 
do_seama_load_image(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])54 static int do_seama_load_image(struct cmd_tbl *cmdtp, int flag, int argc,
55 			       char *const argv[])
56 {
57 	struct mtd_info *mtd;
58 	uintptr_t load_addr;
59 	unsigned long image_index;
60 	u32 len;
61 	size_t readsz;
62 	int ret;
63 	u32 *start;
64 	u32 *offset;
65 	u32 *end;
66 	u32 tmp;
67 
68 	if (argc < 2 || argc > 3)
69 		return CMD_RET_USAGE;
70 
71 	load_addr = hextoul(argv[1], NULL);
72 	if (!load_addr) {
73 		printf("Invalid load address\n");
74 		return CMD_RET_USAGE;
75 	}
76 
77 	/* Can be 0 for first image */
78 	image_index = hextoul(argv[2], NULL);
79 
80 	/* We only support one NAND, the first one */
81 	nand_curr_device = 0;
82 	mtd = get_nand_dev_by_index(0);
83 	if (!mtd) {
84 		printf("NAND Device 0 not available\n");
85 		return CMD_RET_FAILURE;
86 	}
87 
88 #ifdef CONFIG_SYS_NAND_SELECT_DEVICE
89 	board_nand_select_device(mtd_to_nand(mtd), 0);
90 #endif
91 
92 	printf("Loading SEAMA image %lu from %s\n", image_index, mtd->name);
93 
94 	readsz = sizeof(shdr);
95 	offset = 0;
96 	ret = nand_read_skip_bad(mtd, 0, &readsz, NULL, mtd->size,
97 				 (u_char *)&shdr);
98 	if (ret) {
99 		printf("Read error reading SEAMA header\n");
100 		return CMD_RET_FAILURE;
101 	}
102 
103 	if (shdr.magic != SEAMA_MAGIC) {
104 		printf("Invalid SEAMA image magic: 0x%08x\n", shdr.magic);
105 		return CMD_RET_FAILURE;
106 	}
107 
108 	/* Only the lower 16 bits are valid */
109 	shdr.meta_size &= 0xFFFF;
110 
111 	if (env_set_val("seama_image_size", 0))
112 		return CMD_RET_FAILURE;
113 
114 	printf("SEMA IMAGE:\n");
115 	printf("  metadata size %d\n", shdr.meta_size);
116 	printf("  image size %d\n", shdr.image_size);
117 	printf("  checksum %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
118 	       shdr.md5[0], shdr.md5[1], shdr.md5[2], shdr.md5[3],
119 	       shdr.md5[4], shdr.md5[5], shdr.md5[6], shdr.md5[7],
120 	       shdr.md5[8], shdr.md5[9], shdr.md5[10], shdr.md5[11],
121 	       shdr.md5[12], shdr.md5[13], shdr.md5[14], shdr.md5[15]);
122 
123 	/* TODO: handle metadata if needed */
124 
125 	len = shdr.image_size;
126 	if (env_set_val("seama_image_size", len))
127 		return CMD_RET_FAILURE;
128 
129 	/* We need to include the header (read full pages) */
130 	readsz = shdr.image_size + SEAMA_HDR_NO_META_SZ + shdr.meta_size;
131 	ret = nand_read_skip_bad(mtd, 0, &readsz, NULL, mtd->size,
132 				 (u_char *)load_addr);
133 	if (ret) {
134 		printf("Read error reading SEAMA main image\n");
135 		return CMD_RET_FAILURE;
136 	}
137 
138 	/* We use a temporary variable tmp to avoid to hairy casts */
139 	start = (u32 *)load_addr;
140 	tmp = (u32)start;
141 	tmp += SEAMA_HDR_NO_META_SZ + shdr.meta_size;
142 	offset = (u32 *)tmp;
143 	tmp += shdr.image_size;
144 	end = (u32 *)tmp;
145 
146 	printf("Decoding SEAMA image 0x%08x..0x%08x to 0x%08x\n",
147 	       (u32)offset, (u32)end, (u32)start);
148 	for (; start < end; start++, offset++)
149 		*start = be32_to_cpu(*offset);
150 
151 	return CMD_RET_SUCCESS;
152 }
153 
154 U_BOOT_CMD
155 	(seama, 3, 1, do_seama_load_image,
156 	 "Load the SEAMA image and sets envs",
157 	 "seama <addr> <imageindex>\n"
158 );
159