summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCástor Muñoz <cmvidal@gmail.com>2016-02-05 00:49:33 +0100
committerCástor Muñoz <cmvidal@gmail.com>2016-08-19 00:15:59 +0200
commit7083110f8154f6ca47845fc747bad5062515658c (patch)
tree2c3bf23060d0435be9966c152710e93b2272516b
parentbf89c757ddee815ebdd42bba91413daa96d50b75 (diff)
downloadrockbox-7083110f8154f6ca47845fc747bad5062515658c.tar.gz
rockbox-7083110f8154f6ca47845fc747bad5062515658c.tar.bz2
rockbox-7083110f8154f6ca47845fc747bad5062515658c.zip
iPod Classic: NOR support for bootloader
- NOR driver (based on emCORE) - read/write IM3 images - read 'flsh' files Change-Id: Ie0654e0d298affc0f47ed64b823767118bd1aa3f
-rw-r--r--firmware/SOURCES1
-rw-r--r--firmware/target/arm/s5l8702/nor-s5l8702.c369
-rw-r--r--firmware/target/arm/s5l8702/nor-target.h167
3 files changed, 537 insertions, 0 deletions
diff --git a/firmware/SOURCES b/firmware/SOURCES
index 391b9af185..6a18639eb2 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -1630,6 +1630,7 @@ target/arm/s5l8702/ipod6g/adc-ipod6g.c
#else
target/arm/s5l8702/spi-s5l8702.c
target/arm/s5l8702/crypto-s5l8702.c
+target/arm/s5l8702/nor-s5l8702.c
#endif
#endif
diff --git a/firmware/target/arm/s5l8702/nor-s5l8702.c b/firmware/target/arm/s5l8702/nor-s5l8702.c
new file mode 100644
index 0000000000..ac60ede2de
--- /dev/null
+++ b/firmware/target/arm/s5l8702/nor-s5l8702.c
@@ -0,0 +1,369 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id:
+ *
+ * Copyright © 2009 Michael Sparmann
+ *
+ * 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.
+ *
+ ****************************************************************************/
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "config.h"
+#include "system.h"
+#include "s5l8702.h"
+#include "spi-s5l8702.h"
+#include "crypto-s5l8702.h"
+#include "nor-target.h"
+
+static void bootflash_ce(int port, bool state)
+{
+ spi_ce(port, state);
+}
+
+void bootflash_init(int port)
+{
+ spi_init(port, true);
+ bootflash_ce(port, false);
+}
+
+void bootflash_close(int port)
+{
+ spi_init(port, false);
+}
+
+static void bootflash_wait_ready(int port)
+{
+ while (true)
+ {
+ bootflash_ce(port, true);
+ spi_write(port, 5);
+ if (!(spi_write(port, 0xff) & 1)) break;
+ bootflash_ce(port, false);
+ }
+ bootflash_ce(port, false);
+}
+
+static void bootflash_enable_writing(int port, bool state)
+{
+ if (!state)
+ {
+ bootflash_ce(port, true);
+ spi_write(port, 4);
+ bootflash_ce(port, false);
+ }
+ bootflash_ce(port, true);
+ spi_write(port, 0x50);
+ bootflash_ce(port, false);
+ bootflash_ce(port, true);
+ spi_write(port, 1);
+ spi_write(port, state ? 0 : 0x1c);
+ bootflash_ce(port, false);
+ if (state)
+ {
+ bootflash_ce(port, true);
+ spi_write(port, 6);
+ bootflash_ce(port, false);
+ }
+}
+
+void bootflash_read(int port, uint32_t addr, uint32_t size, void* buf)
+{
+ spi_prepare(port);
+ bootflash_wait_ready(port);
+ bootflash_ce(port, true);
+ spi_write(port, 3);
+ spi_write(port, (addr >> 16) & 0xff);
+ spi_write(port, (addr >> 8) & 0xff);
+ spi_write(port, addr & 0xff);
+ spi_read(port, size, buf);
+ bootflash_ce(port, false);
+ spi_release(port);
+}
+
+static void bootflash_write_internal(int port, uint32_t addr, uint32_t size, void* buf)
+{
+ uint8_t* buffer = (uint8_t*)buf;
+ bool first = true;
+ spi_prepare(port);
+ bootflash_wait_ready(port);
+ bootflash_enable_writing(port, true);
+ while (size)
+ {
+ bootflash_ce(port, true);
+ spi_write(port, 0xad);
+ if (first)
+ {
+ spi_write(port, (addr >> 16) & 0xff);
+ spi_write(port, (addr >> 8) & 0xff);
+ spi_write(port, addr & 0xff);
+ first = false;
+ }
+ spi_write(port, *buffer++);
+ spi_write(port, *buffer++);
+ bootflash_ce(port, false);
+ bootflash_wait_ready(port);
+ size -= 2;
+ }
+ bootflash_enable_writing(port, false);
+ spi_release(port);
+}
+
+static void bootflash_erase_internal(int port, uint32_t addr)
+{
+ spi_prepare(port);
+ bootflash_wait_ready(port);
+ bootflash_enable_writing(port, true);
+ bootflash_ce(port, true);
+ spi_write(port, 0x20);
+ spi_write(port, (addr >> 16) & 0xff);
+ spi_write(port, (addr >> 8) & 0xff);
+ spi_write(port, addr & 0xff);
+ bootflash_ce(port, false);
+ bootflash_enable_writing(port, false);
+ spi_release(port);
+}
+
+void bootflash_erase_blocks(int port, int first, int n)
+{
+ uint32_t offset = first << 12;
+ while (n--)
+ {
+ bootflash_erase_internal(port, offset);
+ offset += 0x1000;
+ }
+}
+
+int bootflash_compare(int port, int offset, void* addr, int size)
+{
+ int i;
+ int result = 0;
+ uint8_t buf[32];
+ spi_prepare(port);
+ bootflash_wait_ready(port);
+ bootflash_ce(port, true);
+ spi_write(port, 3);
+ spi_write(port, (offset >> 16) & 0xff);
+ spi_write(port, (offset >> 8) & 0xff);
+ spi_write(port, offset & 0xff);
+ while (size > 0)
+ {
+ spi_read(port, MIN((int)sizeof(buf), size), buf);
+ if (memcmp((uint8_t*)addr, buf, MIN((int)sizeof(buf), size)))
+ result |= 1;
+ for (i = 0; i < MIN((int)sizeof(buf), size); i += 2)
+ if (buf[i] != 0xff) result |= 2;
+ addr = (void*)(((uint32_t)addr) + sizeof(buf));
+ size -= sizeof(buf);
+ }
+ bootflash_ce(port, false);
+ spi_release(port);
+ return result;
+}
+
+void bootflash_write(int port, int offset, void* addr, int size)
+{
+ int i;
+ bool needswrite;
+ while (size > 0)
+ {
+ int remainder = MIN(0x1000 - (offset & 0xfff), size);
+ int contentinfo = bootflash_compare(port, offset, addr, remainder);
+ if (contentinfo & 1)
+ {
+ if (contentinfo & 2)
+ bootflash_erase_internal(port, offset & ~0xfff);
+ needswrite = false;
+ for (i = 0; i < remainder; i += 1)
+ if (((uint8_t*)addr)[i] != 0xff)
+ {
+ needswrite = true;
+ break;
+ }
+ if (needswrite)
+ bootflash_write_internal(port, offset, remainder, addr);
+ }
+ addr = (void*)(((uint32_t)addr) + remainder);
+ offset += remainder;
+ size -= remainder;
+ }
+}
+
+
+/*
+ * IM3
+ */
+static uint32_t get_uint32le(unsigned char *p)
+{
+ return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+}
+
+/* return full IM3 size aligned to NOR sector size */
+unsigned im3_nor_sz(struct Im3Info* hinfo)
+{
+ return ALIGN_UP(IM3HDR_SZ + get_uint32le(hinfo->data_sz), 0x1000);
+}
+
+/* calculates SHA1, truncate the result to 128 bits, and encrypt it */
+void im3_sign(uint32_t keyidx, void* data, uint32_t size, void* sign)
+{
+ unsigned char hash[SHA1_SZ];
+ sha1(data, size, hash);
+ memcpy(sign, hash, SIGN_SZ);
+ hwkeyaes(HWKEYAES_ENCRYPT, keyidx, sign, SIGN_SZ);
+}
+
+/* only supports enc_type 1 and 2 (UKEY) */
+void im3_crypt(enum hwkeyaes_direction direction,
+ struct Im3Info *hinfo, void *fw_addr)
+{
+ uint32_t fw_size = get_uint32le(hinfo->data_sz);
+ hinfo->enc_type = (direction == HWKEYAES_ENCRYPT) ? 1 : 2;
+ im3_sign(HWKEYAES_UKEY, hinfo, IM3INFOSIGN_SZ, hinfo->info_sign);
+ hwkeyaes(direction, HWKEYAES_UKEY, fw_addr, fw_size);
+}
+
+int im3_read(uint32_t offset, struct Im3Info *hinfo, void *fw_addr)
+{
+ unsigned char hash[SIGN_SZ];
+ uint32_t fw_size;
+
+ /* header */
+ bootflash_init(SPI_PORT);
+ bootflash_read(SPI_PORT, offset, IM3HDR_SZ, hinfo);
+ bootflash_close(SPI_PORT);
+
+ if (memcmp(hinfo, IM3_IDENT, 4) != 0)
+ return -1; /* OF not found */
+
+ im3_sign(HWKEYAES_UKEY, hinfo, IM3INFOSIGN_SZ, hash);
+ if (memcmp(hash, hinfo->info_sign, SIGN_SZ) != 0)
+ return -2; /* corrupt header */
+
+ fw_size = get_uint32le(hinfo->data_sz);
+ if ((fw_size > NORBOOT_MAXSZ - IM3HDR_SZ) ||
+ (get_uint32le(hinfo->entry) > fw_size))
+ return -3; /* wrong info */
+
+ if (hinfo->enc_type != 1 && hinfo->enc_type != 2)
+ return -4; /* encrypt type not supported */
+
+ if (fw_addr)
+ {
+ /* body */
+ bootflash_init(SPI_PORT);
+ bootflash_read(SPI_PORT, offset + IM3HDR_SZ, fw_size, fw_addr);
+ bootflash_close(SPI_PORT);
+ if (hinfo->enc_type == 1)
+ im3_crypt(HWKEYAES_DECRYPT, hinfo, fw_addr);
+ im3_sign(HWKEYAES_UKEY, fw_addr, fw_size, hash);
+ if (memcmp(hash, hinfo->u.enc12.data_sign, SIGN_SZ) != 0)
+ return -5; /* corrupt data */
+ }
+
+ return 0;
+}
+
+bool im3_write(int offset, void *im3_addr)
+{
+ bool res;
+ struct Im3Info *hinfo = (struct Im3Info *)im3_addr;
+ uint32_t im3_size = get_uint32le(hinfo->data_sz) + IM3HDR_SZ;
+ bootflash_init(SPI_PORT);
+ bootflash_write(SPI_PORT, offset, im3_addr, im3_size);
+ /* check if the IM3 image was written correctly */
+ res = !(bootflash_compare(SPI_PORT, offset, im3_addr, im3_size) & 1);
+ bootflash_close(SPI_PORT);
+ return res;
+}
+
+
+/*
+ * flsh FS
+ */
+unsigned flsh_get_unused(void)
+{
+ unsigned tail = DIR_OFF;
+ bootflash_init(SPI_PORT);
+ for (int i = 0; i < MAX_ENTRY; i++)
+ {
+ image_t entry;
+ bootflash_read(SPI_PORT, DIR_OFF + ENTRY_SZ*i, ENTRY_SZ, &entry);
+ if (memcmp(entry.type, "hslf", 4))
+ break; /* no more entries */
+ if (entry.devOffset < tail)
+ tail = entry.devOffset;
+ }
+ bootflash_close(SPI_PORT);
+ return tail - (NORBOOT_OFF + NORBOOT_MAXSZ);
+}
+
+int flsh_find_file(char *name, image_t *entry)
+{
+ int found = 0;
+ bootflash_init(SPI_PORT);
+ for (int off = 0; off < ENTRY_SZ*MAX_ENTRY; off += ENTRY_SZ)
+ {
+ bootflash_read(SPI_PORT, DIR_OFF+off, ENTRY_SZ, entry);
+ if (memcmp(&(entry->type), "hslf", 4))
+ break; /* no more entries */
+ if (!memcmp(&(entry->id), name, 4)) {
+ found = 1;
+ break;
+ }
+ }
+ bootflash_close(SPI_PORT);
+ return found;
+}
+
+#define KEYIDX_OFF 0x8 /* TBC */
+#define DATALEN_OFF 0x14
+#define DATASHA_OFF 0x1c
+#define HEADSHA_OFF 0x1d4
+
+int flsh_load_file(image_t *entry, void *hdr, void *data)
+{
+ uint8_t orig_hash[SHA1_SZ];
+ uint8_t calc_hash[SHA1_SZ];
+ uint32_t data_len;
+
+ /* header */
+ bootflash_init(SPI_PORT);
+ bootflash_read(SPI_PORT, entry->devOffset, FILEHDR_SZ, hdr);
+ bootflash_close(SPI_PORT);
+ memcpy(orig_hash, hdr + HEADSHA_OFF, SHA1_SZ);
+ memset(hdr+HEADSHA_OFF, 0, SHA1_SZ);
+ sha1(hdr, FILEHDR_SZ, calc_hash);
+ if (memcmp(calc_hash, orig_hash, SHA1_SZ))
+ return -1; /* corrupt header */
+ memcpy(hdr+HEADSHA_OFF, orig_hash, SHA1_SZ);
+ data_len = ALIGN_UP(*(uint32_t*)(hdr+DATALEN_OFF), 0x10);
+ if (data_len > ALIGN_UP(entry->len, 0x10))
+ return -2; /* bad size */
+
+ if (data)
+ {
+ bootflash_init(SPI_PORT);
+ bootflash_read(SPI_PORT, entry->devOffset+FILEHDR_SZ, data_len, data);
+ bootflash_close(SPI_PORT);
+ hwkeyaes(HWKEYAES_DECRYPT, *(uint8_t*)(hdr+KEYIDX_OFF), data, data_len);
+ sha1(data, data_len, calc_hash);
+ if (memcmp(calc_hash, hdr+DATASHA_OFF, SHA1_SZ))
+ return -3; /* corrupt data */
+ }
+
+ return 0;
+}
diff --git a/firmware/target/arm/s5l8702/nor-target.h b/firmware/target/arm/s5l8702/nor-target.h
new file mode 100644
index 0000000000..4ebe1d58d4
--- /dev/null
+++ b/firmware/target/arm/s5l8702/nor-target.h
@@ -0,0 +1,167 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id:
+ *
+ * Copyright © 2009 Michael Sparmann
+ *
+ * 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.
+ *
+ ****************************************************************************/
+#ifndef __NOR_TARGET_H__
+#define __NOR_TARGET_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "config.h"
+#include "crypto-s5l8702.h"
+
+
+/* NOR memory map (Classic 6G):
+ *
+ * 1MB ______________
+ * | |
+ * | flsh DIR |
+ * 1MB-0x200 |______________|
+ * | |
+ * | File 1 |
+ * |..............|
+ * | |
+ * | File 2 |
+ * |..............|
+ * | |
+ * . .
+ * . .
+ * . .
+ * | |
+ * |..............|
+ * | |
+ * | File N |
+ * |______________|
+ * | |
+ * | |
+ * | |
+ * | | IM3 (128KB)
+ * | Unused | . . . . . . ______________
+ * | | / | |
+ * | | / | |
+ * | | / | |
+ * 160KB |______________|/ | |
+ * | | | |
+ * | | | EFI |
+ * | Original | | Volumen |
+ * | NOR Boot | | |
+ * | | | |
+ * | | | |
+ * 32KB |______________| | |
+ * | |\ 0x800 |______________|
+ * | | \ | |
+ * | | \ | IM3 Header |
+ * |______________| \ . . . . . . |______________|
+ * | |
+ * | SysCfg |
+ * 0 |______________|
+ */
+
+#define SPI_PORT 0
+
+#define NOR_SZ 0x100000
+#define NORBOOT_OFF 0x8000
+#define NORBOOT_MAXSZ 0x20000
+
+void bootflash_init(int port);
+void bootflash_read(int port, uint32_t addr, uint32_t size, void* buf);
+void bootflash_write(int port, int offset, void* addr, int size);
+int bootflash_compare(int port, int offset, void* addr, int size);
+void bootflash_erase_blocks(int port, int first, int n);
+void bootflash_close(int port);
+
+
+/*
+ * IM3
+ */
+#define IM3_IDENT "8702"
+#define IM3_VERSION "1.0"
+#define IM3HDR_SZ 0x800
+#define IM3INFO_SZ (sizeof(struct Im3Info))
+#define IM3INFOSIGN_SZ (offsetof(struct Im3Info, info_sign))
+
+#define SIGN_SZ 16
+
+struct Im3Info
+{
+ uint8_t ident[4];
+ uint8_t version[3];
+ uint8_t enc_type;
+ uint8_t entry[4]; /* LE */
+ uint8_t data_sz[4]; /* LE */
+ union {
+ struct {
+ uint8_t data_sign[SIGN_SZ];
+ uint8_t _reserved[32];
+ } enc12;
+ struct {
+ uint8_t sign_off[4]; /* LE */
+ uint8_t cert_off[4]; /* LE */
+ uint8_t cert_sz[4]; /* LE */
+ uint8_t _reserved[36];
+ } enc34;
+ } u;
+ uint8_t info_sign[SIGN_SZ];
+} __attribute__ ((packed));
+
+struct Im3Hdr
+{
+ struct Im3Info info;
+ uint8_t _zero[IM3HDR_SZ - sizeof(struct Im3Info)];
+} __attribute__ ((packed));
+
+unsigned im3_nor_sz(struct Im3Info* hinfo);
+void im3_sign(uint32_t keyidx, void* data, uint32_t size, void* sign);
+void im3_crypt(enum hwkeyaes_direction direction,
+ struct Im3Info *hinfo, void *fw_addr);
+int im3_read(uint32_t offset, struct Im3Info *hinfo, void *fw_addr);
+bool im3_write(int offset, void *im3_addr);
+
+
+/*
+ * flsh FS
+ */
+#define DIR_SZ 0x200
+#define DIR_OFF (NOR_SZ - DIR_SZ)
+#define ENTRY_SZ ((int)sizeof(image_t))
+#define MAX_ENTRY (DIR_SZ / ENTRY_SZ)
+
+#define FILEHDR_SZ 0x200
+
+/* from tools/ipod_fw.c */
+typedef struct _image {
+ char type[4]; /* '' */
+ unsigned id; /* */
+ char pad1[4]; /* 0000 0000 */
+ unsigned devOffset; /* byte offset of start of image code */
+ unsigned len; /* length in bytes of image */
+ unsigned addr; /* load address */
+ unsigned entryOffset; /* execution start within image */
+ unsigned chksum; /* checksum for image */
+ unsigned vers; /* image version */
+ unsigned loadAddr; /* load address for image */
+} image_t;
+
+int flsh_get_info(int *used, int *unused);
+unsigned flsh_get_unused(void);
+int flsh_find_file(char *name, image_t *entry);
+int flsh_load_file(image_t *entry, void *hdr, void *data);
+
+#endif /* __NOR_TARGET_H__ */