Implement a PIN-checking system.

Idea and original code by @reworks
This commit is contained in:
TuxSH 2016-08-03 14:13:26 +02:00
parent e4ed713fce
commit 709aefba5d
9 changed files with 300 additions and 39 deletions

View File

@ -28,7 +28,7 @@
#define BUTTON_R1 (1 << 8) #define BUTTON_R1 (1 << 8)
#define BUTTON_L1 (1 << 9) #define BUTTON_L1 (1 << 9)
#define BUTTON_A 1 #define BUTTON_A (1 << 0)
#define BUTTON_B (1 << 1) #define BUTTON_B (1 << 1)
#define BUTTON_X (1 << 10) #define BUTTON_X (1 << 10)
#define BUTTON_Y (1 << 11) #define BUTTON_Y (1 << 11)
@ -42,4 +42,5 @@
#define SAFE_MODE (BUTTON_R1 | BUTTON_L1 | BUTTON_A | BUTTON_UP) #define SAFE_MODE (BUTTON_R1 | BUTTON_L1 | BUTTON_A | BUTTON_UP)
#define SINGLE_PAYLOAD_BUTTONS (BUTTON_LEFT | BUTTON_RIGHT | BUTTON_UP | BUTTON_DOWN | BUTTON_START | BUTTON_X | BUTTON_Y) #define SINGLE_PAYLOAD_BUTTONS (BUTTON_LEFT | BUTTON_RIGHT | BUTTON_UP | BUTTON_DOWN | BUTTON_START | BUTTON_X | BUTTON_Y)
#define L_PAYLOAD_BUTTONS (BUTTON_R1 | BUTTON_A | BUTTON_SELECT) #define L_PAYLOAD_BUTTONS (BUTTON_R1 | BUTTON_A | BUTTON_SELECT)
#define MENU_BUTTONS (BUTTON_LEFT | BUTTON_RIGHT | BUTTON_UP | BUTTON_DOWN | BUTTON_A | BUTTON_START) #define MENU_BUTTONS (BUTTON_LEFT | BUTTON_RIGHT | BUTTON_UP | BUTTON_DOWN | BUTTON_A | BUTTON_START)
#define PIN_BUTTONS (BUTTON_A | BUTTON_B | BUTTON_X | BUTTON_Y | BUTTON_START)

View File

@ -25,13 +25,11 @@
#include "screen.h" #include "screen.h"
#include "draw.h" #include "draw.h"
#include "fs.h" #include "fs.h"
#include "i2c.h"
#include "buttons.h" #include "buttons.h"
void configureCFW(const char *configPath) void configureCFW(const char *configPath)
{ {
bool needToDeinit = initScreens(); clearScreens();
drawString(CONFIG_TITLE, 10, 10, COLOR_TITLE); drawString(CONFIG_TITLE, 10, 10, COLOR_TITLE);
drawString("Press A to select, START to save", 10, 30, COLOR_WHITE); drawString("Press A to select, START to save", 10, 30, COLOR_WHITE);
@ -44,7 +42,8 @@ void configureCFW(const char *configPath)
"( ) Enable region/language emu. and ext. .code", "( ) Enable region/language emu. and ext. .code",
"( ) Show current NAND in System Settings", "( ) Show current NAND in System Settings",
"( ) Show GBA boot screen in patched AGB_FIRM", "( ) Show GBA boot screen in patched AGB_FIRM",
"( ) Enable splash screen with no screen-init" }; "( ) Enable splash screen with no screen-init",
"( ) Use a PIN" };
struct multiOption { struct multiOption {
int posXs[4]; int posXs[4];
@ -202,12 +201,4 @@ void configureCFW(const char *configPath)
//Wait for the pressed buttons to change //Wait for the pressed buttons to change
while(HID_PAD == BUTTON_START); while(HID_PAD == BUTTON_START);
if(needToDeinit)
{
//Turn off backlight
i2cWriteRegister(I2C_DEV_MCU, 0x22, 0x16);
deinitScreens();
PDN_GPU_CNT = 1;
}
} }

View File

@ -294,16 +294,16 @@ static void sha(void *res, const void *src, u32 size, u32 mode)
* NAND/FIRM crypto * NAND/FIRM crypto
****************************************************************/ ****************************************************************/
static u8 nandCTR[0x10], static u8 __attribute__((aligned(4))) nandCTR[0x10];
nandSlot; static u8 nandSlot;
static u32 fatStart; static u32 fatStart;
//Initialize the CTRNAND crypto //Initialize the CTRNAND crypto
void ctrNandInit(void) void ctrNandInit(void)
{ {
u8 cid[0x10]; u8 __attribute__((aligned(4))) cid[0x10];
u8 shaSum[0x20]; u8 __attribute__((aligned(4))) shaSum[0x20];
sdmmc_get_cid(1, (u32 *)cid); sdmmc_get_cid(1, (u32 *)cid);
sha(shaSum, cid, 0x10, SHA_256_MODE); sha(shaSum, cid, 0x10, SHA_256_MODE);
@ -311,7 +311,7 @@ void ctrNandInit(void)
if(isN3DS) if(isN3DS)
{ {
u8 keyY0x5[0x10] = {0x4D, 0x80, 0x4F, 0x4E, 0x99, 0x90, 0x19, 0x46, 0x13, 0xA2, 0x04, 0xAC, 0x58, 0x44, 0x60, 0xBE}; u8 __attribute__((aligned(4))) keyY0x5[0x10] = {0x4D, 0x80, 0x4F, 0x4E, 0x99, 0x90, 0x19, 0x46, 0x13, 0xA2, 0x04, 0xAC, 0x58, 0x44, 0x60, 0xBE};
aes_setkey(0x05, keyY0x5, AES_KEYY, AES_INPUT_BE | AES_INPUT_NORMAL); aes_setkey(0x05, keyY0x5, AES_KEYY, AES_INPUT_BE | AES_INPUT_NORMAL);
nandSlot = 0x05; nandSlot = 0x05;
fatStart = 0x5CAD7; fatStart = 0x5CAD7;
@ -326,7 +326,7 @@ void ctrNandInit(void)
//Read and decrypt from the selected CTRNAND //Read and decrypt from the selected CTRNAND
u32 ctrNandRead(u32 sector, u32 sectorCount, u8 *outbuf) u32 ctrNandRead(u32 sector, u32 sectorCount, u8 *outbuf)
{ {
u8 tmpCTR[0x10]; u8 __attribute__((aligned(4))) tmpCTR[0x10];
memcpy(tmpCTR, nandCTR, 0x10); memcpy(tmpCTR, nandCTR, 0x10);
aes_advctr(tmpCTR, ((sector + fatStart) * 0x200) / AES_BLOCK_SIZE, AES_INPUT_BE | AES_INPUT_NORMAL); aes_advctr(tmpCTR, ((sector + fatStart) * 0x200) / AES_BLOCK_SIZE, AES_INPUT_BE | AES_INPUT_NORMAL);
@ -350,8 +350,8 @@ u32 ctrNandRead(u32 sector, u32 sectorCount, u8 *outbuf)
//Sets the 7.x NCCH KeyX and the 6.x gamecard save data KeyY //Sets the 7.x NCCH KeyX and the 6.x gamecard save data KeyY
void setRSAMod0DerivedKeys(void) void setRSAMod0DerivedKeys(void)
{ {
const u8 keyX0x25[0x10] = {0xCE, 0xE7, 0xD8, 0xAB, 0x30, 0xC0, 0x0D, 0xAE, 0x85, 0x0E, 0xF5, 0xE3, 0x82, 0xAC, 0x5A, 0xF3}, const u8 __attribute__((aligned(4))) keyX0x25[0x10] = {0xCE, 0xE7, 0xD8, 0xAB, 0x30, 0xC0, 0x0D, 0xAE, 0x85, 0x0E, 0xF5, 0xE3, 0x82, 0xAC, 0x5A, 0xF3};
keyY0x2F[0x10] = {0xC3, 0x69, 0xBA, 0xA2, 0x1E, 0x18, 0x8A, 0x88, 0xA9, 0xAA, 0x94, 0xE5, 0x50, 0x6A, 0x9F, 0x16}; const u8 __attribute__((aligned(4))) keyY0x2F[0x10] = {0xC3, 0x69, 0xBA, 0xA2, 0x1E, 0x18, 0x8A, 0x88, 0xA9, 0xAA, 0x94, 0xE5, 0x50, 0x6A, 0x9F, 0x16};
aes_setkey(0x25, keyX0x25, AES_KEYX, AES_INPUT_BE | AES_INPUT_NORMAL); aes_setkey(0x25, keyX0x25, AES_KEYX, AES_INPUT_BE | AES_INPUT_NORMAL);
aes_setkey(0x2F, keyY0x2F, AES_KEYY, AES_INPUT_BE | AES_INPUT_NORMAL); aes_setkey(0x2F, keyY0x2F, AES_KEYY, AES_INPUT_BE | AES_INPUT_NORMAL);
@ -394,9 +394,9 @@ void arm9Loader(u8 *arm9Section)
} }
//Firm keys //Firm keys
u8 keyY[0x10], u8 __attribute__((aligned(4))) keyY[0x10];
arm9BinCTR[0x10], u8 __attribute__((aligned(4))) arm9BinCTR[0x10];
arm9BinSlot = a9lVersion ? 0x16 : 0x15; u8 arm9BinSlot = a9lVersion ? 0x16 : 0x15;
//Setup keys needed for arm9bin decryption //Setup keys needed for arm9bin decryption
memcpy(keyY, arm9Section + 0x10, 0x10); memcpy(keyY, arm9Section + 0x10, 0x10);
@ -410,9 +410,9 @@ void arm9Loader(u8 *arm9Section)
if(a9lVersion) if(a9lVersion)
{ {
const u8 key1[0x10] = {0x07, 0x29, 0x44, 0x38, 0xF8, 0xC9, 0x75, 0x93, 0xAA, 0x0E, 0x4A, 0xB4, 0xAE, 0x84, 0xC1, 0xD8}, const u8 __attribute__((aligned(4))) key1[0x10] = {0x07, 0x29, 0x44, 0x38, 0xF8, 0xC9, 0x75, 0x93, 0xAA, 0x0E, 0x4A, 0xB4, 0xAE, 0x84, 0xC1, 0xD8};
key2[0x10] = {0x42, 0x3F, 0x81, 0x7A, 0x23, 0x52, 0x58, 0x31, 0x6E, 0x75, 0x8E, 0x3A, 0x39, 0x43, 0x2E, 0xD0}; const u8 __attribute__((aligned(4))) key2[0x10] = {0x42, 0x3F, 0x81, 0x7A, 0x23, 0x52, 0x58, 0x31, 0x6E, 0x75, 0x8E, 0x3A, 0x39, 0x43, 0x2E, 0xD0};
u8 keyX[0x10]; u8 __attribute__((aligned(4))) keyX[0x10];
aes_setkey(0x11, a9lVersion == 2 ? key2 : key1, AES_KEYNORMAL, AES_INPUT_BE | AES_INPUT_NORMAL); aes_setkey(0x11, a9lVersion == 2 ? key2 : key1, AES_KEYNORMAL, AES_INPUT_BE | AES_INPUT_NORMAL);
aes_use_keyslot(0x11); aes_use_keyslot(0x11);
@ -430,8 +430,8 @@ void arm9Loader(u8 *arm9Section)
//Set >=9.6 KeyXs //Set >=9.6 KeyXs
if(a9lVersion == 2) if(a9lVersion == 2)
{ {
u8 keyData[0x10] = {0xDD, 0xDA, 0xA4, 0xC6, 0x2C, 0xC4, 0x50, 0xE9, 0xDA, 0xB6, 0x9B, 0x0D, 0x9D, 0x2A, 0x21, 0x98}, u8 __attribute__((aligned(4))) keyData[0x10] = {0xDD, 0xDA, 0xA4, 0xC6, 0x2C, 0xC4, 0x50, 0xE9, 0xDA, 0xB6, 0x9B, 0x0D, 0x9D, 0x2A, 0x21, 0x98};
decKey[0x10]; u8 __attribute__((aligned(4))) decKey[0x10];
//Set keys 0x19..0x1F keyXs //Set keys 0x19..0x1F keyXs
aes_use_keyslot(0x11); aes_use_keyslot(0x11);
@ -442,4 +442,16 @@ void arm9Loader(u8 *arm9Section)
keyData[0xF] += 1; keyData[0xF] += 1;
} }
} }
}
void computePINHash(u8 out[32], u8 *in, u32 blockCount)
{
u8 __attribute__((aligned(4))) cid[0x10];
u8 __attribute__((aligned(4))) cipherText[0x10];
sdmmc_get_cid(1, (u32 *)cid);
aes_use_keyslot(4); // console-unique keyslot which keys are set by the Arm9 bootROM
aes(cipherText, in, blockCount, cid, AES_CBC_ENCRYPT_MODE, AES_INPUT_BE | AES_INPUT_NORMAL);
sha(out, cipherText, 0x10, SHA_256_MODE);
} }

View File

@ -107,4 +107,6 @@ void ctrNandInit(void);
u32 ctrNandRead(u32 sector, u32 sectorCount, u8 *outbuf); u32 ctrNandRead(u32 sector, u32 sectorCount, u8 *outbuf);
void setRSAMod0DerivedKeys(void); void setRSAMod0DerivedKeys(void);
void decryptExeFs(u8 *inbuf); void decryptExeFs(u8 *inbuf);
void arm9Loader(u8 *arm9Section); void arm9Loader(u8 *arm9Section);
void computePINHash(u8 out[32], u8 *in, u32 blockCount);

View File

@ -32,6 +32,8 @@
#include "draw.h" #include "draw.h"
#include "screen.h" #include "screen.h"
#include "buttons.h" #include "buttons.h"
#include "pin.h"
#include "i2c.h"
#include "../build/injector.h" #include "../build/injector.h"
static firmHeader *const firm = (firmHeader *)0x24000000; static firmHeader *const firm = (firmHeader *)0x24000000;
@ -44,6 +46,8 @@ bool isN3DS;
FirmwareSource firmSource; FirmwareSource firmSource;
PINData pin;
void main(void) void main(void)
{ {
bool isFirmlaunch, bool isFirmlaunch,
@ -67,6 +71,7 @@ void main(void)
//Attempt to read the configuration file //Attempt to read the configuration file
needConfig = fileRead(&config, configPath) ? MODIFY_CONFIGURATION : CREATE_CONFIGURATION; needConfig = fileRead(&config, configPath) ? MODIFY_CONFIGURATION : CREATE_CONFIGURATION;
bool pinExists = CONFIG(7) && readPin(&pin);
//Determine if this is a firmlaunch boot //Determine if this is a firmlaunch boot
if(*(vu8 *)0x23F00005) if(*(vu8 *)0x23F00005)
@ -123,10 +128,19 @@ void main(void)
} }
//If no configuration file exists or SELECT is held, load configuration menu //If no configuration file exists or SELECT is held, load configuration menu
if(needConfig == CREATE_CONFIGURATION || ((pressed & BUTTON_SELECT) && !(pressed & BUTTON_L1))) bool loadConfigurationMenu = needConfig == CREATE_CONFIGURATION || ((pressed & BUTTON_SELECT) && !(pressed & BUTTON_L1));
bool needToDeinit = false;
if(CFG_BOOTENV == 0 || loadConfigurationMenu)
{
if(loadConfigurationMenu || pinExists) needToDeinit = initScreens();
if(pinExists) verifyPin(&pin, true);
}
if(loadConfigurationMenu)
{ {
configureCFW(configPath); configureCFW(configPath);
if(!pinExists && CONFIG(7)) pin = newPin();
//Zero the last booted FIRM flag //Zero the last booted FIRM flag
CFG_BOOTENV = 0; CFG_BOOTENV = 0;
@ -137,6 +151,13 @@ void main(void)
//Update pressed buttons //Update pressed buttons
pressed = HID_PAD; pressed = HID_PAD;
} }
if(needToDeinit)
{
//Turn off backlight
i2cWriteRegister(I2C_DEV_MCU, 0x22, 0x16);
deinitScreens();
PDN_GPU_CNT = 1;
}
if(isA9lh && !CFG_BOOTENV && pressed == SAFE_MODE) if(isA9lh && !CFG_BOOTENV && pressed == SAFE_MODE)
{ {
@ -294,7 +315,7 @@ static inline void patchNativeFirm(u32 firmVersion, FirmwareSource nandType, u32
//Apply anti-anti-DG patches //Apply anti-anti-DG patches
patchTitleInstallMinVersionCheck(process9Offset, process9Size); patchTitleInstallMinVersionCheck(process9Offset, process9Size);
//Restore SVCBackdoor //Restore svcBackdoor
reimplementSvcBackdoor((u8 *)firm + section[1].offset, section[1].size); reimplementSvcBackdoor((u8 *)firm + section[1].offset, section[1].size);
} }
} }

181
source/pin.c Normal file
View File

@ -0,0 +1,181 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016 Aurora Wright, TuxSH
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b of GPLv3 applies to this file: Requiring preservation of specified
* reasonable legal notices or author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
*/
/*
* pin.c
* Code to manage pin locking for 3ds. By reworks.
*/
#include "draw.h"
#include "screen.h"
#include "utils.h"
#include "memory.h"
#include "buttons.h"
#include "fs.h"
#include "i2c.h"
#include "pin.h"
#include "crypto.h"
bool readPin(PINData *out)
{
u8 __attribute__((aligned(4))) zeroes[16] = {0};
u8 __attribute__((aligned(4))) tmp[32] = {0};
if(fileRead(out, "/luma/pin.bin") != sizeof(PINData)) return false;
else if(memcmp(out->magic, "PINF", 4) != 0) return false;
computePINHash(tmp, zeroes, 1);
fileWrite(tmp, "/luma/testhash.bin", 32);
return memcmp(out->testHash, tmp, 32) == 0; //test vector verification (SD card has (or hasn't) been used on another console)
}
static inline char PINKeyToLetter(u32 pressed)
{
const char keys[] = "AB--------XY";
u32 i;
__asm__ volatile("clz %[i], %[pressed]" : [i] "=r" (i) : [pressed] "r" (pressed));
return keys[31 - i];
}
PINData newPin(void)
{
clearScreens();
drawString("Enter your NEW PIN (4 keys): ", 10, 10, COLOR_WHITE);
u32 pressed = 0;
// Set the default value as 0x00 so we can check if there are any unentered characters.
u8 __attribute__((aligned(4))) enteredPassword[16 * ((PIN_LENGTH + 15) / 16)] = {0}; // pad to AES block length
int charDrawPos = 29 * SPACING_X;
int cnt = 0;
while(true)
{
do
{
pressed = waitInput();
}
while(!(pressed & PIN_BUTTONS & ~BUTTON_START));
pressed &= PIN_BUTTONS & ~BUTTON_START;
if(!pressed) continue;
char key = PINKeyToLetter(pressed);
enteredPassword[cnt++] = (u8)key; // add character to password.
// visualize character on screen.
drawCharacter(key, 10 + charDrawPos, 10, COLOR_WHITE);
charDrawPos += 2 * SPACING_X;
// we leave the rest of the array zeroed out.
if (cnt >= PIN_LENGTH)
{
PINData pin = {0};
u8 __attribute__((aligned(4))) tmp[32] = {0};
u8 __attribute__((aligned(4))) zeroes[16] = {0};
memcpy(pin.magic, "PINF", 4);
pin.formatVersionMajor = 1;
pin.formatVersionMinor = 0;
computePINHash(tmp, zeroes, 1);
memcpy(pin.testHash, tmp, 32);
computePINHash(tmp, enteredPassword, (PIN_LENGTH + 15) / 16);
memcpy(pin.hash, tmp, 32);
fileWrite(&pin, "/luma/pin.bin", sizeof(PINData));
return pin;
}
}
}
void verifyPin(PINData *in, bool allowQuit)
{
clearScreens();
drawString("Press START to shutdown or enter pin to proceed.", 10, 10, COLOR_WHITE);
drawString("Pin: ", 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
u32 pressed = 0;
// Set the default characters as 0x00 so we can check if there are any unentered characters.
u8 __attribute__((aligned(4))) enteredPassword[16 * ((PIN_LENGTH + 15) / 16)] = {0};
bool unlock;
int charDrawPos = 5 * SPACING_X, cnt = 0;
while(true)
{
do
{
pressed = waitInput();
}
while(!(pressed & PIN_BUTTONS));
pressed &= PIN_BUTTONS;// & ~BUTTON_START;
if(!allowQuit) pressed &= ~BUTTON_START;
if(!pressed) continue;
if(pressed & BUTTON_START)
{
clearScreens();
mcuPowerOff();
}
char key = PINKeyToLetter(pressed);
enteredPassword[cnt++] = (u8)key; // add character to password.
// visualize character on screen.
drawCharacter(key, 10 + charDrawPos, 10 + 2 * SPACING_Y, COLOR_WHITE);
charDrawPos += 2 * SPACING_X;
if(cnt >= PIN_LENGTH)
{
u8 __attribute__((aligned(4))) tmp[32] = {0};
computePINHash(tmp, enteredPassword, (PIN_LENGTH + 15) / 16);
unlock = memcmp(in->hash, tmp, 32) == 0;
if (!unlock)
{
// re zero out all 16 just in case.
memset32(enteredPassword, 0, 16);
pressed = 0;
charDrawPos = 5 * SPACING_X;
cnt = 0;
clearScreens();
drawString("Press START to shutdown or enter pin to proceed.", 10, 10, COLOR_WHITE);
drawString("Pin: ", 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
drawString("Wrong pin! Try again!", 10, 10 + 3 * SPACING_Y, COLOR_RED);
}
else return;
}
}
}

46
source/pin.h Normal file
View File

@ -0,0 +1,46 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016 Aurora Wright, TuxSH
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b of GPLv3 applies to this file: Requiring preservation of specified
* reasonable legal notices or author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
*/
/*
* pin.h
*
* Code to manage pin locking for 3ds. By reworks.
*/
#pragma once
#include "types.h"
#define PIN_LENGTH 4
typedef struct __attribute__((packed))
{
char magic[4];
u16 formatVersionMajor, formatVersionMinor;
u8 testHash[32];
u8 hash[32];
} PINData;
bool readPin(PINData* out);
PINData newPin(void);
void verifyPin(PINData *in, bool allowQuit);

View File

@ -59,7 +59,15 @@ void mcuReboot(void)
flushEntireDCache(); //Ensure that all memory transfers have completed and that the data cache has been flushed flushEntireDCache(); //Ensure that all memory transfers have completed and that the data cache has been flushed
i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 2); i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 2);
while(1); while(true);
}
void mcuPowerOff(void)
{
flushEntireDCache(); //Ensure that all memory transfers have completed and that the data cache has been flushed
i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 0);
while(true);
} }
//TODO: add support for TIMER IRQ //TODO: add support for TIMER IRQ
@ -102,15 +110,12 @@ void stopChrono(void)
void error(const char *message) void error(const char *message)
{ {
initScreens(); initScreens();
clearScreens();
drawString("An error has occurred:", 10, 10, COLOR_RED); drawString("An error has occurred:", 10, 10, COLOR_RED);
int posY = drawString(message, 10, 30, COLOR_WHITE); int posY = drawString(message, 10, 30, COLOR_WHITE);
drawString("Press any button to shutdown", 10, posY + 2 * SPACING_Y, COLOR_WHITE); drawString("Press any button to shutdown", 10, posY + 2 * SPACING_Y, COLOR_WHITE);
waitInput(); waitInput();
mcuPowerOff();
flushEntireDCache(); //Ensure that all memory transfers have completed and that the data cache has been flushed
i2cWriteRegister(I2C_DEV_MCU, 0x20, 1);
while(1);
} }

View File

@ -30,6 +30,8 @@
u32 waitInput(void); u32 waitInput(void);
void mcuReboot(void); void mcuReboot(void);
void mcuPowerOff(void);
void chrono(u32 seconds); void chrono(u32 seconds);
void stopChrono(void); void stopChrono(void);
void error(const char *message); void error(const char *message);