blob: c4a040cb8cc7ee34f31a8f985f7c6d530f3a18f8 [file] [log] [blame]
Andrew Scull43d72e12017-11-20 14:14:55 +00001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <memory>
18#include <vector>
19
20#include <getopt.h>
21#include <openssl/sha.h>
22#include <stdarg.h>
23#include <stddef.h>
24#include <stdint.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <sys/stat.h>
29
30/* From Nugget OS */
31#include <application.h>
32#include <app_nugget.h>
33#include <flash_layout.h>
34#include <signed_header.h>
35
36#include <nos/AppClient.h>
37#include <nos/NuggetClient.h>
38#ifdef ANDROID
39#include <nos/CitadeldProxyClient.h>
40#endif
41
42namespace {
43
44using nos::AppClient;
45using nos::NuggetClient;
46using nos::NuggetClientInterface;
47#ifdef ANDROID
48using nos::CitadeldProxyClient;
49#endif
50
51/* Global options */
52struct options_s {
53 /* actions to take */
54 int version;
55 int ro;
56 int rw;
57 int reboot;
58 /* generic connection options */
59 const char *device;
60#ifdef ANDROID
61 int citadeld;
62#endif
63} options;
64
65enum no_short_opts_for_these {
66 OPT_DEVICE = 1000,
67 OPT_RO,
68 OPT_RW,
69 OPT_REBOOT,
70#ifdef ANDROID
71 OPT_CITADELD
72#endif
73};
74
75const char *short_opts = ":hv";
76const struct option long_opts[] = {
77 /* name hasarg *flag val */
78 {"version", 0, NULL, 'v'},
79 {"ro", 0, NULL, OPT_RO},
80 {"rw", 0, NULL, OPT_RW},
81 {"reboot", 0, NULL, OPT_REBOOT},
82 {"device", 1, NULL, OPT_DEVICE},
83#ifdef ANDROID
84 {"citadeld", 0, NULL, OPT_CITADELD},
85#endif
86 {"help", 0, NULL, 'h'},
87 {NULL, 0, NULL, 0},
88};
89
90void usage(const char *progname)
91{
92 fprintf(stderr, "\n");
93 fprintf(stderr,
94 "Usage: %s [actions] [image.bin]\n"
95 "\n"
96 "Citadel firmware boots in two stages. The first stage\n"
97 "bootloader (aka \"RO\") is provided by the SOC hardware team\n"
98 "and seldom changes. The application image (\"RW\") is invoked\n"
99 "by the RO image. There are two copies (A/B) of each stage,\n"
100 "so that the active copy can be protected while the unused\n"
101 "copy may be updated. At boot, the newer (valid) copy of each\n"
102 "stage is selected.\n"
103 "\n"
104 "The Citadel image file is the same size of the internal\n"
105 "flash, and contains all four firmware components (RO_A,\n"
106 "RW_A, RO_B, RW_B) located at the correct offsets. Only the\n"
107 "inactive copy (A/B) of each stage (RO/RW) can be modified.\n"
108 "The tool will update the correct copies automatically.\n"
109 "\n"
110 "You must specify the actions to perform. With no options,\n"
111 "this help message is displayed.\n"
112 "\n"
113 "Actions:\n"
114 "\n"
115 " -v, --version Display the Citadel version info\n"
116 " --rw Update RW firmware from the image file\n"
117 " --ro Update RO firmware from the image file\n"
118 " --reboot Tell Citadel to reboot\n"
119#ifdef ANDROID
120 "\n"
121 "Android options:\n"
122 "\n"
123 " --citadeld Communicate with Citadel via citadeld\n"
124#endif
125 "\n",
126 progname);
127}
128
129/****************************************************************************/
130/* Handy stuff */
131
132#ifndef MIN
133#define MIN(a, b) ((a) < (b) ? (a) : (b))
134#endif
135
136int errorcnt;
137void Error(const char *format, ...)
138{
139 va_list ap;
140
141 va_start(ap, format);
142 fprintf(stderr, "ERROR: ");
143 vfprintf(stderr, format, ap);
144 fprintf(stderr, "\n");
145 va_end(ap);
146
147 errorcnt++;
148}
149
150/* Return true on APP_SUCESS, display error message if it's not */
151int is_app_success(uint32_t retval)
152{
153 if (retval == APP_SUCCESS)
154 return 1;
155
156 errorcnt++;
157
158 fprintf(stderr, "Error code 0x%x: ", retval);
159 switch (retval) {
160 case APP_ERROR_BOGUS_ARGS:
161 fprintf(stderr, "bogus args");
162 break;
163 case APP_ERROR_INTERNAL:
164 fprintf(stderr, "app is being stupid");
165 break;
166 case APP_ERROR_TOO_MUCH:
167 fprintf(stderr, "caller sent too much data");
168 break;
169 default:
170 if (retval >= APP_SPECIFIC_ERROR &&
171 retval < APP_LINE_NUMBER_BASE) {
172 fprintf(stderr, "app-specific error #%d",
173 retval - APP_SPECIFIC_ERROR);
174 } else if (retval >= APP_LINE_NUMBER_BASE) {
175 fprintf(stderr, "error at line %d",
176 retval - APP_LINE_NUMBER_BASE);
177 } else {
178 fprintf(stderr, "unknown)");
179 }
180 }
181 fprintf(stderr, "\n");
182
183 return 0;
184}
185
186/****************************************************************************/
187
188std::vector<uint8_t> read_image_from_file(const char *name)
189{
190 FILE *fp;
191 struct stat st;
192
193 fp = fopen(name, "rb");
194 if (!fp) {
195 perror("fopen");
196 Error("Can't open file %s", name);
197 return {};
198 }
199
200 if (fstat(fileno(fp), &st)) {
201 perror("fstat");
202 Error("Can't fstat file %s", name);
203 fclose(fp);
204 return {};
205 }
206
207 if (st.st_size != CHIP_FLASH_SIZE) {
208 Error("The firmware image must be exactly %d bytes",
209 CHIP_FLASH_SIZE);
210 fclose(fp);
211 return {};
212 }
213
214 std::vector<uint8_t> buf(st.st_size);
215 if (1 != fread(buf.data(), st.st_size, 1, fp)) {
216 perror("fread");
217 Error("Can't read %zd bytes", st.st_size);
218 fclose(fp);
219 return {};
220 }
221
222 fclose(fp);
223 buf.resize(st.st_size);
224
225 return buf;
226}
227
228uint32_t compute_digest(struct nugget_app_flash_block *blk)
229{
230 uint8_t *start_here = ((uint8_t *)blk) +
231 offsetof(struct nugget_app_flash_block, offset);
232 size_t size_to_hash = sizeof(*blk) -
233 offsetof(struct nugget_app_flash_block, offset);
234 SHA_CTX ctx;
235 uint8_t digest[SHA_DIGEST_LENGTH];
236 uint32_t retval;
237
238 SHA1_Init(&ctx);
239 SHA1_Update(&ctx, start_here, size_to_hash);
240 SHA1_Final(digest, &ctx);
241
242 memcpy(&retval, digest, sizeof(retval));
243 return retval;
244}
245
246uint32_t try_update(AppClient &app, const std::vector<uint8_t> &image,
247 uint32_t offset, uint32_t imagesize)
248{
249 uint32_t stop = offset + imagesize;
250 uint32_t rv;
251
252 printf("Updating image from 0x%05x to 0x%05x, size 0x%05x\n",
253 CHIP_FLASH_BASE + offset, CHIP_FLASH_BASE + stop, imagesize);
254
255 for (; offset < stop; offset += CHIP_FLASH_BANK_SIZE) {
256 int retries = 3;
257 std::vector<uint8_t> data(sizeof(struct nugget_app_flash_block));
258 struct nugget_app_flash_block *fb =
259 (struct nugget_app_flash_block*)data.data();
260
261 fb->offset = offset;
262 memcpy(fb->payload, image.data() + offset, CHIP_FLASH_BANK_SIZE);
263 fb->block_digest = compute_digest(fb);
264
265 printf("writing 0x%05x / 0x%05x",
266 CHIP_FLASH_BASE + offset, CHIP_FLASH_BASE + stop);
267 do {
268 rv = app.Call(NUGGET_PARAM_FLASH_BLOCK, data, nullptr);
269 if (rv == NUGGET_ERROR_RETRY)
270 printf(" retrying");
271 } while (rv == NUGGET_ERROR_RETRY && retries--);
272 printf(" %s\n", rv ? "fail" : "ok");
273 if (rv)
274 break;
275 }
276
277 return rv;
278}
279
280
281uint32_t do_update(AppClient &app, const std::vector<uint8_t> &image,
282 uint32_t offset_A, uint32_t offset_B)
283{
284 struct SignedHeader *hdr;
285 uint32_t rv_A, rv_B;
286
287 /* Try image A first */
288 hdr = (struct SignedHeader *)(image.data() + offset_A);
289 rv_A = try_update(app, image, offset_A, hdr->image_size);
290
291 /* If that worked, we're done */
292 if (rv_A == APP_SUCCESS) {
293 return rv_A;
294 }
295
296 /* Else try image B */
297 hdr = (struct SignedHeader *)(image.data() + offset_B);
298 rv_B = try_update(app, image, offset_B, hdr->image_size);
299
300 return rv_B;
301}
302
303uint32_t do_version(AppClient &app)
304{
305 uint32_t retval;
306 std::vector<uint8_t> buffer;
307 buffer.reserve(512);
308
309 retval = app.Call(NUGGET_PARAM_VERSION, buffer, &buffer);
310
311 if (is_app_success(retval)) {
312 printf("%.*s\n", (int) buffer.size(), buffer.data());
313 }
314
315 return retval;
316}
317
318uint32_t do_reboot(AppClient &app)
319{
320 uint32_t retval;
321 std::vector<uint8_t> data = {0};
322
323 retval = app.Call(NUGGET_PARAM_REBOOT, data, nullptr);
324
325 if (is_app_success(retval)) {
326 printf("Citadel reboot requested\n");
327 }
328
329 return retval;
330}
331
332std::unique_ptr<NuggetClientInterface> select_client()
333{
334#ifdef ANDROID
335 if (options.citadeld) {
336 return std::unique_ptr<NuggetClientInterface>(
337 new CitadeldProxyClient());
338 }
339#endif
340 /* Default to a direct client */
341 return std::unique_ptr<NuggetClientInterface>(
342 new NuggetClient(options.device ? options.device : ""));
343}
344
345int update_to_image(const std::vector<uint8_t> &image)
346{
347 auto client = select_client();
348 client->Open();
349 if (!client->IsOpen()) {
350 Error("Unable to connect");
351 return 1;
352 }
353 AppClient app(*client, APP_ID_NUGGET);
354
355 /* Try all requested actions in reasonable order, bail out on error */
356
357 if (options.version &&
358 do_version(app) != APP_SUCCESS) {
359 return 2;
360 }
361
362 if (options.rw &&
363 do_update(app, image,
364 CHIP_RW_A_MEM_OFF, CHIP_RW_B_MEM_OFF) != APP_SUCCESS) {
365 return 3;
366 }
367
368 if (options.ro &&
369 do_update(app, image,
370 CHIP_RO_A_MEM_OFF, CHIP_RO_B_MEM_OFF) != APP_SUCCESS) {
371 return 4;
372 }
373
374 if (options.reboot &&
375 do_reboot(app) != APP_SUCCESS) {
376 return 5;
377 }
378 return 0;
379}
380
381} // namespace
382
383int main(int argc, char *argv[])
384{
385 int i;
386 int idx = 0;
387 char *this_prog;
388 std::vector<uint8_t> image;
389 int got_action = 0;
390
391 this_prog= strrchr(argv[0], '/');
392 if (this_prog) {
393 this_prog++;
394 } else {
395 this_prog = argv[0];
396 }
397
398 opterr = 0; /* quiet, you */
399 while ((i = getopt_long(argc, argv,
400 short_opts, long_opts, &idx)) != -1) {
401 switch (i) {
402 /* program-specific options */
403 case 'v':
404 options.version = 1;
405 got_action = 1;
406 break;
407 case OPT_RO:
408 options.ro = 1;
409 got_action = 1;
410 break;
411 case OPT_RW:
412 options.rw = 1;
413 got_action = 1;
414 break;
415 case OPT_REBOOT:
416 options.reboot = 1;
417 got_action = 1;
418 break;
419
420 /* generic options below */
421 case OPT_DEVICE:
422 options.device = optarg;
423 break;
424#ifdef ANDROID
425 case OPT_CITADELD:
426 options.citadeld = 1;
427 break;
428#endif
429 case 'h':
430 usage(this_prog);
431 return 0;
432 case 0:
433 break;
434 case '?':
435 if (optopt)
436 Error("Unrecognized options: -%c", optopt);
437 else
438 Error("Unrecognized options: %s",
439 argv[optind - 1]);
440 usage(this_prog);
441 break;
442 case ':':
443 Error("Missing argument to %s", argv[optind - 1]);
444 break;
445 default:
446 Error("Internal error at %s:%d", __FILE__, __LINE__);
447 exit(1);
448 }
449 }
450
451 if (errorcnt) {
452 goto out;
453 }
454
455 if (!got_action) {
456 usage(this_prog);
457 goto out;
458 }
459
460 if (options.ro || options.rw) {
461 if (optind < argc) {
462 /* Sets errorcnt on failure */
463 image = read_image_from_file(argv[optind]);
464 } else {
465 Error("An image file is required with --ro and --rw");
466 }
467 }
468
469 if (errorcnt)
470 goto out;
471
472 /* Okay, let's do something */
473 (void) update_to_image(image);
474 /* This is the last action, so fall through either way */
475
476out:
477 return !!errorcnt;
478}