aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--projects/hsm/Makefile1
-rwxr-xr-xprojects/hsm/cryptech_upload51
-rw-r--r--projects/hsm/hsm.c22
-rw-r--r--projects/hsm/mgmt-bootloader.c31
-rw-r--r--projects/hsm/mgmt-fpga.c32
-rw-r--r--projects/hsm/mgmt-misc.c158
-rw-r--r--projects/hsm/mgmt-misc.h7
-rw-r--r--sdram-malloc.c88
-rw-r--r--sdram-malloc.h36
9 files changed, 326 insertions, 100 deletions
diff --git a/projects/hsm/Makefile b/projects/hsm/Makefile
index acb9962..5153a64 100644
--- a/projects/hsm/Makefile
+++ b/projects/hsm/Makefile
@@ -22,6 +22,7 @@ BOARD_OBJS = \
$(TOPLEVEL)/stm-keystore.o \
$(TOPLEVEL)/stm-sdram.o \
$(TOPLEVEL)/stm-flash.o \
+ $(TOPLEVEL)/sdram-malloc.o \
$(BOARD_DIR)/TOOLCHAIN_GCC_ARM/startup_stm32f429xx_rtos.o \
$(BOARD_DIR)/system_stm32f4xx.o \
$(BOARD_DIR)/stm32f4xx_hal_msp.o \
diff --git a/projects/hsm/cryptech_upload b/projects/hsm/cryptech_upload
index 7590b38..9f401b8 100755
--- a/projects/hsm/cryptech_upload
+++ b/projects/hsm/cryptech_upload
@@ -79,16 +79,17 @@ def parse_args():
# positional argument(s)
parser.add_argument('filename')
+ parser.add_argument('signature')
return parser.parse_args()
def _write(dst, data):
dst.write(data)
- #if len(data) == 4:
- # print("Wrote 0x{!s}".format(data.encode('hex')))
- #else:
- # print("Wrote {!r}".format(data))
+ if len(data) == 4:
+ print("Wrote 0x{!s}".format(data.encode('hex')))
+ else:
+ print("Wrote {!r}".format(data))
def _read(dst):
@@ -99,7 +100,7 @@ def _read(dst):
while x:
res += x
x = dst.read(1)
- #print ("Read {!r}".format(res))
+ print ("Read {!r}".format(res))
return res
pin = None
@@ -123,9 +124,16 @@ def _execute(dst, cmd):
response = _read(dst)
return response
-def send_file(filename, args, dst):
- s = os.stat(filename)
- size = s.st_size
+def send_file(filename, signature, args, dst):
+ def fsize(fn):
+ try:
+ s = os.stat(fn)
+ except OSError as e:
+ print e
+ exit(1)
+ return s.st_size
+
+ size = fsize(filename)
src = open(filename, 'rb')
if args.fpga:
chunk_size = FPGA_CHUNK_SIZE
@@ -178,14 +186,29 @@ def send_file(filename, args, dst):
crc = crc32(data, crc) & 0xffffffff
- _read(dst)
+ src.close()
- # 3. Write CRC-32 (4 bytes)
- _write(dst, struct.pack('<I', crc))
response = _read(dst)
- print response
- src.close()
+ if response.startswith('Send CRC-32'):
+
+ # 3a. Write CRC-32 (4 bytes)
+ _write(dst, struct.pack('<I', crc))
+ response = _read(dst)
+ print response
+
+ elif response.startswith('Send signature'):
+
+ # 3b. Write ECDSA signature
+ # write signature size
+ _write(dst, struct.pack('<I', fsize(signature)))
+ response = _read(dst)
+ if not response.startswith('Send '):
+ print response
+ return False
+ _write(dst, open(signature, 'rb').read())
+ response = _read(dst)
+ print response
if args.fpga:
# tell the fpga to read its new configuration
@@ -201,7 +224,7 @@ def send_file(filename, args, dst):
def main(args):
dst = serial.Serial(args.device, 921600, timeout=2)
- send_file(args.filename, args, dst)
+ send_file(args.filename, args.signature, args, dst)
dst.close()
return True
diff --git a/projects/hsm/hsm.c b/projects/hsm/hsm.c
index 3186bc5..d133c6f 100644
--- a/projects/hsm/hsm.c
+++ b/projects/hsm/hsm.c
@@ -206,28 +206,6 @@ void dispatch_thread(void const *args)
}
osThreadDef_t thread_def[NUM_RPC_TASK];
-/* Allocate memory from SDRAM1. There is only malloc, no free, so we don't
- * worry about fragmentation. */
-static uint8_t *sdram_malloc(size_t size)
-{
- /* end of variables declared with __attribute__((section(".sdram1"))) */
- extern uint8_t _esdram1 __asm ("_esdram1");
- /* end of SDRAM1 section */
- extern uint8_t __end_sdram1 __asm ("__end_sdram1");
-
- static uint8_t *sdram_heap = &_esdram1;
- uint8_t *p = sdram_heap;
-
-#define pad(n) (((n) + 3) & ~3)
- size = pad(size);
-
- if (p + size > &__end_sdram1)
- return NULL;
-
- sdram_heap += size;
- return p;
-}
-
/* The main thread. This does all the setup, and the worker threads handle
* the rest.
*/
diff --git a/projects/hsm/mgmt-bootloader.c b/projects/hsm/mgmt-bootloader.c
index d2d7ffe..68a0b38 100644
--- a/projects/hsm/mgmt-bootloader.c
+++ b/projects/hsm/mgmt-bootloader.c
@@ -38,6 +38,7 @@
#include "stm-uart.h"
#include "stm-flash.h"
#include "stm-fpgacfg.h"
+#include "sdram-malloc.h"
#include "mgmt-cli.h"
#include "mgmt-misc.h"
@@ -50,15 +51,6 @@
extern hal_user_t user;
-static uint32_t dfu_offset;
-
-static int _flash_write_callback(uint8_t *buf, size_t len)
-{
- stm_flash_write32(dfu_offset, (uint32_t *)buf, sizeof(buf)/4);
- dfu_offset += DFU_UPLOAD_CHUNK_SIZE;
- return 1;
-}
-
static int cmd_bootloader_upload(struct cli_def *cli, const char *command, char *argv[], int argc)
{
if (user < HAL_USER_SO) {
@@ -78,13 +70,24 @@ static int cmd_bootloader_upload(struct cli_def *cli, const char *command, char
return CLI_ERROR;
}
- uint8_t buf[DFU_UPLOAD_CHUNK_SIZE];
- dfu_offset = DFU_BOOTLOADER_ADDR;
+ uint8_t *filebuf;
+ size_t file_len;
- cli_receive_data(cli, buf, sizeof(buf), _flash_write_callback);
+ if (cli_receive_data(cli, &filebuf, &file_len, DFU_UPLOAD_CHUNK_SIZE) == CLI_ERROR)
+ return CLI_ERROR;
- cli_print(cli, "DFU offset now: %li (%li chunks)", dfu_offset, dfu_offset / DFU_UPLOAD_CHUNK_SIZE);
- return CLI_OK;
+ cli_print(cli, "Writing flash");
+ int res = stm_flash_write32(DFU_BOOTLOADER_ADDR, (uint32_t *)filebuf, file_len/4) == 1;
+ sdram_free(filebuf);
+
+ if (res) {
+ cli_print(cli, "SUCCESS");
+ return CLI_OK;
+ }
+ else {
+ cli_print(cli, "FAIL");
+ return CLI_ERROR;
+ }
}
void configure_cli_bootloader(struct cli_def *cli)
diff --git a/projects/hsm/mgmt-fpga.c b/projects/hsm/mgmt-fpga.c
index 45bd33c..78978b6 100644
--- a/projects/hsm/mgmt-fpga.c
+++ b/projects/hsm/mgmt-fpga.c
@@ -37,6 +37,7 @@
#include "stm-init.h"
#include "stm-uart.h"
#include "stm-fpgacfg.h"
+#include "sdram-malloc.h"
#include "mgmt-cli.h"
#include "mgmt-fpga.h"
@@ -52,15 +53,6 @@
extern hal_user_t user;
-static volatile uint32_t dfu_offset = 0;
-
-
-static int _flash_write_callback(uint8_t *buf, size_t len) {
- int res = fpgacfg_write_data(dfu_offset, buf, BITSTREAM_UPLOAD_CHUNK_SIZE) == 1;
- dfu_offset += BITSTREAM_UPLOAD_CHUNK_SIZE;
- return res;
-}
-
static int cmd_fpga_bitstream_upload(struct cli_def *cli, const char *command, char *argv[], int argc)
{
if (user < HAL_USER_SO) {
@@ -68,9 +60,8 @@ static int cmd_fpga_bitstream_upload(struct cli_def *cli, const char *command, c
return CLI_ERROR;
}
- uint8_t buf[BITSTREAM_UPLOAD_CHUNK_SIZE];
-
- dfu_offset = 0;
+ uint8_t *filebuf;
+ size_t file_len;
fpgacfg_access_control(ALLOW_ARM);
@@ -80,12 +71,23 @@ static int cmd_fpga_bitstream_upload(struct cli_def *cli, const char *command, c
return CLI_ERROR;
}
- cli_receive_data(cli, &buf[0], sizeof(buf), _flash_write_callback);
+ if (cli_receive_data(cli, &filebuf, &file_len, BITSTREAM_UPLOAD_CHUNK_SIZE) == CLI_ERROR)
+ return CLI_ERROR;
+
+ cli_print(cli, "Writing flash");
+ int res = fpgacfg_write_data(0, filebuf, file_len) == 1;
fpgacfg_access_control(ALLOW_FPGA);
+ sdram_free(filebuf);
- cli_print(cli, "DFU offset now: %li (%li chunks)", dfu_offset, dfu_offset / BITSTREAM_UPLOAD_CHUNK_SIZE);
- return CLI_OK;
+ if (res) {
+ cli_print(cli, "SUCCESS");
+ return CLI_OK;
+ }
+ else {
+ cli_print(cli, "FAIL");
+ return CLI_ERROR;
+ }
}
static int cmd_fpga_bitstream_erase(struct cli_def *cli, const char *command, char *argv[], int argc)
diff --git a/projects/hsm/mgmt-misc.c b/projects/hsm/mgmt-misc.c
index 15b2d13..5eecdb3 100644
--- a/projects/hsm/mgmt-misc.c
+++ b/projects/hsm/mgmt-misc.c
@@ -32,74 +32,174 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+/* Rename both CMSIS HAL_OK and libhal HAL_OK to disambiguate */
+#define HAL_OK CMSIS_HAL_OK
#include "stm-init.h"
#include "stm-uart.h"
+#include "sdram-malloc.h"
#include "mgmt-cli.h"
#include "mgmt-misc.h"
-#include <string.h>
+#undef HAL_OK
+#define HAL_OK LIBHAL_OK
+#include "hal.h"
+#include "hal_internal.h"
+#include "cryptech_signing_key.h"
+#undef HAL_OK
+#include <string.h>
-extern uint32_t update_crc(uint32_t crc, uint8_t *buf, int len);
+#define pad(n, sz) (((n) + (sz-1)) & ~(sz-1))
+static hal_error_t verify_signature(const uint8_t * const data,
+ const size_t data_len,
+ const uint8_t * const signature,
+ const size_t signature_len)
+{
+ hal_error_t err;
+ const hal_core_t *core = NULL;
+ const hal_hash_descriptor_t * const descriptor = hal_hash_sha256;
+ hal_hash_state_t *state = NULL;
+ uint8_t statebuf[descriptor->hash_state_length];
+ uint8_t digest[signature_len];
+
+#if 1
+ /* HACK - find the second sha256 core, to avoid interfering with rpc.
+ * If there isn't a second one, this will set core to NULL, and
+ * hal_hash_initialize will find the first one.
+ */
+ core = hal_core_find(descriptor->core_name, core);
+ core = hal_core_find(descriptor->core_name, core);
+#endif
+
+ if ((err = hal_hash_initialize(core, descriptor, &state, statebuf, sizeof(statebuf))) != LIBHAL_OK ||
+ (err = hal_hash_update(state, data, data_len)) != LIBHAL_OK ||
+ (err = hal_hash_finalize(state, digest, sizeof(digest))) != LIBHAL_OK)
+ return err;
+
+ const uint8_t name[] = "Cryptech signing key";
+ const size_t name_len = sizeof(name);
+ uint8_t der[65]; /* ?? */
+ int ks_hint;
+
+ if ((err = hal_ks_fetch(HAL_KEY_TYPE_EC_PUBLIC, name, name_len, NULL, NULL, der, NULL, sizeof(der), &ks_hint)) != LIBHAL_OK)
+ /* get the built-in signing key */
+ memcpy(der, cryptech_signing_key, sizeof(der));
+
+ uint8_t keybuf[hal_ecdsa_key_t_size];
+ hal_ecdsa_key_t *key = NULL;
+
+ if ((err = hal_ecdsa_public_key_from_der(&key, keybuf, sizeof(keybuf), der, sizeof(der))) != LIBHAL_OK ||
+ (err = hal_ecdsa_verify(NULL, key, digest, sizeof(digest), signature, signature_len)) != LIBHAL_OK)
+ return err;
+
+ return LIBHAL_OK;
+}
-int cli_receive_data(struct cli_def *cli, uint8_t *buf, size_t len, cli_data_callback data_callback)
+int cli_receive_data(struct cli_def *cli, uint8_t **filebuf, size_t *file_len, size_t chunksize)
{
- uint32_t filesize = 0, crc = 0, my_crc = 0, counter = 0;
- size_t n = len;
+ uint32_t filesize = 0, counter = 0;
+ uint8_t *writeptr;
+ size_t n = chunksize;
if (! control_mgmt_uart_dma_rx(DMA_RX_STOP)) {
cli_print(cli, "Failed stopping DMA");
return CLI_OK;
}
- cli_print(cli, "OK, write size (4 bytes), data in %li byte chunks, CRC-32 (4 bytes)", (uint32_t) n);
+ cli_print(cli, "OK, write size (4 bytes), data in %li byte chunks", (uint32_t) n);
- if (uart_receive_bytes(STM_UART_MGMT, (void *) &filesize, 4, 1000) != HAL_OK) {
+ if (uart_receive_bytes(STM_UART_MGMT, (void *) &filesize, 4, 1000) != CMSIS_HAL_OK) {
cli_print(cli, "Receive timed out");
return CLI_ERROR;
}
+ *file_len = (size_t)(pad(filesize, chunksize));
+ if ((*filebuf = sdram_malloc(*file_len)) == NULL) {
+ cli_print(cli, "File buffer allocation failed");
+ return CLI_ERROR;
+ }
+
+ /* By initializing buf to the same value that erased flash has (0xff), we don't
+ * have to try and be smart when writing the last page of data to a flash memory.
+ */
+ memset(*filebuf, 0xff, *file_len);
+ writeptr = *filebuf;
+
cli_print(cli, "Send %li bytes of data", filesize);
while (filesize) {
- /* By initializing buf to the same value that erased flash has (0xff), we don't
- * have to try and be smart when writing the last page of data to a flash memory.
- */
- memset(buf, 0xff, len);
if (filesize < n) n = filesize;
- if (uart_receive_bytes(STM_UART_MGMT, (void *) buf, n, 1000) != HAL_OK) {
+ if (uart_receive_bytes(STM_UART_MGMT, (void *) writeptr, n, 1000) != CMSIS_HAL_OK) {
cli_print(cli, "Receive timed out");
- return CLI_ERROR;
+ goto errout;
}
+ writeptr += n;
filesize -= n;
- my_crc = update_crc(my_crc, buf, n);
-
- /* After reception of a chunk but before ACKing we have "all" the time in the world to
- * calculate CRC and invoke the data_callback.
- */
- if (data_callback != NULL && ! data_callback(buf, (size_t) n)) {
- cli_print(cli, "Data processing failed");
- return CLI_OK;
- }
counter++;
uart_send_bytes(STM_UART_MGMT, (void *) &counter, 4);
}
- cli_print(cli, "Send CRC-32");
- uart_receive_bytes(STM_UART_MGMT, (void *) &crc, 4, 1000);
- cli_print(cli, "CRC-32 0x%x, calculated CRC 0x%x", (unsigned int) crc, (unsigned int) my_crc);
- if (crc == my_crc) {
- cli_print(cli, "CRC checksum MATCHED");
- } else {
- cli_print(cli, "CRC checksum did NOT match");
+ uint32_t sigsize;
+ cli_print(cli, "Send signature size (4 bytes)");
+ if (uart_receive_bytes(STM_UART_MGMT, (void *) &sigsize, 4, 1000) != CMSIS_HAL_OK) {
+ cli_print(cli, "Receive timed out");
+ goto errout;
}
+ if (sigsize < 70 || sigsize > 72) {
+ cli_print(cli, "Unexpected signature size %d, expected 70-72", (int)sigsize);
+ goto errout;
+ }
+ uint8_t sigbuf[72];
+ cli_print(cli, "Send %li bytes of data", sigsize);
+ if (uart_receive_bytes(STM_UART_MGMT, (void *) sigbuf, sigsize, 1000) != CMSIS_HAL_OK) {
+ cli_print(cli, "Receive timed out");
+ goto errout;
+ }
+ /* Signature is in DER format: sequence of 2 integers. Decode into
+ * what ecdsa expects: an octet string consisting of concatenated
+ * values for r and s, each of which occupies half of the octet string.
+ */
+ /* XXX should use the asn1 decoder, but just going to hack this for now */
+ int i = 0;
+ #define ASN1_SEQUENCE 0x30
+ #define ASN1_INTEGER 0x02
+ if (sigbuf[i++] != ASN1_SEQUENCE) {
+ cli_print(cli, "Error parsing signature");
+ goto errout;
+ }
+ i++;
+ if (sigbuf[i++] != ASN1_INTEGER) {
+ cli_print(cli, "Error parsing signature");
+ goto errout;
+ }
+ if (sigbuf[i++] == 0x21) i++;
+ memmove(&sigbuf[0], &sigbuf[i], 32);
+ i += 32;
+ if (sigbuf[i++] != ASN1_INTEGER) {
+ cli_print(cli, "Error parsing signature");
+ goto errout;
+ }
+ if (sigbuf[i++] == 0x21) i++;
+ memmove(&sigbuf[32], &sigbuf[i], 32);
+
+ if (verify_signature(*filebuf, *file_len, sigbuf, 64) != LIBHAL_OK) {
+ cli_print(cli, "Signature verification FAILED");
+ goto errout;
+ }
+ cli_print(cli, "Signature verification SUCCEEDED");
return CLI_OK;
+
+errout:
+ sdram_free(*filebuf);
+ *filebuf = NULL;
+ *file_len = 0;
+ return CLI_ERROR;
}
static int cmd_reboot(struct cli_def *cli, const char *command, char *argv[], int argc)
diff --git a/projects/hsm/mgmt-misc.h b/projects/hsm/mgmt-misc.h
index b7eb4f4..619b94e 100644
--- a/projects/hsm/mgmt-misc.h
+++ b/projects/hsm/mgmt-misc.h
@@ -38,12 +38,7 @@
#include "stm-init.h"
#include <libcli.h>
-
-#define FILETRANSFER_UPLOAD_CHUNK_SIZE 256
-
-typedef int (*cli_data_callback)(uint8_t *, size_t);
-
extern void configure_cli_misc(struct cli_def *cli);
-extern int cli_receive_data(struct cli_def *cli, uint8_t *buf, size_t len, cli_data_callback data_callback);
+extern int cli_receive_data(struct cli_def *cli, uint8_t **filebuf, size_t *file_len, size_t chunksize);
#endif /* __STM32_CLI_MGMT_MISC_H */
diff --git a/sdram-malloc.c b/sdram-malloc.c
new file mode 100644
index 0000000..3c2eabe
--- /dev/null
+++ b/sdram-malloc.c
@@ -0,0 +1,88 @@
+/*
+ * sdram_malloc.c
+ * --------------
+ * Use SDRAM for a very limited sort of heap.
+ *
+ * Copyright (c) 2016, NORDUnet A/S All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the NORDUnet nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* Allocate memory from SDRAM1. This is not a general allocator, and
+ * should only be used with the greatest of caution and care.
+ *
+ * This is intended for allocating memory that will either never be freed
+ * (e.g. thread stack buffers), or will be freed as soon as the requesting
+ * function is done with it (e.g. file upload buffers).
+ *
+ * Memory should be freed in reverse order of allocation (most
+ * recent first).
+ *
+ * There are no memory block control fields, so no protection to ensure
+ * that memory that is freed is actually the memory that was allocated.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* end of variables declared with __attribute__((section(".sdram1"))) */
+extern uint8_t _esdram1 __asm ("_esdram1");
+static uint8_t * const sdram_heap_base = &_esdram1;
+
+/* end of sdram1 section */
+extern uint8_t __end_sdram1 __asm ("__end_sdram1");
+static uint8_t * const sdram_heap_end = &__end_sdram1;
+
+/* current heap top */
+static uint8_t *sdram_heap = &_esdram1;
+
+/* Allocate some memory. Allocations are not padded, because file upload will
+ * call repeatedly to write blocks of data to a "file". OTOH, it's a good
+ * idea to allocate padding after the last write, so the next allocation
+ * will be aligned.
+ */
+uint8_t *sdram_malloc(size_t size)
+{
+ uint8_t *p = sdram_heap;
+
+ if (p + size > sdram_heap_end)
+ return NULL;
+
+ sdram_heap += size;
+ return p;
+}
+
+/* Free some memory allocated from sdram. This does only the most basic of
+ * sanity checks.
+ */
+void sdram_free(uint8_t *s)
+{
+ if (s < sdram_heap_base || s >= sdram_heap_end)
+ return;
+ if (s < sdram_heap)
+ sdram_heap = s;
+}
diff --git a/sdram-malloc.h b/sdram-malloc.h
new file mode 100644
index 0000000..5fc80fc
--- /dev/null
+++ b/sdram-malloc.h
@@ -0,0 +1,36 @@
+/*
+ * sdram_malloc.h
+ * --------------
+ * Use SDRAM for a very limited sort of heap.
+ *
+ * Copyright (c) 2016, NORDUnet A/S All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the NORDUnet nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+extern uint8_t *sdram_malloc(size_t size);
+extern void sdram_free(uint8_t *s);