summaryrefslogtreecommitdiffstats
path: root/firmware/target/arm/imx233/dualboot-imx233.c
blob: 39d70f17ea45a5a943e05bdc59c4f99df20b8939 (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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright © 2011 by Amaury Pouly
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

/* IMPORTANT NOTE This file is used by both Rockbox (firmware and bootloader)
 * and the dualboot stub. The stub #include this file directly, so make sure
 * this file is independent and does not requires anything from the firmware */
#include "dualboot-imx233.h"

#ifdef HAVE_DUALBOOT_STUB
/** Persistent registers usage by the OF based on the Firmware SDK
 * and support for firmware upgrades in Rockbox
 * (this includes the Fuze+, ZEN X-Fi3, NWZ-E360/E370/E380)
 *
 * The following are used:
 * - PERSISTENT0: mostly standard stuff described in the datasheet
 * - PERSISTENT1: mostly proprietary stuff + some bits described in the datasheet
 * - PERSISTENT2: used to keep track of time (see below)
 * - PERSISTENT3: proprietary stuff
 * - PERSISTENT4: unused
 * - PERSISTENT5: used by Rockbox to tell the dualboot stub what to do
 *
 * In particular, the following bits are involved in the firmware upgrade process
 * and thus worth mentioning (Px means PERSISTENTx). Some of this information
 * might not be entirely accurate:
 * - P1[18]: when 0, indicates to the freescale boot stub to start the updater
 *           rather than the main firmware (play) or the usb firmware (host)
 * - P1[22]: when 0, indicates that the OF database/store should be rebuilt
 * - P3[10]: when 0, indicates that the firmware has been upgraded
 * - P3[11]: when 1, indicates to the freescale boot stub to boot without
 *           requiring the user to hold the power button for a small delay
 * - P3[12]: when 1, indicates that the internal drive or micro-sd card was
 *           modified in USB mode
 * - P3[16]: when 1, indicates that a firmware upgrade was attempted but aborted
 *           due to a too low battery
 *
 * To understand how all this works out together, recall that the boot sequence
 * usually looks as follows (fslx = freescale boot stub stage x, in section 0
 * of the firmware; rb = rockbox dualboot stub), where arrows indicate boot flow
 * (since every stage can choose to continue in the same section or jump to another):
 *
 *                                 +---> host (usb)
 *                                 |
 * fsl0 -> fsl1 -> fsl2 -> rb -> fsl3 -> fsl4 (updater)
 *                         |       |
 *                         |       +---> play (firmware)
 *                         |
 *                         +-----------> rock (bootloader) (-> loads rockbox)
 *
 * Note that the exact number of fsl stages is device-dependent, there 5 on the
 * fuze+, 3 on the NWZs for example.
 *
 * The fsl3 decides which stage to boot based on the following logic (order is
 * important):
 * - if P1[18] is 0, it goes to fsl4, to perform a firmware upgrade
 * - if usb is plugged, it goes to host, the OF USB mode
 * - if P1[22] is 1, it requires the user to hold the power button for small
 *   delay and aborts boot if this is not the case
 * - it goes to play, the OF normal firmware
 *
 * The fsl4 (updater) performs the following action:
 * - it clears P1[18] so that next boot will be a normal boot (ie NOT updater)
 * - if firmware.sb does not exist or is invalid, it reboots
 * - if the battery is too low for an upgrade, it sets P3[16]
 *   otherwise, it performs a firmware upgrade and clear P1[22]
 * - it shutdowns
 *
 * The play (firmware) performs the following actions:
 * - if P1[22] is 0 or P3[12] is 1, it rebuilds the store (the 'loading' screen)
 *   and set P1[22] to 1 and P3[12] to 0
 * - if P3[16] is 1, it displays a 'battery was too low to upgrade' message
 *   and clears P3[16]
 * - if P3[10] is 0, it displays a 'firmware was successfully upgraded' message
 *   and sets P3[10] to 1
 * - it performs its usual (crappy) functions
 *
 * The host (USB) performs the following actions:
 * - it clears P1[18] so that the next boot will run the updater
 * - it sets P3[11] to 1 so that the device will reboot without user intervention
 *   at the end
 * - if the host modifies the internal drive or micro-SD card, it sets P3[12]
 *   to 1 and clears P1[22]
 * - after USB is unplugged, it reboots
 *
 * Thus a typical firmware upgrade sequence will look like this:
 * - initially, the main firmware is running and flags are in the following state:
 *     P1[18] = 1 (normal boot)
 *     P1[22] = 1 (store is clean)
 *     P3[10] = 1 (firmware has not been upgraded)
 *     P3[11] = 0 (user needs to hold power button to boot)
 *     P3[12] = 0 (drive is clean)
 * - the user plugs the USB cable, play reboots, fsl3 boots to host because
 *   P1[18] = 1, the users put firmware.sb on the drive, thus modifying its
 *   content and then unplugs the drive; the device reboots with the following
 *   flags:
 *     P1[18] = 0 (updater boot)
 *     P1[22] = 0 (store is dirty)
 *     P3[10] = 1 (firmware has not been upgraded)
 *     P3[11] = 1 (user does not needs to hold power button to boot)
 *     P3[12] = 1 (drive is dirty)
 * - fsl3 boots to the updater because P1[18] = 0, the updater sees firmware.sb
 *   and performs a firmware upgrade; the device then shutdowns with the following
 *   flags:
 *     P1[18] = 1 (normal boot)
 *     P1[22] = 0 (store is dirty)
 *     P3[10] = 0 (firmware has been upgraded)
 *     P3[11] = 1 (user does not needs to hold power button to boot)
 *     P3[12] = 1 (drive is dirty)
 * - the user presses the power button, fsl3 boots to play (firmware) because
 *   P1[18] = 1, it rebuilds the store because P1[22] is clear, it then display
 *   a message to the user saying that the firmware has been upgraded because
 *   P3[10] is 0, and it resets the flags to same state as initially
 *
 * Note that the OF is lazy: it reboots to updater after USB mode in all cases
 * (even if firmware.sb was not present). In this case, the updater simply clears
 * the update flags and reboot immediately, thus it looks like a normal boot.
 *
 *
 * To support firmware upgrades in Rockbox, we need to two things:
 * - a way to tell rb (rockbox dual stub) to continue to fsl3 instead of booting
 *   rock (our bootloader)
 * - a way to setup the persistent bits so that fsl3 will boot to fsl4 (updater)
 *   instead of booting host (usb) or play (firmware)
 *
 * The approach taken is to use PERSISTENT5 to tell the dualboot stub what we want
 * to do. Since previous dualboot stubs did not support this, and that other actions
 * may be added in the future, the registers stores both the capabilities of the
 * dualboot stub (so that Rockbox can read them) and the actions that the dualboot
 * stub must perform (so that Rockbox can write them). The register is encoded
 * so that older/random values will be detected as garbage by newer Rockbox and
 * dualboot stub, and that a value of 0 for a field always behaves as when it did
 * not exist. More precisely, the bottom 16-bit must be 'RB' and
 * the top 16-bit store the actual data. The following fields are currently defined:
 * - CAP_BOOT(1 bit): supports booting to OF and UPDATER using the BOOT field
 * - BOOT(2 bits): sets boot mode
 * 
 * At the moment, BOOT supports three values:
 * - IMX233_BOOT_NORMAL: the dualboot will do a normal boot (booting to Rockbox
 *   unless the user presses the magic button that boots to the OF)
 * - IMX233_BOOT_OF: the dualboot stub will continue booting with fsl3 instead
 *   of Rockbox, but it will not touch any of OF persistent bits (this is useful
 *   to simply reboot to the OF for example)
 * - IMX233_BOOT_UPDATER: the dualboot will setup OF persistents bits and
 *   continue so that fsl3 enters fsl4 (updater)
 * In this scheme, Rockbox does not have to care about how exactly those actions
 * are achieved, only the dualboot stub has to deal with the persistent bits.
 * When the dualboot stubs see either OF or UPDATER, it clears BOOT back
 * to NORMAL before continuing, so as to avoid any boot loop.
 *
 */

#include "regs/rtc.h"

/* the persistent register we use */
#define REG_DUALBOOT    HW_RTC_PERSISTENT5
/* the bottom 16-bit are a magic value to indicate that the content is valid */
#define MAGIC_MASK      0xffff
#define MAGIC_VALUE     ('R' | 'B' << 8)
/* CAP_BOOT: 1-bit (16) */
#define CAP_BOOT_POS    16
#define CAP_BOOT_MASK   (1 << 16)
/* BOOT field: 2-bits (18-17) */
#define BOOT_POS        17
#define BOOT_MASK       (3 << 17)

unsigned imx233_dualboot_get_field(enum imx233_dualboot_field_t field)
{
    unsigned val = HW_RTC_PERSISTENT5;
    /* if signature doesn't match, assume everything is 0 */
    if((val & MAGIC_MASK) != MAGIC_VALUE)
        return 0;
#define match(field) \
    case DUALBOOT_##field: return ((val & field##_MASK) >> field##_POS);
    switch(field)
    {
        match(CAP_BOOT)
        match(BOOT)
        default: return 0; /* unknown */
    }
#undef match
}

void imx233_dualboot_set_field(enum imx233_dualboot_field_t field, unsigned fval)
{
    unsigned val = HW_RTC_PERSISTENT5;
    /* if signature doesn't match, create an empty register */
    if((val & MAGIC_MASK) != MAGIC_VALUE)
        val = MAGIC_VALUE; /* all field are 0 */
#define match(field) \
    case DUALBOOT_##field: \
        val &= ~field##_MASK; \
        val |= (fval << field##_POS) & field##_MASK; \
        break;
    switch(field)
    {
        match(CAP_BOOT)
        match(BOOT)
        default: break;
    }
    HW_RTC_PERSISTENT5 = val;
#undef match
}

#endif /* HAVE_DUALBOOT_STUB */