Commit 9ac63c59 authored by Achin Gupta's avatar Achin Gupta Committed by Dan Handley
Browse files

Add helper library for cpu context management

This patch introduces functions for saving and restoring shared system
registers between secure and non-secure EL1 exception levels, VFP
registers and essential EL3 system register and other state. It also
defines the 'cpu_context' data structure which will used for saving and
restoring execution context for a given security state. These functions
will allow runtime services like PSCI and Secure payload dispatcher to
implement logic for switching between the secure and non-secure states.

The save and restore functions follow AArch64 PCS and only use
caller-saved temporary registers.

Change-Id: I8ee3aaa061d3caaedb28ae2c5becb9a206b6fd74
parent b739f22a
/*
* Copyright (c) 2013-2014, ARM Limited and Contributors. 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 ARM 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 <context.h>
/* -----------------------------------------------------
* The following function strictly follows the AArch64
* PCS to use x9-x17 (temporary caller-saved registers)
* to save essential EL3 system register context. It
* assumes that 'x0' is pointing to a 'el1_sys_regs'
* structure where the register context will be saved.
* -----------------------------------------------------
*/
.global el3_sysregs_context_save
el3_sysregs_context_save:
mrs x9, scr_el3
mrs x10, sctlr_el3
stp x9, x10, [x0, #CTX_SCR_EL3]
mrs x11, cptr_el3
stp x11, xzr, [x0, #CTX_CPTR_EL3]
mrs x13, cntfrq_el0
mrs x14, mair_el3
stp x13, x14, [x0, #CTX_CNTFRQ_EL0]
mrs x15, tcr_el3
mrs x16, ttbr0_el3
stp x15, x16, [x0, #CTX_TCR_EL3]
mrs x17, daif
and x17, x17, #(DAIF_ABT_BIT | DAIF_DBG_BIT)
stp x17, xzr, [x0, #CTX_DAIF_EL3]
ret
/* -----------------------------------------------------
* The following function strictly follows the AArch64
* PCS to use x9-x17 (temporary caller-saved registers)
* to restore essential EL3 system register context. It
* assumes that 'x0' is pointing to a 'el1_sys_regs'
* structure from where the register context will be
* restored.
*
* Note that the sequence differs from that of the save
* function as we want the MMU to be enabled last
* -----------------------------------------------------
*/
.global el3_sysregs_context_restore
el3_sysregs_context_restore:
ldp x11, xzr, [x0, #CTX_CPTR_EL3]
msr cptr_el3, x11
ldp x13, x14, [x0, #CTX_CNTFRQ_EL0]
msr cntfrq_el0, x13
msr mair_el3, x14
ldp x15, x16, [x0, #CTX_TCR_EL3]
msr tcr_el3, x15
msr ttbr0_el3, x16
ldp x17, xzr, [x0, #CTX_DAIF_EL3]
mrs x11, daif
orr x17, x17, x11
msr daif, x17
/* Make sure all the above changes are observed */
isb
ldp x9, x10, [x0, #CTX_SCR_EL3]
msr scr_el3, x9
msr sctlr_el3, x10
isb
ret
/* -----------------------------------------------------
* The following function strictly follows the AArch64
* PCS to use x9-x17 (temporary caller-saved registers)
* to save EL1 system register context. It assumes that
* 'x0' is pointing to a 'el1_sys_regs' structure where
* the register context will be saved.
* -----------------------------------------------------
*/
.global el1_sysregs_context_save
el1_sysregs_context_save:
mrs x9, spsr_el1
mrs x10, elr_el1
stp x9, x10, [x0, #CTX_SPSR_EL1]
mrs x11, spsr_abt
mrs x12, spsr_und
stp x11, x12, [x0, #CTX_SPSR_ABT]
mrs x13, spsr_irq
mrs x14, spsr_fiq
stp x13, x14, [x0, #CTX_SPSR_IRQ]
mrs x15, sctlr_el1
mrs x16, actlr_el1
stp x15, x16, [x0, #CTX_SCTLR_EL1]
mrs x17, cpacr_el1
mrs x9, csselr_el1
stp x17, x9, [x0, #CTX_CPACR_EL1]
mrs x10, sp_el1
mrs x11, esr_el1
stp x10, x11, [x0, #CTX_SP_EL1]
mrs x12, ttbr0_el1
mrs x13, ttbr1_el1
stp x12, x13, [x0, #CTX_TTBR0_EL1]
mrs x14, mair_el1
mrs x15, amair_el1
stp x14, x15, [x0, #CTX_MAIR_EL1]
mrs x16, tcr_el1
mrs x17, tpidr_el1
stp x16, x17, [x0, #CTX_TCR_EL1]
mrs x9, tpidr_el0
mrs x10, tpidrro_el0
stp x9, x10, [x0, #CTX_TPIDR_EL0]
mrs x11, dacr32_el2
mrs x12, ifsr32_el2
stp x11, x12, [x0, #CTX_DACR32_EL2]
mrs x13, par_el1
mrs x14, far_el1
stp x13, x14, [x0, #CTX_PAR_EL1]
mrs x15, afsr0_el1
mrs x16, afsr1_el1
stp x15, x16, [x0, #CTX_AFSR0_EL1]
mrs x17, contextidr_el1
mrs x9, vbar_el1
stp x17, x9, [x0, #CTX_CONTEXTIDR_EL1]
mrs x10, cntp_ctl_el0
mrs x11, cntp_cval_el0
stp x10, x11, [x0, #CTX_CNTP_CTL_EL0]
mrs x12, cntv_ctl_el0
mrs x13, cntv_cval_el0
stp x12, x13, [x0, #CTX_CNTV_CTL_EL0]
mrs x14, cntkctl_el1
mrs x15, fpexc32_el2
stp x14, x15, [x0, #CTX_CNTKCTL_EL1]
ret
/* -----------------------------------------------------
* The following function strictly follows the AArch64
* PCS to use x9-x17 (temporary caller-saved registers)
* to restore EL1 system register context. It assumes
* that 'x0' is pointing to a 'el1_sys_regs' structure
* from where the register context will be restored
* -----------------------------------------------------
*/
.global el1_sysregs_context_restore
el1_sysregs_context_restore:
ldp x9, x10, [x0, #CTX_SPSR_EL1]
msr spsr_el1, x9
msr elr_el1, x10
ldp x11, x12, [x0, #CTX_SPSR_ABT]
msr spsr_abt, x11
msr spsr_und, x12
ldp x13, x14, [x0, #CTX_SPSR_IRQ]
msr spsr_irq, x13
msr spsr_fiq, x14
ldp x15, x16, [x0, #CTX_SCTLR_EL1]
msr sctlr_el1, x15
msr actlr_el1, x16
ldp x17, x9, [x0, #CTX_CPACR_EL1]
msr cpacr_el1, x17
msr csselr_el1, x9
ldp x10, x11, [x0, #CTX_SP_EL1]
msr sp_el1, x10
msr esr_el1, x11
ldp x12, x13, [x0, #CTX_TTBR0_EL1]
msr ttbr0_el1, x12
msr ttbr1_el1, x13
ldp x14, x15, [x0, #CTX_MAIR_EL1]
msr mair_el1, x14
msr amair_el1, x15
ldp x16, x17, [x0, #CTX_TCR_EL1]
msr tcr_el1, x16
msr tpidr_el1, x17
ldp x9, x10, [x0, #CTX_TPIDR_EL0]
msr tpidr_el0, x9
msr tpidrro_el0, x10
ldp x11, x12, [x0, #CTX_DACR32_EL2]
msr dacr32_el2, x11
msr ifsr32_el2, x12
ldp x13, x14, [x0, #CTX_PAR_EL1]
msr par_el1, x13
msr far_el1, x14
ldp x15, x16, [x0, #CTX_AFSR0_EL1]
msr afsr0_el1, x15
msr afsr1_el1, x16
ldp x17, x9, [x0, #CTX_CONTEXTIDR_EL1]
msr contextidr_el1, x17
msr vbar_el1, x9
ldp x10, x11, [x0, #CTX_CNTP_CTL_EL0]
msr cntp_ctl_el0, x10
msr cntp_cval_el0, x11
ldp x12, x13, [x0, #CTX_CNTV_CTL_EL0]
msr cntv_ctl_el0, x12
msr cntv_cval_el0, x13
ldp x14, x15, [x0, #CTX_CNTKCTL_EL1]
msr cntkctl_el1, x14
msr fpexc32_el2, x15
/* No explict ISB required here as ERET covers it */
ret
/* -----------------------------------------------------
* The followsing function follows the aapcs_64 strictly
* to use x9-x17 (temporary caller-saved registers
* according to AArch64 PCS) to save floating point
* register context. It assumes that 'x0' is pointing to
* a 'fp_regs' structure where the register context will
* be saved.
*
* Access to VFP registers will trap if CPTR_EL3.TFP is
* set. However currently we don't use VFP registers
* nor set traps in Trusted Firmware, and assume it's
* cleared
*
* TODO: Revisit when VFP is used in secure world
* -----------------------------------------------------
*/
.global fpregs_context_save
fpregs_context_save:
stp q0, q1, [x0, #CTX_FP_Q0]
stp q2, q3, [x0, #CTX_FP_Q2]
stp q4, q5, [x0, #CTX_FP_Q4]
stp q6, q7, [x0, #CTX_FP_Q6]
stp q8, q9, [x0, #CTX_FP_Q8]
stp q10, q11, [x0, #CTX_FP_Q10]
stp q12, q13, [x0, #CTX_FP_Q12]
stp q14, q15, [x0, #CTX_FP_Q14]
stp q16, q17, [x0, #CTX_FP_Q16]
stp q18, q19, [x0, #CTX_FP_Q18]
stp q20, q21, [x0, #CTX_FP_Q20]
stp q22, q23, [x0, #CTX_FP_Q22]
stp q24, q25, [x0, #CTX_FP_Q24]
stp q26, q27, [x0, #CTX_FP_Q26]
stp q28, q29, [x0, #CTX_FP_Q28]
stp q30, q31, [x0, #CTX_FP_Q30]
mrs x9, fpsr
str x9, [x0, #CTX_FP_FPSR]
mrs x10, fpcr
str x10, [x0, #CTX_FP_FPCR]
ret
/* -----------------------------------------------------
* The following function follows the aapcs_64 strictly
* to use x9-x17 (temporary caller-saved registers
* according to AArch64 PCS) to restore floating point
* register context. It assumes that 'x0' is pointing to
* a 'fp_regs' structure from where the register context
* will be restored.
*
* Access to VFP registers will trap if CPTR_EL3.TFP is
* set. However currently we don't use VFP registers
* nor set traps in Trusted Firmware, and assume it's
* cleared
*
* TODO: Revisit when VFP is used in secure world
* -----------------------------------------------------
*/
.global fpregs_context_restore
fpregs_context_restore:
ldp q0, q1, [x0, #CTX_FP_Q0]
ldp q2, q3, [x0, #CTX_FP_Q2]
ldp q4, q5, [x0, #CTX_FP_Q4]
ldp q6, q7, [x0, #CTX_FP_Q6]
ldp q8, q9, [x0, #CTX_FP_Q8]
ldp q10, q11, [x0, #CTX_FP_Q10]
ldp q12, q13, [x0, #CTX_FP_Q12]
ldp q14, q15, [x0, #CTX_FP_Q14]
ldp q16, q17, [x0, #CTX_FP_Q16]
ldp q18, q19, [x0, #CTX_FP_Q18]
ldp q20, q21, [x0, #CTX_FP_Q20]
ldp q22, q23, [x0, #CTX_FP_Q22]
ldp q24, q25, [x0, #CTX_FP_Q24]
ldp q26, q27, [x0, #CTX_FP_Q26]
ldp q28, q29, [x0, #CTX_FP_Q28]
ldp q30, q31, [x0, #CTX_FP_Q30]
ldr x9, [x0, #CTX_FP_FPSR]
msr fpsr, x9
str x10, [x0, #CTX_FP_FPCR]
msr fpcr, x10
/*
* No explict ISB required here as ERET to
* swtich to secure EL1 or non-secure world
* covers it
*/
ret
...@@ -63,7 +63,8 @@ BL31_OBJS += bl31_arch_setup.o \ ...@@ -63,7 +63,8 @@ BL31_OBJS += bl31_arch_setup.o \
gic_v3_sysregs.o \ gic_v3_sysregs.o \
bakery_lock.o \ bakery_lock.o \
runtime_svc.o \ runtime_svc.o \
early_exceptions.o early_exceptions.o \
context.o
BL31_ENTRY_POINT := bl31_entrypoint BL31_ENTRY_POINT := bl31_entrypoint
BL31_MAPFILE := bl31.map BL31_MAPFILE := bl31.map
......
/*
* Copyright (c) 2013-2014, ARM Limited and Contributors. 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 ARM 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.
*/
#ifndef __CONTEXT_H__
#define __CONTEXT_H__
#include <bl_common.h>
#include <arch.h>
/*******************************************************************************
* Constants that allow assembler code to access members of and the 'el3_state'
* structure at their correct offsets. Note that some of the registers are only
* 32-bits wide but are stored as 64-bit values for convenience
******************************************************************************/
#define CTX_EL3STATE_OFFSET 0x0
#define CTX_SAVED_SP_EL3 0x0
#define CTX_SAVED_SP_EL0 0x8
#define CTX_SPSR_EL3 0x10
#define CTX_ELR_EL3 0x18
#define CTX_SCR_EL3 0x20
#define CTX_SCTLR_EL3 0x28
#define CTX_CPTR_EL3 0x30
/* Unused space to allow registers to be stored as pairs */
#define CTX_CNTFRQ_EL0 0x40
#define CTX_MAIR_EL3 0x48
#define CTX_TCR_EL3 0x50
#define CTX_TTBR0_EL3 0x58
#define CTX_DAIF_EL3 0x60
#define CTX_VBAR_EL3 0x68 /* Currently unused */
#define CTX_EL3STATE_END 0x70
/*******************************************************************************
* Constants that allow assembler code to access members of and the
* 'el1_sys_regs' structure at their correct offsets. Note that some of the
* registers are only 32-bits wide but are stored as 64-bit values for
* convenience
******************************************************************************/
#define CTX_SYSREGS_OFFSET (CTX_EL3STATE_OFFSET + CTX_EL3STATE_END)
#define CTX_SPSR_EL1 0x0
#define CTX_ELR_EL1 0x8
#define CTX_SPSR_ABT 0x10
#define CTX_SPSR_UND 0x18
#define CTX_SPSR_IRQ 0x20
#define CTX_SPSR_FIQ 0x28
#define CTX_SCTLR_EL1 0x30
#define CTX_ACTLR_EL1 0x38
#define CTX_CPACR_EL1 0x40
#define CTX_CSSELR_EL1 0x48
#define CTX_SP_EL1 0x50
#define CTX_ESR_EL1 0x58
#define CTX_TTBR0_EL1 0x60
#define CTX_TTBR1_EL1 0x68
#define CTX_MAIR_EL1 0x70
#define CTX_AMAIR_EL1 0x78
#define CTX_TCR_EL1 0x80
#define CTX_TPIDR_EL1 0x88
#define CTX_TPIDR_EL0 0x90
#define CTX_TPIDRRO_EL0 0x98
#define CTX_DACR32_EL2 0xa0
#define CTX_IFSR32_EL2 0xa8
#define CTX_PAR_EL1 0xb0
#define CTX_FAR_EL1 0xb8
#define CTX_AFSR0_EL1 0xc0
#define CTX_AFSR1_EL1 0xc8
#define CTX_CONTEXTIDR_EL1 0xd0
#define CTX_VBAR_EL1 0xd8
#define CTX_CNTP_CTL_EL0 0xe0
#define CTX_CNTP_CVAL_EL0 0xe8
#define CTX_CNTV_CTL_EL0 0xf0
#define CTX_CNTV_CVAL_EL0 0xf8
#define CTX_CNTKCTL_EL1 0x100
#define CTX_FP_FPEXC32_EL2 0x108
#define CTX_SYSREGS_END 0x110
/*******************************************************************************
* Constants that allow assembler code to access members of and the 'fp_regs'
* structure at their correct offsets.
******************************************************************************/
#define CTX_FPREGS_OFFSET (CTX_SYSREGS_OFFSET + CTX_SYSREGS_END)
#define CTX_FP_Q0 0x0
#define CTX_FP_Q1 0x10
#define CTX_FP_Q2 0x20
#define CTX_FP_Q3 0x30
#define CTX_FP_Q4 0x40
#define CTX_FP_Q5 0x50
#define CTX_FP_Q6 0x60
#define CTX_FP_Q7 0x70
#define CTX_FP_Q8 0x80
#define CTX_FP_Q9 0x90
#define CTX_FP_Q10 0xa0
#define CTX_FP_Q11 0xb0
#define CTX_FP_Q12 0xc0
#define CTX_FP_Q13 0xd0
#define CTX_FP_Q14 0xe0
#define CTX_FP_Q15 0xf0
#define CTX_FP_Q16 0x100
#define CTX_FP_Q17 0x110
#define CTX_FP_Q18 0x120
#define CTX_FP_Q19 0x130
#define CTX_FP_Q20 0x140
#define CTX_FP_Q21 0x150
#define CTX_FP_Q22 0x160
#define CTX_FP_Q23 0x170
#define CTX_FP_Q24 0x180
#define CTX_FP_Q25 0x190
#define CTX_FP_Q26 0x1a0
#define CTX_FP_Q27 0x1b0
#define CTX_FP_Q28 0x1c0
#define CTX_FP_Q29 0x1d0
#define CTX_FP_Q30 0x1e0
#define CTX_FP_Q31 0x1f0
#define CTX_FP_FPSR 0x200
#define CTX_FP_FPCR 0x208
#define CTX_FPREGS_END 0x210
#ifndef __ASSEMBLY__
/*
* Common constants to help define the 'cpu_context' structure and its
* members below.
*/
#define DWORD_SHIFT 3
#define DEFINE_REG_STRUCT(name, num_regs) \
typedef struct { \
uint64_t _regs[num_regs]; \
} __aligned(16) name
/* Constants to determine the size of individual context structures */
#define CTX_SYSREG_ALL (CTX_SYSREGS_END >> DWORD_SHIFT)
#define CTX_FPREG_ALL (CTX_FPREGS_END >> DWORD_SHIFT)
#define CTX_EL3STATE_ALL (CTX_EL3STATE_END >> DWORD_SHIFT)
/*
* AArch64 EL1 system register context structure for preserving the
* architectural state during switches from one security state to
* another in EL1.
*/
DEFINE_REG_STRUCT(el1_sys_regs, CTX_SYSREG_ALL);
/*
* AArch64 floating point register context structure for preserving
* the floating point state during switches from one security state to
* another.
*/
DEFINE_REG_STRUCT(fp_regs, CTX_FPREG_ALL);
/*
* Miscellaneous registers used by EL3 firmware to maintain its state
* across exception entries and exits
*/
DEFINE_REG_STRUCT(el3_state, CTX_EL3STATE_ALL);
/*
* Macros to access members of any of the above structures using their
* offsets
*/
#define read_ctx_reg(ctx, offset) ((ctx)->_regs[offset >> DWORD_SHIFT])
#define write_ctx_reg(ctx, offset, val) (((ctx)->_regs[offset >> DWORD_SHIFT]) \
= val)
/*
* Top-level context structure which is used by EL3 firmware to
* preserve the state of a core at EL1 in one of the two security
* states and save enough EL3 meta data to be able to return to that
* EL and security state. The context management library will be used
* to ensure that SP_EL3 always points to an instance of this
* structure at exception entry and exit. Each instance will
* correspond to either the secure or the non-secure state.
*/
typedef struct {
el3_state el3state_ctx;
el1_sys_regs sysregs_ctx;
fp_regs fpregs_ctx;
} cpu_context;
/* Macros to access members of the 'cpu_context' structure */
#define get_el3state_ctx(h) (&((cpu_context *) h)->el3state_ctx)
#define get_fpregs_ctx(h) (&((cpu_context *) h)->fpregs_ctx)
#define get_sysregs_ctx(h) (&((cpu_context *) h)->sysregs_ctx)
/*
* Compile time assertions related to the 'cpu_context' structure to
* ensure that the assembler and the compiler view of the offsets of
* the structure members is the same.
*/
CASSERT(CTX_SYSREGS_OFFSET == __builtin_offsetof(cpu_context, sysregs_ctx), \
assert_core_context_sys_offset_mismatch);
CASSERT(CTX_FPREGS_OFFSET == __builtin_offsetof(cpu_context, fpregs_ctx), \
assert_core_context_fp_offset_mismatch);
CASSERT(CTX_EL3STATE_OFFSET == __builtin_offsetof(cpu_context, el3state_ctx), \
assert_core_context_el3state_offset_mismatch);
/*******************************************************************************
* Function prototypes
******************************************************************************/
void el3_sysregs_context_save(el3_state *regs);
void el3_sysregs_context_restore(el3_state *regs);
void el1_sysregs_context_save(el1_sys_regs *regs);
void el1_sysregs_context_restore(el1_sys_regs *regs);
void fpregs_context_save(fp_regs *regs);
void fpregs_context_restore(fp_regs *regs);
#undef CTX_SYSREG_ALL
#undef CTX_FP_ALL
#undef CTX_EL3STATE_ALL
#endif /* __ASSEMBLY__ */
#endif /* __CONTEXT_H__ */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment