new file mode 100644
@@ -0,0 +1,35 @@
+/*
+ * Boot entry point and assembler functions for armv7 tests.
+ *
+ * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+
+.arm
+
+.section .init
+
+.globl start
+start:
+ /*
+ * bootloader params are in r0-r2
+ * See the kernel doc Documentation/arm/Booting
+ */
+ ldr sp, =stacktop
+ bl setup
+
+ /* run the test */
+ ldr r0, =__argc
+ ldr r0, [r0]
+ ldr r1, =__argv
+ bl main
+ bl exit
+ b halt
+
+.text
+
+.globl halt
+halt:
+1: wfi
+ b 1b
new file mode 100644
@@ -0,0 +1,18 @@
+
+SECTIONS
+{
+ .text : { *(.init) *(.text) *(.text.*) }
+ . = ALIGN(4K);
+ .data : { *(.data) }
+ . = ALIGN(16);
+ .rodata : { *(.rodata) }
+ . = ALIGN(16);
+ .bss : { *(.bss) }
+ . = ALIGN(4K);
+ edata = .;
+ . += 8K;
+ . = ALIGN(4K);
+ stacktop = .;
+}
+
+ENTRY(start)
new file mode 100755
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+if [ ! -f config.mak ]; then
+ echo run ./configure first. See ./configure -h
+ exit 2
+fi
+source config.mak
+
+qemu="${QEMU:-qemu-system-arm}"
+qpath=$(which $qemu 2>/dev/null)
+
+if [ -z "$qpath" ]; then
+ echo $qemu not found.
+ exit 2
+fi
+
+if ! $qemu -machine '?' 2>&1 | grep 'ARM Virtual Machine' > /dev/null; then
+ echo "$qpath doesn't support mach-virt ('-machine virt'). Exiting."
+ exit 2
+fi
+
+M='-machine virt'
+
+if ! $qemu $M -device '?' 2>&1 | grep virtconsole > /dev/null; then
+ echo "$qpath doesn't support virtio-console for chr-testdev. Exiting."
+ exit 2
+fi
+
+if $qemu $M -chardev testdev,id=id -kernel . 2>&1 \
+ | grep backend > /dev/null; then
+ echo "$qpath doesn't support chr-testdev. Exiting."
+ exit 2
+fi
+
+M='-machine virt,accel=kvm:tcg'
+chr_testdev='-device virtio-serial-device'
+chr_testdev+=' -device virtconsole,chardev=ctd -chardev testdev,id=ctd'
+
+command="$qemu $M -cpu $PROCESSOR $chr_testdev"
+command+=" -display none -serial stdio -kernel"
+
+echo $command "$@"
+$command "$@"
+ret=$?
+echo Return value from qemu: $ret
+exit $ret
new file mode 100644
@@ -0,0 +1,89 @@
+/*
+ * Test the framework itself. These tests confirm that setup works.
+ *
+ * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+#include "libcflat.h"
+#include "asm/setup.h"
+
+#define TESTGRP "selftest"
+
+static char testname[64];
+
+static void testname_set(const char *subtest)
+{
+ strcpy(testname, TESTGRP);
+ if (subtest) {
+ strcat(testname, "::");
+ strcat(testname, subtest);
+ }
+}
+
+static void assert_args(int num_args, int needed_args)
+{
+ if (num_args < needed_args) {
+ printf("%s: not enough arguments\n", testname);
+ abort();
+ }
+}
+
+static char *split_var(char *s, long *val)
+{
+ char *p;
+
+ p = strchr(s, '=');
+ if (!p)
+ return NULL;
+
+ *val = atol(p+1);
+ *p = '\0';
+
+ return s;
+}
+
+static void check_setup(int argc, char **argv)
+{
+ int nr_tests = 0, i;
+ char *var;
+ long val;
+
+ for (i = 0; i < argc; ++i) {
+
+ var = split_var(argv[i], &val);
+ if (!var)
+ continue;
+
+ if (strcmp(argv[i], "mem") == 0) {
+
+ phys_addr_t memsize =
+ memregions[nr_memregions-1].addr
+ + memregions[nr_memregions-1].size
+ - PHYS_OFFSET;
+ phys_addr_t expected = ((phys_addr_t)val)*1024*1024;
+
+ report("%s[%s]", memsize == expected, testname, "mem");
+ ++nr_tests;
+
+ } else if (strcmp(argv[i], "smp") == 0) {
+
+ report("%s[%s]", nr_cpus == (int)val, testname, "smp");
+ ++nr_tests;
+ }
+ }
+
+ assert_args(nr_tests, 2);
+}
+
+int main(int argc, char **argv)
+{
+ testname_set(NULL);
+ assert_args(argc, 1);
+ testname_set(argv[0]);
+
+ if (strcmp(argv[0], "setup") == 0)
+ check_setup(argc-1, &argv[1]);
+
+ return report_summary();
+}
new file mode 100644
@@ -0,0 +1,18 @@
+# Define your new unittest following the convention:
+# [unittest_name]
+# file = foo.flat # Name of the flat file to be used
+# smp = 2 # Number of processors the VM will use during this test
+# extra_params = -append <params...> # Additional parameters used
+# arch = arm/arm64 # Only if test case is specific to one
+# groups = group1 group2 # Used to identify test cases with run_tests -g ...
+
+#
+# Test that the configured number of processors (smp = <num>), and
+# that the configured amount of memory (-m <MB>) are correctly setup
+# by the framework.
+#
+[selftest::setup]
+file = selftest.flat
+smp = 1
+extra_params = -m 256 -append 'setup smp=1 mem=256'
+groups = selftest
new file mode 100644
@@ -0,0 +1,73 @@
+#
+# arm makefile
+#
+# Authors: Andrew Jones <drjones@redhat.com>
+#
+
+tests-common = \
+ $(TEST_DIR)/selftest.flat
+
+tests =
+
+all: test_cases
+
+##################################################################
+bits = 32
+ldarch = elf32-littlearm
+
+ifeq ($(LOADADDR),)
+ LOADADDR = 0x40000000
+endif
+phys_base = $(LOADADDR)
+kernel_offset = 0x10000
+
+CFLAGS += -D__arm__
+CFLAGS += -marm
+CFLAGS += -mcpu=$(PROCESSOR)
+CFLAGS += -std=gnu99
+CFLAGS += -ffreestanding
+CFLAGS += -Wextra
+CFLAGS += -O2
+CFLAGS += -I lib -I lib/libfdt
+
+cflatobjs += \
+ lib/alloc.o \
+ lib/devicetree.o \
+ lib/virtio.o \
+ lib/chr-testdev.o \
+ lib/arm/io.o \
+ lib/arm/setup.o
+
+libeabi = lib/arm/libeabi.a
+eabiobjs = lib/arm/eabi_compat.o
+
+libgcc := $(shell $(CC) -m$(ARCH) --print-libgcc-file-name)
+start_addr := $(shell printf "%x\n" $$(( $(phys_base) + $(kernel_offset) )))
+
+FLATLIBS = $(libcflat) $(LIBFDT_archive) $(libgcc) $(libeabi)
+%.elf: LDFLAGS = $(CFLAGS) -nostdlib
+%.elf: %.o $(FLATLIBS) arm/flat.lds
+ $(CC) $(LDFLAGS) -o $@ \
+ -Wl,-T,arm/flat.lds,--build-id=none,-Ttext=$(start_addr) \
+ $(filter %.o, $^) $(FLATLIBS)
+
+%.flat: %.elf
+ $(OBJCOPY) -O binary $^ $@
+
+$(libeabi): $(eabiobjs)
+ $(AR) rcs $@ $^
+
+arch_clean: libfdt_clean
+ $(RM) $(TEST_DIR)/*.{o,flat,elf} $(libeabi) $(eabiobjs) \
+ $(TEST_DIR)/.*.d lib/arm/.*.d
+
+##################################################################
+
+tests_and_config = $(TEST_DIR)/*.flat $(TEST_DIR)/unittests.cfg
+
+cstart.o = $(TEST_DIR)/cstart.o
+
+test_cases: $(tests-common) $(tests)
+
+$(TEST_DIR)/selftest.elf: $(cstart.o) $(TEST_DIR)/selftest.o
+
@@ -6,8 +6,7 @@ cc=gcc
ld=ld
objcopy=objcopy
ar=ar
-arch=`uname -m | sed -e s/i.86/i386/`
-processor="$arch"
+arch=`uname -m | sed -e s/i.86/i386/ | sed -e 's/arm.*/arm/'`
cross_prefix=
usage() {
@@ -16,6 +15,7 @@ usage() {
Options include:
--arch=ARCH architecture to compile for ($arch)
+ --processor=PROCESSOR processor to compile for ($arch)
--cross-prefix=PREFIX cross compiler prefix
--cc=CC c compiler to use ($cc)
--ld=LD ld linker to use ($ld)
@@ -62,6 +62,12 @@ while [[ "$1" = -* ]]; do
;;
esac
done
+[ -z "$processor" ] && processor="$arch"
+
+if [ "$processor" = "arm" ]; then
+ processor="cortex-a15"
+fi
+
if [ "$arch" = "i386" ] || [ "$arch" = "x86_64" ]; then
testdir=x86
else
@@ -76,6 +82,7 @@ if [ -f $testdir/run ]; then
fi
# check for dependent 32 bit libraries
+if [ "$arch" != "arm" ]; then
cat << EOF > lib_test.c
#include <stdc++.h>
#include <boost_thread-mt.h>
@@ -90,6 +97,7 @@ if [ $exit -eq 0 ]; then
api=true
fi
rm -f lib_test.c
+fi
# link lib/asm for the architecture
rm -f lib/asm
@@ -31,3 +31,9 @@ void __setup_args(void)
}
__argc = argv - __argv;
}
+
+void setup_args(char *args)
+{
+ __args = args;
+ __setup_args();
+}
new file mode 100644
@@ -0,0 +1,18 @@
+#ifndef _ASMARM_BARRIER_H_
+#define _ASMARM_BARRIER_H_
+/*
+ * Adapted form arch/arm/include/asm/barrier.h
+ */
+
+#define isb(option) __asm__ __volatile__ ("isb " #option : : : "memory")
+#define dsb(option) __asm__ __volatile__ ("dsb " #option : : : "memory")
+#define dmb(option) __asm__ __volatile__ ("dmb " #option : : : "memory")
+
+#define mb() dsb()
+#define rmb() dsb()
+#define wmb() dsb(st)
+#define smp_mb() dmb(ish)
+#define smp_rmb() smp_mb()
+#define smp_wmb() dmb(ishst)
+
+#endif /* _ASMARM_BARRIER_H_ */
new file mode 100644
@@ -0,0 +1,24 @@
+#ifndef _ASMARM_IO_H_
+#define _ASMARM_IO_H_
+#include "libcflat.h"
+#include "asm/barrier.h"
+
+#define __bswap16 bswap16
+static inline u16 bswap16(u16 val)
+{
+ u16 ret;
+ asm volatile("rev16 %0, %1" : "=r" (ret) : "r" (val));
+ return ret;
+}
+
+#define __bswap32 bswap32
+static inline u32 bswap32(u32 val)
+{
+ u32 ret;
+ asm volatile("rev %0, %1" : "=r" (ret) : "r" (val));
+ return ret;
+}
+
+#include "asm-generic/io.h"
+
+#endif /* _ASMARM_IO_H_ */
new file mode 100644
@@ -0,0 +1 @@
+#include "asm-generic/page.h"
new file mode 100644
@@ -0,0 +1,63 @@
+#ifndef _ASMARM_SETUP_H_
+#define _ASMARM_SETUP_H_
+/*
+ * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+#include "libcflat.h"
+
+#define NR_CPUS 8
+extern u32 cpus[NR_CPUS];
+extern int nr_cpus;
+
+typedef u64 phys_addr_t;
+
+/*
+ * memregions implement a very simple allocator which allows physical
+ * memory to be partitioned into regions until all memory is allocated.
+ * Also, as long as not all memory has been allocated, one region (the
+ * highest indexable region) is used to represent the start and size of
+ * the remaining free memory. This means that there will always be a
+ * minimum of two regions: one for the unit test code, initially loaded
+ * at the base of physical memory (PHYS_OFFSET), and another for the
+ * remaining free memory.
+ *
+ * Note: This is such a simple allocator that there is no way to free
+ * a memregion. For more complicated memory management a single region
+ * can be allocated, but then have its memory managed by a more
+ * sophisticated allocator, e.g. a page allocator.
+ */
+#define NR_MEMREGIONS 128
+struct memregion {
+ phys_addr_t addr;
+ phys_addr_t size;
+ bool free;
+};
+
+extern struct memregion memregions[NR_MEMREGIONS];
+extern int nr_memregions;
+
+/*
+ * memregion_new returns a new memregion of size @size, with a region
+ * address (mr->addr) aligned to @align, or NULL if there isn't enough
+ * free memory to satisfy the request.
+ */
+extern struct memregion *memregion_new(phys_addr_t size, phys_addr_t align);
+
+/*
+ * memregions_show outputs all memregions with the following format
+ * <start_addr>-<end_addr> [<USED|FREE>]
+ */
+extern void memregions_show(void);
+
+#define PHYS_OFFSET ({ memregions[0].addr; })
+#define PHYS_SHIFT 40
+#define PHYS_SIZE (1ULL << PHYS_SHIFT)
+#define PHYS_MASK (PHYS_SIZE - 1ULL)
+
+#define L1_CACHE_SHIFT 6
+#define L1_CACHE_BYTES (1 << L1_CACHE_SHIFT)
+#define SMP_CACHE_BYTES L1_CACHE_BYTES
+
+#endif /* _ASMARM_SETUP_H_ */
new file mode 100644
@@ -0,0 +1,16 @@
+#ifndef _ASMARM_SPINLOCK_H_
+#define _ASMARM_SPINLOCK_H_
+
+struct spinlock {
+ int v;
+};
+
+//TODO
+static inline void spin_lock(struct spinlock *lock __unused)
+{
+}
+static inline void spin_unlock(struct spinlock *lock __unused)
+{
+}
+
+#endif /* _ASMARM_SPINLOCK_H_ */
new file mode 100644
@@ -0,0 +1,20 @@
+/*
+ * Adapted from u-boot's arch/arm/lib/eabi_compat.c
+ */
+#include "libcflat.h"
+
+int raise(int signum __unused)
+{
+ printf("Divide by zero!\n");
+ abort();
+ return 0;
+}
+
+/* Dummy functions to avoid linker complaints */
+void __aeabi_unwind_cpp_pr0(void)
+{
+}
+
+void __aeabi_unwind_cpp_pr1(void)
+{
+}
new file mode 100644
@@ -0,0 +1,65 @@
+/*
+ * Each architecture must implement puts() and exit() with the I/O
+ * devices exposed from QEMU, e.g. pl011 and chr-testdev. That's
+ * what's done here, along with initialization functions for those
+ * devices.
+ *
+ * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+#include "libcflat.h"
+#include "devicetree.h"
+#include "chr-testdev.h"
+#include "asm/spinlock.h"
+#include "asm/io.h"
+
+extern void halt(int code);
+
+/*
+ * Use this guess for the pl011 base in order to make an attempt at
+ * having earlier printf support. We'll overwrite it with the real
+ * base address that we read from the device tree later.
+ */
+#define QEMU_MACH_VIRT_PL011_BASE 0x09000000UL
+
+static struct spinlock uart_lock;
+static volatile u8 *uart0_base = (u8 *)QEMU_MACH_VIRT_PL011_BASE;
+
+static void uart0_init(void)
+{
+ const char *compatible = "arm,pl011";
+ struct dt_pbus_reg base;
+ int ret;
+
+ ret = dt_pbus_get_base_compatible(compatible, &base);
+ assert(ret == 0 || ret == -FDT_ERR_NOTFOUND);
+
+ if (ret) {
+ printf("%s: %s not found in the device tree, aborting...\n",
+ __func__, compatible);
+ abort();
+ }
+
+ uart0_base = ioremap(base.addr, base.size);
+}
+
+void io_init(void)
+{
+ uart0_init();
+ chr_testdev_init();
+}
+
+void puts(const char *s)
+{
+ spin_lock(&uart_lock);
+ while (*s)
+ writeb(*s++, uart0_base);
+ spin_unlock(&uart_lock);
+}
+
+void exit(int code)
+{
+ chr_testdev_exit(code);
+ halt(code);
+}
new file mode 100644
@@ -0,0 +1,188 @@
+/*
+ * Initialize machine setup information and I/O.
+ *
+ * After running setup() unit tests may query how many cpus they have
+ * (nr_cpus), how much free memory they have, and at what physical
+ * address that free memory starts (memregions[1].{addr,size}),
+ * printf() and exit() will both work, and (argc, argv) are ready
+ * to be passed to main().
+ *
+ * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+#include "libcflat.h"
+#include "alloc.h"
+#include "libfdt/libfdt.h"
+#include "devicetree.h"
+#include "asm/spinlock.h"
+#include "asm/setup.h"
+#include "asm/page.h"
+
+#define ALIGN_UP_MASK(x, mask) (((x) + (mask)) & ~(mask))
+#define ALIGN_UP(x, a) ALIGN_UP_MASK(x, (typeof(x))(a) - 1)
+
+extern unsigned long stacktop;
+extern void io_init(void);
+extern void setup_args(const char *args);
+
+u32 cpus[NR_CPUS] = { [0 ... NR_CPUS-1] = (~0UL) };
+int nr_cpus;
+
+static struct spinlock memregion_lock;
+struct memregion memregions[NR_MEMREGIONS];
+int nr_memregions;
+
+static void cpu_set(int fdtnode __unused, u32 regval, void *info __unused)
+{
+ assert(nr_cpus < NR_CPUS);
+ cpus[nr_cpus++] = regval;
+}
+
+static void cpu_init(void)
+{
+ nr_cpus = 0;
+ assert(dt_for_each_cpu_node(cpu_set, NULL) == 0);
+}
+
+static void memregions_init(phys_addr_t freemem_start)
+{
+ /* we only expect one membank to be defined in the DT */
+ struct dt_pbus_reg regs[1];
+ phys_addr_t addr, size, mem_end;
+
+ nr_memregions = dt_get_memory_params(regs, 1);
+
+ assert(nr_memregions > 0);
+
+ addr = regs[0].addr;
+ size = regs[0].size;
+ mem_end = addr + size;
+
+ assert(!(addr & ~PHYS_MASK) && !((mem_end-1) & ~PHYS_MASK));
+
+ memregions[0].addr = PAGE_ALIGN(addr); /* PHYS_OFFSET */
+
+ assert(freemem_start >= PHYS_OFFSET && freemem_start < mem_end);
+
+ memregions[0].size = freemem_start - PHYS_OFFSET;
+ memregions[1].addr = freemem_start;
+ memregions[1].size = mem_end - freemem_start;
+ memregions[1].free = true;
+ nr_memregions = 2;
+
+#ifdef __arm__
+ /*
+ * Make sure 32-bit unit tests don't have any surprises when
+ * running without virtual memory, by ensuring the initial
+ * memory region uses 32-bit addresses. Other memory regions
+ * may have > 32-bit addresses though, and the unit tests are
+ * free to do as they wish with that.
+ */
+ assert(!(memregions[0].addr >> 32));
+ assert(!((memregions[0].addr + memregions[0].size - 1) >> 32));
+#endif
+}
+
+struct memregion *memregion_new(phys_addr_t size, phys_addr_t align)
+{
+ phys_addr_t freemem_start, mem_end, addr, size_orig = size;
+ struct memregion *mr;
+
+ spin_lock(&memregion_lock);
+
+ mr = &memregions[nr_memregions-1];
+
+ addr = ALIGN_UP(mr->addr, align);
+ size += addr - mr->addr;
+
+ if (!mr->free || mr->size < size) {
+ printf("%s: requested=0x%llx (align=0x%llx), "
+ "need=0x%llx, but free=0x%llx.\n", __func__,
+ size_orig, align, size, mr->free ? mr->size : 0ULL);
+ return NULL;
+ }
+
+ mem_end = mr->addr + mr->size;
+ freemem_start = mr->addr + size;
+
+ mr->addr = addr;
+ mr->size = size_orig;
+ mr->free = false;
+
+ if (freemem_start < mem_end && nr_memregions < NR_MEMREGIONS) {
+ memregions[nr_memregions].addr = freemem_start;
+ memregions[nr_memregions].size = mem_end - freemem_start;
+ memregions[nr_memregions].free = true;
+ ++nr_memregions;
+ }
+
+ spin_unlock(&memregion_lock);
+
+ return mr;
+}
+
+void memregions_show(void)
+{
+ int i;
+ for (i = 0; i < nr_memregions; ++i)
+ printf("%016llx-%016llx [%s]\n",
+ memregions[i].addr,
+ memregions[i].addr + memregions[i].size - 1,
+ memregions[i].free ? "FREE" : "USED");
+}
+
+static void *early_alloc_aligned(size_t size, size_t align)
+{
+ struct memregion *mr;
+ void *addr;
+
+ mr = memregion_new(size, align);
+ if (!mr)
+ return NULL;
+
+ addr = __va(mr->addr);
+ memset(addr, 0, size);
+
+ return addr;
+}
+
+static void *early_alloc(size_t size)
+{
+ phys_addr_t align = size < SMP_CACHE_BYTES ? SMP_CACHE_BYTES : size;
+ return early_alloc_aligned(size, align);
+}
+
+static void early_free(const void *addr __unused)
+{
+}
+
+static const struct alloc_ops early_alloc_ops = {
+ .alloc = early_alloc,
+ .alloc_aligned = early_alloc_aligned,
+ .free = early_free,
+};
+
+void setup(unsigned long arg __unused, unsigned long id __unused,
+ const void *fdt)
+{
+ const char *bootargs;
+ u32 fdt_size;
+
+ /*
+ * Move the fdt to just above the stack. The free memory
+ * then starts just after the fdt.
+ */
+ fdt_size = fdt_totalsize(fdt);
+ assert(fdt_move(fdt, &stacktop, fdt_size) == 0);
+ assert(dt_init(&stacktop) == 0);
+
+ memregions_init((unsigned long)&stacktop + fdt_size);
+ alloc_ops = early_alloc_ops;
+
+ io_init();
+ cpu_init();
+
+ assert(dt_get_bootargs(&bootargs) == 0);
+ setup_args(bootargs);
+}