summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2015-08-13 15:57:14 +0100
committerPeter Maydell <peter.maydell@linaro.org>2015-08-13 15:57:14 +0100
commit6e227821358e13b9423dd26dcdfeffcf5c412370 (patch)
treeb11020bcc4bd375c0b9c3ce433ab9a500fe6e581
semihosting tests: initial commit
Usermode tests work; system emulation tests work for A32 at least. Note: includes a third-party 3-clause-BSD printf implementation. The semihosting.[ch] and string.[ch] files are from the Linaro boot-wrapper. Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--Makefile56
-rw-r--r--baremetal.lds25
-rw-r--r--printf/README2
-rwxr-xr-xprintf/printf.c245
-rwxr-xr-xprintf/printf.h124
-rw-r--r--semicall.S45
-rw-r--r--semihosting.c174
-rw-r--r--semihosting.h52
-rw-r--r--start.S38
-rw-r--r--string.c137
-rw-r--r--string.h16
-rw-r--r--testdata.txt2
-rw-r--r--usertest.c69
13 files changed, 985 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..35c8aa7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,56 @@
+# Makefile for semihosting tests
+#
+# Copyright (c) 2015 Linaro Limited
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. 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.
+# 3. Neither the name of Linaro Limited nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+#
+# We have several orthogonal cases we'd like to test:
+# A32 vs T32 vs A64
+# usermode vs system
+# gdb syscalls vs normal
+#
+# For the moment this is only testing usermode.
+
+A32GCC := arm-linux-gnueabihf-gcc -marm
+T32GCC := arm-linux-gnueabihf-gcc -mthumb
+A64GCC := aarch64-linux-gnu-gcc
+
+A32LD := arm-linux-gnueabihf-ld
+
+all: usertest-a32 usertest-a64 usertest-t32
+
+usertest-srcs = usertest.c semihosting.c semicall.S
+
+systest-srcs = start.S string.c printf/printf.c $(usertest-srcs)
+
+usertest-a32: $(usertest-srcs)
+ $(A32GCC) --static -o $@ $^
+
+usertest-t32: $(usertest-srcs)
+ $(T32GCC) --static -o $@ $^
+
+usertest-a64: $(usertest-srcs)
+ $(A64GCC) --static -o $@ $^
+
+systest-a32.axf: $(systest-srcs)
+ $(A32GCC) -nostdlib -o $@ $^ -lgcc -Xlinker --script=baremetal.lds
+
+systest-t32.axf: $(systest-srcs)
+ $(T32GCC) -nostdlib -o $@ $^ -Xlinker --script=baremetal.lds
+
+systest-a64.axf: $(systest-srcs)
+ $(A64GCC) -nostdlib -o $@ $^ -lgcc -Xlinker --script=baremetal.lds
+
diff --git a/baremetal.lds b/baremetal.lds
new file mode 100644
index 0000000..6d98672
--- /dev/null
+++ b/baremetal.lds
@@ -0,0 +1,25 @@
+/*
+ * baremetal.lds : simple linker script for baremetal test cases
+ *
+ * Copyright (C) 2015 Linaro Limited. All rights reserved.
+ *
+ * Memory layout is for the virt board which puts RAM at 0x40000000
+ *
+ * Use:
+ * $(LD) -o image.axf input.o --script=baremetal.lds
+ */
+
+OUTPUT_FORMAT("elf32-littlearm")
+OUTPUT_ARCH(arm)
+TARGET(binary)
+RAMSTART = 0x40000000;
+STACKTOP = 0x40200000;
+SECTIONS
+{
+ . = RAMSTART;
+ .text : { *(.text) }
+ .data : { *(.data) }
+ .bss : { *(.bss) }
+ . = STACKTOP;
+ stacktop = .;
+}
diff --git a/printf/README b/printf/README
new file mode 100644
index 0000000..15478d9
--- /dev/null
+++ b/printf/README
@@ -0,0 +1,2 @@
+printf implementation from http://www.sparetimelabs.com/tinyprintf/tinyprintf.php
+under 3-clause BSD
diff --git a/printf/printf.c b/printf/printf.c
new file mode 100755
index 0000000..4db1289
--- /dev/null
+++ b/printf/printf.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2004,2012 Kustaa Nyholm / SpareTimeLabs
+ *
+ * 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 Kustaa Nyholm or SpareTimeLabs 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.
+ */
+
+#include "printf.h"
+
+typedef void (*putcf) (void*,char);
+static putcf stdout_putf;
+static void* stdout_putp;
+
+
+#ifdef PRINTF_LONG_SUPPORT
+
+static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf)
+ {
+ int n=0;
+ unsigned int d=1;
+ while (num/d >= base)
+ d*=base;
+ while (d!=0) {
+ int dgt = num / d;
+ num%=d;
+ d/=base;
+ if (n || dgt>0|| d==0) {
+ *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10);
+ ++n;
+ }
+ }
+ *bf=0;
+ }
+
+static void li2a (long num, char * bf)
+ {
+ if (num<0) {
+ num=-num;
+ *bf++ = '-';
+ }
+ uli2a(num,10,0,bf);
+ }
+
+#endif
+
+static void ui2a(unsigned int num, unsigned int base, int uc,char * bf)
+ {
+ int n=0;
+ unsigned int d=1;
+ while (num/d >= base)
+ d*=base;
+ while (d!=0) {
+ int dgt = num / d;
+ num%= d;
+ d/=base;
+ if (n || dgt>0 || d==0) {
+ *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10);
+ ++n;
+ }
+ }
+ *bf=0;
+ }
+
+static void i2a (int num, char * bf)
+ {
+ if (num<0) {
+ num=-num;
+ *bf++ = '-';
+ }
+ ui2a(num,10,0,bf);
+ }
+
+static int a2d(char ch)
+ {
+ if (ch>='0' && ch<='9')
+ return ch-'0';
+ else if (ch>='a' && ch<='f')
+ return ch-'a'+10;
+ else if (ch>='A' && ch<='F')
+ return ch-'A'+10;
+ else return -1;
+ }
+
+static char a2i(char ch, char** src,int base,int* nump)
+ {
+ char* p= *src;
+ int num=0;
+ int digit;
+ while ((digit=a2d(ch))>=0) {
+ if (digit>base) break;
+ num=num*base+digit;
+ ch=*p++;
+ }
+ *src=p;
+ *nump=num;
+ return ch;
+ }
+
+static void putchw(void* putp,putcf putf,int n, char z, char* bf)
+ {
+ char fc=z? '0' : ' ';
+ char ch;
+ char* p=bf;
+ while (*p++ && n > 0)
+ n--;
+ while (n-- > 0)
+ putf(putp,fc);
+ while ((ch= *bf++))
+ putf(putp,ch);
+ }
+
+void tfp_format(void* putp,putcf putf,char *fmt, va_list va)
+ {
+ char bf[12];
+
+ char ch;
+
+
+ while ((ch=*(fmt++))) {
+ if (ch!='%')
+ putf(putp,ch);
+ else {
+ char lz=0;
+#ifdef PRINTF_LONG_SUPPORT
+ char lng=0;
+#endif
+ int w=0;
+ ch=*(fmt++);
+ if (ch=='0') {
+ ch=*(fmt++);
+ lz=1;
+ }
+ if (ch>='0' && ch<='9') {
+ ch=a2i(ch,&fmt,10,&w);
+ }
+#ifdef PRINTF_LONG_SUPPORT
+ if (ch=='l') {
+ ch=*(fmt++);
+ lng=1;
+ }
+#endif
+ switch (ch) {
+ case 0:
+ goto abort;
+ case 'u' : {
+#ifdef PRINTF_LONG_SUPPORT
+ if (lng)
+ uli2a(va_arg(va, unsigned long int),10,0,bf);
+ else
+#endif
+ ui2a(va_arg(va, unsigned int),10,0,bf);
+ putchw(putp,putf,w,lz,bf);
+ break;
+ }
+ case 'd' : {
+#ifdef PRINTF_LONG_SUPPORT
+ if (lng)
+ li2a(va_arg(va, unsigned long int),bf);
+ else
+#endif
+ i2a(va_arg(va, int),bf);
+ putchw(putp,putf,w,lz,bf);
+ break;
+ }
+ case 'x': case 'X' :
+#ifdef PRINTF_LONG_SUPPORT
+ if (lng)
+ uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf);
+ else
+#endif
+ ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf);
+ putchw(putp,putf,w,lz,bf);
+ break;
+ case 'c' :
+ putf(putp,(char)(va_arg(va, int)));
+ break;
+ case 's' :
+ putchw(putp,putf,w,0,va_arg(va, char*));
+ break;
+ case '%' :
+ putf(putp,ch);
+ default:
+ break;
+ }
+ }
+ }
+ abort:;
+ }
+
+
+void init_printf(void* putp,void (*putf) (void*,char))
+ {
+ stdout_putf=putf;
+ stdout_putp=putp;
+ }
+
+void tfp_printf(char *fmt, ...)
+ {
+ va_list va;
+ va_start(va,fmt);
+ tfp_format(stdout_putp,stdout_putf,fmt,va);
+ va_end(va);
+ }
+
+static void putcp(void* p,char c)
+ {
+ *(*((char**)p))++ = c;
+ }
+
+
+
+void tfp_sprintf(char* s,char *fmt, ...)
+ {
+ va_list va;
+ va_start(va,fmt);
+ tfp_format(&s,putcp,fmt,va);
+ putcp(&s,0);
+ va_end(va);
+ }
+
+
diff --git a/printf/printf.h b/printf/printf.h
new file mode 100755
index 0000000..3790e72
--- /dev/null
+++ b/printf/printf.h
@@ -0,0 +1,124 @@
+/*
+File: printf.h
+
+Copyright (c) 2004,2012 Kustaa Nyholm / SpareTimeLabs
+
+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 Kustaa Nyholm or SpareTimeLabs 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.
+
+----------------------------------------------------------------------
+
+This library is realy just two files: 'printf.h' and 'printf.c'.
+
+They provide a simple and small (+200 loc) printf functionality to
+be used in embedded systems.
+
+I've found them so usefull in debugging that I do not bother with a
+debugger at all.
+
+They are distributed in source form, so to use them, just compile them
+into your project.
+
+Two printf variants are provided: printf and sprintf.
+
+The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'.
+
+Zero padding and field width are also supported.
+
+If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the
+long specifier is also
+supported. Note that this will pull in some long math routines (pun intended!)
+and thus make your executable noticably longer.
+
+The memory foot print of course depends on the target cpu, compiler and
+compiler options, but a rough guestimate (based on a H8S target) is about
+1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space.
+Not too bad. Your milage may vary. By hacking the source code you can
+get rid of some hunred bytes, I'm sure, but personally I feel the balance of
+functionality and flexibility versus code size is close to optimal for
+many embedded systems.
+
+To use the printf you need to supply your own character output function,
+something like :
+
+void putc ( void* p, char c)
+ {
+ while (!SERIAL_PORT_EMPTY) ;
+ SERIAL_PORT_TX_REGISTER = c;
+ }
+
+Before you can call printf you need to initialize it to use your
+character output function with something like:
+
+init_printf(NULL,putc);
+
+Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc',
+the NULL (or any pointer) you pass into the 'init_printf' will eventually be
+passed to your 'putc' routine. This allows you to pass some storage space (or
+anything realy) to the character output function, if necessary.
+This is not often needed but it was implemented like that because it made
+implementing the sprintf function so neat (look at the source code).
+
+The code is re-entrant, except for the 'init_printf' function, so it
+is safe to call it from interupts too, although this may result in mixed output.
+If you rely on re-entrancy, take care that your 'putc' function is re-entrant!
+
+The printf and sprintf functions are actually macros that translate to
+'tfp_printf' and 'tfp_sprintf'. This makes it possible
+to use them along with 'stdio.h' printf's in a single source file.
+You just need to undef the names before you include the 'stdio.h'.
+Note that these are not function like macros, so if you have variables
+or struct members with these names, things will explode in your face.
+Without variadic macros this is the best we can do to wrap these
+fucnction. If it is a problem just give up the macros and use the
+functions directly or rename them.
+
+For further details see source code.
+
+regs Kusti, 23.10.2004
+*/
+
+
+#ifndef __TFP_PRINTF__
+#define __TFP_PRINTF__
+
+#include <stdarg.h>
+
+void init_printf(void* putp,void (*putf) (void*,char));
+
+void tfp_printf(char *fmt, ...);
+void tfp_sprintf(char* s,char *fmt, ...);
+
+void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va);
+
+#define printf tfp_printf
+#define sprintf tfp_sprintf
+
+#endif
+
+
+
diff --git a/semicall.S b/semicall.S
new file mode 100644
index 0000000..6634594
--- /dev/null
+++ b/semicall.S
@@ -0,0 +1,45 @@
+/*
+ * semicall.S - assembly stub for making semihosting call
+ *
+ * Copyright (c) 2015 Linaro Limited
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Linaro Limited nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ */
+
+#ifndef __aarch64__
+ .syntax unified
+#endif
+
+ .text
+
+#if defined(__thumb__)
+ .thumb
+#endif
+
+ .globl __semi_call
+__semi_call:
+#if defined(__aarch64__)
+ hlt 0xf000
+ ret
+#else
+
+#if defined(__thumb__)
+ svc 0xab
+#else
+ svc 0x123456
+#endif
+ bx lr
+
+#endif
diff --git a/semihosting.c b/semihosting.c
new file mode 100644
index 0000000..ee13273
--- /dev/null
+++ b/semihosting.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2012 Linaro Limited
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Linaro Limited nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+#include "semihosting.h"
+
+int semi_open(char const *filename, int mode)
+{
+ struct {
+ char const *filename;
+ intptr_t mode;
+ intptr_t filename_length;
+ } args;
+
+ args.filename = filename;
+ args.mode = mode;
+ args.filename_length = strlen(filename);
+
+ return __semi_call(SYS_OPEN, &args);
+}
+
+int semi_close(int fd)
+{
+ struct {
+ intptr_t fd;
+ } args;
+
+ args.fd = fd;
+ return __semi_call(SYS_CLOSE, &args);
+}
+
+int semi_write0(char const *string)
+{
+ return __semi_call(SYS_WRITE0, string);
+}
+
+void semi_writec(char c)
+{
+ __semi_call(SYS_WRITEC, &c);
+}
+
+int semi_read(int fd, char *buffer, int length)
+{
+ struct {
+ intptr_t fd;
+ char *buffer;
+ intptr_t length;
+ } args;
+
+ args.fd = fd;
+ args.buffer = buffer;
+ args.length = length;
+
+ return __semi_call(SYS_READ, &args);
+}
+
+int semi_flen(int fd)
+{
+ struct {
+ intptr_t fd;
+ } args;
+
+ args.fd = fd;
+ return __semi_call(SYS_FLEN, &args);
+}
+
+int semi_get_cmdline(char *buffer, int size, int *length)
+{
+ int result;
+ struct {
+ char *buffer;
+ intptr_t size;
+ } args;
+
+ args.buffer = buffer;
+ args.size = size;
+
+ result = __semi_call(SYS_GET_CMDLINE, &args);
+ if(result)
+ return result;
+
+ if(length)
+ *length = args.size;
+
+ return 0;
+}
+
+int semi_reportexc(int reason, int subcode)
+{
+#ifdef __aarch64__
+ /* The A64 interface to this call takes an arg block,
+ * whereas the A32/T32 interface just takes the reason
+ * code in a register.
+ */
+ struct {
+ intptr_t reason;
+ intptr_t subcode;
+ } args;
+
+ args.reason = reason;
+ args.subcode = subcode;
+ return __semi_call(SYS_REPORTEXC, &args);
+#else
+ return __semi_call(SYS_REPORTEXC, (void *)reason);
+#endif
+}
+
+void semi_exit(int subcode)
+{
+ semi_reportexc(REPORTEXC_REASON_APP_EXIT, subcode);
+ while(1); /* should not be reached */
+}
+
+void semi_fatal(char const *message)
+{
+ semi_write0(message);
+ semi_exit(1);
+}
+
+int semi_load_file(void **dest, unsigned *size, char const *filename)
+{
+ int result = -1; /* fail by default */
+ int fd = -1;
+ int filesize;
+
+ fd = semi_open(filename, OPEN_RDONLY);
+ if(fd == -1) {
+ semi_write0("Cannot open file: ");
+ goto out;
+ }
+
+ filesize = semi_flen(fd);
+ if(filesize == -1) {
+ semi_write0("Cannot get file size for: ");
+ goto out;
+ }
+
+ if(semi_read(fd, *dest, filesize)) {
+ semi_write0("Could not read: ");
+ goto out;
+ }
+
+ result = 0; /* success */
+ *dest = (char *)*dest + filesize;
+
+out:
+ if(fd != -1)
+ semi_close(fd);
+
+ if(result) { /* print context for the error message */
+ semi_write0(filename);
+ semi_write0("\n");
+ } else
+ if(size)
+ *size = filesize;
+
+ return result;
+}
diff --git a/semihosting.h b/semihosting.h
new file mode 100644
index 0000000..dc1751f
--- /dev/null
+++ b/semihosting.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012 Linaro Limited
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Linaro Limited nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ */
+
+#ifndef SEMIHOSTING_H
+#define SEMIHOSTING_H
+
+#define SYS_OPEN 1
+#define OPEN_RDONLY 1
+#define SYS_CLOSE 2
+#define SYS_WRITEC 3
+#define SYS_WRITE0 4
+#define SYS_READ 6
+#define SYS_FLEN 0x0C
+#define SYS_GET_CMDLINE 0x15
+#define SYS_REPORTEXC 0x18
+#define REPORTEXC_REASON_APP_EXIT 0x20026
+#define SEMIHOSTING_SVC 0x123456 /* SVC comment field for semihosting */
+
+#ifndef __ASSEMBLER__
+
+int __semi_call(int id, ...);
+int semi_open(char const *filename, int mode);
+int semi_close(int fd);
+int semi_write0(char const *string);
+void semi_writec(char c);
+int semi_read(int fd, char *buffer, int length);
+int semi_flen(int fd);
+int semi_get_cmdline(char *buffer, int size, int *length);
+int semi_reportexc(int reason, int subcode);
+void semi_fatal(char const *message);
+void semi_exit(int subcode);
+/* semi_load_file: *dest is advanced to point to the end of the loaded data */
+int semi_load_file(void **dest, unsigned *size, char const *filename);
+
+#endif /* ! __ASSEMBLER__ */
+
+#endif /* ! SEMIHOSTING_H */
diff --git a/start.S b/start.S
new file mode 100644
index 0000000..5a1db58
--- /dev/null
+++ b/start.S
@@ -0,0 +1,38 @@
+/*
+ * start.S - assembly code for startup on baremetal tests
+ *
+ * Copyright (c) 2015 Linaro Limited
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Linaro Limited nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ */
+
+#ifndef __aarch64__
+ .syntax unified
+#endif
+
+ .text
+
+ .globl start
+start:
+ # We assume uniprocessor.
+ # TODO: enable floating point??
+
+ # Set up a stack
+ ldr sp, =stacktop
+ bl main
+ bl semi_exit
+ # not reached
+1: b 1b
+
diff --git a/string.c b/string.c
new file mode 100644
index 0000000..a807baa
--- /dev/null
+++ b/string.c
@@ -0,0 +1,137 @@
+#include <string.h>
+
+static void *__memmove_down(void *__dest, __const void *__src, size_t __n)
+{
+ unsigned char *d = (unsigned char *)__dest, *s = (unsigned char *)__src;
+
+ while (__n--)
+ *d++ = *s++;
+
+ return __dest;
+}
+
+static void *__memmove_up(void *__dest, __const void *__src, size_t __n)
+{
+ unsigned char *d = (unsigned char *)__dest + __n - 1, *s = (unsigned char *)__src + __n - 1;
+
+ while (__n--)
+ *d-- = *s--;
+
+ return __dest;
+}
+
+void *(memcpy)(void *__dest, __const void *__src, size_t __n)
+{
+ return __memmove_down(__dest, __src, __n);
+}
+
+void *(memmove)(void *__dest, __const void *__src, size_t __n)
+{
+ if(__dest > __src)
+ return __memmove_up(__dest, __src, __n);
+ else
+ return __memmove_down(__dest, __src, __n);
+}
+
+void *(memchr)(void const *s, int c, size_t n)
+{
+ unsigned char const *_s = (unsigned char const *)s;
+
+ while(n && *_s != c) {
+ ++_s;
+ --n;
+ }
+
+ if(n)
+ return (void *)_s; /* the C library casts const away */
+ else
+ return (void *)0;
+}
+
+size_t (strlen)(const char *s)
+{
+ const char *sc = s;
+
+ while (*sc != '\0')
+ sc++;
+ return sc - s;
+}
+
+void *(memset)(void *s, int c, size_t count)
+{
+ char *xs = s;
+ while (count--)
+ *xs++ = c;
+ return s;
+}
+
+int (memcmp)(void const *p1, void const *p2, size_t n)
+{
+ unsigned char const *_p1 = p1;
+ unsigned char const *_p2 = p2;
+
+ while(n--) {
+ if(*_p1 < *_p2)
+ return -1;
+ else if(*_p1 > *_p2)
+ return 1;
+
+ ++_p1;
+ ++_p2;
+ }
+
+ return 0;
+}
+
+int (strcmp)(char const *s1, char const *s2)
+{
+ while(*s1 && *s2) {
+ if(*s1 < *s2)
+ return -1;
+ else if(*s1 > *s2)
+ return 1;
+
+ ++s1;
+ ++s2;
+ }
+
+ if(!*s1 && !*s2)
+ return 0;
+ else if(!*s1)
+ return -1;
+ else
+ return 1;
+}
+
+int (strncmp)(char const *s1, char const *s2, size_t n)
+{
+ while(*s1 && *s2 && n--) {
+ if(*s1 < *s2)
+ return -1;
+ else if(*s1 > *s2)
+ return 1;
+
+ ++s1;
+ ++s2;
+ }
+
+ if(n == 0 || (!*s1 && !*s2))
+ return 0;
+ else if(!*s1)
+ return -1;
+ else
+ return 1;
+}
+
+char *(strchr)(char const *s, int c)
+{
+ unsigned char const *_s = (unsigned char const *)s;
+
+ while(*_s && *_s != c)
+ ++_s;
+
+ if(*_s)
+ return (char *)_s; /* the C library casts const away */
+ else
+ return (char *)0;
+}
diff --git a/string.h b/string.h
new file mode 100644
index 0000000..f1aebdf
--- /dev/null
+++ b/string.h
@@ -0,0 +1,16 @@
+#ifndef STRING_H
+#define STRING_H
+
+#include <stddef.h>
+
+extern void *(memcpy)(void *__dest, __const void *__src, size_t __n);
+extern void *(memmove)(void *__dest, __const void *__src, size_t __n);
+extern void *(memchr)(void const *s, int c, size_t n);
+extern size_t (strlen)(const char *s);
+extern void *(memset)(void *s, int c, size_t count);
+extern int (memcmp)(void const *p1, void const *p2, size_t n);
+extern int (strcmp)(char const *s1, char const *s2);
+extern int (strncmp)(char const *s1, char const *s2, size_t n);
+extern char *(strchr)(char const *s, int c);
+
+#endif
diff --git a/testdata.txt b/testdata.txt
new file mode 100644
index 0000000..4a422bc
--- /dev/null
+++ b/testdata.txt
@@ -0,0 +1,2 @@
+Small file of text data for test.
+Note that this file must not be longer than the buffer used to read it in the test code...
diff --git a/usertest.c b/usertest.c
new file mode 100644
index 0000000..16ef0fe
--- /dev/null
+++ b/usertest.c
@@ -0,0 +1,69 @@
+/*
+ * usertest.c -- top level test file for usermode tests
+ *
+ * Copyright (c) 2015 Linaro Limited
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Linaro Limited nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ */
+
+#include <stdio.h>
+#include "semihosting.h"
+#include "printf/printf.h"
+
+char filebuf[2048];
+
+/* This is needed by libgcc */
+int raise(int sig)
+{
+ semi_fatal("raise called (division by zero?)\n");
+}
+
+void semi_putc(void *p, char c)
+{
+ semi_writec(c);
+}
+
+int main(void)
+{
+ void *bufp;
+ unsigned int sz;
+
+ init_printf(NULL, semi_putc);
+
+ semi_write0("hello world via semi_write0\n");
+
+ semi_write0("open file test\n");
+
+ /* caution -- this will not do a length check on the buffer! */
+ bufp = filebuf;
+ if (semi_load_file(&bufp, &sz, "testdata.txt") < 0) {
+ semi_write0("semi_load_file failed!\n");
+ return 1;
+ }
+ if (sz >= sizeof(filebuf)) {
+ semi_write0("test file too big for buffer\n");
+ return 1;
+ }
+
+ filebuf[sizeof(filebuf) - 1] = 0;
+
+ printf("read %d bytes from file\n", sz);
+ semi_write0("buffer contents:\n");
+ semi_write0(filebuf);
+ semi_write0("tests complete, exiting via semihosting\n");
+
+ semi_exit(0);
+ /* not reached */
+}