From patchwork Wed Dec 10 20:00:01 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Jones X-Patchwork-Id: 5472161 Return-Path: X-Original-To: patchwork-kvm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id C11B09F1D4 for ; Wed, 10 Dec 2014 20:46:37 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 2D8032017D for ; Wed, 10 Dec 2014 20:46:36 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 2E9B220172 for ; Wed, 10 Dec 2014 20:46:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932726AbaLJUqa (ORCPT ); Wed, 10 Dec 2014 15:46:30 -0500 Received: from mx1.redhat.com ([209.132.183.28]:43405 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932116AbaLJUq3 (ORCPT ); Wed, 10 Dec 2014 15:46:29 -0500 Received: from int-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id sBAKjgx9010538 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 10 Dec 2014 15:46:25 -0500 Received: from hawk.usersys.redhat.com ([10.34.1.145]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id sBAK0CaT028915; Wed, 10 Dec 2014 15:00:26 -0500 From: Andrew Jones To: kvmarm@lists.cs.columbia.edu, kvm@vger.kernel.org Cc: christoffer.dall@linaro.org, pbonzini@redhat.com, alex.bennee@linaro.org Subject: [PATCH 08/15] arm64: initial drop Date: Wed, 10 Dec 2014 21:00:01 +0100 Message-Id: <1418241608-13966-9-git-send-email-drjones@redhat.com> In-Reply-To: <1418241608-13966-1-git-send-email-drjones@redhat.com> References: <1418241608-13966-1-git-send-email-drjones@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.22 Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This is the initial drop of the arm64 test framework and a first test that just checks that setup completed (a selftest). kvm isn't needed to run this test unless testing with smp > 1. Try it out with yum install gcc-aarch64-linux-gnu ./configure --cross-prefix=aarch64-linux-gnu- --arch=arm64 make QEMU=[qemu with aarch64, mach-virt, and chr-testdev] ./run_tests.sh Signed-off-by: Andrew Jones --- arm/cstart64.S | 48 +++++++++++++++++++++++++ arm/run | 10 ++++-- arm/selftest.c | 6 ++++ arm/unittests.cfg | 2 ++ config/config-arm-common.mak | 68 +++++++++++++++++++++++++++++++++++ config/config-arm.mak | 75 +++++---------------------------------- config/config-arm64.mak | 20 +++++++++++ configure | 12 ++++++- lib/arm/asm-offsets.c | 7 +--- lib/arm64/.gitignore | 1 + lib/arm64/asm-offsets.c | 14 ++++++++ lib/arm64/asm/asm-offsets.h | 1 + lib/arm64/asm/barrier.h | 17 +++++++++ lib/arm64/asm/io.h | 84 ++++++++++++++++++++++++++++++++++++++++++++ lib/arm64/asm/mmu.h | 18 ++++++++++ lib/arm64/asm/page.h | 1 + lib/arm64/asm/setup.h | 1 + lib/arm64/asm/spinlock.h | 15 ++++++++ lib/kbuild.h | 8 +++++ 19 files changed, 333 insertions(+), 75 deletions(-) create mode 100644 arm/cstart64.S create mode 100644 config/config-arm-common.mak create mode 100644 config/config-arm64.mak create mode 100644 lib/arm64/.gitignore create mode 100644 lib/arm64/asm-offsets.c create mode 100644 lib/arm64/asm/asm-offsets.h create mode 100644 lib/arm64/asm/barrier.h create mode 100644 lib/arm64/asm/io.h create mode 100644 lib/arm64/asm/mmu.h create mode 100644 lib/arm64/asm/page.h create mode 100644 lib/arm64/asm/setup.h create mode 100644 lib/arm64/asm/spinlock.h create mode 100644 lib/kbuild.h diff --git a/arm/cstart64.S b/arm/cstart64.S new file mode 100644 index 0000000000000..1d98066d0e187 --- /dev/null +++ b/arm/cstart64.S @@ -0,0 +1,48 @@ +/* + * Boot entry point and assembler functions for aarch64 tests. + * + * Copyright (C) 2014, Red Hat Inc, Andrew Jones + * + * This work is licensed under the terms of the GNU LGPL, version 2. + */ +#define __ASSEMBLY__ +#include + +.section .init + +.globl start +start: + /* + * bootloader params are in x0-x3 + * The physical address of the dtb is in x0, x1-x3 are reserved + * See the kernel doc Documentation/arm64/booting.txt + */ + adr x4, stacktop + mov sp, x4 + stp x0, x1, [sp, #-16]! + + /* Enable FP/ASIMD */ + mov x0, #(3 << 20) + msr cpacr_el1, x0 + + /* set up exception handling */ +// bl exceptions_init + + /* complete setup */ + ldp x0, x1, [sp], #16 + bl setup + + /* run the test */ + adr x0, __argc + ldr x0, [x0] + adr x1, __argv + bl main + bl exit + b halt + +.text + +.globl halt +halt: +1: wfi + b 1b diff --git a/arm/run b/arm/run index 4c5e52525d687..662a8564674a3 100755 --- a/arm/run +++ b/arm/run @@ -5,8 +5,9 @@ if [ ! -f config.mak ]; then exit 2 fi source config.mak +processor="$PROCESSOR" -qemu="${QEMU:-qemu-system-arm}" +qemu="${QEMU:-qemu-system-$ARCH_NAME}" qpath=$(which $qemu 2>/dev/null) if [ -z "$qpath" ]; then @@ -36,7 +37,12 @@ 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" +# arm64 must use '-cpu host' with kvm +if [ "$(arch)" = "aarch64" ] && [ "$ARCH" = "arm64" ] && [ -c /dev/kvm ]; then + processor="host" +fi + +command="$qemu $M -cpu $processor $chr_testdev" command+=" -display none -serial stdio -kernel" echo $command "$@" diff --git a/arm/selftest.c b/arm/selftest.c index 885a54fee0e4a..30f44261d47db 100644 --- a/arm/selftest.c +++ b/arm/selftest.c @@ -8,10 +8,12 @@ #include #include #include +#ifdef __arm__ #include #include #include #include +#endif #define TESTGRP "selftest" @@ -78,6 +80,7 @@ static void check_setup(int argc, char **argv) assert_args(nr_tests, 2); } +#ifdef __arm__ static struct pt_regs expected_regs; /* * Capture the current register state and execute an instruction @@ -184,6 +187,7 @@ static void check_vectors(void *arg __unused) report("%s", check_und() && check_svc(), testname); exit(report_summary()); } +#endif int main(int argc, char **argv) { @@ -195,6 +199,7 @@ int main(int argc, char **argv) check_setup(argc-1, &argv[1]); +#ifdef __arm__ } else if (strcmp(argv[0], "vectors-kernel") == 0) { check_vectors(NULL); @@ -204,6 +209,7 @@ int main(int argc, char **argv) void *sp = memalign(PAGE_SIZE, PAGE_SIZE); memset(sp, 0, PAGE_SIZE); start_usr(check_vectors, NULL, (unsigned long)sp + PAGE_SIZE); +#endif } return report_summary(); diff --git a/arm/unittests.cfg b/arm/unittests.cfg index efcca6bf24af6..9ac6ecaa55d3b 100644 --- a/arm/unittests.cfg +++ b/arm/unittests.cfg @@ -22,9 +22,11 @@ groups = selftest file = selftest.flat extra_params = -append 'vectors-kernel' groups = selftest +arch = arm # Test vector setup and exception handling (user mode). [selftest::vectors-user] file = selftest.flat extra_params = -append 'vectors-user' groups = selftest +arch = arm diff --git a/config/config-arm-common.mak b/config/config-arm-common.mak new file mode 100644 index 0000000000000..b61a2a6044ab2 --- /dev/null +++ b/config/config-arm-common.mak @@ -0,0 +1,68 @@ +# +# arm common makefile +# +# Authors: Andrew Jones +# + +ifeq ($(LOADADDR),) + # qemu mach-virt default load address + LOADADDR = 0x40000000 +endif + +tests-common = \ + $(TEST_DIR)/selftest.flat + +all: test_cases + +################################################################## +phys_base = $(LOADADDR) + +CFLAGS += -std=gnu99 +CFLAGS += -ffreestanding +CFLAGS += -Wextra +CFLAGS += -O2 +CFLAGS += -I lib -I lib/libfdt + +asm-offsets = lib/$(ARCH)/asm-offsets.h +include config/asm-offsets.mak + +cflatobjs += lib/alloc.o +cflatobjs += lib/devicetree.o +cflatobjs += lib/virtio.o +cflatobjs += lib/virtio-mmio.o +cflatobjs += lib/chr-testdev.o +cflatobjs += lib/arm/io.o +cflatobjs += lib/arm/setup.o + +libeabi = lib/arm/libeabi.a +eabiobjs = lib/arm/eabi_compat.o + +libgcc := $(shell $(CC) $(machine) --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 $@ $^ + +arm_clean: libfdt_clean asm_offsets_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 + +generated_files = $(asm-offsets) + +test_cases: $(generated_files) $(tests-common) $(tests) + +$(TEST_DIR)/selftest.elf: $(cstart.o) $(TEST_DIR)/selftest.o diff --git a/config/config-arm.mak b/config/config-arm.mak index 86e1d75169b59..96686fb639d2d 100644 --- a/config/config-arm.mak +++ b/config/config-arm.mak @@ -3,80 +3,23 @@ # # Authors: Andrew Jones # - -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 +machine = -marm CFLAGS += -D__arm__ -CFLAGS += -marm +CFLAGS += $(machine) CFLAGS += -mcpu=$(PROCESSOR) -CFLAGS += -std=gnu99 -CFLAGS += -ffreestanding -CFLAGS += -Wextra -CFLAGS += -O2 -CFLAGS += -I lib -I lib/libfdt - -asm-offsets = lib/arm/asm-offsets.h -include config/asm-offsets.mak - -cflatobjs += \ - lib/alloc.o \ - lib/devicetree.o \ - lib/virtio.o \ - lib/virtio-mmio.o \ - lib/chr-testdev.o \ - lib/arm/io.o \ - lib/arm/setup.o \ - lib/arm/spinlock.o \ - lib/arm/processor.o \ - lib/arm/mmu.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 asm_offsets_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 +cflatobjs += lib/arm/spinlock.o +cflatobjs += lib/arm/processor.o +cflatobjs += lib/arm/mmu.o -generated_files = $(asm-offsets) - -test_cases: $(generated_files) $(tests-common) $(tests) +# arm specific tests +tests = -$(TEST_DIR)/selftest.elf: $(cstart.o) $(TEST_DIR)/selftest.o +include config/config-arm-common.mak +arch_clean: arm_clean diff --git a/config/config-arm64.mak b/config/config-arm64.mak new file mode 100644 index 0000000000000..37db3d6026424 --- /dev/null +++ b/config/config-arm64.mak @@ -0,0 +1,20 @@ +# +# arm64 makefile +# +# Authors: Andrew Jones +# +bits = 64 +ldarch = elf64-littleaarch64 +kernel_offset = 0x80000 + +CFLAGS += -D__aarch64__ + +cstart.o = $(TEST_DIR)/cstart64.o + +# arm64 specific tests +tests = + +include config/config-arm-common.mak + +arch_clean: arm_clean + $(RM) lib/arm64/.*.d diff --git a/configure b/configure index afdd62800f56e..b2ad32a3e3a52 100755 --- a/configure +++ b/configure @@ -62,14 +62,23 @@ while [[ "$1" = -* ]]; do ;; esac done + +arch_name=$arch +[ "$arch" = "aarch64" ] && arch="arm64" +[ "$arch_name" = "arm64" ] && arch_name="aarch64" + [ -z "$processor" ] && processor="$arch" -if [ "$processor" = "arm" ]; then +if [ "$processor" = "arm64" ]; then + processor="cortex-a57" +elif [ "$processor" = "arm" ]; then processor="cortex-a15" fi if [ "$arch" = "i386" ] || [ "$arch" = "x86_64" ]; then testdir=x86 +elif [ "$arch" = "arm" ] || [ "$arch" = "arm64" ]; then + testdir=arm else testdir=$arch fi @@ -114,6 +123,7 @@ cat < config.mak PREFIX=$prefix KERNELDIR=$(readlink -f $kerneldir) ARCH=$arch +ARCH_NAME=$arch_name PROCESSOR=$processor CC=$cross_prefix$cc LD=$cross_prefix$ld diff --git a/lib/arm/asm-offsets.c b/lib/arm/asm-offsets.c index 76380dfa15ab8..1ee9da070f609 100644 --- a/lib/arm/asm-offsets.c +++ b/lib/arm/asm-offsets.c @@ -6,14 +6,9 @@ * This work is licensed under the terms of the GNU LGPL, version 2. */ #include +#include #include -#define DEFINE(sym, val) \ - asm volatile("\n->" #sym " %0 " #val : : "i" (val)) -#define OFFSET(sym, str, mem) DEFINE(sym, offsetof(struct str, mem)) -#define COMMENT(x) asm volatile("\n->#" x) -#define BLANK() asm volatile("\n->" : : ) - int main(void) { OFFSET(S_R0, pt_regs, ARM_r0); diff --git a/lib/arm64/.gitignore b/lib/arm64/.gitignore new file mode 100644 index 0000000000000..84872bf197c67 --- /dev/null +++ b/lib/arm64/.gitignore @@ -0,0 +1 @@ +asm-offsets.[hs] diff --git a/lib/arm64/asm-offsets.c b/lib/arm64/asm-offsets.c new file mode 100644 index 0000000000000..c85b9a1e97e44 --- /dev/null +++ b/lib/arm64/asm-offsets.c @@ -0,0 +1,14 @@ +/* + * Adapted from arch/arm64/kernel/asm-offsets.c + * + * Copyright (C) 2014, Red Hat Inc, Andrew Jones + * + * This work is licensed under the terms of the GNU LGPL, version 2. + */ +#include +#include + +int main(void) +{ + return 0; +} diff --git a/lib/arm64/asm/asm-offsets.h b/lib/arm64/asm/asm-offsets.h new file mode 100644 index 0000000000000..d370ee36a182b --- /dev/null +++ b/lib/arm64/asm/asm-offsets.h @@ -0,0 +1 @@ +#include diff --git a/lib/arm64/asm/barrier.h b/lib/arm64/asm/barrier.h new file mode 100644 index 0000000000000..8ebdfdf7f1037 --- /dev/null +++ b/lib/arm64/asm/barrier.h @@ -0,0 +1,17 @@ +#ifndef _ASMARM64_BARRIER_H_ +#define _ASMARM64_BARRIER_H_ +/* + * From Linux arch/arm64/include/asm/barrier.h + */ + +#define isb() asm volatile("isb" : : : "memory") +#define dmb(opt) asm volatile("dmb " #opt : : : "memory") +#define dsb(opt) asm volatile("dsb " #opt : : : "memory") +#define mb() dsb(sy) +#define rmb() dsb(ld) +#define wmb() dsb(st) +#define smp_mb() dmb(ish) +#define smp_rmb() dmb(ishld) +#define smp_wmb() dmb(ishst) + +#endif /* _ASMARM64_BARRIER_H_ */ diff --git a/lib/arm64/asm/io.h b/lib/arm64/asm/io.h new file mode 100644 index 0000000000000..07b82cb9d8695 --- /dev/null +++ b/lib/arm64/asm/io.h @@ -0,0 +1,84 @@ +#ifndef _ASMARM64_IO_H_ +#define _ASMARM64_IO_H_ +/* + * From Linux arch/arm64/include/asm/io.h + * Generic IO read/write. These perform native-endian accesses. + */ +#include +#include +#include + +#define __iomem +#define __force + +#define __raw_writeb __raw_writeb +static inline void __raw_writeb(u8 val, volatile void __iomem *addr) +{ + asm volatile("strb %w0, [%1]" : : "r" (val), "r" (addr)); +} + +#define __raw_writew __raw_writew +static inline void __raw_writew(u16 val, volatile void __iomem *addr) +{ + asm volatile("strh %w0, [%1]" : : "r" (val), "r" (addr)); +} + +#define __raw_writel __raw_writel +static inline void __raw_writel(u32 val, volatile void __iomem *addr) +{ + asm volatile("str %w0, [%1]" : : "r" (val), "r" (addr)); +} + +#define __raw_writeq __raw_writeq +static inline void __raw_writeq(u64 val, volatile void __iomem *addr) +{ + asm volatile("str %0, [%1]" : : "r" (val), "r" (addr)); +} + +#define __raw_readb __raw_readb +static inline u8 __raw_readb(const volatile void __iomem *addr) +{ + u8 val; + asm volatile("ldrb %w0, [%1]" : "=r" (val) : "r" (addr)); + return val; +} + +#define __raw_readw __raw_readw +static inline u16 __raw_readw(const volatile void __iomem *addr) +{ + u16 val; + asm volatile("ldrh %w0, [%1]" : "=r" (val) : "r" (addr)); + return val; +} + +#define __raw_readl __raw_readl +static inline u32 __raw_readl(const volatile void __iomem *addr) +{ + u32 val; + asm volatile("ldr %w0, [%1]" : "=r" (val) : "r" (addr)); + return val; +} + +#define __raw_readq __raw_readq +static inline u64 __raw_readq(const volatile void __iomem *addr) +{ + u64 val; + asm volatile("ldr %0, [%1]" : "=r" (val) : "r" (addr)); + return val; +} + +#define virt_to_phys virt_to_phys +static inline phys_addr_t virt_to_phys(const volatile void *x) +{ + return __virt_to_phys((unsigned long)(x)); +} + +#define phys_to_virt phys_to_virt +static inline void *phys_to_virt(phys_addr_t x) +{ + return (void *)__phys_to_virt(x); +} + +#include + +#endif /* _ASMARM64_IO_H_ */ diff --git a/lib/arm64/asm/mmu.h b/lib/arm64/asm/mmu.h new file mode 100644 index 0000000000000..cbafbca6701e7 --- /dev/null +++ b/lib/arm64/asm/mmu.h @@ -0,0 +1,18 @@ +#ifndef __ASMARM64_MMU_H_ +#define __ASMARM64_MMU_H_ +/* + * Copyright (C) 2014, Red Hat Inc, Andrew Jones + * + * This work is licensed under the terms of the GNU LGPL, version 2. + */ + +static inline bool mmu_enabled(void) +{ + return false; +} + +static inline void mmu_enable_idmap(void) +{ +} + +#endif /* __ASMARM64_MMU_H_ */ diff --git a/lib/arm64/asm/page.h b/lib/arm64/asm/page.h new file mode 100644 index 0000000000000..395760cad5f82 --- /dev/null +++ b/lib/arm64/asm/page.h @@ -0,0 +1 @@ +#include "../../arm/asm/page.h" diff --git a/lib/arm64/asm/setup.h b/lib/arm64/asm/setup.h new file mode 100644 index 0000000000000..e3b4702716c6b --- /dev/null +++ b/lib/arm64/asm/setup.h @@ -0,0 +1 @@ +#include "../../arm/asm/setup.h" diff --git a/lib/arm64/asm/spinlock.h b/lib/arm64/asm/spinlock.h new file mode 100644 index 0000000000000..36b7b44fa4edf --- /dev/null +++ b/lib/arm64/asm/spinlock.h @@ -0,0 +1,15 @@ +#ifndef _ASMARM64_SPINLOCK_H_ +#define _ASMARM64_SPINLOCK_H_ + +struct spinlock { + int v; +}; + +static inline void spin_lock(struct spinlock *lock __unused) +{ +} +static inline void spin_unlock(struct spinlock *lock __unused) +{ +} + +#endif /* _ASMARM64_SPINLOCK_H_ */ diff --git a/lib/kbuild.h b/lib/kbuild.h new file mode 100644 index 0000000000000..ab99db6770efa --- /dev/null +++ b/lib/kbuild.h @@ -0,0 +1,8 @@ +#ifndef _KBUILD_H_ +#define _KBUILD_H_ +#define DEFINE(sym, val) \ + asm volatile("\n->" #sym " %0 " #val : : "i" (val)) +#define OFFSET(sym, str, mem) DEFINE(sym, offsetof(struct str, mem)) +#define COMMENT(x) asm volatile("\n->#" x) +#define BLANK() asm volatile("\n->" : : ) +#endif