summaryrefslogtreecommitdiff
path: root/chip/g/upgrade.c
blob: ca0fe07a8fb239853ccb2170300a2789b2bb9638 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/* Copyright 2016 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "config.h"
#include "console.h"
#include "endian.h"
#include "extension.h"
#include "flash.h"
#include "flash_info.h"
#include "hooks.h"
#include "signed_header.h"
#include "system.h"
#include "upgrade_fw.h"
#include "util.h"

#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ## args)

static void deferred_reboot(void)
{
	system_reset(SYSTEM_RESET_MANUALLY_TRIGGERED | SYSTEM_RESET_HARD);
}
DECLARE_DEFERRED(deferred_reboot);

#define MAX_REBOOT_TIMEOUT_MS 1000

/*
 * Verify if the header at the passed in flash offset needs to be restored,
 * and restore it if so. If this is an RO header - enable writing into that RO
 * section (the currently active RO writes can not be enabled).
 *
 * Return true if a corruption was detected and restored.
 */
static int header_restored(uint32_t offset)
{
	struct SignedHeader *header;
	uint32_t new_size;
	int rv;
	bool ro_header;

	header = (struct SignedHeader *)(CONFIG_PROGRAM_MEMORY_BASE + offset);

	new_size = header->image_size;
	if (!(new_size & TOP_IMAGE_SIZE_BIT))
		return 0;

	new_size &= ~TOP_IMAGE_SIZE_BIT;
	/*
	 * Clear only in case the size is sensible (i.e. not set to all
	 * ones).
	 */
	if (new_size > CONFIG_RW_SIZE)
		return 0;

	ro_header = (offset == CONFIG_RO_MEM_OFF) ||
		    (offset == CHIP_RO_B_MEM_OFF);
	if (ro_header)
		flash_open_ro_window(offset, sizeof(struct SignedHeader));

	/* rv is set to TRUE on success. */
	rv = flash_physical_write(offset + offsetof(struct SignedHeader,
						    image_size),
				  sizeof(header->image_size),
				  (char *)&new_size) == EC_SUCCESS;
	if (ro_header)
		flash_close_ro_window();

	return rv;
}

/*
 * Try restoring inactive RO and RW headers, Return the number of restored
 * headers.
 *
 * Since the RO could come with new keys, we don't want create a situation
 * where the RO is restored and the RW is not (say due to power failure or an
 * exception, etc.). So, restore the RW first, and then the RO. In this case
 * if restoring failed, the currently running RO is still guaranteed to boot
 * and start the currently running RW, so the update could be attempted again.
 */
static uint8_t headers_restored(void)
{
	uint8_t total_restored;

	/* Examine the RW first. */
	if (system_get_image_copy() == SYSTEM_IMAGE_RW)
		total_restored = header_restored(CONFIG_RW_B_MEM_OFF);
	else
		total_restored = header_restored(CONFIG_RW_MEM_OFF);

	/* Now the RO */
	if (system_get_ro_image_copy() == SYSTEM_IMAGE_RO)
		total_restored += header_restored(CHIP_RO_B_MEM_OFF);
	else
		total_restored += header_restored(CONFIG_RO_MEM_OFF);

	return total_restored;
}

/*
 * The TURN_UPDATE_ON command comes with a single parameter, which is a 16 bit
 * integer value of the number of milliseconds to wait before reboot in case
 * there has been an update waiting.
 *
 * Maximum wait time is 1000 ms.
 *
 * If the requested timeout exceeds the allowed maximum, or the command is
 * malformed, a protocol error is returned.
 *
 * If there was no errors, the number of restored headers is returned to the
 * host in a single byte.
 *
 * If at least one header was restored AND the host supplied a nonzero timeout
 * value, the H1 will be reset after this many milliseconds.
 *
 * Sending this command with the zero timeout value results in reporting to
 * the host how many headers were restored, the reset is not triggered.
 */
static enum vendor_cmd_rc turn_update_on(enum vendor_cmd_cc code,
					 void *buf,
					 size_t input_size,
					 size_t *response_size)
{
	uint16_t timeout;
	uint8_t *response;

	/* Just in case. */
	*response_size = 0;

	if (input_size < sizeof(uint16_t)) {
		CPRINTF("%s: incorrect request size %d\n",
			__func__, input_size);
		return VENDOR_RC_BOGUS_ARGS;
	}

	/* Retrieve the requested timeout. */
	memcpy(&timeout, buf, sizeof(timeout));
	timeout = be16toh(timeout);

	if (timeout > MAX_REBOOT_TIMEOUT_MS) {
		CPRINTF("%s: incorrect timeout value %d\n",
			__func__, timeout);
		return VENDOR_RC_BOGUS_ARGS;
	}

	*response_size = 1;
	response = buf;

	*response = headers_restored();
	if (*response && timeout) {
		/*
		 * At least one header was restored, and timeout is not zero,
		 * set up the reboot.
		 */
		CPRINTF("%s: rebooting in %d ms\n", __func__, timeout);
		hook_call_deferred(&deferred_reboot_data, timeout * MSEC);
	}

	return VENDOR_RC_SUCCESS;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_TURN_UPDATE_ON, turn_update_on);

/* This command's implementation is shared with USB updater. */
DECLARE_EXTENSION_COMMAND(EXTENSION_FW_UPGRADE, fw_upgrade_command_handler);