summaryrefslogtreecommitdiff
path: root/chip/stm32/dfu_bootmanager_main.c
blob: 452a7a6443121a760f56244fb0cf068d9b0768e1 (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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* Copyright 2022 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * DFU Boot Manager Main for STM32
 *
 * When the Boot Manager Main is enabled, the RO application skips the
 * common runtime and setup. This reduces the flash size and avoids clock,
 * interrupt, and setup steps which conflict with the built in Boot Loaders
 * while minimizing the Flash Size.
 *
 * The Boot Manager Main will perform self checks of the Flash and backup
 * memory. Based on these results it will boot into the DFU or RW Application.
 */

#include "clock.h"
#include "dfu_bootmanager_shared.h"
#include "flash.h"
#include "registers.h"
#include "task.h"

#ifdef CONFIG_DFU_BOOTMANAGER_MAX_REBOOT_COUNT
#if CONFIG_DFU_BOOTMANAGER_MAX_REBOOT_COUNT <= 0 || \
	CONFIG_DFU_BOOTMANAGER_MAX_REBOOT_COUNT > DFU_BOOTMANAGER_VALUE_DFU
#error "Max reboot count is out of range"
#endif
#endif /* CONFIG_DFU_BOOTMANAGER_MAX_REBOOT_COUNT */

/*
 * Checks if the RW region is valid by reading the first 8 bytes of flash, it
 * should not start with an erased block.
 *
 * The DFU boot manager should not jump into the RW region if it contains
 * invalid code as the EC is be unstable. A check will be performed to validate
 * the start of the RW region to verify that it contains valid data.
 * DFU programmers should erase this section of flash first and at this point,
 * the EC will no longer be able to jump into the RW application.
 *
 * The normal DFU programming sequence programming will work, but by
 * splitting into the following sequence we can protect against additional
 * failures.
 *
 * 1. Erase the first RW flash section. This will lock the EC out of RW.
 * 2. Update the remaining flash. Erase, program, and read back flash to
 *      to verify the operation was successful. Regions of the flash which
 *      are difficult to repair if an error occurs should be programmed next.
 * 3. Program the first RW flash section and exit DFU mode if verification is
 *      successful.
 *
 * @return 1 if erased, 0 if not erased
 */
static int rw_is_empty(void)
{
	return crec_flash_is_erased(CONFIG_RW_MEM_OFF, 8);
}

/*
 * Reads the backup registers. This will trigger a jump to DFU if either
 * the application has requested it or if the reboot counter indicates
 * the device is likely in a bad state. A counter recording the number
 * of reboots will be incremented.
 *
 * @returns True if the backup memory region indicates we should boot into DFU.
 */
static bool backup_boot_checks(void)
{
	uint8_t value;

	if (dfu_bootmanager_backup_read(&value)) {
		/* Value stored is not valid, set it to a valid value. */
		dfu_bootmanager_backup_write(DFU_BOOTMANAGER_VALUE_CLEAR);
		return false;
	}
	if (value == DFU_BOOTMANAGER_VALUE_DFU)
		return true;
#ifdef CONFIG_DFU_BOOTMANAGER_MAX_REBOOT_COUNT
	if (value >= CONFIG_DFU_BOOTMANAGER_MAX_REBOOT_COUNT)
		return true;
	/* Increment the reboot loop counter. */
	value++;
	dfu_bootmanager_backup_write(value);
#endif /* CONFIG_DFU_BOOTMANAGER_MAX_REBOOT_COUNT */
	return false;
}

/*
 * Performs the minimal set of initialization required for the boot manager.
 * The main application region or DFU boot loader have different prerequisites,
 * any configurations that are enabled either need to be benign with both
 * images or disabled prior to the jumps.
 */
static void dfu_bootmanager_init(void)
{
	/* enable clock on Power module */
#ifndef CHIP_FAMILY_STM32H7
#ifdef CHIP_FAMILY_STM32L4
	STM32_RCC_APB1ENR1 |= STM32_RCC_PWREN;
#else
	STM32_RCC_APB1ENR |= STM32_RCC_PWREN;
#endif
#endif
#if defined(CHIP_FAMILY_STM32F4)
	/* enable backup registers */
	STM32_RCC_AHB1ENR |= STM32_RCC_AHB1ENR_BKPSRAMEN;
#elif defined(CHIP_FAMILY_STM32H7)
	/* enable backup registers */
	STM32_RCC_AHB4ENR |= BIT(28);
#elif defined(CHIP_FAMILY_STM32L4)
	/* enable RTC APB clock */
	STM32_RCC_APB1ENR1 |= STM32_RCC_APB1ENR1_RTCAPBEN;
#else
	/* enable backup registers */
	STM32_RCC_APB1ENR |= BIT(27);
#endif
	/* Delay 1 APB clock cycle after the clock is enabled */
	clock_wait_bus_cycles(BUS_APB, 1);
	/* Enable access to RCC CSR register and RTC backup registers */
	STM32_PWR_CR |= BIT(8);
}

static void jump_to_rw(void)
{
	void (*addr)(void);

	addr = (void (*)(void))(*((uint32_t *)(CONFIG_PROGRAM_MEMORY_BASE +
					       CONFIG_RW_MEM_OFF + 4)));

	addr();
}

static void jump_to_dfu(void)
{
	void (*addr)(void);

	addr = (void (*)(void))(*((uint32_t *)(STM32_DFU_BASE + 4)));

	/* Clear the scratchpad. */
	dfu_bootmanager_backup_write(DFU_BOOTMANAGER_VALUE_CLEAR);
	addr();
}

/*
 * DFU Boot Manager main. It'll check if the RW region is not fully programmed
 * or if the backup memory indicates we should reboot into DFU.
 */
int main(void)
{
	dfu_bootmanager_init();

	if (rw_is_empty() || backup_boot_checks())
		jump_to_dfu();
	jump_to_rw();

	return 0;
}

/*
 * The RW application will replace the vector table and exception handlers
 * shortly after the jump. If the application is corrupt and fails before
 * this, the only action that can be done is jumping into DFU mode.
 */
void exception_panic(void)
{
	dfu_bootmanager_enter_dfu();
}

/*
 * Function stubs which are required by bkpdata.c and system.c:
 * Interrupts are always disabled in the Boot Manager so we do not
 * need to worry about concurrent access.
 */

void task_clear_pending_irq(int irq)
{
}
void interrupt_disable(void)
{
}
void mutex_lock(mutex_t *mtx)
{
}
void mutex_unlock(mutex_t *mtx)
{
}

bool in_interrupt_context(void)
{
	return false;
}