diff options
author | Glenn Miles <milesg@us.ibm.com> | 2015-02-18 13:54:50 -0600 |
---|---|---|
committer | Derk Rembold <rembold@de.ibm.com> | 2015-02-19 09:45:49 -0600 |
commit | f7a56090b73768f8fec063e41aba12662ee59a45 (patch) | |
tree | d2373137870269e93d215ba65319ad1af4b131c3 /pk | |
parent | d84a1393ddec82dda67207a6f21edb5153877b5a (diff) | |
download | talos-sbe-f7a56090b73768f8fec063e41aba12662ee59a45.tar.gz talos-sbe-f7a56090b73768f8fec063e41aba12662ee59a45.zip |
Seed the files in the pk directory
Change-Id: I03398098e6625f0e06e4a96769b03002a1c71d35
Reviewed-on: http://gfw160.aus.stglabs.ibm.com:8080/gerrit/15813
Reviewed-by: Glenn R. Miles <milesg@us.ibm.com>
Reviewed-by: Derk Rembold <rembold@de.ibm.com>
Tested-by: Derk Rembold <rembold@de.ibm.com>
Diffstat (limited to 'pk')
63 files changed, 10396 insertions, 0 deletions
diff --git a/pk/cme/Makefile b/pk/cme/Makefile new file mode 100644 index 00000000..60c82c87 --- /dev/null +++ b/pk/cme/Makefile @@ -0,0 +1,50 @@ +# This Makefile compiles all of the PK code required for the CME port +# of PK. See the "pk.mk" file in this directory. + +#all generated files from this makefile will end up in obj/$(IMAGE_NAME)/pk +export SUB_OBJDIR = /pk + +include img_defs.mk +include pkcmefiles.mk + +ifeq "$(PK_TIMER_SUPPORT)" "1" +CME_OBJECTS += ${CME-TIMER-C-SOURCES:.c=.o} ${CME-TIMER-S-SOURCES:.S=.o} +endif + +ifeq "$(PK_THREAD_SUPPORT)" "1" +CME_OBJECTS += ${CME-THREAD-C-SOURCES:.c=.o} ${CME-THREAD-S-SOURCES:.S=.o} +endif + +ifeq "$(CME_ASYNC_SUPPORT)" "1" +CME_OBJECTS += ${CME-ASYNC-C-SOURCES:.c=.o} ${CME-ASYNC-S-SOURCES:.S=.o} +endif + +OBJS := $(addprefix $(OBJDIR)/, $(CME_OBJECTS)) + +libpk.a: kernel ppe42 trace cme + $(AR) crs $(OBJDIR)/libpk.a $(OBJDIR)/*.o + +.PHONY: clean cme kernel ppe42 trace +cme: $(OBJS) + +trace: + $(MAKE) -I $(IMAGE_SRCDIR) -C ../trace + +kernel: + $(MAKE) -I $(IMAGE_SRCDIR) -C ../kernel + +ppe42: + $(MAKE) -I $(IMAGE_SRCDIR) -C ../ppe42 + + +$(OBJS) $(OBJS:.o=.d): | $(OBJDIR) + +$(OBJDIR): + mkdir -p $(OBJDIR) + +clean: + rm -fr $(OBJDIR) + +ifneq ($(MAKECMDGOALS),clean) +include $(OBJS:.o=.d) +endif diff --git a/pk/cme/cme.h b/pk/cme/cme.h new file mode 100644 index 00000000..d789ff3b --- /dev/null +++ b/pk/cme/cme.h @@ -0,0 +1,22 @@ +#ifndef __CME_H__ +#define __CME_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pgp.h +/// \brief The CME environment for PK. + +// This is a 'circular' reference in PK, but included here to simplify PGAS +// programming. + +#ifndef HWMACRO_CME +#define HWMACRO_CME +#include "ppe42.h" +#endif + +#include "cme_common.h" + +#endif /* __CME_H__ */ diff --git a/pk/cme/cme_common.h b/pk/cme/cme_common.h new file mode 100644 index 00000000..23eb59ff --- /dev/null +++ b/pk/cme/cme_common.h @@ -0,0 +1,57 @@ +#ifndef __CME_COMMON_H__ +#define __CME_COMMON_H__ + +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file cme_common.h +/// \brief Common header for CME +/// +/// This header is maintained as part of the PK port for CME, but needs to be +/// physically present in the PMX area to allow dropping PMX code as a whole +/// to other teams. + +// -*- WARNING: This file is maintained as part of PK. Do not edit in -*- +// -*- the PMX area as your edits will be lost. -*- + +#ifndef __ASSEMBLER__ +#include <stdint.h> +#endif + +/// This constant is used to define the size of the table of interrupt handler +/// structures as well as a limit for error checking. +/// NOTE: This can be specific to each PPE type (SBE, CME, GPE) +#define EXTERNAL_IRQS 64 + +#ifdef __ASSEMBLER__ +/// This macro contains CME specific code for determining what IRQ caused the +/// external exception handler to be invoked by the PPE + +/// Load noncritical status 0 and the handler array base address. Check +/// for interrupts pending in status register 0 while the IRQ is +/// computed. The IRQ is expected to be stored in r3. + .macro hwmacro_get_ext_irq + +#_lwzi %r4, %r4, OCB_ONISR0 + cntlzw %r3, %r4 + cmpwible %r3, 31, external_irq_found #branch if irq is lt or eq to 31 + + ## No IRQ pending in interrupt set 0. Try set 1. + +#_lwzi %r4, %r4, OCB_ONISR1 + cntlzw %r3, %r4 + addi %r3, %r3, 32 + + .endm + +/// Redirect the .hwmacro_irq_cfg_bitmaps macro to call our cme specific implementation +/// This is called from the ppe42_exceptions.S file. + .macro .hwmacro_irq_cfg_bitmaps + .endm + +#endif /* __ASSEMBLER__ */ + +#endif /* __CME_COMMON_H__ */ diff --git a/pk/cme/cme_init.c b/pk/cme/cme_init.c new file mode 100644 index 00000000..3a0c3910 --- /dev/null +++ b/pk/cme/cme_init.c @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file cme_init.c +/// \brief PK initialization for CME +/// +/// The entry points in this routine are used during initialization. This +/// code space can be deallocated and reassigned after application +/// initialization if required. + +#include "pk.h" + +/// CME environment initial setup. +/// +/// This is setup common to all CME HW Macro applications. This setup takes place +/// during boot, before main() is called. + +void +__hwmacro_setup(void) +{ + //async_initialize(); +} diff --git a/pk/cme/pk_port.h b/pk/cme/pk_port.h new file mode 100644 index 00000000..bf750d13 --- /dev/null +++ b/pk/cme/pk_port.h @@ -0,0 +1,16 @@ +#ifndef __PK_PORT_H__ +#define __PK_PORT_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_port.h +/// \brief The top-level CME environment header for PK. + +#define HWMACRO_CME + +#include "ppe42.h" + +#endif /* __PK_PORT_H__ */ diff --git a/pk/cme/pkcmefiles.mk b/pk/cme/pkcmefiles.mk new file mode 100644 index 00000000..66b0e4fb --- /dev/null +++ b/pk/cme/pkcmefiles.mk @@ -0,0 +1,33 @@ +# @file pkcmefiles.mk +# +# @brief mk for including cme object files +# +# @page ChangeLogs Change Logs +# @section pkcmefiles.mk +# @verbatim +# +# +# Change Log ****************************************************************** +# Flag Defect/Feature User Date Description +# ------ -------------- ---------- ------------ ----------- +# +# @endverbatim +# +########################################################################## +# Object Files +########################################################################## + +CME-C-SOURCES = cme_init.c +CME-S-SOURCES = + +CME-TIMER-C-SOURCES = +CME-TIMER-S-SOURCES = + +CME-THREAD-C-SOURCES = +CME-THREAD-S-SOURCES = + +CME-ASYNC-C-SOURCES = +CME-ASYNC-S-SOURCES = + +CME_OBJECTS += $(CME-C-SOURCES:.c=.o) $(CME-S-SOURCES:.S=.o) + diff --git a/pk/gpe/Makefile b/pk/gpe/Makefile new file mode 100644 index 00000000..324107dc --- /dev/null +++ b/pk/gpe/Makefile @@ -0,0 +1,50 @@ +# This Makefile compiles all of the PK code required for the GPE port +# of PK. See the "pk.mk" file in this directory. + +#all generated files from this makefile will end up in obj/$(IMAGE_NAME)/pk +export SUB_OBJDIR = /pk + +include img_defs.mk +include pkgpefiles.mk + +ifeq "$(PK_TIMER_SUPPORT)" "1" +GPE_OBJECTS += ${GPE-TIMER-C-SOURCES:.c=.o} ${GPE-TIMER-S-SOURCES:.S=.o} +endif + +ifeq "$(PK_THREAD_SUPPORT)" "1" +GPE_OBJECTS += ${GPE-THREAD-C-SOURCES:.c=.o} ${GPE-THREAD-S-SOURCES:.S=.o} +endif + +ifeq "$(GPE_ASYNC_SUPPORT)" "1" +GPE_OBJECTS += ${GPE-ASYNC-C-SOURCES:.c=.o} ${GPE-ASYNC-S-SOURCES:.S=.o} +endif + +OBJS := $(addprefix $(OBJDIR)/, $(GPE_OBJECTS)) + +libpk.a: kernel ppe42 trace gpe + $(AR) crs $(OBJDIR)/libpk.a $(OBJDIR)/*.o + +.PHONY: clean gpe kernel ppe42 trace +gpe: $(OBJS) + +trace: + $(MAKE) -I $(IMAGE_SRCDIR) -C ../trace + +kernel: + $(MAKE) -I $(IMAGE_SRCDIR) -C ../kernel + +ppe42: + $(MAKE) -I $(IMAGE_SRCDIR) -C ../ppe42 + + +$(OBJS) $(OBJS:.o=.d): | $(OBJDIR) + +$(OBJDIR): + mkdir -p $(OBJDIR) + +clean: + rm -fr $(OBJDIR) + +ifneq ($(MAKECMDGOALS),clean) +include $(OBJS:.o=.d) +endif diff --git a/pk/gpe/gpe.h b/pk/gpe/gpe.h new file mode 100644 index 00000000..3b8e634e --- /dev/null +++ b/pk/gpe/gpe.h @@ -0,0 +1,35 @@ +#ifndef __GPE_H__ +#define __GPE_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pgp.h +/// \brief The GPE environment for PK. + +// This is a 'circular' reference in PK, but included here to simplify PGAS +// programming. + +#ifndef HWMACRO_GPE +#define HWMACRO_GPE +#include "ppe42.h" +#endif + +#include "ocb_register_addresses.h" +#include "gpe_common.h" + +/* +#include "pcbs_register_addresses.h" +#include "pcbs_firmware_registers.h" + +#include "tod_register_addresses.h" +#include "tod_firmware_registers.h" + +#include "plb_arbiter_register_addresses.h" +#include "plb_arbiter_firmware_registers.h" + +*/ + +#endif /* __GPE_H__ */ diff --git a/pk/gpe/gpe_common.h b/pk/gpe/gpe_common.h new file mode 100644 index 00000000..05ec4464 --- /dev/null +++ b/pk/gpe/gpe_common.h @@ -0,0 +1,62 @@ +#ifndef __GPE_COMMON_H__ +#define __GPE_COMMON_H__ + +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file gpe_common.h +/// \brief Common header for GPE +/// +/// This header is maintained as part of the PK port for GPE, but needs to be +/// physically present in the PMX area to allow dropping PMX code as a whole +/// to other teams. + +// -*- WARNING: This file is maintained as part of PK. Do not edit in -*- +// -*- the PMX area as your edits will be lost. -*- + +#ifndef __ASSEMBLER__ +#include <stdint.h> +#endif + +#include "occhw_common.h" + +/// Each GPE instance has it's own interrupt status register these macros +/// are added for convenience in accessing the correct register +#define GPE_GISR0(instance_id) (OCB_G0ISR0 + (instance_id * 8)) +#define GPE_GISR1(instance_id) (OCB_G0ISR1 + (instance_id * 8)) + +#ifdef __ASSEMBLER__ +/// This macro contains GPE specific code for determining what IRQ caused the +/// external exception handler to be invoked by the PPE + +/// Check for interrupts pending in status register 0 while the IRQ is +/// computed. The IRQ is expected to be stored in r4. If no IRQ is +/// pending then load the phantom irq # (EXTERNAL_IRQS). + .macro hwmacro_get_ext_irq + + _lwzi %r3, %r3, GPE_GISR0(APPCFG_OCC_INSTANCE_ID) + cntlzw %r4, %r3 + cmpwible %r4, 31, external_irq_found #branch if irq is lt or eq to 31 + + ## No IRQ pending in interrupt set 0. Try set 1. + ## Note: irq # will be 64 (EXTERNAL_IRQS) if no bits were set in either register + + _lwzi %r3, %r3, GPE_GISR1(APPCFG_OCC_INSTANCE_ID) + cntlzw %r4, %r3 + addi %r4, %r4, 32 + + .endm + + +/// Redirect the .hwmacro_irq_cfg_bitmaps macro to call our macro that is common for both +/// GPE's and the 405 inside the OCC complex. This is called from the ppe42_exceptions.S +/// file. + .macro .hwmacro_irq_cfg_bitmaps + .occhw_irq_cfg_bitmaps + .endm +#endif /* __ASSEMBLER__ */ + +#endif /* __GPE_COMMON_H__ */ diff --git a/pk/gpe/gpe_init.c b/pk/gpe/gpe_init.c new file mode 100644 index 00000000..3801a2bd --- /dev/null +++ b/pk/gpe/gpe_init.c @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file gpe_init.c +/// \brief PK initialization for GPE +/// +/// The entry points in this routine are used during initialization. This +/// code space can be deallocated and reassigned after application +/// initialization if required. + +#include "pk.h" +#include "ocb_register_addresses.h" + +/// GPE environment initial setup. +/// +/// This is setup common to all GPE HW Macro applications. This setup takes place +/// during boot, before main() is called. + +void +__hwmacro_setup(void) +{ + uint64_t oirrA; + uint64_t oirrB; + uint64_t oirrC; + uint64_t owned_actual; + uint64_t reverse_polarity; + + //verify that this code is running on the correct GPE instance (one time check) + if((mfspr(SPRN_PIR) & PIR_PPE_INSTANCE_MASK) != APPCFG_OCC_INSTANCE_ID) + { + //APPCFG_OCC_INSTANCE_ID does not match actual instance ID! + PK_PANIC(OCCHW_INSTANCE_MISMATCH); + } + +#if (APPCFG_OCC_INSTANCE_ID == OCCHW_IRQ_ROUTE_OWNER) + //If this instance is the owner of the interrupt routting registers + //then write the routing registers for all OCC interrupts. + //This instance must be the first instance to run within the OCC + //This will be done while all external interrupts are masked. + PKTRACE("Initializing External Interrupt Routing Registers"); + out32(OCB_OIMR0_OR, 0xffffffff); + out32(OCB_OIMR1_OR, 0xffffffff); + out32(OCB_OIRR0A, (uint32_t)(g_ext_irqs_routeA >> 32)); + out32(OCB_OIRR1A, (uint32_t)g_ext_irqs_routeA); + out32(OCB_OIRR0B, (uint32_t)(g_ext_irqs_routeB >> 32)); + out32(OCB_OIRR1B, (uint32_t)g_ext_irqs_routeB); + out32(OCB_OIRR0C, (uint32_t)(g_ext_irqs_routeC >> 32)); + out32(OCB_OIRR1C, (uint32_t)g_ext_irqs_routeC); +#endif + + //Determine from the routing registers which irqs are owned by this instance + //NOTE: If a bit is not set in the routeA register, it is not owned by a GPE + + oirrA = ((uint64_t)in32(OCB_OIRR0A)) << 32; + oirrA |= in32(OCB_OIRR1A); + oirrB = ((uint64_t)in32(OCB_OIRR0B)) << 32; + oirrB |= in32(OCB_OIRR1B); + oirrC = ((uint64_t)in32(OCB_OIRR0C)) << 32; + oirrC |= in32(OCB_OIRR1C); + + //All interrupts routed to a GPE will have a bit set in routeA + owned_actual = oirrA; + + //wittle it down by bits in the routeB register +#if APPCFG_OCC_INSTANCE_ID & 0x2 + owned_actual &= oirrB; +#else + owned_actual &= ~oirrB; +#endif + + //wittle it down further by bits in the routeC register +#if APPCFG_OCC_INSTANCE_ID & 0x1 + owned_actual &= oirrC; +#else + owned_actual &= ~oirrC; +#endif + + //Panic if we don't own the irqs we were expecting + //NOTE: we don't panic if we are given more IRQ's than expected + if((owned_actual & g_ext_irqs_owned) != g_ext_irqs_owned) + { + //IRQ's were not routed to us correctly. + PK_PANIC(OCC_IRQ_ROUTING_ERROR); + } + + //Mask all external interrupts owned by this instance + //(even the ones given to us that we weren't expecting) + out32(OCB_OIMR0_OR, (uint32_t)(owned_actual >> 32)); + out32(OCB_OIMR1_OR, (uint32_t)owned_actual); + + //Set the interrupt type for all interrupts owned by this instance + out32(OCB_OITR0_CLR, (uint32_t)(g_ext_irqs_owned >> 32)); + out32(OCB_OITR1_CLR, (uint32_t)g_ext_irqs_owned); + out32(OCB_OITR0_OR, (uint32_t)(g_ext_irqs_type >> 32)); + out32(OCB_OITR1_OR, (uint32_t)g_ext_irqs_type); + + //Set the interrupt polarity for all interrupts owned by this instance + out32(OCB_OIEPR0_CLR, (uint32_t)(g_ext_irqs_owned >> 32)); + out32(OCB_OIEPR1_CLR, (uint32_t)g_ext_irqs_owned); + out32(OCB_OIEPR0_OR, (uint32_t)(g_ext_irqs_polarity >> 32)); + out32(OCB_OIEPR1_OR, (uint32_t)g_ext_irqs_polarity); + + //clear the status of all external interrupts owned by this instance + out32(OCB_OISR0_CLR, ((uint32_t)(g_ext_irqs_owned >> 32))); + out32(OCB_OISR1_CLR, ((uint32_t)g_ext_irqs_owned)); + + //set the status for interrupts that have reverse polarity + reverse_polarity = ~g_ext_irqs_polarity & g_ext_irqs_owned; + out32(OCB_OISR0_OR, ((uint32_t)(reverse_polarity >> 32))); + out32(OCB_OISR1_OR, ((uint32_t)reverse_polarity)); + + //Unmask the interrupts owned by this instance that are to be enabled by default + out32(OCB_OIMR0_CLR, (uint32_t)(g_ext_irqs_enable >> 32)); + out32(OCB_OIMR1_CLR, (uint32_t)g_ext_irqs_enable); + + //Wait for the last out32 operation to complete + sync(); + + //async_initialize(); +} diff --git a/pk/gpe/gpe_irq.h b/pk/gpe/gpe_irq.h new file mode 100644 index 00000000..3b018916 --- /dev/null +++ b/pk/gpe/gpe_irq.h @@ -0,0 +1,247 @@ +#ifndef __GPE_IRQ_H__ +#define __GPE_IRQ_H__ + +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file occhw_irq.h +/// \brief GPE-OCCHW Interrupt handling for PK +/// +/// The OCCHW interrupt controller supports a maximum of 64 interrupts, split +/// into 2 x 32-bit non-cascaded interrupt controllers with simple OR +/// combining of the interrupt signals. +/// +/// The OCB interrupt controller allows interrupt status to be set directly by +/// software, as well as providing a mode that causes an enabled pending +/// interrupt to trigger an Unconditional Debug Event. The OCB interrupt +/// controller contains a 'mask' register, unlike other 405 interrupt +/// controllers that have an 'enable' register. The OCCHW mask and status +/// registers also have atomic CLR/OR function so that it is never necessary +/// to enter a critical section to enable/disable/clear interrupts and +/// interrupt status. + +#include "occhw_common.h" +#include "ocb_register_addresses.h" +#include "ppe42.h" + +#ifndef __ASSEMBLER__ + +/// Enable an interrupt by clearing the mask bit. + +UNLESS__PPE42_IRQ_CORE_C__(extern) +inline void +pk_irq_enable(PkIrqId irq) +{ + out32(OCCHW_OIMR_CLR(irq), OCCHW_IRQ_MASK32(irq)); +} + + +/// Disable an interrupt by setting the mask bit. + +UNLESS__PPE42_IRQ_CORE_C__(extern) +inline void +pk_irq_disable(PkIrqId irq) +{ + out32(OCCHW_OIMR_OR(irq), OCCHW_IRQ_MASK32(irq)); +} + + +/// Clear interrupt status with an CLR mask. Only meaningful for +/// edge-triggered interrupts. + +UNLESS__PPE42_IRQ_CORE_C__(extern) +inline void +pk_irq_status_clear(PkIrqId irq) +{ + out32(OCCHW_OISR_CLR(irq), OCCHW_IRQ_MASK32(irq)); +} + + +/// Get IRQ status as a 0 or non-0 integer + +UNLESS__PPE42_IRQ_CORE_C__(extern) +inline int +pk_irq_status_get(PkIrqId irq) +{ + return (in32(OCCHW_OISR(irq)) & OCCHW_IRQ_MASK32(irq)) != 0; +} + + +/// Set or clear interrupt status explicitly. + +UNLESS__PPE42_IRQ_CORE_C__(extern) +inline void +pk_irq_status_set(PkIrqId irq, int value) +{ + if (value) { + out32(OCCHW_OISR_OR(irq), OCCHW_IRQ_MASK32(irq)); + } else { + out32(OCCHW_OISR_CLR(irq), OCCHW_IRQ_MASK32(irq)); + } +} + + +#endif /* __ASSEMBLER__ */ + +/// \page occhw_irq_macros OCCHW IRQ API Assembler Macros +/// +/// These macros encapsulate the PK API for the OCCHW interrupt +/// controller. These macros require 2 scratch registers in addition to the \c +/// irq parameter register passed into the handler from PK interrupt +/// dispatch. These macros also modify CR0. +/// +/// \arg \c rirq A register that holds the \c irq parameter passed to +/// the handler from PK interrupt dispatch. This register is not +/// modified. +/// \arg \c rmask A scratch register - At the end of macro execution this +/// register contains the 32-bit mask form of the irq. +/// +/// \arg \c raddr A scratch register - At the end of macro execution this +/// register holds the address of the interrupt +/// controller facility that implements the action. +/// +/// \arg \c imm An immediate (0/non-0) value for certain macros. +/// +/// Forms: +/// +/// \b _pk_irq_enable \a rirq, \a rmask, \a raddr - Enable an \c irq. \n +/// \b _pk_irq_disable \a rirq, \a rmask, \a raddr - Disable an \c irq. \n +/// \b _pk_irq_status_clear \a rirq, \a rmask, \a raddr - Clear \c irq +/// interrupt status. \n +/// \b _pk_irq_status_set \a rirq, \a rmask, \a raddr, \a imm - Set \c irq status +/// with an immediate (0/non-0) value. \n +/// +/// \todo Once the logic design is locked down, revisit whether these macros +/// (and C-code versions) can be implemented without branching. This could be +/// done in theory by converting bit 26 into the byte offset between addresses +/// in interupt controller 0 and interrupt controller 1 - assuming the +/// distances are all the same power-of-two. +/// +/// \cond + +// IRQ numbers are in the range 0..63. IRQs are converted to the 32-bit +// residue used to compute the mask. CR0 is set as a test of IRQ > 32 - the +// register \c raddr is used as scratch for these computations. Hopefully the +// local labels 888 and 999 are unique enough. + +// Register names must be compared as strings - e.g., %r0 is not +// a symbol, it is converted to "0" by the assembler. + +#ifdef __ASSEMBLER__ + + .macro .two_unique, ra, rb + .ifnc \ra, \rb + .exitm + .endif + .error "Both register arguments must be unique" + .endm + + + .macro .three_unique, ra, rb, rc + .ifnc \ra, \rb + .ifnc \rb, \rc + .ifnc \ra, \rc + .exitm + .endif + .endif + .endif + .error "All three register arguments must be unique" + .endm + + + .macro _occhw_irq_or_mask, rirq:req, rmask:req + .two_unique \rirq, \rmask + lis \rmask, 0x8000 + srw \rmask, \rmask, \rirq + .endm + + .macro _occhw_irq_clr_mask, rirq:req, rmask:req + .two_unique \rirq, \rmask + _occhw_irq_or_mask \rirq, \rmask + .endm + + + .macro _pk_irq_enable, rirq:req, rmask:req, raddr:req + .three_unique \rirq, \rmask, \raddr + + andi. \raddr, \rirq, 0x20 + clrlwi \raddr, \rirq, 27 + _occhw_irq_clr_mask \raddr, \rmask + bne- 888f + _stwi \rmask, \raddr, OCB_OIMR0_CLR + b 999f +888: + _stwi \rmask, \raddr, OCB_OIMR1_CLR +999: + eieio + .endm + + + .macro _pk_irq_disable, rirq:req, rmask:req, raddr:req + .three_unique \rirq, \rmask, \raddr + + andi. \raddr, \rirq, 0x20 + clrlwi \raddr, \rirq, 27 + _occhw_irq_or_mask \raddr, \rmask + bne- 888f + _stwi \rmask, \raddr, OCB_OIMR0_OR + b 999f +888: + _stwi \rmask, \raddr, OCB_OIMR1_OR +999: + eieio + .endm + + + .macro _pk_irq_status_clear, rirq:req, rmask:req, raddr:req + .three_unique \rirq, \rmask, \raddr + + andi. \raddr, \rirq, 0x20 + clrlwi \raddr, \rirq, 27 + _occhw_irq_clr_mask \raddr, \rmask + bne- 888f + _stwi \rmask, \raddr, OCB_OISR0_CLR + b 999f +888: + _stwi \rmask, \raddr, OCB_OISR1_CLR +999: + eieio + .endm + + + .macro _pk_irq_status_set, rirq:req, rmask:req, raddr:req, imm:req + .three_unique \rirq, \rmask, \raddr + + andi. \raddr, \rirq, 0x20 + clrlwi \raddr, \rirq, 27 + + .if \imm + _occhw_irq_or_mask \raddr, \rmask + bne- 888f + _stwi \rmask, \raddr, OCB_OISR0_OR + b 999f +888: + _stwi \rmask, \raddr, OCB_OISR1_OR + + .else + + _occhw_irq_clr_mask \raddr, \rmask + bne- 888f + _stwi \rmask, \raddr, OCB_OISR0_CLR + b 999f +888: + _stwi \rmask, \raddr, OCB_OISR1_CLR + .endif + +999: + eieio + .endm + +#endif /* __ASSEMBLER__ */ + +/// \endcond + +#endif /* __GPE_IRQ_H__ */ diff --git a/pk/gpe/gpe_irq_init.c b/pk/gpe/gpe_irq_init.c new file mode 100644 index 00000000..078d2fa7 --- /dev/null +++ b/pk/gpe/gpe_irq_init.c @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2015 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file gpe_irq_init.c +/// \brief OCCHW IRQ initialization code for PK +/// +/// The entry points in this file are initialization rotines that could be +/// eliminated/deallocated by the application to free up storage if they are +/// no longer needed after initialization. + +#include "pk.h" + +/// Define the polarity and trigger condition for an interrupt. +/// +/// It is up to the application to take care of any side effects that may +/// occur from programming or reprogramming the interrupt controller. For +/// example, changing edge/level sensitivity or active level may set or clear +/// interrupt status in the controller. +/// +/// Note that PK allows this API to be called from any context, and changes +/// to the interrupt controller are made from a critical +/// section. +/// +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_ARGUMENT_IRQ_SETUP One or more arguments are invalid, +/// including an invalid \a irq, or invalid \a polarity or \a trigger parameters. + +int +pk_irq_setup(PkIrqId irq, + int polarity, + int trigger) +{ + PkMachineContext ctx; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(!OCCHW_IRQ_VALID(irq) || + !OCCHW_IRQ_OWNED(irq) || + !((polarity == PK_IRQ_POLARITY_ACTIVE_HIGH) || + (polarity == PK_IRQ_POLARITY_ACTIVE_LOW)) || + !((trigger == PK_IRQ_TRIGGER_LEVEL_SENSITIVE) || + (trigger == PK_IRQ_TRIGGER_EDGE_SENSITIVE)), + PK_INVALID_ARGUMENT_IRQ_SETUP); + } + + pk_critical_section_enter(&ctx); + + if (polarity == PK_IRQ_POLARITY_ACTIVE_HIGH) { + out32(OCCHW_OIEPR_OR(irq), OCCHW_IRQ_MASK32(irq)); + } else { + out32(OCCHW_OIEPR_CLR(irq), OCCHW_IRQ_MASK32(irq)); + } + + if (trigger == PK_IRQ_TRIGGER_EDGE_SENSITIVE) { + out32(OCCHW_OITR_OR(irq), OCCHW_IRQ_MASK32(irq)); + } else { + out32(OCCHW_OITR_CLR(irq), OCCHW_IRQ_MASK32(irq)); + } + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + + +/// (Re)define the IRQ handler and priority for an interrupt. +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// Note that PK allows this API to be called from any context, and changes +/// to the interrupt controller are made from a critical +/// section. +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_ARGUMENT_IRQ_HANDLER One or more arguments are +/// invalid, including an invalid \a irq, a null (0) \a handler, +/// or invalid \a priority. + +int +pk_irq_handler_set(PkIrqId irq, + PkIrqHandler handler, + void *arg) +{ + PkMachineContext ctx; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(!OCCHW_IRQ_VALID(irq) || + (handler == 0), + PK_INVALID_ARGUMENT_IRQ_HANDLER); + } + + pk_critical_section_enter(&ctx); + + __ppe42_irq_handlers[irq].handler = handler; + __ppe42_irq_handlers[irq].arg = arg; + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + + + diff --git a/pk/gpe/pk_port.h b/pk/gpe/pk_port.h new file mode 100644 index 00000000..887c123e --- /dev/null +++ b/pk/gpe/pk_port.h @@ -0,0 +1,16 @@ +#ifndef __PK_PORT_H__ +#define __PK_PORT_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_port.h +/// \brief The top-level GPE environment header for PK. + +#define HWMACRO_GPE + +#include "ppe42.h" + +#endif /* __PK_PORT_H__ */ diff --git a/pk/gpe/pkgpefiles.mk b/pk/gpe/pkgpefiles.mk new file mode 100644 index 00000000..8c33aa86 --- /dev/null +++ b/pk/gpe/pkgpefiles.mk @@ -0,0 +1,33 @@ +# @file pkgpefiles.mk +# +# @brief mk for including gpe object files +# +# @page ChangeLogs Change Logs +# @section pkgpefiles.mk +# @verbatim +# +# +# Change Log ****************************************************************** +# Flag Defect/Feature User Date Description +# ------ -------------- ---------- ------------ ----------- +# +# @endverbatim +# +########################################################################## +# Object Files +########################################################################## + +GPE-C-SOURCES = gpe_init.c gpe_irq_init.c +GPE-S-SOURCES = + +GPE-TIMER-C-SOURCES = +GPE-TIMER-S-SOURCES = + +GPE-THREAD-C-SOURCES = +GPE-THREAD-S-SOURCES = + +GPE-ASYNC-C-SOURCES = +GPE-ASYNC-S-SOURCES = + +GPE_OBJECTS += $(GPE-C-SOURCES:.c=.o) $(GPE-S-SOURCES:.S=.o) + diff --git a/pk/kernel/Makefile b/pk/kernel/Makefile new file mode 100644 index 00000000..3fad153e --- /dev/null +++ b/pk/kernel/Makefile @@ -0,0 +1,26 @@ +# This Makefile is designed to be invoked with the -I argument set to +# the location of the "pk.mk" for the build + +include img_defs.mk +include pkkernelfiles.mk + +ifeq "$(PK_TIMER_SUPPORT)" "1" +PK_OBJECTS += ${PK-TIMER-C-SOURCES:.c=.o} +endif + +ifeq "$(PK_THREAD_SUPPORT)" "1" +PK_OBJECTS += ${PK-THREAD-C-SOURCES:.c=.o} +endif + +OBJS := $(addprefix $(OBJDIR)/, $(PK_OBJECTS)) + +all: $(OBJS) + +$(OBJS) $(OBJS:.o=.d): | $(OBJDIR) + +$(OBJDIR): + mkdir -p $(OBJDIR) + +ifneq ($(MAKECMDGOALS),clean) +include $(OBJS:.o=.d) +endif diff --git a/pk/kernel/pk.h b/pk/kernel/pk.h new file mode 100644 index 00000000..2efcdf11 --- /dev/null +++ b/pk/kernel/pk.h @@ -0,0 +1,125 @@ +#ifndef __PK_H__ +#define __PK_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk.h +/// \brief The combined header of the PK kernel. +/// +/// This header will be included in any C or assembler source file that +/// requires any of the PK API. All headers defined by PK and co-compiled +/// code should be protected such that they can be included without error into +/// assembly. + +#ifndef __ASSEMBLER__ +#include <stdint.h> +#include <stddef.h> +#endif /* __ASSEMBLER__ */ + +#ifndef __PK__ +#define __PK__ 1 +#endif + +/// The application environment specifies whether or not it will provide an +/// application configuration file, which must be named "pk_app_cfg.h". + +#ifndef USE_PK_APP_CFG_H +#define USE_PK_APP_CFG_H 0 +#endif + +#if USE_PK_APP_CFG_H +#include "pk_app_cfg.h" +#endif + +#include "pk_macros.h" +#include "pk_api.h" +#include "pk_port.h" +#include "pk_kernel.h" +//#include "pk_io.h" + +#ifndef __ASSEMBLER__ + +#define MIN(X, Y) \ + ({ \ + typeof (X) __x = (X); \ + typeof (Y) __y = (Y); \ + (__x < __y) ? __x : __y; }) + +#define MAX(X, Y) \ + ({ \ + typeof (X) __x = (X); \ + typeof (Y) __y = (Y); \ + (__x > __y) ? __x : __y; \ + }) + +/// \todo These don't require 32/64 bit versions, can always promote 32->64. + +#define FLOOR_LOG2_32(x) (32 - 1 - cntlz32(x)) +#define FLOOR_LOG2_64(x) (64 - 1 - cntlz64(x)) + +#define CEILING_LOG2(x) \ + ({ \ + uint64_t __x = (uint64_t)(x); \ + int __y; \ + __y = FLOOR_LOG2_64(__x); \ + if ((__x & (__x - 1)) != 0) { \ + __y++; \ + } \ + __y;}) + + +#define POW2_32(x) ((uint32_t)1 << (x)) +#define POW2_64(x) ((uint64_t)1 << (x)) + +/// Cast a pointer to another type +/// +/// This macro is necessary when casting a pointer to a longer integer type. +/// The pointer is first cast to the equivalent integer type 'unsigned long', +/// then cast to the final type. You can also use this to cast integers longer +/// than pointers back to pointers. + +#define CAST_POINTER(t, p) ((t)((unsigned long)(p))) + + +/// Create an alignment attribute. +#define ALIGNED_ATTRIBUTE(alignment) __attribute__ ((aligned (alignment))) + +/// Create a specific-section attribute +/// +/// Note that the section \a s here must be a string. Also note that data +/// specified to reside in specific sections must always be +/// initialized. Otherwise it confuses the linker which wants to put +/// uninitialized data into .bss sections. +/// +/// \code +/// +/// int foo SECTION_ATTRIBUTE(".noncacheable") = 0; +/// int bar[10] SECTION_ATTRIBUTE(".noncacheable") = {0}; +/// +/// \endcode +#define SECTION_ATTRIBUTE(s) __attribute__ ((section (s))) + +/// Create a 'used' attribute +/// +/// This is required for example to avoid "function unused" warnings when a +/// function is declared static but only referenced by inline assembler: +/// +/// \code +/// +/// static USED_ATTRIBUTE void +/// _checkstop(void* arg, PkIrqId irq, int priority) +/// { +/// PK_PANIC(VALIDATION_CHECKSTOP); +/// } +/// +/// PK_IRQ_FAST2FULL(_validationCheckstopHandler, _checkstop); +/// +/// \endcode +#define USED_ATTRIBUTE __attribute__ ((used)) + +#endif /* __ASSEMBLER__ */ + +#endif /* __PK_H__ */ diff --git a/pk/kernel/pk_api.h b/pk/kernel/pk_api.h new file mode 100644 index 00000000..b0e7419c --- /dev/null +++ b/pk/kernel/pk_api.h @@ -0,0 +1,1010 @@ +#ifndef __PK_API_H__ +#define __PK_API_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_api.h +/// \brief Macros and declarations for the PK API. + +// Basic constants + +/// Although the number of threads is defined as a manifest constant, +/// numerous parts of the PK code assume this definition. The number of +/// supported threads _can not_ be changed simply by changing this constant. + +#define PK_THREADS 32 + +#define PK_IDLE_THREAD_PRIORITY PK_THREADS + +// Interrupt API + +#define PK_IRQ_POLARITY_ACTIVE_LOW 0 +#define PK_IRQ_POLARITY_ACTIVE_HIGH 1 + +#define PK_IRQ_TRIGGER_LEVEL_SENSITIVE 0 +#define PK_IRQ_TRIGGER_EDGE_SENSITIVE 1 + +// API return codes + +#define PK_OK 0 +#define PK_ILLEGAL_CONTEXT_THREAD_CONTEXT 0x00779002 +#define PK_ILLEGAL_CONTEXT_INTERRUPT_CONTEXT 0x00779003 +#define PK_ILLEGAL_CONTEXT_THREAD 0x00779004 +#define PK_ILLEGAL_CONTEXT_TIMER 0x00779005 +#define PK_INVALID_THREAD_AT_RESUME1 0x00779007 +#define PK_INVALID_THREAD_AT_RESUME2 0x00779008 +#define PK_INVALID_THREAD_AT_SUSPEND1 0x00779009 +#define PK_INVALID_THREAD_AT_SUSPEND2 0x0077900a +#define PK_INVALID_THREAD_AT_DELETE 0x0077900b +#define PK_INVALID_THREAD_AT_INFO 0x0077900c +#define PK_INVALID_THREAD_AT_CHANGE 0x0077900d +#define PK_INVALID_THREAD_AT_SWAP1 0x0077900e +#define PK_INVALID_THREAD_AT_SWAP2 0x0077900f +#define PK_INVALID_THREAD_AT_CREATE 0x00779010 +#define PK_INVALID_SEMAPHORE_AT_POST 0x00779011 +#define PK_INVALID_SEMAPHORE_AT_PEND 0x00779012 +#define PK_INVALID_SEMAPHORE_AT_RELEASE 0x00779013 +#define PK_INVALID_SEMAPHORE_AT_INFO 0x00779014 +#define PK_INVALID_SEMAPHORE_AT_CREATE 0x00779015 +#define PK_INVALID_TIMER_AT_SCHEDULE 0x00779016 +#define PK_INVALID_TIMER_AT_CANCEL 0x00779017 +#define PK_INVALID_TIMER_AT_INFO 0x00779018 +#define PK_INVALID_TIMER_AT_CREATE 0x00779019 +#define PK_INVALID_ARGUMENT_IRQ_SETUP 0x0077901a +#define PK_INVALID_ARGUMENT_IRQ_HANDLER 0x0077901b +#define PK_INVALID_ARGUMENT_INTERRUPT 0x00779024 +#define PK_INVALID_ARGUMENT_CONTEXT_SET 0x00779025 +#define PK_INVALID_ARGUMENT_CONTEXT_GET 0x00779026 +#define PK_INVALID_ARGUMENT_FIT 0x00779027 +#define PK_INVALID_ARGUMENT_WATCHDOG 0x00779028 +#define PK_INVALID_ARGUMENT_INIT 0x00779029 +#define PK_INVALID_ARGUMENT_SEMAPHORE 0x0077902a +#define PK_INVALID_ARGUMENT_THREAD_CHANGE 0x0077902b +#define PK_INVALID_ARGUMENT_THREAD_PRIORITY 0x0077902c +#define PK_INVALID_ARGUMENT_THREAD1 0x0077902d +#define PK_INVALID_ARGUMENT_THREAD2 0x0077902e +#define PK_INVALID_ARGUMENT_THREAD3 0x0077902f +#define PK_STACK_OVERFLOW 0x00779030 +#define PK_TIMER_ACTIVE 0x00779031 +#define PK_TIMER_NOT_ACTIVE 0x00779032 +#define PK_PRIORITY_IN_USE_AT_RESUME 0x00779033 +#define PK_PRIORITY_IN_USE_AT_CHANGE 0x00779034 +#define PK_PRIORITY_IN_USE_AT_SWAP 0x00779035 +#define PK_SEMAPHORE_OVERFLOW 0x00779036 +#define PK_SEMAPHORE_PEND_NO_WAIT 0x00779037 +#define PK_SEMAPHORE_PEND_TIMED_OUT 0x00779038 +#define PK_SEMAPHORE_PEND_WOULD_BLOCK 0x00779039 +#define PK_INVALID_DEQUE_SENTINEL 0x0077903a +#define PK_INVALID_DEQUE_ELEMENT 0x0077903b +#define PK_INVALID_OBJECT 0x0077903c + +// Kernel panics + +#define PK_NO_TIMER_SUPPORT 0x0077903d +#define PK_START_THREADS_RETURNED 0x0077903e +#define PK_UNIMPLEMENTED 0x0077903f +#define PK_SCHEDULING_INVARIANT 0x00779040 +#define PK_TIMER_HANDLER_INVARIANT 0x00779041 +#define PK_THREAD_TIMEOUT_STATE 0x00779045 + + +/// \defgroup pk_thread_states PK Thread States +/// +/// Threads are created in the state PK_THREAD_STATE_SUSPENDED_RUNNABLE. +/// When the thread is mapped it transitions to state PK_THREAD_STATE_MAPPED. +/// A mapped thread is runnable if it appears in the run queue; there is no +/// other flag or status to indicate a runnable thread. If a blocked thread +/// is suspended it goes into state PK_THREAD_STATE_SUSPENDED_BLOCKED. For +/// all threads the reason for blockage is detailed in the \a flags field of +/// the thread; See \ref pk_thread_flags. PK_THREAD_STATE_DELETED and +/// PK_THREAD_STATE_COMPLETED are effectively equivalent but named +/// individually for reporting purposes. +/// +/// \note This separation of the thread \a state and \a flags allows the use +/// of an PK semaphore as a thread barrier, as it supports a non-iterative +/// implementation of pk_semaphore_release_all() in which all threads blocked +/// on the semaphore are simultaneously inserted into the run queue with an +/// atomic operation, followed by each individual thread readjusting its flags +/// appropriately once the thread runs again. +/// +/// @{ + +#define PK_THREAD_STATE_SUSPENDED_RUNNABLE 1 +#define PK_THREAD_STATE_MAPPED 2 +#define PK_THREAD_STATE_SUSPENDED_BLOCKED 3 +#define PK_THREAD_STATE_COMPLETED 4 +#define PK_THREAD_STATE_DELETED 5 + +/// @} + + +/// \defgroup pk_thread_flags PK Thread Flags +/// +/// The \a flag field of the thread extends the information contained in the +/// \a state field; See \ref pk_thread_states. Blocked threads will show +/// PK_THREAD_FLAG_SEMAPHORE_PEND, PK_THREAD_FLAG_TIMER_PEND or both (if +/// blocked on a semaphore with timeout). The flag PK_THREAD_FLAG_TIMED_OUT +/// indicates that a thread timer timed out before the thread became +/// runnable. Currently only the semaphore-pend-with-timeout code uses this +/// flag. +/// +/// Note that a thread can be mapped and runnable (in the run queue) even +/// though PK_THREAD_FLAG_SEMAPHORE_PEND and/or PK_THREAD_FLAG_TIMER_PEND +/// are set. These flags are always cleared by the thread itself, not the code +/// that unblocks the thread. This allows the implementation of the +/// pk_semaphore_release_all() as explained in \ref pk_thread_states. +/// +/// @{ + +#define PK_THREAD_FLAG_SEMAPHORE_PEND 0x1 +#define PK_THREAD_FLAG_TIMER_PEND 0x2 +#define PK_THREAD_FLAG_TIMED_OUT 0x4 + +/// @} + + +// Critical Sections + +/// Enter a critical section, saving the current machine +/// context. + +#define pk_critical_section_enter(pctx) \ + pk_interrupt_disable(pctx) + +/// Exit a critical section by restoring the previous machine context. + +#define pk_critical_section_exit(pctx) \ + pk_machine_context_set(pctx) + + +/// Execute a statement atomically + +#define PK_ATOMIC(stmt) \ + do { \ + PkMachineContext __ctx; \ + pk_critical_section_enter(&__ctx); \ + stmt; \ + pk_critical_section_exit(&__ctx); \ + } while (0) + + +// Application-overrideable definitions + +/// Control whether or not the API functions check for errors. +/// +/// This definition can be overriden by the application. + +#ifndef PK_ERROR_CHECK_API +#define PK_ERROR_CHECK_API 1 +#endif + +/// Control whether API errors cause kernel panics or return negative error +/// codes. +/// +/// This selection is only valid if \c PK_ERROR_CHECK_API is defined +/// non-0. This definition can be overriden by the application. + +#ifndef PK_ERROR_PANIC +#define PK_ERROR_PANIC 1 +#endif + +/// Control whether or not the PK kernel checks key invariants. +/// +/// Violations of kernel invariants always cause kernel panics. This +/// definition can be overriden by the application. + +#ifndef PK_ERROR_CHECK_KERNEL +#define PK_ERROR_CHECK_KERNEL 1 +#endif + +/// Define the time interval type, which must be an unsigned type of a size +/// less then or equal to the size of \c PkTimebase. This definition can be +/// overridden by the application. + +#ifndef PK_TIME_INTERVAL_TYPE +#define PK_TIME_INTERVAL_TYPE uint32_t +#endif + +/// Provide support for the PkTimer APIs in addition to the default +/// initerrupt APIs. This definition can be overridden by the application. + +#ifndef PK_TIMER_SUPPORT +#define PK_TIMER_SUPPORT 1 +#endif + +/// Provide support for the all PK APIs. Thread support requires/implies +/// support for time services and semaphores. This definition can be +/// overridden by the application. + +#ifndef PK_THREAD_SUPPORT +#define PK_THREAD_SUPPORT 1 +#endif + +/// Control the level of stack checking. +/// +/// This definition can be overriden by the application. +/// +/// 0 : No stack prepatterning or checking is made for thread and kernel +/// stacks. +/// +/// 1 : Kernel interrupt stacks are prepatterned during +/// \c pk_initialize(). Thread stacks are prepatterned during +/// \c pk_thread_create(). +/// +/// 2 : (\b Default - Currently Unimplemented) In addition to prepatterning, +/// stack utilization is computed at the exit of context switches and +/// noncritical interrupt processing. The maximum utilization is stored in +/// the thread data structure. The kernel will panic if stack overflow is +/// detected. Stack utilization is not computed for the idle thread. + +#ifndef PK_STACK_CHECK +#define PK_STACK_CHECK 1 +#endif + +/// A hook for main() +/// +/// This hook macro is expanded in the body of __pk_main() prior to the call +/// of the application main(). The application can redefine this hook macro +/// in (or in headers referred to in) the application header +/// pk_app_cfg.h. The PK_MAIN_HOOK will run on the stack of main(). + +#ifndef PK_MAIN_HOOK +#define PK_MAIN_HOOK do {} while (0) +#endif + +/// A hook for pk_start_threads() +/// +/// This hook macro is expanded in the call-tree of pk_start_threads() before +/// threads are actually started. The application can redefine this hook +/// macro in (or in headers referred to in) the application header +/// pk_app_cfg.h. +/// +/// The PK_START_THREADS_HOOK runs as a pseudo-interrupt handler on the +/// noncritical interrupt stack, with noncritical interrupts disabled. + +#ifndef PK_START_THREADS_HOOK +#define PK_START_THREADS_HOOK do {} while (0) +#endif + +/// The maximum value of the \c PkTimebase type. + +#define PK_TIMEBASE_MAX ((PkTimebase)-1) + +/// A special value that specifies that the timebase will not be reset during +/// pk_init(). + +#define PK_TIMEBASE_CONTINUES PK_TIMEBASE_MAX + +/// By convention, a timeout value indicating 'no waiting' in a call of \c +/// pk_semaphore_pend(). + +#define PK_NO_WAIT 0 + +/// By convention, a timeout value indicating 'wait forever' in a call of \c +/// pk_semaphore_pend(). + +#define PK_WAIT_FOREVER ((PkInterval)-1) + +/// The PK timebase frequency in Hz +/// +/// Earlier version of PK defined the timbase frequency as a preprocessor +/// macro. Now, the timebase frequency is specified as a parameter of the +/// pk_initialize() API. The macro remains defined for backwards +/// compatibility, however all kernel uses of the timebase frequency are now +/// optimized around the timebase parameter. + +#define PK_TIMEBASE_FREQUENCY_HZ __pk_timebase_frequency_hz + +/// Convert a time in integral seconds to a time interval - overflows are +/// ignored. The application can redefine this macro. + +#ifndef PK_SECONDS +#define PK_SECONDS(s) ((PkInterval)(__pk_timebase_frequency_hz * (PkInterval)(s))) +#endif + +/// Convert a time in integral milliseconds to a time interval - overflows are +/// ignored, and a frequency evenly (or closely) divisible by 1000 is +/// assumed. The application can redefine this macro. + +#ifndef PK_MILLISECONDS +#define PK_MILLISECONDS(m) ((PkInterval)(__pk_timebase_frequency_khz * (PkInterval)(m))) +#endif + +/// Convert a time in integral microseconds to a time interval - overflows are +/// ignored, and a frequncy evenly (or closely) divisible by 1,000,000 is +/// assumed. The application can redefine this macro. + +#ifndef PK_MICROSECONDS +#define PK_MICROSECONDS(u) ((PkInterval)(__pk_timebase_frequency_mhz * (PkInterval)(u))) +#endif + +/// Convert a time in integral nanoseconds to a time interval - overflows are +/// ignored, and a frequeyncy evenly (or closely) divisible by 1,000,000 is +/// assumed. The application can redefine this macro. + +#ifndef PK_NANOSECONDS +#define PK_NANOSECONDS(n) \ + ((PkInterval)((__pk_timebase_frequency_mhz * (PkInterval)(n)) / 1000)) +#endif + + +/// Enable PK application tracing (enabled by default) +#ifndef PK_TRACE_ENABLE +#define PK_TRACE_ENABLE 1 +#endif + +/// Enable PK kernel tracing (disabled by default) +#ifndef PK_KERNEL_TRACE_ENABLE +#define PK_KERNEL_TRACE_ENABLE 0 +#endif + +//Application trace macros +#if !PK_TRACE_ENABLE +#define PK_TRACE(...) +#define PK_TRACE_BIN(str, bufp, buf_size) +#else +#define PK_TRACE(...) PKTRACE(__VA_ARGS__) +#define PK_TRACE_BIN(str, bufp, buf_size) PKTRACE_BIN(str, bufp, buf_size) +#endif + + +//Kernel trace macros +#if !PK_KERNEL_TRACE_ENABLE + +#define PK_TRACE_THREAD_SLEEP(priority) +#define PK_TRACE_THREAD_WAKEUP(priority) +#define PK_TRACE_THREAD_SEMAPHORE_PEND(priority) +#define PK_TRACE_THREAD_SEMAPHORE_POST(priority) +#define PK_TRACE_THREAD_SEMAPHORE_TIMEOUT(priority) +#define PK_TRACE_THREAD_SUSPENDED(priority) +#define PK_TRACE_THREAD_DELETED(priority) +#define PK_TRACE_THREAD_COMPLETED(priority) +#define PK_TRACE_THREAD_MAPPED_RUNNABLE(priority) +#define PK_TRACE_THREAD_MAPPED_SEMAPHORE_PEND(priority) +#define PK_TRACE_THREAD_MAPPED_SLEEPING(priority) + +#else + +#define PK_TRACE_THREAD_SLEEP(priority) PKTRACE("THREAD_SLEEP(%d)", priority) +#define PK_TRACE_THREAD_WAKEUP(priority) PKTRACE("THREAD_WAKEUP(%d)", priority) +#define PK_TRACE_THREAD_SEMAPHORE_PEND(priority) PKTRACE("SEMAPHORE_PEND(%d)", priority) +#define PK_TRACE_THREAD_SEMAPHORE_POST(priority) PKTRACE("SEMAPHORE_POST(%d)", priority) +#define PK_TRACE_THREAD_SEMAPHORE_TIMEOUT(priority) PKTRACE("SEMAPHORE_TIMEOUT(%d)", priority) +#define PK_TRACE_THREAD_SUSPENDED(priority) PKTRACE("THREAD_SUSPENDED(%d)", priority) +#define PK_TRACE_THREAD_DELETED(priority) PKTRACE("THREAD_DELETED(%d)", priority) +#define PK_TRACE_THREAD_COMPLETED(priority) PKTRACE("THREAD_COMPLETED(%d)", priority) +#define PK_TRACE_THREAD_MAPPED_RUNNABLE(priority) PKTRACE("THREAD_MAPPED_RUNNABLE(%d)", priority) +#define PK_TRACE_THREAD_MAPPED_SEMAPHORE_PEND(priority) PKTRACE("THREAD_MAPPED_SEMAPHORE_PEND(%d)", priority) +#define PK_TRACE_THREAD_MAPPED_SLEEPING(priority) PKTRACE("THREAD_MAPPED_SLEEPING(%d)", priority) +#endif /* PK_KERNEL_TRACE_ENABLE */ + + +/// Add a string to the trace buffer with an optional register holding a 16bit value +/// WARNING: This calls a c function which may clobber any of the volatile registers +#if (PK_TRACE_SUPPORT && PK_TIMER_SUPPORT) +#define PK_TRACE_ASM16(...) TRACE_ASM_HELPER16(VARG_COUNT(__VA_ARGS__), __VA_ARGS__) +#else +#define PK_TRACE_ASM16(...) +#endif /* PK_TRACE_SUPPORT */ + +/// The following macros are helper macros for tracing. They should not be called +/// directly. +#define VARG_COUNT_HELPER(_0, _1, _2, _3, _4, _5, _6, _7, N, ...) N +#define VARG_COUNT(...) VARG_COUNT_HELPER(, ##__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0) + +#ifdef __ASSEMBLER__ +#define TRACE_ASM_HELPER16_CALL(count, ...) TINY_TRACE_ASM ## count (__VA_ARGS__) +#define TRACE_ASM_HELPER16(count, ...) TRACE_ASM_HELPER16_CALL(count, __VA_ARGS__) + +#define TINY_TRACE_ASM0() .error "format string required" +#define TINY_TRACE_ASM1(str) \ + .tiny_trace_asm1 trace_adal_hash(str, PK_TRACE_HASH_PREFIX) +#define TINY_TRACE_ASM2(str, reg) \ + .tiny_trace_asm2 trace_adal_hash(str, PK_TRACE_HASH_PREFIX), reg +#define TINY_TRACE_ASM3() .error "too many parameters" +#define TINY_TRACE_ASM4() .error "too many parameters" +#define TINY_TRACE_ASM5() .error "too many parameters" +#define TINY_TRACE_ASM6() .error "too many parameters" +#define TINY_TRACE_ASM7() .error "too many parameters" + +//TODO: add support for tracing more than 1 parameter and binary data in assembly + + .global pk_trace_tiny + + .macro .tiny_trace_asm1 hash16 + lis %r3, \hash16 + bl pk_trace_tiny + .endm + + .macro .tiny_trace_asm2 hash16, parm16 + clrlwi %r3, \parm16, 16 + oris %r3, %r3, \hash16 + bl pk_trace_tiny + .endm + +#endif /*__ASSEMBLER__*/ + + + +#ifndef __ASSEMBLER__ + +#include <stddef.h> +#include <stdint.h> + +/// The timebase frequency in Hz; A parameter to pk_initialize() +extern uint32_t __pk_timebase_frequency_hz; + +/// The timebase frequency in KHz +extern uint32_t __pk_timebase_frequency_khz; + +/// The timebase frequency in Mhz +extern uint32_t __pk_timebase_frequency_mhz; + + +typedef unsigned long int PkAddress; + +typedef uint8_t PkThreadState; + +typedef uint8_t PkThreadPriority; + +typedef uint8_t PkThreadFlags; + +typedef uint32_t PkSemaphoreCount; + +typedef uint64_t PkTimebase; + +typedef PK_TIME_INTERVAL_TYPE PkInterval; + +#include "pk_port_types.h" + +typedef struct { + + /// A priority queue of threads pending on the semaphore. + PkThreadQueue pending_threads; + + /// The current semaphore count. + PkSemaphoreCount count; + + /// The maximum allowable count - for error checking. + PkSemaphoreCount max_count; + +} PkSemaphore; + + +/// Compile-time initialize an PkSemaphore structure +/// +/// This low-level macro creates a structure initializatin of an PkSemaphore +/// structure. This can be used for example to create compile-time initialized +/// arrays of semaphores. +#define PK_SEMAPHORE_INITIALIZATION(_initial_count, _max_count) \ + {.pending_threads = 0, \ + .count = (_initial_count), \ + .max_count = (_max_count)} + + +/// Declare and initialize a semaphore +#define PK_SEMAPHORE(sem, initial_count, max_count) \ + PkSemaphore sem = PK_SEMAPHORE_INITIALIZATION(initial_count, max_count) + + +/// Trace macros for C functions +#define HASH_ARG_COMBO(str, arg) \ + ((((uint32_t)trace_adal_hash(str, PK_TRACE_HASH_PREFIX)) << 16) | ((uint32_t)(arg) & 0x0000ffff)) + +#define PKTRACE0(...) pk_trace_tiny() //will fail at compile time + +#define PKTRACE1(str) \ + pk_trace_tiny((trace_adal_hash(str, PK_TRACE_HASH_PREFIX) << 16)) + +#define PKTRACE2(str, parm0) \ + ((sizeof(parm0) <= 2)? \ + pk_trace_tiny(HASH_ARG_COMBO(str, parm0)): \ + pk_trace_big(HASH_ARG_COMBO(str, 1), ((uint64_t)parm0) << 32, 0)) + +#define PKTRACE3(str, parm0, parm1) \ + pk_trace_big(HASH_ARG_COMBO(str, 2), ((((uint64_t)parm0) << 32) | parm1), 0) + +#define PKTRACE4(str, parm0, parm1, parm2) \ + pk_trace_big(HASH_ARG_COMBO(str, 3), ((((uint64_t)parm0) << 32) | parm1),\ + ((uint64_t)parm2) << 32 ) + +#define PKTRACE5(str, parm0, parm1, parm2, parm3) \ + pk_trace_big(HASH_ARG_COMBO(str, 4), ((((uint64_t)parm0) << 32) | parm1),\ + ((((uint64_t)parm2) << 32) | parm3) ) + +#define PKTRACE6(...) pk_trace_tiny() //will fail at compile time +#define PKTRACE7(...) pk_trace_tiny() //will fail at compile time + +#define PKTRACE_HELPER2(count, ...) PKTRACE ## count (__VA_ARGS__) +#define PKTRACE_HELPER(count, ...) PKTRACE_HELPER2(count, __VA_ARGS__) + +#if (PK_TRACE_SUPPORT && PK_TIMER_SUPPORT) +#define PKTRACE(...) PKTRACE_HELPER(VARG_COUNT(__VA_ARGS__), __VA_ARGS__) +#define PKTRACE_BIN(str, bufp, buf_size) \ + pk_trace_binary(((buf_size < 255)? HASH_ARG_COMBO(str, buf_size): HASH_ARG_COMBO(str, 255)), bufp) +#else +#define PKTRACE(...) +#define PKTRACE_BIN(str, bufp, buf_size) +#endif //PK_TRACE_SUPPORT + + + +/// A generic doubly-linked list object +/// +/// This object functions both as a sentinel mode for a deque as well as a +/// pointer container for elements in deques. The PK API assumes that +/// queueable structures will be defined with an PkDeque structure as the +/// initial 'data member' of the structure. This allows a pointer to a queue +/// element to be cast to a pointer to an PkDeque and vice-versa. + +typedef struct PkDeque { + + /// Pointer to the head or the next element in a deque. + /// + /// When an PkDeque is used as the sentinel node for a queue, \a next + /// points to the head of the queue, and the condition (next == \<self\>) + /// indicates an empty PkDeque. By convention the condition (\a next == + /// 0) is used to indicate that a queue element is not enqueued. + struct PkDeque* next; + + /// Pointer to the tail or previous element in a deque. + /// + /// When a DQueue is used as the sentinel node for a queue, \a previous + /// points to the tail of the queue. + struct PkDeque* previous; + +} PkDeque; + + +typedef void (*PkTimerCallback)(void *); + +#define PK_TIMER_CALLBACK(callback) void callback(void *) + +struct PkTimer; + +/// The PK timer object + +typedef struct PkTimer { + + /// The time queue management pointers + /// + /// This pointer container is defined as the first element of the + /// structure to allow the PkTimer to be cast to an PkDeque and + /// vice-versa. + PkDeque deque; + + /// The absolute timeout of the timer. + PkTimebase timeout; + + /// The timer period + /// + /// If not 0, then this is a periodic timer and it will be automatically + /// rescheduled in absolute time from the previous timeout. + PkInterval period; + + /// The timer callback + /// + /// For PK thread timers used to implement Sleep and semaphore pend + /// timeouts this field is initialized to __pk_thread_timeout(). + PkTimerCallback callback; + + /// Private data passed to the callback. + /// + /// For PK thread timers used to implement Sleep and semaphore pend this + /// field is initialized to a pointer to the thread. + void *arg; + + /// Options for timer processing; See \ref pk_timer_options + uint8_t options; + +} PkTimer; + +/// \defgroup pk_timer_options PK Timer Options +/// @{ + +/// Allow interrupt preemption during the callback +/// +/// This is the normal mode for PkTimer objects scheduled by PK kernal +/// mechanisms. The timer callbacks effectively run as if inside a +/// highest-priority thread, allowing other interrupts to preempt them. +#define PK_TIMER_CALLBACK_PREEMPTIBLE 0x1 + +/// @} + + +// Threads + +typedef void (*PkThreadRoutine)(void *arg); + +#define PK_THREAD_ROUTINE(f) void f(void *arg); + +typedef struct { + + /// Stack pointer saved during context switches. Assembler code expects + /// this to always be at address offset 0 from the thread pointer. + PkAddress saved_stack_pointer; + + /// This is 1 past the last valid byte address of the thread stack. + /// Assembler code expects this to always be at address offset (sizeof + /// PkAddress) from the thread pointer. + PkAddress stack_limit; + + /// This is the original base of the stack. + /// Assembler code expects this to always be at address offset 2 * (sizeof + /// PkAddress) from the thread pointer. + PkAddress stack_base; + + /// If the thread is blocked on a semaphore, then this is the semaphore the + /// thread is blocked on. + PkSemaphore *semaphore; + + /// The thread priority. + PkThreadPriority priority; + + /// The thread state; See \ref pk_thread_states + PkThreadState state; + + /// Thread flags; See \ref pk_thread_flags + PkThreadFlags flags; + + /// The timer structure handles Sleep and blocking on a semaphore with + /// timeout. + PkTimer timer; + +} PkThread; + + +// Initialization APIs + +int +pk_initialize(PkAddress noncritical_stack, + size_t noncritical_stack_size, + PkTimebase initial_timebase, + uint32_t timebase_frequency_hz); + + +// Timebase APIs + +PkTimebase +pk_timebase_get(void); + +void +pk_timebase_set(PkTimebase timebase); + +// Interrupt preemption APIs + +int +pk_interrupt_preemption_enable(void); + +int +pk_interrupt_preemption_disable(void); + +// Timer APIs + +int +pk_timer_create(PkTimer *timer, + PkTimerCallback callback, + void *arg); + +int +pk_timer_create_nonpreemptible(PkTimer *timer, + PkTimerCallback callback, + void *arg); + +int +pk_timer_schedule_absolute(PkTimer *timer, + PkTimebase time, + PkInterval period); + +int +pk_timer_schedule(PkTimer *timer, + PkInterval interval, + PkInterval period); + +int +pk_timer_cancel(PkTimer *timer); + +int +pk_timer_info_get(PkTimer *timer, + PkTimebase *timeout, + int *active); + +// Thread APIs + +int +pk_thread_create(PkThread *thread, + PkThreadRoutine thread_routine, + void *arg, + PkAddress stack, + size_t stack_size, + PkThreadPriority priority); + +int +pk_start_threads(void); + +int +pk_thread_resume(PkThread *thread); + +int +pk_thread_suspend(PkThread *thread); + +int +pk_thread_delete(PkThread *thread); + +int +pk_complete(void); + +int +pk_sleep_absolute(PkTimebase time); + +int +pk_sleep(PkInterval interval); + +int +pk_thread_info_get(PkThread *thread, + PkThreadState *state, + PkThreadPriority *priority, + int *runnable); + +int +pk_thread_priority_change(PkThread *thread, + PkThreadPriority new_priority, + PkThreadPriority *old_priority); + +int +pk_thread_at_priority(PkThreadPriority priority, + PkThread **thread); + +int +pk_thread_priority_swap(PkThread* thread_a, PkThread* thread_b); + + +// Semaphore APIs + +int +pk_semaphore_create(PkSemaphore *semaphore, + PkSemaphoreCount initial_count, + PkSemaphoreCount max_count); + +int +pk_semaphore_post(PkSemaphore *semaphore); + +int +pk_semaphore_pend(PkSemaphore *semaphore, + PkInterval timeout); + +int +pk_semaphore_release_all(PkSemaphore *semaphore); + + +int +pk_semaphore_info_get(PkSemaphore *semaphore, + PkSemaphoreCount *count, + int *pending); + +void +pk_semaphore_post_handler(void *arg, + PkIrqId irq); + +// Misc. APIs + +void +pk_halt() __attribute__ ((noreturn)); + +// Deque APIs + +int +pk_deque_sentinel_create(PkDeque *deque); + +#define PK_DEQUE_SENTINEL_INIT(dq_addr) \ +{\ + .next = dq_addr, \ + .previous = dq_addr \ +} + +#define PK_DEQUE_SENTINEL_STATIC_CREATE(deque) \ + PkDeque deque = PK_DEQUE_SENTINEL_INIT(&deque) + +int +pk_deque_element_create(PkDeque *element); + +#define PK_DEQUE_ELEMENT_INIT() \ +{\ + .next = 0, \ + .previous = 0 \ +} + +#define PK_DEQUE_ELEMENT_STATIC_CREATE(deque) \ + PkDeque deque = PK_DEQUE_ELEMENT_INIT() + +/// Check for an empty PkDeque +/// +/// \param deque The sentinel node of a deque +/// +/// \retval 0 The PkDeque is not empty +/// +/// \retval 1 The PkDeque is empty + +static inline int +pk_deque_is_empty(PkDeque *deque) +{ + return (deque == deque->next); +} + + +/// Check if an PkDeque element is currently enqueued +/// +/// \param element Typically the PkDeque object of a queable structure +/// +/// \retval 0 The element is not currently enqueued +/// +/// \retval 1 The element is currently enqueued + +static inline int +pk_deque_is_queued(PkDeque *element) +{ + return (element->next != 0); +} + + +/// Append an element to the tail of a deque (FIFO order) +/// +/// \param deque The sentinel node of a deque +/// +/// \param element Typically the PkDeque object of a queable structure +/// +/// It is an error to call this API on an element that is already enqueued, +/// but the API does not check for this error. + +static inline void +pk_deque_push_back(PkDeque *deque, PkDeque *element) +{ + deque->previous->next = element; + element->previous = deque->previous; + element->next = deque; + deque->previous = element; +} + + +/// Push an element at the head of a deque (LIFO order) +/// +/// \param deque The sentinel node of a deque +/// +/// \param element Typically the PkDeque object of a queable structure +/// +/// It is an error to call this API on an element that is already enqueued, +/// but the API does not check for this error. + +static inline void +pk_deque_push_front(PkDeque *deque, PkDeque *element) +{ + deque->next->previous = element; + element->next = deque->next; + element->previous = deque; + deque->next = element; +} + +/// Pop an element from the head of a deque +/// +/// \param deque The sentinel node of a deque +/// +/// \retval 0 The PkDeque was empty prior to the call +/// +/// \retval non-0 A pointer to the previous head of the deque, which has been +/// removed from the deque and marked as no longer queued. + +// The cast of 'head' is used to remove the 'volatile' attribute. + +static inline PkDeque * +pk_deque_pop_front(PkDeque *deque) +{ + PkDeque *head; + + if (pk_deque_is_empty(deque)) { + return 0; + } else { + head = (PkDeque *)(deque->next); + deque->next = head->next; + deque->next->previous = deque; + head->next = 0; + return head; + } +} + + +/// Remove a deque element from any position in the deque +/// +/// \param element Typically the PkDeque object of a queable structure +/// +/// It is an error to call this API on an element that is not currently +/// enqueued, but the API does not check for this error. + +static inline void +pk_deque_delete(PkDeque *element) +{ + element->previous->next = element->next; + element->next->previous = element->previous; + element->next = 0; +} + + +void pk_trace_tiny(uint32_t i_parm); +void pk_trace_big(uint32_t i_hash_and_count, + uint64_t i_parm1, uint64_t i_parm2); +void pk_trace_binary(uint32_t i_hash_and_size, void* bufp); + +/// Cast a pointer to another type, in a way that won't cause warnings + +#define PK_CAST_POINTER(t, p) ((t)((PkAddress)(p))) + +// Static Assert Macro for Compile time assertions. +// - This macro can be used both inside and outside of a function. +// - A value of false will cause the ASSERT to produce this error +// - This will show up on a compile fail as: +// <file>:<line> error: size of array '_static_assert' is negative +// - It would be trivial to use the macro to paste a more descriptive +// array name for each assert, but we will leave it like this for now. +#define PK_STATIC_ASSERT(cond) extern uint8_t _static_assert[(cond) ? 1 : -1] __attribute__ ((unused)) + +/// \page pk_errors PK API and Kernel Error Handling +/// +/// Error checking in the PK API consumes a significant amount of code space. +/// Approximately 20% of the object code in the PPC405 port is devoted to +/// error checking. Presumably a like amount of time overhead is also added to +/// PK API calls by this checking. +/// +/// API error checking can be disabled to save space and time in the kernel. +/// API errors can also be configured to cause kernel panics, allowing +/// applications to be coded without the overhead of error checking but still +/// providing an escape in the event of application errors or (unlikely) +/// hardware failures. The PK default is to check for API errors and kernel +/// invariants, and panic should errors occur. +/// +/// PK follows the Unix convention that a successful call of an API returns 0 +/// (PK_OK), but returns a negative code in the event of failure, or to +/// provide further information. The error codes are all defined as manifest +/// constants. +/// +/// Some negative codes returned by PK APIs are not considered errors. These +/// conditions are always checked, never cause a panic if they occur, and +/// their interpretation is always left to the application. See the detailed +/// documentation for each API for lists of error and non-error codes returned +/// by the API. +/// +/// There are three configuration options that control error handling in the +/// PK API and kernel: +/// +/// \c PK_ERROR_CHECK_API +/// +/// \arg \b 0 - No PK API error checking. APIs that potentially return error +/// codes will always return 0 (PK_OK) instead of an error code. Those +/// APIs that return negative codes that are not errors (see Table 1.5) +/// always return the negative non-error codes when appropriate. +/// +/// \arg \b 1 - (Default) All PK API errors are checked. The behavior in +/// the event of an error is defined by the configuration option +/// PK_ERROR_PANIC. +/// +/// \c PK_ERROR_CHECK_KERNEL +/// +/// \arg \b 0 - No kernel invariant error checking is done. +/// +/// \arg \b 1 - (Default) Selected kernel invariants are checked. The overhead +/// for these checks should be minimal. +/// +/// \c PK_ERROR_PANIC +/// +/// \arg \b 0 - PK API calls return negative error codes in the event of +/// errors. Note that PK kernel invariants always cause a panic if +/// violations occur. +/// +/// \arg \b 1 - (Default) In the event of errors PK APIs invoke PK_PANIC(code), +/// where code is a positive error code. Kernel invariant checks always +/// cause a panic if violations are detected. + +#endif /* __ASSEMBLER__ */ + +#endif /* __PK_API_H__ */ diff --git a/pk/kernel/pk_core.c b/pk/kernel/pk_core.c new file mode 100644 index 00000000..bfa6d6be --- /dev/null +++ b/pk/kernel/pk_core.c @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_core.c +/// \brief Core routines for the PK kernel. +/// +/// The entry points in this file are routines that are expected to be needed +/// at runtime by all PK applications. This file also serves as a place for +/// kernel global variables to be realized. + +#define __PK_CORE_C__ + +#include "pk.h" + +#if !PK_TIMER_SUPPORT + +/// If there is no timer support, then any call of the timer interrupt handler +/// is considered a fatal error. + +void +__pk_timer_handler() +{ + PK_PANIC(PK_NO_TIMER_SUPPORT); +} + +#endif /* PK_TIMER_SUPPORT */ + + +/// Initialize an PkDeque sentinel node +/// +/// \param deque The sentinel node of the deque +/// +/// PK has no way of knowing whether the \a deque is currently in use, so +/// this API must only be called on unitialized or otherwise unused sentinel +/// nodes. +/// +/// \retval 0 success +/// +/// \retval -PK_INVALID_DEQUE_SENTINEL The \a deque pointer was null + +int +pk_deque_sentinel_create(PkDeque *deque) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(deque == 0, PK_INVALID_DEQUE_SENTINEL); + } + + deque->next = deque->previous = deque; + return 0; +} + + +/// Initialize an PkDeque element +/// +/// \param element Typically the PkDeque object of a queable structure +/// +/// PK has no way of knowing whether the \a element is currently in use, so +/// this API must only be called on unitialized or otherwise unused deque +/// elements. +/// +/// \retval 0 success +/// +/// \retval -PK_INVALID_DEQUE_ELEMENT The \a element pointer was null + +int +pk_deque_element_create(PkDeque *element) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(element == 0, PK_INVALID_DEQUE_ELEMENT); + } + + element->next = 0; + return 0; +} + +#undef __PK_CORE_C__ diff --git a/pk/kernel/pk_init.c b/pk/kernel/pk_init.c new file mode 100644 index 00000000..9174e51d --- /dev/null +++ b/pk/kernel/pk_init.c @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_init.c +/// \brief PK initialization +/// +/// The entry points in this file are initialization routines - they are never +/// needed after PK initialization and their code space could be reclaimed by +/// the application after initialization if required. + +#include "pk.h" +#include "pk_trace.h" + +uint32_t __pk_timebase_frequency_hz; +uint32_t __pk_timebase_frequency_khz; +uint32_t __pk_timebase_frequency_mhz; + +/// Initialize PK. +/// +/// \param noncritical_stack A stack area for noncritical interrupt handlers. +/// +/// \param noncritical_stack_size The size (in bytes) of the stack area for +/// noncritical interrupt handlers. +/// +/// \param critical_stack A stack area for critical interrupt handlers. +/// +/// \param critical_stack_size The size (in bytes) of the stack area for +/// critical interrupt handlers. +/// +/// \param initial_timebase The initial value of the PK timebase. If this +/// argument is given as the special value \c PK_TIMEBASE_CONTINUE, then the +/// timebase is not reset. +/// +/// \param timebase_frequency_hz The frequency of the PK timebase in Hz. +/// +/// This routine \e must be called before any other PK / routines, and \e +/// should be called before any interrupts are enabled. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_ARGUMENT_INIT A stack pointer is 0 or is given +/// a 0 size. +/// +/// \retval -PK_STACK_OVERFLOW One or both stacks are not large enough to +/// support a minimum context save in the event of an interrupt. + +// Note that PK does not rely on any static initialization of dynamic +// variables. In debugging sessions using RAM-resident PK images it is +// assumed that the processor may be reset at any time, so we always need to +// reset everything at initialization. + +int +pk_initialize(PkAddress noncritical_stack, + size_t noncritical_stack_size, + PkTimebase initial_timebase, + uint32_t timebase_frequency_hz) +{ + int rc; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF((noncritical_stack == 0) || + (noncritical_stack_size == 0), + PK_INVALID_ARGUMENT_INIT); + } + + if (initial_timebase != PK_TIMEBASE_CONTINUES) { + pk_timebase_set(initial_timebase); + } + + __pk_timebase_frequency_hz = timebase_frequency_hz; + __pk_timebase_frequency_khz = timebase_frequency_hz / 1000; + __pk_timebase_frequency_mhz = timebase_frequency_hz / 1000000; + + __pk_thread_machine_context_default = PK_THREAD_MACHINE_CONTEXT_DEFAULT; + + rc = __pk_stack_init(&noncritical_stack, &noncritical_stack_size); + if (rc) { + return rc; + } + + __pk_noncritical_stack = noncritical_stack; + __pk_noncritical_stack_size = noncritical_stack_size; + +#if PK_TIMER_SUPPORT + + // Initialize the time queue sentinel as a circular queue, set the next + // timeout and clear the cursor. + + pk_deque_sentinel_create((PkDeque*)&__pk_time_queue); + __pk_time_queue.cursor = 0; + __pk_time_queue.next_timeout = PK_TIMEBASE_MAX; + +#if PK_TRACE_SUPPORT +extern PkTimer g_pk_trace_timer; +extern PkTraceBuffer g_pk_trace_buf; + + // Schedule the timer that puts a 64bit timestamp in the trace buffer + // periodically. This allows us to use 32bit timestamps. + pk_timer_schedule(&g_pk_trace_timer, + PK_TRACE_TIMER_PERIOD, + 0); + + //set the trace timebase HZ + g_pk_trace_buf.hz = timebase_frequency_hz; + + //TODO: set the ppe instance id (for CME's) + +#endif /* PK_TRACE_SUPPORT */ + +#endif /* PK_TIMER_SUPPORT */ + +#if PK_THREAD_SUPPORT + + // Clear the priority map. The final entry [PK_THREADS] is for the idle + // thread. + + int i; + for (i = 0; i <= PK_THREADS; i++) { + __pk_priority_map[i] = 0; + } + + // Initialize the thread scheduler + + __pk_thread_queue_clear(&__pk_run_queue); + __pk_current_thread = 0; + __pk_next_thread = 0; + __pk_delayed_switch = 0; + +#endif /* PK_THREAD_SUPPORT */ + + return PK_OK; +} + + +/// Call the application main() +/// +/// __pk_main() is called from the bootloader. It's only purpose is to +/// provide a place for the PK_MAIN_HOOK to be called before main() is +/// called. + +void +__pk_main(int argc, char **argv) +{ + PK_MAIN_HOOK; + + int main(int argc, char **argv); + main(argc, argv); +} + + + + + + + + + diff --git a/pk/kernel/pk_kernel.h b/pk/kernel/pk_kernel.h new file mode 100644 index 00000000..7e88b828 --- /dev/null +++ b/pk/kernel/pk_kernel.h @@ -0,0 +1,266 @@ +#ifndef __PK_KERNEL_H__ +#define __PK_KERNEL_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_kernel.h +/// \brief PK portable kernel (non-API) data and data structures +/// +/// \todo In theory, as long as the critical section entry/exit macros use GCC +/// memory barriers, we should be able to eliminate all of the 'volatile' +/// declarations in PK code. These have been added to the port, so +/// we should try it. + +#ifdef __PK_CORE_C__ +#define IF__PK_CORE_C__(x) x +#define UNLESS__PK_CORE_C__(x) +#else +#define IF__PK_CORE_C__(x) +#define UNLESS__PK_CORE_C__(x) x +#endif + +#if PK_MINIMIZE_KERNEL_CODE_SPACE +#define IF_PK_MINIMIZE_KERNEL_CODE_SPACE(x) x +#define UNLESS_PK_MINIMIZE_KERNEL_CODE_SPACE(x) +#else +#define IF_PK_MINIMIZE_KERNEL_CODE_SPACE(x) +#define UNLESS_PK_MINIMIZE_KERNEL_CODE_SPACE(x) x +#endif + + +#ifndef __ASSEMBLER__ + +/// This is the stack pointer saved when switching from a thread or +/// non-critical interrupt context to a full-mode critical interrupt context. + +UNLESS__PK_CORE_C__(extern) +volatile +PkAddress __pk_saved_sp_critical; + +/// The critical interrupt stack; constant once defined by the call of +/// pk_initialize(). + +UNLESS__PK_CORE_C__(extern) +volatile +PkAddress __pk_critical_stack; + +/// This is the stack pointer saved when switching from a thread context to a +/// full-mode non-critical interrupt context. + +UNLESS__PK_CORE_C__(extern) +volatile +PkAddress __pk_saved_sp_noncritical; + +/// The non-critical interrupt stack; constant once defined by the call of +/// pk_initialize(). + +UNLESS__PK_CORE_C__(extern) +volatile +PkAddress __pk_noncritical_stack; + +/// This is the run queue - the queue of mapped runnable tasks. +UNLESS__PK_CORE_C__(extern) +volatile +PkThreadQueue __pk_run_queue; + +/// This flag is set by \c __pk_schedule() if a new highest-priority thread +/// becomes runnable during an interrupt handler. The context switch will +/// take place at the end of non-critical interrupt processing, and the +/// interrupt handling code will clear the flag. + +UNLESS__PK_CORE_C__(extern) +volatile +int __pk_delayed_switch; + +/// The currently running thread, or NULL (0) to indicate the idle thread +/// +/// \a __pk_current_thread holds a pointer to the currently executing +/// thread. This pointer will be NULL (0) under the following conditions: +/// +/// - After pk_initialize() but prior to pk_start_threads() +/// +/// - After pk_start_threads(), when no threads are runnable. In this case +/// the NULL (0) value indicates that the PK idle thread is 'running'. +/// +/// - After pk_start_threads(), when the current (non-idle) thread has +/// completed or been deleted. +/// +/// If \a __pk_current_thread == 0 then there is no requirement to save any +/// register state on a context switch, either because the PK idle thread has +/// no permanent context, or because any thread context on the kernel stack is +/// associated with a deleted thread. +/// +/// If \a __pk_current_thread != 0 then \a __pk_current_thread is a pointer +/// to the currently executing thread. In an interrupt handler \a +/// pk_current_thread is a pointer to the thread whose context is saved on +/// the kernel stack. +UNLESS__PK_CORE_C__(extern) +volatile +PkThread* __pk_current_thread; + +/// The thread to switch to during the next context switch, or NULL (0). +/// +/// \a __pk_next_thread is computed by __pk_schedule(). \a +/// __pk_next_thread holds a pointer to the thread to switch to at the next +/// context switch. In a thread context the switch happens immediately if \a +/// __pk_next_thread == 0 or \a __pk_next_thread != \a __pk_current_thread. +/// In an interrupt context the check happens at the end of processing all +/// interrupts. +/// +/// \a __pk_next_thread may be NULL (0) under the following +/// conditions: +/// +/// - After pk_initialize() but prior to pk_start_threads(), assuming no +/// threads have been made runnable. +/// +/// - After pk_start_threads(), when no threads are runnable. In this case +/// the NULL (0) value indicates that the PK idle thread is the next thread +/// to 'run'. +/// +/// If \a __pk_next_thread == 0 then there is no requirement to restore +/// any register state on a context switch, because the PK idle thread has +/// no permanent context. +/// +/// If \a __pk_next_thread != 0 then \a __pk_next_thread is a pointer +/// to the thread whose context will be restored at the next context switch. +UNLESS__PK_CORE_C__(extern) +volatile +PkThread* __pk_next_thread; + +/// The priority of \a __pk_next_thread +/// +/// If \a __pk_next_thread == 0, the \a __pk_next_priority == PK_THREADS. +UNLESS__PK_CORE_C__(extern) +volatile +PkThreadPriority __pk_next_priority; + +/// This variable holds the default thread machine context for newly created +/// threads. The idle thread also uses this context. This variable is normally +/// constant after the call of \c pk_initialize(). + +UNLESS__PK_CORE_C__(extern) +volatile +PkMachineContext __pk_thread_machine_context_default; + + +/// The size of the noncritical stack (bytes). + +UNLESS__PK_CORE_C__(extern) +volatile +size_t __pk_noncritical_stack_size; + +/// The size of the critical stack (bytes). + +UNLESS__PK_CORE_C__(extern) +volatile +size_t __pk_critical_stack_size; + +/// This table maps priorities to threads, and contains PK_THREADS + 1 +/// entries. The final entry is for the idle thread and will always be null +/// after initizlization. + +UNLESS__PK_CORE_C__(extern) +volatile +PkThread* __pk_priority_map[PK_THREADS + 1]; + +/// The PK time queue structure +/// +/// This structure is defined for use by the kernel, however applications +/// could also use this structure to define their own time queues. + +typedef struct { + + /// A sentinel node for the time queue. + /// + /// The time queue is an PkDeque managed as a FIFO queue for queue + /// management purpose, although events time out in time order. + /// + /// This pointer container is defined as the first element of the + /// structure to allow the PkTimeQueue to be cast to an PkDeque. + PkDeque queue; + + /// The next timeout in absolute time. + PkTimebase next_timeout; + + /// A pointer to allow preemption of time queue processing + /// + /// If non-0, then this is the next timer in the time queue to handle, or + /// a pointer to the \a queue object indicating no more timers to handle. + /// + /// \a cursor != 0 implies that time queue handler is in the midst of + /// processing the time queue, but has enabled interrupt preemption for + /// processing a timer handler. This means that 1) if the timer pointed to + /// by \a cursor is deleted then the cursor must be assigned to the + /// next timer in the queue; and 2) if a new timer is scheduled then + /// activating the next timeout will be handled by the timer handler. + PkDeque* cursor; + +} PkTimeQueue; + +UNLESS__PK_CORE_C__(extern) +PkTimeQueue __pk_time_queue; + +/// Return a pointer to the PkThread object of the currently running thread, +/// or NULL (0) if PK is idle or has not been started. +/// +/// In this API the current thread is not volatile - it will never change +/// inside application code - thus the 'volatile' is cast away. The PK kernel +/// does not (must not) use this API. + +UNLESS__PK_CORE_C__(extern) +inline PkThread * +pk_current(void) +{ + return (PkThread *)__pk_current_thread; +} + +/// Schedule the next timeout in a machine-specific way. + +void +__pk_schedule_hardware_timeout(PkTimebase timeout); + +/// The thread timeout handler. Portable. + +PK_TIMER_CALLBACK(__pk_thread_timeout); + +/// Generic stack initialization. Portable. + +int +__pk_stack_init(PkAddress *stack, + size_t *size); + +/// Machine-specific thread context initialization. + +void +__pk_thread_context_initialize(PkThread *thread, + PkThreadRoutine thread_routine, + void *arg); + +/// Machine specific resumption of __pk_next_thread at __pk_next_priority +/// without saving the current context. +void +__pk_next_thread_resume(void); + +/// Schedule a timer in the time queue. Portable. +void +__pk_timer_schedule(PkTimer *timer); + +/// Remove a timer from the time queue. Portable. +int +__pk_timer_cancel(PkTimer *timer); + +void +__pk_schedule(void); + + +// Call the application main(). Portable. + +void +__pk_main(int argc, char **argv); + +#endif /* __ASSEMBLER__ */ + +#endif /* __PK_KERNEL_H__ */ diff --git a/pk/kernel/pk_macros.h b/pk/kernel/pk_macros.h new file mode 100644 index 00000000..45bfbac6 --- /dev/null +++ b/pk/kernel/pk_macros.h @@ -0,0 +1,110 @@ +#ifndef __PK_MACROS_H__ +#define __PK_MACROS_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_macros.h +/// \brief Boilerplate macros for PK + +/// This macro encapsulates error handling boilerplate for code that uses the +/// PK API-type error handling, for errors that do not occur in critical +/// sections. + +#define PK_ERROR(code) \ + do { \ + if (PK_ERROR_PANIC) { \ + PK_PANIC(code); \ + } else { \ + return -(code); \ + } \ + } while (0) + + +/// This macro encapsulates error handling boilerplate in the PK API +/// functions, for errors that do not occur in critical sections. + +#define PK_ERROR_IF(condition, code) \ + do { \ + if (condition) { \ + PK_ERROR(code); \ + } \ + } while (0) + + +/// This macro encapsulates error handling boilerplate in the PK API +/// functions, for errors that do not occur in critical sections and always +/// force a kernel panic, indicating a kernel or API bug. + +#define PK_PANIC_IF(condition, code) \ + do { \ + if (condition) { \ + PK_PANIC(code); \ + } \ + } while (0) + + +/// This macro encapsulates error handling boilerplate in the PK API +/// functions, for errors that do not occur in critical sections. +/// The error handling will only be enabled when PK_ERROR_CHECK_API +/// is enabled. + +#define PK_ERROR_IF_CHECK_API(condition, code) \ + do { \ + if (PK_ERROR_CHECK_API) { \ + PK_ERROR_IF(condition, code); \ + } \ + } while (0) + +/// This macro encapsulates error handling boilerplate in the PK API +/// functions, for errors that occur in critical sections. + +#define PK_ERROR_IF_CRITICAL(condition, code, context) \ + do { \ + if (condition) { \ + if (PK_ERROR_PANIC) { \ + PK_PANIC(code); \ + pk_critical_section_exit(context); \ + } else { \ + pk_critical_section_exit(context); \ + return -(code); \ + } \ + } \ + } while (0) + + +/// This is a general macro for errors that require cleanup before returning +/// the error code. + +#define PK_ERROR_IF_CLEANUP(condition, code, cleanup) \ + do { \ + if (condition) { \ + if (PK_ERROR_PANIC) { \ + PK_PANIC(code); \ + cleanup; \ + } else { \ + cleanup; \ + return -(code); \ + } \ + } \ + } while (0) + + + +/// Some PK APIs can only be called from thread contexts - these are APIs +/// that threads call on 'themselves'. + +#define PK_ERROR_UNLESS_THREAD_CONTEXT() \ + PK_ERROR_IF(!__pk_kernel_context_thread(), \ + PK_ILLEGAL_CONTEXT_THREAD_CONTEXT) + + +/// Some PK APIs must be called from an interrupt context only. + +#define PK_ERROR_UNLESS_ANY_INTERRUPT_CONTEXT() \ + PK_ERROR_IF(!__pk_kernel_context_any_interrupt(), \ + PK_ILLEGAL_CONTEXT_INTERRUPT_CONTEXT) + +#endif /* __PK_MACROS_H__ */ diff --git a/pk/kernel/pk_semaphore_core.c b/pk/kernel/pk_semaphore_core.c new file mode 100644 index 00000000..9079ff01 --- /dev/null +++ b/pk/kernel/pk_semaphore_core.c @@ -0,0 +1,328 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_semaphore_core.c +/// \brief PK semaphore APIs +/// +/// The entry points in this file are considered 'core' routines that will +/// always be present at runtime in any PK application that enables +/// semaphores. + +#include "pk.h" + +/// Post a count to a semaphore +/// +/// \param semaphore A pointer to the semaphore +/// +/// If any thread is pending on the semaphore, the highest priority thread +/// will be made runnable and the internal count will remain 0. +/// +/// If no thread is pending on the semaphore then the internal count will be +/// incremented by 1, with overflow wrapping the internal count through 0. If +/// the \a max_count argument supplied when the semaphore was created is +/// non-zero and the new internal count is greater than the \a max_count, an +/// overflow error will be signalled. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_ILLEGAL_CONTEXT The API was called from a critical interrupt +/// context. +/// +/// \retval -PK_INVALID_SEMAPHORE_AT_POST The \a semaphore is a null (0) pointer. +/// +/// \retval -PK_SEMAPHORE_OVERFLOW The \a max_count argument supplied when +/// the semaphore was created is non-zero and the new internal count is +/// greater than the \a max_count. + +int +pk_semaphore_post(PkSemaphore *semaphore) +{ + PkMachineContext ctx; + PkThreadPriority priority; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(semaphore == 0, PK_INVALID_SEMAPHORE_AT_POST); + } + + pk_critical_section_enter(&ctx); + + priority = __pk_thread_queue_min(&(semaphore->pending_threads)); + + if (priority != PK_IDLE_THREAD_PRIORITY) { + + __pk_thread_queue_delete(&(semaphore->pending_threads), priority); + __pk_thread_queue_insert(&__pk_run_queue, priority); + + PK_TRACE_THREAD_SEMAPHORE_POST(priority); + + __pk_schedule(); + + } else { + + semaphore->count++; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF((semaphore->max_count > 0) && + (semaphore->count > semaphore->max_count), + PK_SEMAPHORE_OVERFLOW); + } + } + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + + +/// Pend on a semaphore with timeout +/// +/// \param semaphore A pointer to the semaphore +/// +/// \param timeout A relative timeout in PK timebase ticks, including the +/// special values PK_NO_WAIT and PK_WAIT_FOREVER +/// +/// This API is normally called from threads, and can only be successfully +/// called from interupt handlers under special conditions. +/// +/// If the internal count of the \a semaphore is non-zero, the internal count +/// is decremented by one and execution of the caller continues. +/// +/// If the internal count of the \a semaphore is zero and the \a timeout is +/// PK_NO_WAIT (0) then the call returns immediately with the informational +/// code -PK_SEMAPHORE_PEND_NO_WAIT. +/// +/// If the internal count of the \a semaphore is zero and the \a timeout is +/// non-zero then a thread will block until either a semaphore count is +/// acquired or the relative timeout expires. If this condition occurs in a +/// call from an interrupt context or before threads have been started then +/// the call will fail with the error \c -PK_SEMAPHORE_PEND_WOULD_BLOCK. +/// +/// Once timed out the thread is removed from the semaphore pending queue and +/// made runnable, and the pk_semaphore_pend() operation will fail, even if +/// the semaphore count becomes available before the thread runs again. The +/// pk_semaphore_pend() API returns the informational code +/// -PK_SEMAPHORE_PEND_TIMED_OUT in this case. +/// +/// By convention, a timeout interval equal to the maximum possible value of +/// the \c PkInterval type is taken to mean "wait forever". A thread blocked +/// on a semaphore in this mode will never time out. PK provides this +/// constant as \c PK_WAIT_FOREVER. +/// +/// Return values other than PK_OK (0) are not necessarily errors; see \ref +/// pk_errors +/// +/// The following return codes are non-error codes: +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_SEMAPHORE_PEND_NO_WAIT timeout is set to PK_NO_WAIT +/// +/// \retval -PK_SEMAPHORE_PEND_TIMED_OUT The semaphore was not acquired +/// before the timeout expired. +/// +/// The following return codes are error codes: +/// +/// \retval -PK_ILLEGAL_CONTEXT The API was called from a critical interrupt +/// context. +/// +/// \retval -PK_INVALID_SEMAPHORE_AT_PEND The \a semaphore is a null (0) +/// pointer. +/// +/// \retval -PK_SEMAPHORE_PEND_WOULD_BLOCK The call was made from an +/// interrupt context (or before threads have been started), the semaphore +/// internal count was 0 and a non-zero timeout was specified. + +// Note: Casting __pk_current_thread removes the 'volatile' attribute. + +int +pk_semaphore_pend(PkSemaphore *semaphore, + PkInterval timeout) +{ + PkMachineContext ctx; + PkThreadPriority priority; + PkThread *thread; + PkTimer *timer = 0; + int rc = PK_OK; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(semaphore == 0, PK_INVALID_SEMAPHORE_AT_PEND); + } + + pk_critical_section_enter(&ctx); + + if (semaphore->count != 0) { + + semaphore->count--; + + } else if (timeout == PK_NO_WAIT) { + + rc = -PK_SEMAPHORE_PEND_NO_WAIT; + + } else { + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF_CRITICAL(!__pk_kernel_context_thread(), + PK_SEMAPHORE_PEND_WOULD_BLOCK, + &ctx); + } + + thread = (PkThread *)__pk_current_thread; + priority = thread->priority; + + __pk_thread_queue_insert(&(semaphore->pending_threads), priority); + + thread->semaphore = semaphore; + thread->flags |= PK_THREAD_FLAG_SEMAPHORE_PEND; + + PK_TRACE_THREAD_SEMAPHORE_PEND(priority); + + if (timeout != PK_WAIT_FOREVER) { + timer = &(thread->timer); + timer->timeout = pk_timebase_get() + timeout; + __pk_timer_schedule(timer); + thread->flags |= PK_THREAD_FLAG_TIMER_PEND; + } + + __pk_thread_queue_delete(&__pk_run_queue, priority); + __pk_schedule(); + + thread->flags &= ~PK_THREAD_FLAG_SEMAPHORE_PEND; + + if (thread->flags & PK_THREAD_FLAG_TIMER_PEND) { + if (thread->flags & PK_THREAD_FLAG_TIMED_OUT) { + rc = -PK_SEMAPHORE_PEND_TIMED_OUT; + __pk_thread_queue_delete(&(semaphore->pending_threads), thread->priority); + } else { + __pk_timer_cancel(timer); + } + thread->flags &= + ~(PK_THREAD_FLAG_TIMER_PEND | PK_THREAD_FLAG_TIMED_OUT); + } + } + + pk_critical_section_exit(&ctx); + + return rc; +} + + +/// Release all threads blocked on a semaphore +/// +/// \param semaphore A pointer to a semaphore +/// +/// This API is provided to allow an PK semaphore to be used as a thread +/// barrier. pk_semaphore_release_all() simultaneously unblocks all threads +/// (if any) currently pending on a semaphore. A semaphore to be used as a +/// thread barrier will typically be initialized with +/// pk_semaphore_create(\a sem, 0, 0), and sxx_semaphore_post() would never be +/// called on the \a sem. +/// +/// This API never modifies the \a count field of the semaphore; If any +/// threads are blocked on a semaphore the semaphore count is 0 by definition. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_ILLEGAL_CONTEXT The API was called from a critical interrupt +/// context. +/// +/// \retval -PK_INVALID_SEMAPHORE_AT_RELEASE The \a semaphore is a null (0) +/// pointer. + +int +pk_semaphore_release_all(PkSemaphore* semaphore) +{ + PkMachineContext ctx; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(semaphore == 0, PK_INVALID_SEMAPHORE_AT_RELEASE); + } + + pk_critical_section_enter(&ctx); + + __pk_thread_queue_union(&__pk_run_queue, &(semaphore->pending_threads)); + __pk_thread_queue_clear(&(semaphore->pending_threads)); + __pk_schedule(); + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + + +/// Get information about a semaphore. +/// +/// \param semaphore A pointer to the PkSemaphore to query +/// +/// \param count The value returned through this pointer is the current count +/// of the semaphore. The caller can set this parameter to the null pointer +/// (0) if this information is not required. +/// +/// \param pending The value returned through this pointer is the current +/// number of threads pending on the semaphore. The caller can set this +/// parameter to the null pointer (0) if this information is not required. +/// +/// The information returned by this API can only be guaranteed consistent if +/// the API is called from a critical section. Since the +/// implementation of this API does not require a critical section, it is not +/// an error to call this API from a critical interrupt context. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_SEMAPHORE_AT_INFO The \a semaphore is a null (0) +/// pointer. + +int +pk_semaphore_info_get(PkSemaphore* semaphore, + PkSemaphoreCount* count, + int* pending) + +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(semaphore == 0, PK_INVALID_SEMAPHORE_AT_INFO); + } + + if (count) { + *count = semaphore->count; + } + if (pending) { + *pending = __pk_thread_queue_count(&(semaphore->pending_threads)); + } + + return PK_OK; +} + + +/// An simple interrupt handler that posts to a semaphore. +/// +/// To implement basic event-driven blocking of a thread, install +/// pk_semaphore_post_handler() as the handler for a non-critical interrupt +/// and provide a pointer to the semaphore as the \a arg argument in +/// pk_irq_handler_set(). The semaphore should be initialized with +/// pk_semaphore_create(&sem, 0, 1). This handler simply disables (masks) +/// the interrupt, clears the status and calls pk_semaphore_post() on the +/// semaphore. +/// +/// Note that clearing the status in the interrupt controller as done here is +/// effectively a no-op for level-sensitive interrupts. In the level-sensitive +/// case any thread pending on the semaphore must reset the interrupt +/// condition in the device before re-enabling the interrupt. +#if 0 +void +pk_semaphore_post_handler_full(void *arg, PkIrqId irq, int priority) +{ + pk_irq_disable(irq); + pk_irq_status_clear(irq); + pk_semaphore_post((PkSemaphore *)arg); +} + +PK_IRQ_FAST2FULL(pk_semaphore_post_handler, pk_semaphore_post_handler_full); +#endif diff --git a/pk/kernel/pk_semaphore_init.c b/pk/kernel/pk_semaphore_init.c new file mode 100644 index 00000000..bed029da --- /dev/null +++ b/pk/kernel/pk_semaphore_init.c @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_semaphore_init.c +/// \brief PK semaphore API initialization routines +/// +/// The entry points in this file are routines that are typically used during +/// initialization, and their code space could be deallocated and recovered if +/// no longer needed by the application after initialization. + +#include "pk.h" + +/// Create (initialize) a semaphore +/// +/// \param semaphore A pointer to an PkSemaphore structure to initialize +/// +/// \param initial_count The initial count of the semaphore +/// +/// \param max_count The maximum count allowed in the semaphore, for error +/// checking +/// +/// Semaphores are created (initialized) by a call of \c +/// pk_semaphore_create(), using an application-provided instance of an \c +/// PkSemaphore structure. This structure \e is the semaphore, so the +/// application must never modify the structure if the semaphore is in use. +/// PK has no way to know if an \c PkSemaphore structure provided to +/// \c pk_semaphore_create() is safe to use as a semaphore, and will silently +/// modify whatever memory is provided. +/// +/// PK provides two simple overflow semantics based on the value of max_count +/// in the call of \c pk_semaphore_create(). +/// +/// If \a max_count = 0, then posting to the semaphore first increments the +/// internal count by 1. Overflows are ignored and will wrap the internal +/// count through 0. +/// +/// If \a max_count != 0, then posting to the semaphore first increments the +/// internal count by 1, wrapping through 0 in the event of overflow. If the +/// resulting count is greater than max_count, \c pk_semaphore_post() will +/// return the error \c -PK_SEMAPHORE_POST_OVERFLOW to the caller. +/// +/// In most applications it is probably best to use the \a max_count != 0 +/// semantics to trap programming errors, unless there is a specific +/// application where overflow is expected and ignorable. As a fine point of +/// the specification, a \a max_count of 0 is equivalent to a max_count of +/// 0xFFFFFFFF. +/// +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_SEMAPHORE_AT_CREATE The \a semaphore is a null (0) +/// pointer. +/// +/// \retval -PK_INVALID_ARGUMENT_SEMAPHORE The \a max_count is non-zero +/// and less than the \a initial_count. + +int +pk_semaphore_create(PkSemaphore *semaphore, + PkSemaphoreCount initial_count, + PkSemaphoreCount max_count) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(semaphore == 0, PK_INVALID_SEMAPHORE_AT_CREATE); + PK_ERROR_IF((max_count != 0) && (initial_count > max_count), + PK_INVALID_ARGUMENT_SEMAPHORE); + } + + __pk_thread_queue_clear(&(semaphore->pending_threads)); + semaphore->count = initial_count; + semaphore->max_count = max_count; + + return PK_OK; +} + + + + + diff --git a/pk/kernel/pk_stack_init.c b/pk/kernel/pk_stack_init.c new file mode 100644 index 00000000..c3ddc90a --- /dev/null +++ b/pk/kernel/pk_stack_init.c @@ -0,0 +1,85 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_stack_init.c +/// \brief PK stack initialization +/// +/// The entry points in this file are initialization routines - they are never +/// needed after PK initialization and their code space could be reclaimed by +/// the application after initialization if required. +/// +/// This code was split out from "pk_init.c" because it may be needed in a +/// thread configuration if threads are being created dynamically. in an +/// interrupt-only configuration it is not needed after \c pk_initialize(). + +#include "pk.h" + +/// Initialize a stack area. +/// +/// \param stack A pointer to the smallest legal address of the stack. The +/// stack address is modified as the stack is aligned and initialized. +/// +/// \param size A pointer to the size of the stack (in bytes). The size is +/// modified as the stack is aligned and initialized. At exit this is the +/// final usable stack area size aligned to the size of the PK_STACK_TYPE. +/// +/// PK makes no assumptions about size or alignment of the area provided as a +/// stack, and carefully aligns and initializes the stack. Regardless of how +/// the stack grows, the \a stack parameter is considered to be the lowest +/// legal address of the stack. + +int +__pk_stack_init(PkAddress *stack, + size_t *size) +{ + PkAddress mask; + size_t excess, i, count; + PK_STACK_TYPE *p; + + if (PK_STACK_DIRECTION < 0) { + + // Stacks grow down. The initial stack pointer is set to just above + // the last allocated stack address. This is legal for pre-decrement + // stacks, otherwise the initial address is first brought into range + // before alignment. The stack is aligned downward, then the size is + // adjusted to a multiple of the stack type. Stacks are optionally + // prepatterned. Alignment is assumed to be a power of 2. + + *stack += *size; + + if (!PK_STACK_PRE_DECREMENT) { + *stack -= sizeof(PK_STACK_TYPE); + *size -= sizeof(PK_STACK_TYPE); + } + + mask = PK_STACK_ALIGNMENT - 1; + excess = *stack & mask; + *stack -= excess; + *size -= excess; + *size = (*size / sizeof(PK_STACK_TYPE)) * sizeof(PK_STACK_TYPE); + + if (PK_STACK_CHECK) { + p = (PK_STACK_TYPE *)(*stack); + count = *size / sizeof(PK_STACK_TYPE); + for (i = 0; i < count; i++) { + if (PK_STACK_PRE_DECREMENT) { + *(--p) = PK_STACK_PATTERN; + } else { + *(p--) = PK_STACK_PATTERN; + } + } + } + + __pk_stack_create_initial_frame(stack, size); + + } else { + + PK_PANIC(PK_UNIMPLEMENTED); + } + + return PK_OK; +} + diff --git a/pk/kernel/pk_thread_core.c b/pk/kernel/pk_thread_core.c new file mode 100644 index 00000000..539b3321 --- /dev/null +++ b/pk/kernel/pk_thread_core.c @@ -0,0 +1,939 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_thread_core.c +/// \brief PK thread APIs +/// +/// The entry points in this file are considered 'core' routines that will +/// always be present at runtime in any PK application that enables threads. + +#include "pk.h" + +#define __PK_THREAD_CORE_C__ + + +// This routine is only used locally. Noncritical interrupts must be disabled +// at entry. + +static inline int +__pk_thread_is_active(PkThread *thread) +{ + return ((thread->state != PK_THREAD_STATE_COMPLETED) && + (thread->state != PK_THREAD_STATE_DELETED)); +} + + +// This routine is only used locally. Noncritical interrupts must be disabled +// at entry. + +static inline int +__pk_thread_is_mapped(PkThread *thread) +{ + return (thread->state == PK_THREAD_STATE_MAPPED); +} + + +// This routine is only used locally. Noncritical interrupts must be disabled +// at entry. This is only called on mapped threads. + +static inline int +__pk_thread_is_runnable(PkThread *thread) +{ + return __pk_thread_queue_member(&__pk_run_queue, thread->priority); +} + + +// This routine is only used locally. Noncritical interrupts must be disabled +// at entry. + +static inline PkThread* +__pk_thread_at_priority(PkThreadPriority priority) +{ + return (PkThread*)__pk_priority_map[priority]; +} + + +// This routine is only used locally. Noncritical interrupts must be disabled +// at entry. The caller must also have checked that the priority is free. +// This routine is only called on threads known to be in a suspended state, +// either PK_THREAD_STATE_SUSPENDED_RUNNABLE or +// PK_THREAD_STATE_SUSPENDED_BLOCKED. Mapping a runnable thread adds it to +// the run queue. Mapping a thread pending on a semaphore either takes the +// count and becomes runnable or adds the thread to the pending queue for the +// semaphore. Mapping a sleeping thread requires no further action +// here. Scheduling after the map must be handled by the caller. + +void +__pk_thread_map(PkThread* thread) +{ + PkThreadPriority priority; + + priority = thread->priority; + __pk_priority_map[priority] = thread; + + if (thread->state == PK_THREAD_STATE_SUSPENDED_RUNNABLE) { + + __pk_thread_queue_insert(&__pk_run_queue, priority); + + } else if (thread->flags & PK_THREAD_FLAG_SEMAPHORE_PEND) { + + if (thread->semaphore->count) { + + thread->semaphore->count--; + __pk_thread_queue_insert(&__pk_run_queue, priority); + + } else { + + __pk_thread_queue_insert(&(thread->semaphore->pending_threads), + priority); + } + } + + thread->state = PK_THREAD_STATE_MAPPED; + + if (PK_KERNEL_TRACE_ENABLE) { + if (__pk_thread_is_runnable(thread)) { + PK_TRACE_THREAD_MAPPED_RUNNABLE(priority); + } else if (thread->flags & PK_THREAD_FLAG_SEMAPHORE_PEND) { + PK_TRACE_THREAD_MAPPED_SEMAPHORE_PEND(priority); + } else { + PK_TRACE_THREAD_MAPPED_SLEEPING(priority); + } + } +} + + +// This routine is only used locally. Noncritical interrupts must be disabled +// at entry. This routine is only ever called on threads in the +// PK_THREAD_STATE_MAPPED. Unmapping a thread removes it from the priority +// map, the run queue and any semaphore pend, but does not cancel any +// timers. Scheduling must be handled by the code calling +// __pk_thread_unmap(). + +void +__pk_thread_unmap(PkThread *thread) +{ + PkThreadPriority priority; + + priority = thread->priority; + __pk_priority_map[priority] = 0; + + if (__pk_thread_is_runnable(thread)) { + + thread->state = PK_THREAD_STATE_SUSPENDED_RUNNABLE; + __pk_thread_queue_delete(&__pk_run_queue, priority); + + } else { + + thread->state = PK_THREAD_STATE_SUSPENDED_BLOCKED; + if (thread->flags & PK_THREAD_FLAG_SEMAPHORE_PEND) { + __pk_thread_queue_delete(&(thread->semaphore->pending_threads), + priority); + } + } +} + + +// Schedule and run the highest-priority mapped runnable thread. +// +// The priority of the next thread to run is first computed. This may be +// PK_THREADS, indicating that the only thread to run is the idle thread. +// This will always cause (or defer) a 'context switch' to the idle thread. +// Otherwise, if the new thread is not equal to the current thread this will +// also cause (or defer) a context switch. Note that scheduling is defined in +// terms of priorities but actually implemented in terms of PkThread pointers. +// +// If we are not yet in thread mode we're done - threads will be started by +// pk_start_threads() later. If we're in thread context a context switch +// happens immediately. In an interrupt context the switch is deferred to the +// end of interrupt processing. + +void +__pk_schedule(void) +{ + __pk_next_priority = __pk_thread_queue_min(&__pk_run_queue); + __pk_next_thread = __pk_priority_map[__pk_next_priority]; + + if ((__pk_next_thread == 0) || + (__pk_next_thread != __pk_current_thread)) { + + if (__pk_kernel_mode_thread()) { + if (__pk_kernel_context_thread()) { + if (__pk_current_thread != 0) { + __pk_switch(); + } else { + __pk_next_thread_resume(); + } + } else { + __pk_delayed_switch = 1; + } + } + } +} + + +// This routine is only used locally. +// +// Completion and deletion are pretty much the same thing. Completion is +// simply self-deletion of the current thread (which is mapped by +// definition.) The complete/delete APIs have slightly different error +// conditions but are otherwise the same. +// +// Deleting a mapped thread first unmaps (suspends) the thread, which takes +// care of removing the thread from any semaphores it may be pending on. Then +// any outstanding timer is also cancelled. +// +// If the current thread is being deleted we install the idle thread as +// __pk_current_thread, so scheduling is forced and no context is saved on +// the context switch. +// +// Note that we do not create trace events for unmapped threads since the trace +// tag only encodes the priority, which may be in use by a mapped thread. + +void +__pk_thread_delete(PkThread *thread, PkThreadState final_state) +{ + PkMachineContext ctx; + int mapped; + + pk_critical_section_enter(&ctx); + + mapped = __pk_thread_is_mapped(thread); + + if (mapped) { + __pk_thread_unmap(thread); + } + + __pk_timer_cancel(&(thread->timer)); + thread->state = final_state; + + if (mapped) { + + if (PK_KERNEL_TRACE_ENABLE) { + if (final_state == PK_THREAD_STATE_DELETED) { + PK_TRACE_THREAD_DELETED(thread->priority); + } else { + PK_TRACE_THREAD_COMPLETED(thread->priority); + } + } + + if (thread == __pk_current_thread) { + __pk_current_thread = 0; + } + __pk_schedule(); + } + + pk_critical_section_exit(&ctx); +} + + +// Generic thread timeout +// +// This routine is called as a timer callback either because a sleeping thread +// has timed out or a thread pending on a semaphore has timed out. If the +// thread is not already runnable then the the timeout flag is set, and if the +// thread is mapped it is scheduled. +// +// This implementation allows that a thread blocked on a timer may have been +// made runnable by some other mechanism, such as acquiring a semaphore. In +// order to provide an iteration-free implementation of +// pk_semaphore_release_all(), cancelling any semaphore timeouts is deferred +// until the thread runs again. +// +// __pk_thread_timeout() is currenly the only timer interrupt called from a +// critical section. +// +// Note that we do not create trace events for unmapped threads since the trace +// tag only encodes the priority, which may be in use by a mapped thread. + +void +__pk_thread_timeout(void *arg) +{ + PkThread *thread = (PkThread *)arg; + + switch (thread->state) { + + case PK_THREAD_STATE_MAPPED: + if (!__pk_thread_is_runnable(thread)) { + thread->flags |= PK_THREAD_FLAG_TIMED_OUT; + __pk_thread_queue_insert(&__pk_run_queue, thread->priority); + __pk_schedule(); + } + break; + + case PK_THREAD_STATE_SUSPENDED_RUNNABLE: + break; + + case PK_THREAD_STATE_SUSPENDED_BLOCKED: + thread->flags |= PK_THREAD_FLAG_TIMED_OUT; + thread->state = PK_THREAD_STATE_SUSPENDED_RUNNABLE; + break; + + default: + PK_PANIC(PK_THREAD_TIMEOUT_STATE); + } +} + + +// This routine serves as a container for the PK_START_THREADS_HOOK and +// actually starts threads. The helper routine __pk_call_pk_start_threads() +// arranges this routine to be called with interrupts disabled while running +// on the noncritical interrupt stack. +// +// The reason for this roundabout is that we want to be able to run a hook +// routine (transparent to the application) that can hand over every last byte +// of free memory to "malloc()" - including the stack of main(). Since we +// always need to run on some stack, we chose to run the hook on the kernel +// noncritical interrupt stack. However to do this safely we need to make sure +// that no interrupts will happen during this time. When __pk_thread_resume() +// is finally called all stack-based context is lost but it doesn't matter at +// that point - it's a one-way street into thread execution. +// +// This is considered part of pk_start_threads() and so is also considered a +// 'core' routine. + +void +__pk_start_threads(void) +{ + PK_START_THREADS_HOOK; + + __pk_next_thread_resume(); + + PK_PANIC(PK_START_THREADS_RETURNED); +} + + +/// Start PK threads +/// +/// This routine starts the PK thread scheduler infrastructure. This routine +/// must be called after a call of \c pk_initialize(). This routine never +/// returns. Interrupt (+ timer) only configurations of PK need not call this +/// routine. +/// +/// Note: This tiny routine is considered a 'core' routine so that the +/// initialziation code can safely recover all 'init' code space before +/// starting threads. +/// +/// This routine typically does not return - any return value indicates an +/// error; see \ref pk_errors +/// +/// \retval -PK_ILLEGAL_CONTEXT_THREAD The API was called twice. + +int +pk_start_threads(void) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(__pk_kernel_mode_thread(), PK_ILLEGAL_CONTEXT_THREAD); + } + + __pk_call_pk_start_threads(); + + return 0; +} + + +/// Resume a suspended thread +/// +/// \param thread The thread to resume +/// +/// PK only allows one thread at a time to run at a given priority, and +/// implements the notion of a thread \e claiming a priority. A suspended +/// thread claims a priority when it is mapped by a call of +/// pk_thread_resume(). This API will succeed only if no other active thread +/// is currently mapped at the priority assigned to the thread. PK provides +/// the pk_thread_at_priority() API which allows an application-level +/// scheduler to correctly manage multiple threads running at the same +/// priority. +/// +/// If the thread was sleeping while suspended it remains asleep. However if +/// the sleep timer timed out while the thread was suspended it will be +/// resumed runnable. +/// +/// If the thread was blocked on a semaphore when it was suspended, then when +/// the thread is resumed it will attempt to reacquire the semaphore. +/// However, if the thread was blocked on a semaphore with timeout while +/// suspended and the timeout interval has passed, the thread will be resumed +/// runnable and see that the semaphore pend timed out. +/// +/// It is not an error to call pk_thread_resume() on a mapped +/// thread. However it is an error to call pk_thread_resume() on a completed +/// or deleted thread. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion, including calls on a \a thread that is +/// already mapped. +/// +/// \retval -PK_ILLEGAL_CONTEXT_THREAD The API was called +/// from a critical interrupt context. +/// +/// \retval -PK_INVALID_THREAD_AT_RESUME1 The \a thread is a null (0) pointer. +/// +/// \retval -PK_INVALID_THREAD_AT_RESUME2 The \a thread is not active, +/// i.e. has completed or been deleted. +/// +/// \retval -PK_PRIORITY_IN_USE_AT_RESUME Another thread is already mapped at +/// the priority of the \a thread. + +int +pk_thread_resume(PkThread *thread) +{ + PkMachineContext ctx; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(thread == 0, PK_INVALID_THREAD_AT_RESUME1); + } + + pk_critical_section_enter(&ctx); + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF_CRITICAL(!__pk_thread_is_active(thread), + PK_INVALID_THREAD_AT_RESUME2, + &ctx); + } + + if (!__pk_thread_is_mapped(thread)) { + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF_CRITICAL(__pk_priority_map[thread->priority] != 0, + PK_PRIORITY_IN_USE_AT_RESUME, + &ctx); + } + __pk_thread_map(thread); + __pk_schedule(); + } + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + + +/// Suspend a thread +/// +/// Any active thread can be suspended. A suspended thread 1) remains active +/// but will not be scheduled; 2) relinquishes its priority assignment, +/// allowing another thread to be resumed at the suspended thread's priority; +/// and 3) disassociates from any semaphore mutual exclusion it may have been +/// participating in. +/// +/// If a sleeping thread is suspended, the sleep timer remains active but a +/// timeout of the timer simply marks the thread as runnable, but does not +/// resume the thread. +/// +/// If a thread blocked on a semaphore is suspended, the thread no longer +/// participates in the semaphore mutual exclusion. If the thread is later +/// resumed it will attempt to acquire the semaphore again the next time it +/// runs (unless it was blocked with a timeout and the timeout has expired). +/// +/// If a thread blocked on a semaphore with timeout is suspended, the +/// semaphore timeout timer continues to run. If the timer times out while the +/// thread is suspended the thread is simply marked runnable. If the thread is +/// later resumed, the suspended call of \c pk_semaphore_pend() will return the +/// timeout code -PK_SEMAPHORE_PEND_TIMED_OUT. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion, including calls on a \a thread that is +/// already suspended. +/// +/// \retval -PK_ILLEGAL_CONTEXT_THREAD The API was called from a critical +/// interrupt context. +/// +/// \retval -PK_INVALID_THREAD_AT_SUSPEND1 The \a thread is a null (0) pointer +/// +/// \retval -PK_INVALID_THREAD_AT_SUSPEND2 The \a thread is not active, +/// i.e. has completed or been deleted. + +int +pk_thread_suspend(PkThread *thread) +{ + PkMachineContext ctx; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF((thread == 0), PK_INVALID_THREAD_AT_SUSPEND1); + } + + pk_critical_section_enter(&ctx); + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF_CRITICAL(!__pk_thread_is_active(thread), + PK_INVALID_THREAD_AT_SUSPEND2, + &ctx); + } + + if (__pk_thread_is_mapped(thread)) { + + PK_TRACE_THREAD_SUSPENDED(thread->priority); + __pk_thread_unmap(thread); + __pk_schedule(); + } + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + + +/// Delete a thread +/// +/// Any active thread can be deleted. If a thread is deleted it is removed +/// from the run queue, deleted from the timer queue (if sleeping or blocked +/// on a semaphore with timeout), and deleted from the semaphore mutual +/// exclusion if blocked on a semaphore. The thread control block is then +/// marked as deleted. +/// +/// Once a thread has completed or been deleted the thread structure and +/// thread stack areas can be used for other purposes. +/// +/// \param thread The thread to delete +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors. If a +/// thread deletes itself this API does not return at all. +/// +/// \retval 0 Successful completion, including calls on a \a thread that has +/// completed or had already been deleted. +/// +/// \retval -PK_ILLEGAL_CONTEXT_THREAD The API was called from a critical +/// interrupt context. +/// +/// \retval -PK_INVALID_THREAD_AT_DELETE The \a thread is a null (0) pointer. + +int +pk_thread_delete(PkThread *thread) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(thread == 0, PK_INVALID_THREAD_AT_DELETE); + } + + __pk_thread_delete(thread, PK_THREAD_STATE_DELETED); + + return PK_OK; +} + + +/// Complete a thread +/// +/// If a thread ever returns from the subroutine defining the thread entry +/// point, the thread is removed from all PK kernel data structures and +/// marked completed. The thread routine can also use the API pk_complete() +/// to make this more explicit if desired. PK makes no distinction between +/// completed and deleted threads, but provides these indications for +/// the benefit of the application. +/// +/// Note that this API is only available from the current thread to mark its +/// own completion. +/// +/// Once a thread has completed or been deleted the thread structure and +/// thread stack areas can be used for other purposes. +/// +/// Any return value indicates an error; see \ref pk_errors. In the event of +/// a successful completion this API does not return to the caller, which is +/// always the thread context being completed. +/// +/// \retval -PK_ILLEGAL_CONTEXT_THREAD The API was not called from a thread +/// context. + +// Note: Casting __pk_current_thread removes the 'volatile' attribute. + +int +pk_complete(void) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_UNLESS_THREAD_CONTEXT(); + } + + __pk_thread_delete((PkThread *)__pk_current_thread, + PK_THREAD_STATE_COMPLETED); + + return PK_OK; +} + + +/// Sleep a thread until an absolute time +/// +/// \param time An absolute time as measured by the PK timebase +/// +/// Threads can use this API to sleep until an absolute time. Sleeping threads +/// are not scheduled, although they maintain their priorities. This differs +/// from thread suspension, where the suspended thread relinquishes its +/// priority. When the sleep timer times out the thread becomes runnable +/// again, and will run as soon as it becomes the highest-priority mapped +/// runnable thread. +/// +/// Sleeping threads may also be later suspended. In this case the Sleep timer +/// continues to run, and if it times out before the thread is resumed the +/// thread will be immediately runnable when it is resumed. +/// +/// See the PK specification for a full discussion of how PK handles +/// scheduling events at absolute times "in the past". Briefly stated, if the +/// \a time is in the past, the thread will Sleep for the briefest possible +/// period supported by the hardware. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion. +/// +/// \retval -PK_ILLEGAL_CONTEXT_THREAD The API was not called from a thread +/// context. + +// Note: Casting __pk_current_thread removes the 'volatile' attribute. + +int +pk_sleep_absolute(PkTimebase time) +{ + PkMachineContext ctx; + PkThread *current; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_UNLESS_THREAD_CONTEXT(); + } + + pk_critical_section_enter(&ctx); + + current = (PkThread *)__pk_current_thread; + + current->timer.timeout = time; + __pk_timer_schedule(&(current->timer)); + + current->flags |= PK_THREAD_FLAG_TIMER_PEND; + + PK_TRACE_THREAD_SLEEP(current->priority); + + __pk_thread_queue_delete(&__pk_run_queue, current->priority); + __pk_schedule(); + + current->flags &= ~(PK_THREAD_FLAG_TIMER_PEND | PK_THREAD_FLAG_TIMED_OUT); + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + +/// Sleep a thread for an interval relative to the current time. +/// +/// \param interval A time interval relative to the current timebase. +/// +/// Threads can use this API to sleep for a time relative to the current +/// timebase. The absolute timeout is \c pk_timebase_get() + \a interval. +/// +/// Sleeping threads are not scheduled, although they maintain their +/// priorities. This differs from thread suspension, where the suspended +/// thread relinquishes its priority. When the sleep timer times out the +/// thread becomes runnable again, and will run as soon as it becomes the +/// highest-priority mapped runnable thread. +/// +/// Sleeping threads may also be later suspended. In this case the Sleep timer +/// continues to run, and if it times out before the thread is resumed the +/// thread will be immediately runnable when it is resumed. +/// +/// See the PK specification for a full discussion of how PK handles +/// scheduling events at absolute times "in the past". Briefly stated, if the +/// \a interval is 0 or is so small that the absolute time becomes a "past" +/// time before the Sleep is actually scheduled, the thread will Sleep for the +/// briefest possible period supported by the hardware. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion. +/// +/// \retval -PK_ILLEGAL_CONTEXT_THREAD The API was not called from a thread +/// context. + +int +pk_sleep(PkInterval interval) +{ + return pk_sleep_absolute(pk_timebase_get() + interval); +} + + +/// Get information about a thread. +/// +/// \param thread A pointer to the PkThread to query +/// +/// \param state The value returned through this pointer is the current state +/// of the thread; See \ref pk_thread_states. The caller can set this +/// parameter to the null pointer (0) if this information is not required. +/// +/// \param priority The value returned through this pointer is the current +/// priority of the thread. The caller can set this parameter to the null +/// pointer (0) if this information is not required. +/// +/// \param runnable The value returned through this pointer is 1 if the thread +/// is in state PK_THREAD_STATE_MAPPED and is currently in the run queue +/// (i.e., neither blocked on a semaphore nor sleeping), otherwise 0. The +/// caller can set this parameter to the null pointer (0) if this information +/// is not required. +/// +/// The information returned by this API can only be guaranteed consistent if +/// the API is called from a critical section. Since the +/// implementation of this API does not enforce a critical section, it is not +/// an error to call this API from a critical interrupt context. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_THREAD_AT_INFO The \a thread is a null (0) pointer. + +int +pk_thread_info_get(PkThread *thread, + PkThreadState *state, + PkThreadPriority *priority, + int *runnable) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(thread == 0, PK_INVALID_THREAD_AT_INFO); + } + + if (state) { + *state = thread->state; + } + if (priority) { + *priority = thread->priority; + } + if (runnable) { + *runnable = ((thread->state == PK_THREAD_STATE_MAPPED) && + __pk_thread_queue_member(&__pk_run_queue, + thread->priority)); + } + return PK_OK; +} + + +/// Change the priority of a thread. +/// +/// \param thread The thread whose priority will be changed +/// +/// \param new_priority The new priority of the thread +/// +/// \param old_priority The value returned through this pointer is the +/// old priority of the thread prior to the change. The caller can set +/// this parameter to the null pointer (0) if this information is not +/// required. +/// +/// Thread priorities can be changed by the \c pk_thread_priority_change() +/// API. This call will fail if the thread pointer is invalid or if the thread +/// is mapped and the new priority is currently in use. The call will succeed +/// even if the \a thread is suspended, completed or deleted. The +/// application-level scheduling algorithm is completely responsible for the +/// correctness of the application in the event of suspended, completed or +/// deleted threads. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion, including the redundant case of +/// attempting to change the priority of the thread to its current priority. +/// +/// \retval -PK_ILLEGAL_CONTEXT_THREAD the API was called from a critical +/// interrupt context. +/// +/// \retval -PK_INVALID_THREAD_AT_CHANGE The \a thread is null (0) or +/// otherwise invalid. +/// +/// \retval -PK_INVALID_ARGUMENT_THREAD_CHANGE The \a new_priority is invalid. +/// +/// \retval -PK_PRIORITY_IN_USE_AT_CHANGE The \a thread is mapped and the \a +/// new_priority is currently in use by another thread. + +int +pk_thread_priority_change(PkThread *thread, + PkThreadPriority new_priority, + PkThreadPriority *old_priority) +{ + PkMachineContext ctx; + PkThreadPriority priority; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(thread == 0, PK_INVALID_THREAD_AT_CHANGE); + PK_ERROR_IF(new_priority > PK_THREADS, + PK_INVALID_ARGUMENT_THREAD_CHANGE); + } + + pk_critical_section_enter(&ctx); + + priority = thread->priority; + + if (priority != new_priority) { + + if (!__pk_thread_is_mapped(thread)) { + + thread->priority = new_priority; + + } else { + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF_CRITICAL(__pk_priority_map[new_priority] != 0, + PK_PRIORITY_IN_USE_AT_CHANGE, + &ctx); + } + + __pk_thread_unmap(thread); + thread->priority = new_priority; + __pk_thread_map(thread); + __pk_schedule(); + } + } + + if (old_priority) { + *old_priority = priority; + } + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + + +/// Return a pointer to the thread (if any) mapped at a given priority. +/// +/// \param priority The thread priority of interest +/// +/// \param thread The value returned through this pointer is a pointer to the +/// thread currently mapped at the given priority level. If no thread is +/// mapped, or if the \a priority is the priority of the idle thread, the +/// pointer returned will be null (0). +/// +/// The information returned by this API can only be guaranteed consistent if +/// the API is called from a critical section. Since the +/// implementation of this API does not require a critical section, it is not +/// an error to call this API from a critical interrupt context. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion. +/// +/// \retval -PK_INVALID_ARGUMENT_THREAD_PRIORITY The \a priority is invalid +/// or the \a thread parameter is null (0). + +int +pk_thread_at_priority(PkThreadPriority priority, + PkThread **thread) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF((priority > PK_THREADS) || (thread == 0), + PK_INVALID_ARGUMENT_THREAD_PRIORITY); + } + + *thread = __pk_thread_at_priority(priority); + + return PK_OK; +} + + +/// Swap thread priorities +/// +/// \param thread_a A pointer to an initialized PkThread +/// +/// \param thread_b A pointer to an initialized PkThread +/// +/// This API swaps the priorities of \a thread_a and \a thread_b. The API is +/// provided to support general and efficient application-directed scheduling +/// algorithms. The requirements on the \a thread_a and \a thread_b arguments +/// are that they are valid pointers to initialized PkThread structures, that +/// the current thread priorities of both threads are legal, and that if a +/// thread is currently mapped, that the new thread priority is not otherwise +/// in use. +/// +/// The API does not require either thread to be mapped, or even to be active. +/// It is legal for one or both of the swap partners to be suspended, deleted +/// or completed threads. The application is completely responsible for the +/// correctness of scheduling algorithms that might operate on inactive or +/// suspended threads. +/// +/// The API does not change the mapped status of a thread. A thread will be +/// mapped after the call of pk_thread_priority_swap() if and only if it was +/// mapped prior to the call. If the new priority of a mapped thread is +/// currently in use (by a thread other than the swap partner), then the +/// PK_PRIORITY_IN_USE_AT_SWAP error is signalled and the swap does not take +/// place. This could only happen if the swap partner is not currently mapped. +/// +/// It is legal for a thread to swap its own priority with another thread. The +/// degenerate case that \a thread_a and \a thread_b are equal is also legal - +/// but has no effect. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion, including the redundant cases that do not +/// actually change priorities, or the cases that assign new priorities to +/// suspended, completed or deleted threads. +/// +/// \retval -PK_ILLEGAL_CONTEXT_THREAD the API was called from a critical +/// interrupt context. +/// +/// \retval -PK_INVALID_THREAD_AT_SWAP1 One or both of \a thread_a and +/// \a thread_b is null (0) or otherwise invalid, +/// +/// \retval -PK_INVALID_THREAD_AT_SWAP2 the priorities of One or both of +/// \a thread_a and \a thread_b are invalid. +/// +/// \retval -PK_INVALID_ARGUMENT One or both of the priorities +/// of \a thread_a and \a thread_b is invalid. +/// +/// \retval -PK_PRIORITY_IN_USE_AT_SWAP Returned if a thread is mapped and the +/// new thread priority is currently in use by another thread (other than the +/// swap partner). + +int +pk_thread_priority_swap(PkThread* thread_a, PkThread* thread_b) +{ + PkMachineContext ctx; + PkThreadPriority priority_a, priority_b; + int mapped_a, mapped_b; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF((thread_a == 0) || (thread_b == 0), + PK_INVALID_THREAD_AT_SWAP1); + } + + pk_critical_section_enter(&ctx); + + if (thread_a != thread_b) { + + mapped_a = __pk_thread_is_mapped(thread_a); + mapped_b = __pk_thread_is_mapped(thread_b); + priority_a = thread_a->priority; + priority_b = thread_b->priority; + + if (PK_ERROR_CHECK_API) { + int priority_in_use; + PK_ERROR_IF_CRITICAL((priority_a > PK_THREADS) || + (priority_b > PK_THREADS), + PK_INVALID_THREAD_AT_SWAP2, + &ctx); + priority_in_use = + (mapped_a && !mapped_b && + (__pk_thread_at_priority(priority_b) != 0)) || + (!mapped_a && mapped_b && + (__pk_thread_at_priority(priority_a) != 0)); + PK_ERROR_IF_CRITICAL(priority_in_use, + PK_PRIORITY_IN_USE_AT_SWAP, &ctx); + } + + if (mapped_a) { + __pk_thread_unmap(thread_a); + } + if (mapped_b) { + __pk_thread_unmap(thread_b); + } + thread_a->priority = priority_b; + thread_b->priority = priority_a; + if (mapped_a) { + __pk_thread_map(thread_a); + } + if (mapped_b) { + __pk_thread_map(thread_b); + } + __pk_schedule(); + } + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + + +#undef __PK_THREAD_CORE_C__ diff --git a/pk/kernel/pk_thread_init.c b/pk/kernel/pk_thread_init.c new file mode 100644 index 00000000..686f3512 --- /dev/null +++ b/pk/kernel/pk_thread_init.c @@ -0,0 +1,137 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_thread_init.c +/// \brief PK thread API initialization routines +/// +/// The entry points in this file are routines that are typically used during +/// initialization, and their code space could be deallocated and recovered if +/// no longer needed by the application after initialization. + +#include "pk.h" + +/// Create (initialize) a thread +/// +/// \param thread A pointer to an PkThread structure to initialize +/// +/// \param thread_routine The subroutine that implements the thread +/// +/// \param arg Private data to be passed as the argument to the thread +/// routine when it begins execution +/// +/// \param stack The stack space of the thread +/// +/// \param stack_size The size of the stack in bytes +/// +/// \param priority The initial priority of the thread +/// +/// The \a thread argument must be a pointer to an uninitialized or completed +/// or deleted thread. This \c PkThread structure \em is the thread, so this +/// memory area must not be modified by the application until the thread +/// completes or is deleted. PK can not tell if an PkThread structure is +/// currently in use as a thread control block.pk_thread_create() will +/// silently overwrite an PkThread structure that is currently in use. +/// +/// The stack area must be large enough to hold the dynamic stack requirements +/// of the entry point routine, and all subroutines and functions that might +/// be invoked on any path from the entry point. The stack must also always +/// be able to hold the thread context in the event the thread is preempted, +/// plus other critical context. PK aligns stack areas in machine-specific +/// ways, so that the actual stack area may reduced in size slightly if it is +/// not already aligned. +/// +/// Threads are created runnable but unmapped. A newly created thread will +/// not be eligible to run until a call of pk_thread_resume() targets the +/// thread. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_THREAD_AT_CREATE The \a thread is a null (0) pointer. +/// +/// \retval -PK_ILLEGAL_CONTEXT The API was called from a critical interrupt +/// context. +/// +/// \retval -PK_INVALID_ARGUMENT_THREAD1 the \a thread_routine is null (0) +/// +/// \retval -PK_INVALID_ARGUMENT_THREAD2 the \a priority is invalid, +/// +/// \retval -PK_INVALID_ARGUMENT_THREAD3 the stack area wraps around +/// the end of memory. +/// +/// \retval -PK_STACK_OVERFLOW The stack area at thread creation is smaller +/// than the minimum safe size. + +int +pk_thread_create(PkThread *thread, + PkThreadRoutine thread_routine, + void *arg, + PkAddress stack, + size_t stack_size, + PkThreadPriority priority) +{ + int rc; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(thread == 0, PK_INVALID_THREAD_AT_CREATE); + PK_ERROR_IF((thread_routine == 0) || + (priority >= PK_THREADS), + PK_INVALID_ARGUMENT_THREAD1); + } + + rc = __pk_stack_init(&stack, &stack_size); + if (rc) { + return rc; + } + + thread->saved_stack_pointer = stack; + thread->stack_base = stack; + + if (PK_STACK_DIRECTION < 0) { + + thread->stack_limit = stack - stack_size; + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(thread->stack_limit > thread->stack_base, + PK_INVALID_ARGUMENT_THREAD2); + } + + } else { + + thread->stack_limit = stack + stack_size; + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(thread->stack_limit < thread->stack_base, + PK_INVALID_ARGUMENT_THREAD3); + } + } + + thread->semaphore = 0; + thread->priority = priority; + thread->state = PK_THREAD_STATE_SUSPENDED_RUNNABLE; + thread->flags = 0; + + pk_timer_create_nonpreemptible(&(thread->timer), + __pk_thread_timeout, + (void *)thread); + + __pk_thread_context_initialize(thread, thread_routine, arg); + + return rc; +} + + + + + + + + + + + + + + diff --git a/pk/kernel/pk_timer_core.c b/pk/kernel/pk_timer_core.c new file mode 100644 index 00000000..abc83bad --- /dev/null +++ b/pk/kernel/pk_timer_core.c @@ -0,0 +1,444 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_timer_core.c +/// \brief PK portable kernel timer handler +/// +/// This file contains core routines that would be needed by any application +/// that requires PK timer support at runtime. +/// +/// PK implements a 'tickless' kernel - all events are scheduled at absolute +/// times of the PK timebase. This approach gives the application full +/// control over granularity of event scheduling. Scheduling in absolute time +/// opens up the possibility of scheduling events "in the past". PK +/// uniformly handles this case by scheduling "past" events to occur 1 +/// timebase tick in the future, so that timer callbacks are always run in the +/// expected noncritical interrupt context. +/// +/// PK implements the time queue as a simple unordered list of events, plus a +/// dedicated variable that holds the earliest timeout of any event in the +/// list. This is thought to be an appropriate data structure for the +/// following reasons: +/// +/// - PK applications will be small and will not schedule a large number of +/// events. Therefore the cost of scanning the list each time an event times +/// out is balanced against the cost of maintaining the list as a sorted data +/// structure each time an event is added or removed from the event queue. +/// +/// - PK applications may schedule and cancel many, many more events (safety +/// timeouts) than are ever allowed to expire. Events can be added and deleted +/// from the simple DEQUE very quickly since there is no sorting +/// overhead. +/// +/// Events are added to the queue simply by placing them at the end of the +/// queue. If the new event times out earlier than the previous earliest +/// event, the hardware timeout is rescheduled for the new event time. Events +/// are deleted from the queue (cancelled) simply by deleting them. Deletion +/// does not affect the hardware timeout, even if the deleted event would have +/// been the next to time out. It is not an error for the timer handler to +/// take a timer interrupt and find no events pending. Pending events can +/// also be rescheduled in place. +/// +/// When a timeout occurs the event list is scanned from the beginning, and +/// any event that has timed out is rescheduled if necessary (periodic events) +/// and its callback is processed. Since event and callback processing take +/// time, the list is potentially scanned multiple times until there are no +/// more timed-out events in the list. +/// +/// Note that callbacks are not necessarily processed in time-order. In this +/// sense the PK time queue is like a traditional tick-based time queue in +/// that events are effectively lumped into groups of events that time out +/// together. In a tick-based kernel the 'lump' is the tick interval; here +/// the 'lump' is a variable interval that corresponds to the time it takes to +/// process the entire event list. +/// +/// Timer callbacks are typically run with interrupt preemption enabled. +/// Special callbacks may run without preemption. This is the only part of +/// the PK kernel where data structures of indeterminate size are processed. +/// During processing of the event list by the timer interrupt handler, the +/// consideration of each event always includes a window of preemptability. + +#define __PK_TIMER_CORE_C__ + +#include "pk.h" + +// This routine is only used in this file, and will always be called in +// critical section. + +static inline int +timer_active(PkTimer* timer) +{ + return pk_deque_is_queued((PkDeque*)timer); +} + + +// This is the kernel version of pk_timer_cancel(). +// +// This routine is used here and by thread and semaphore routines. +// Noncritical interrupts must be disabled at entry. +// +// If the timer is active, then there is a special case if we are going to +// delete the 'cursor' - that is the timer that __pk_timer_handler() is going +// to handle next. In this case we need to move the cursor to the next timer +// in the queue. +// +// Note that cancelling a timer does not cause a re-evaluation of the next +// timeout. This will happen naturally when the current timeout expires. + +int +__pk_timer_cancel(PkTimer *timer) +{ + int rc; + PkDeque* timer_deque = (PkDeque*)timer; + PkTimeQueue* tq = &__pk_time_queue; + + if (!timer_active(timer)) { + + rc = -PK_TIMER_NOT_ACTIVE; + + } else { + + if (timer_deque == tq->cursor) { + tq->cursor = tq->cursor->next; + } + pk_deque_delete(timer_deque); + rc = 0; + } + return rc; +} + + +// This is the kernel version of pk_timer_schedule(). +// +// This routine is used here and by thread and semaphore routines. +// Noncritical interrupts must be disabled at entry. +// +// Unless the timer is already active it is enqueued in the doubly-linked +// timer list by inserting the timer at the end of the queue. Then the +// hardware timeout is scheduled if necessary. If the time queue 'cursor' != 0 +// we are in the midst of processing the time queue, and the end of time queue +// processing will schedule the next hardware timemout. + +void +__pk_timer_schedule(PkTimer* timer) +{ + PkTimeQueue* tq = &__pk_time_queue; + + if (!timer_active(timer)) { + pk_deque_push_back((PkDeque*)tq, (PkDeque*)timer); + } + + if (timer->timeout < tq->next_timeout) { + tq->next_timeout = timer->timeout; + if (tq->cursor == 0) { + __pk_schedule_hardware_timeout(tq->next_timeout); + } + } +} + + +// The tickless timer mechanism has timed out. Note that due to timer +// deletions and other factors, there may not actually be a timer in the queue +// that has timed out - but it doesn't matter (other than for efficiency). +// +// Noncritical interrupts are (must be) disabled at entry, and this invariant +// is checked. This routine must not be entered reentrantly. +// +// First, time out any timers that have expired. Timers in the queue are +// unordered, so we have to check every one. Since passing through the +// loop takes time, we may have to make multiple passes until we know +// that there are no timers in the queue that have already timed +// out. Note that it would also work to only go through the loop once and +// let the hardware scheduler take care of looping, but that would imply +// more overhead than the current implementation. +// +// On each pass through the loop tq->next_timeout computes the minimum timeout +// of events remaining in the queue. This is the only part of the kernel that +// searches a list of indefinite length. Kernel interrupt latency is mitigated +// by running callbacks with interrupts disabled either during or after the +// call for timed out events, and also after every check for events that have +// not timed out. +// +// Because interrupt preemption is enabled during processing, and preempting +// handlers may invoke time queue operations, we need to establish a pointer +// to the next entry to be examined (tq->cursor) before enabling interupts. +// It's possible that this pointer will be changed by other interrupt handlers +// that cancel the timer pointed to by tq->cursor. +// +// The main loop iterates on the PkDeque form of the time queue, casting each +// element back up to the PkTimer as it is processed. + +void +__pk_timer_handler() +{ + PkTimeQueue* tq; + PkTimebase now; + PkTimer* timer; + PkDeque* timer_deque; + PkTimerCallback callback; + + tq = &__pk_time_queue; + + if (PK_ERROR_CHECK_KERNEL) { + if (tq->cursor != 0) { + PK_PANIC(PK_TIMER_HANDLER_INVARIANT); + } + } + + while ((now = pk_timebase_get()) >= tq->next_timeout) { + + tq->next_timeout = PK_TIMEBASE_MAX; + timer_deque = ((PkDeque*)tq)->next; + + while (timer_deque != (PkDeque*)tq) { + + timer = (PkTimer*)timer_deque; + tq->cursor = timer_deque->next; + + if (timer->timeout <= now) { + + // The timer timed out. It is removed from the queue unless + // it is a peridic timer that needs to be rescheduled. We do + // rescheduling here in the critical section to correctly + // handle timers whose callbacks may cancel the timer. The + // timer is rescheduled in absolute time. + // + // The callback may be made with interrupt preemption enabled + // or disabled. However to mitigate kernel interrupt latency + // we go ahead and open up to interrupts after the callback if + // the callback itself was not preemptible. + + if (timer->period == 0) { + pk_deque_delete(timer_deque); + } else { + timer->timeout += timer->period; + tq->next_timeout = MIN(timer->timeout, tq->next_timeout); + } + + callback = timer->callback; + if (callback) { + if (timer->options & PK_TIMER_CALLBACK_PREEMPTIBLE) { + pk_interrupt_preemption_enable(); + callback(timer->arg); + } else { + callback(timer->arg); + pk_interrupt_preemption_enable(); + } + } + pk_interrupt_preemption_disable(); + + } else { + + // This timer has not timed out. Its timeout will simply + // participate in the computation of the next timeout. For + // interrupt latency reasons we always allow a period of + // interrupt preemption. + + tq->next_timeout = MIN(timer->timeout, tq->next_timeout); + pk_interrupt_preemption_enable(); + pk_interrupt_preemption_disable(); + } + + timer_deque = tq->cursor; + } + } + + tq->cursor = 0; + + // Finally, reschedule the next timeout + + __pk_schedule_hardware_timeout(tq->next_timeout); +} + + +/// Schedule a timer in absolute time. +/// +/// \param timer The PkTimer to schedule. +/// +/// \param timeout The timer will be scheduled to time out at this absolute +/// time. Note that if the \a timeout is less than the current time then the +/// timer will be scheduled at a minimum timeout in the future and the +/// callback will be executed in an interrupt context. +/// +/// \param period If non-0, then when the timer times out it will rescheduled +/// to time out again at the absolute time equal to the last timeout time plus +/// the \a period. By convention a \a period of 0 indicates a one-shot +/// timer that is not rescheduled. +/// +/// Once created with pk_timer_create() a timer can be \e scheduled, which +/// queues the timer in the kernel time queue. It is not an error to call +/// pk_timer_schedule() on a timer that is already scheduled in the time +/// queue - the timer is simply rescheduled with the new characteristics. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_TIMER_AT_SCHEDULE A a null (0) pointer was provided as +/// the \a timer argument. +/// +/// \retval -PK_ILLEGAL_CONTEXT_TIMER The call was made from a critical +/// interrupt context. + +int +pk_timer_schedule_absolute(PkTimer *timer, + PkTimebase timeout, + PkInterval period) + +{ + PkMachineContext ctx; + + pk_critical_section_enter(&ctx); + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(timer == 0, PK_INVALID_TIMER_AT_SCHEDULE); +// PK_ERROR_IF(__pk_kernel_context_critical_interrupt(), +// PK_ILLEGAL_CONTEXT_TIMER); + } + + timer->timeout = timeout; + timer->period = period; + __pk_timer_schedule(timer); + + pk_critical_section_exit(&ctx); + + return PK_OK; +} + + +/// Schedule a timer for an interval relative to the current time. +/// +/// \param timer The PkTimer to schedule. +/// +/// \param interval The timer will be scheduled to time out at the current +/// time (pk_timebase_get()) plus this \a interval. +/// +/// \param period If non-0, then when the timer times out it will rescheduled +/// to time out again at the absolute time equal to the last timeout time plus +/// the \a period. By convention a \a period of 0 indicates a one-shot +/// timer that is not rescheduled. +/// +/// Once created with pk_timer_create() a timer can be \e scheduled, which +/// queues the timer in the kernel time queue. It is not an error to call \c +/// pk_timer_schedule() on a timer that is already scheduled in the time +/// queue - the timer is simply rescheduled with the new characteristics. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_TIMER_AT_SCHEDULE A a null (0) pointer was provided as +/// the \a timer argument. +/// +/// \retval -PK_ILLEGAL_CONTEXT_TIMER The call was made from a critical +/// interrupt context. + +int +pk_timer_schedule(PkTimer *timer, + PkInterval interval, + PkInterval period) +{ + return pk_timer_schedule_absolute(timer, + pk_timebase_get() + interval, + period); +} + + +/// Cancel (dequeue) a timer. +/// +/// \param timer The PkTimer to cancel. +/// +/// Timers can be canceled at any time. It is never an error to call +/// pk_timer_cancel() on an PkTimer object after it is created. Memory used +/// by an PkTimer can be safely reused for another purpose after a successful +/// call ofpk_timer_cancel(). +/// +/// Return values other than PK_OK (0) are not necessarily errors; see \ref +/// pk_errors +/// +/// The following return codes are non-error codes: +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_TIMER_NOT_ACTIVE The \a timer is not currently scheduled, +/// i.e. it was never scheduled or has timed out. This code is returned for +/// information only and is not considered an error. +/// +/// The following return codes are error codes: +/// +/// \retval -PK_INVALID_TIMER_AT_CANCEL The \a timer is a null (0) pointer. +/// +/// \retval -PK_ILLEGAL_CONTEXT_TIMER The call was made from a critical +/// interrupt context. +/// + +int +pk_timer_cancel(PkTimer *timer) +{ + PkMachineContext ctx; + int rc = PK_OK; + + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(timer == 0, PK_INVALID_TIMER_AT_CANCEL); + } + + pk_critical_section_enter(&ctx); + + rc = __pk_timer_cancel(timer); + + pk_critical_section_exit(&ctx); + + return rc; +} + + +/// Get information about a timer. +/// +/// \param timer The PkTimer to query +/// +/// \param timeout The API returns the absolute timeout of the timer through +/// this pointer. If the timer is active, this is the current timeout. If +/// the timer has timed out then this is the previous absolute timeout. If +/// the timer was never scheduled this will be 0. The caller can set this +/// parameter to the null pointer (0) if this information is not required. +/// +/// \param active If the value returned through this pointer is 1 then the +/// timer is active (currently scheduled), otherwise the value will be 0 +/// indicating an inactive timer. The caller can set this parameter to the +/// null pointer (0) if this information is not required. +/// +/// The information returned by this API can only be guaranteed consistent if +/// the API is called from a critical section. Since the +/// implementation of this API does not require a critical section, it is not +/// an error to call this API from a critical interrupt context. +/// +/// Return values other than PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_TIMER_AT_INFO The \a timer is a null (0) pointer. + +int +pk_timer_info_get(PkTimer *timer, + PkTimebase *timeout, + int *active) + +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(timer == 0, PK_INVALID_TIMER_AT_INFO); + } + + if (timeout) { + *timeout = timer->timeout; + } + if (active) { + *active = timer_active(timer); + } + + return PK_OK; +} + +#undef __PK_TIMER_CORE_C__ diff --git a/pk/kernel/pk_timer_init.c b/pk/kernel/pk_timer_init.c new file mode 100644 index 00000000..6851f3d3 --- /dev/null +++ b/pk/kernel/pk_timer_init.c @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_timer_init.c +/// \brief PK timer initialization +/// +/// The entry points in this file might only be used during initialization of +/// the application. In this case the code space for these routines could be +/// recovered and reused after initialization. + +#include "pk.h" + +// Implementation of timer creation + +static int +_pk_timer_create(PkTimer *timer, + PkTimerCallback callback, + void *arg, + int options) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF((timer == 0), PK_INVALID_TIMER_AT_CREATE); + } + + pk_deque_element_create((PkDeque*)timer); + timer->timeout = 0; + timer->period = 0; + timer->callback = callback; + timer->arg = arg; + timer->options = options; + + return PK_OK; +} + + +/// Create (initialize) a preemptible timer. +/// +/// \param timer The PkTimer to initialize. +/// +/// \param callback The timer callback +/// +/// \param arg Private data provided to the callback. +/// +/// Once created with pk_timer_create() a timer can be scheduled with +/// pk_timer_schedule() or pk_timer_schedule_absolute(), which queues the +/// timer in the kernel time queue. Timers can be cancelled by a call of +/// pk_timer_cancel(). +/// +/// Timers created with pk_timer_create() are always run as noncritical +/// interrupt handlers with interrupt preemption enabled. Timer callbacks are +/// free to enter critical sections of any priorioty if required, but must +/// always exit with noncritical interrupts enabled. +/// +/// Caution: PK has no way to know if an PkTimer structure provided to +/// pk_timer_create() is safe to use as a timer, and will silently modify +/// whatever memory is provided. +/// +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_TIMER_AT_CREATE The \a timer is a null (0) pointer. + +int +pk_timer_create(PkTimer *timer, + PkTimerCallback callback, + void *arg) +{ + return _pk_timer_create(timer, callback, arg, + PK_TIMER_CALLBACK_PREEMPTIBLE); +} + + +/// Create (initialize) a nonpreemptible timer. +/// +/// \param timer The PkTimer to initialize. +/// +/// \param callback The timer callback +/// +/// \param arg Private data provided to the callback. +/// +/// Once created with pk_timer_create_preemptible() a timer can be scheduled +/// with pk_timer_schedule() or pk_timer_schedule_absolute(), which queues +/// the timer in the kernel time queue. Timers can be cancelled by a call of +/// pk_timer_cancel(). +/// +/// Timers created with pk_timer_create_nonpreemptible() are always run as +/// noncritical interrupt handlers with interrupt preemption disabled. Timer +/// callbacks are free to later enable preemption if desired, but must always +/// exit with noncritical interrupts disabled. +/// +/// \note The use of pk_timer_create_nonpreemptible() should be rare, and the +/// timer callbacks should be short and sweet to avoid long interrupt +/// latencies for other interrupts. This API was initially introduced for use +/// by the PK kernel itself when scheduling thread-timer callbacks to avoid +/// potential race conditions with other interrupts that may modify thread +/// state or the state of the time queue. Applications may also require this +/// facility to guarantee a consistent state in the event that other +/// interrupts may cancel the timer. +/// +/// Caution: PK has no way to know if an PkTimer structure provided to +/// pk_timer_create() is safe to use as a timer, and will silently modify +/// whatever memory is provided. +/// +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_TIMER_AT_CREATE The \a timer is a null (0) pointer. + +int +pk_timer_create_nonpreemptible(PkTimer *timer, + PkTimerCallback callback, + void *arg) +{ + return _pk_timer_create(timer, callback, arg, 0); +} + + diff --git a/pk/kernel/pkkernelfiles.mk b/pk/kernel/pkkernelfiles.mk new file mode 100644 index 00000000..958e4ddf --- /dev/null +++ b/pk/kernel/pkkernelfiles.mk @@ -0,0 +1,32 @@ +# @file pkkernelfiles.mk +# +# @brief mk for including architecture independent pk object files +# +# @page ChangeLogs Change Logs +# @section pkkernelfiles.mk +# @verbatim +# +# +# Change Log ****************************************************************** +# Flag Defect/Feature User Date Description +# ------ -------------- ---------- ------------ ----------- +# +# @endverbatim +# +########################################################################## +# Include +########################################################################## + + +########################################################################## +# Object Files +########################################################################## +PK-C-SOURCES = pk_core.c pk_init.c pk_stack_init.c + +PK-TIMER-C-SOURCES += pk_timer_core.c pk_timer_init.c + +PK-THREAD-C-SOURCES += pk_thread_init.c pk_thread_core.c \ + pk_semaphore_init.c pk_semaphore_core.c + +PK_OBJECTS += $(PK-C-SOURCES:.c=.o) + diff --git a/pk/ppe/Makefile b/pk/ppe/Makefile new file mode 100644 index 00000000..833dbfdf --- /dev/null +++ b/pk/ppe/Makefile @@ -0,0 +1,50 @@ +# This Makefile compiles all of the PK code required for the PPE port +# of PK. See the "pk.mk" file in this directory. + +#all generated files from this makefile will end up in obj/$(IMAGE_NAME)/pk +export SUB_OBJDIR = /pk + +include img_defs.mk +include pkppefiles.mk + +ifeq "$(PK_TIMER_SUPPORT)" "1" +PPE_OBJECTS += ${PPE-TIMER-C-SOURCES:.c=.o} ${PPE-TIMER-S-SOURCES:.S=.o} +endif + +ifeq "$(PK_THREAD_SUPPORT)" "1" +PPE_OBJECTS += ${PPE-THREAD-C-SOURCES:.c=.o} ${PPE-THREAD-S-SOURCES:.S=.o} +endif + +ifeq "$(PPE_ASYNC_SUPPORT)" "1" +PPE_OBJECTS += ${PPE-ASYNC-C-SOURCES:.c=.o} ${PPE-ASYNC-S-SOURCES:.S=.o} +endif + +OBJS := $(addprefix $(OBJDIR)/, $(PPE_OBJECTS)) + +libpk.a: kernel ppe42 trace ppe + $(AR) crs $(OBJDIR)/libpk.a $(OBJDIR)/*.o + +.PHONY: clean ppe kernel ppe42 trace +ppe: $(OBJS) + +trace: + $(MAKE) -I $(IMAGE_SRCDIR) -C ../trace + +kernel: + $(MAKE) -I $(IMAGE_SRCDIR) -C ../kernel + +ppe42: + $(MAKE) -I $(IMAGE_SRCDIR) -C ../ppe42 + + +$(OBJS) $(OBJS:.o=.d): | $(OBJDIR) + +$(OBJDIR): + mkdir -p $(OBJDIR) + +clean: + rm -fr $(OBJDIR) + +ifneq ($(MAKECMDGOALS),clean) +include $(OBJS:.o=.d) +endif diff --git a/pk/ppe/pk_port.h b/pk/ppe/pk_port.h new file mode 100644 index 00000000..f9010581 --- /dev/null +++ b/pk/ppe/pk_port.h @@ -0,0 +1,16 @@ +#ifndef __PK_PORT_H__ +#define __PK_PORT_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_port.h +/// \brief The top-level CME environment header for PK. + +#define HWMACRO_PPE + +#include "ppe42.h" + +#endif /* __PK_PORT_H__ */ diff --git a/pk/ppe/pkppefiles.mk b/pk/ppe/pkppefiles.mk new file mode 100644 index 00000000..79b7baae --- /dev/null +++ b/pk/ppe/pkppefiles.mk @@ -0,0 +1,33 @@ +# @file pkppefiles.mk +# +# @brief mk for including ppe object files +# +# @page ChangeLogs Change Logs +# @section pkppefiles.mk +# @verbatim +# +# +# Change Log ****************************************************************** +# Flag Defect/Feature User Date Description +# ------ -------------- ---------- ------------ ----------- +# +# @endverbatim +# +########################################################################## +# Object Files +########################################################################## + +PPE-C-SOURCES = ppe_init.c +PPE-S-SOURCES = + +PPE-TIMER-C-SOURCES = +PPE-TIMER-S-SOURCES = + +PPE-THREAD-C-SOURCES = +PPE-THREAD-S-SOURCES = + +PPE-ASYNC-C-SOURCES = +PPE-ASYNC-S-SOURCES = + +PPE_OBJECTS += $(PPE-C-SOURCES:.c=.o) $(PPE-S-SOURCES:.S=.o) + diff --git a/pk/ppe/ppe.h b/pk/ppe/ppe.h new file mode 100644 index 00000000..05df6a61 --- /dev/null +++ b/pk/ppe/ppe.h @@ -0,0 +1,22 @@ +#ifndef __PPE_H__ +#define __PPE_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pgp.h +/// \brief The PPE environment for PK. + +// This is a 'circular' reference in PK, but included here to simplify PGAS +// programming. + +#ifndef HWMACRO_PPE +#define HWMACRO_PPE +#include "ppe42.h" +#endif + +#include "ppe_common.h" + +#endif /* __PPE_H__ */ diff --git a/pk/ppe/ppe_common.h b/pk/ppe/ppe_common.h new file mode 100644 index 00000000..bc8519ea --- /dev/null +++ b/pk/ppe/ppe_common.h @@ -0,0 +1,60 @@ +#ifndef __PPE_COMMON_H__ +#define __PPE_COMMON_H__ + +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe_common.h +/// \brief Common header for PPE +/// +/// This header is maintained as part of the PK port for PPE, but needs to be +/// physically present in the PMX area to allow dropping PMX code as a whole +/// to other teams. + +// -*- WARNING: This file is maintained as part of PK. Do not edit in -*- +// -*- the PMX area as your edits will be lost. -*- + +#ifndef __ASSEMBLER__ +#include <stdint.h> +#endif + +/// This constant is used to define the size of the table of interrupt handler +/// structures as well as a limit for error checking. +/// NOTE: This can be specific to each PPE type (SBE, PPE, GPE) +#define EXTERNAL_IRQS 64 + +#ifdef __ASSEMBLER__ +/// This macro contains PPE specific code for determining what IRQ caused the +/// external exception handler to be invoked by the PPE + +/// Load noncritical status 0 and the handler array base address. Check +/// for interrupts pending in status register 0 while the IRQ is +/// computed. The IRQ is expected to be stored in r3. + .macro hwmacro_get_ext_irq + +#_lwzi %r4, %r4, OCB_ONISR0 + cntlzw %r3, %r4 + cmpwible %r3, 31, external_irq_found #branch if irq is lt or eq to 31 + + ## No IRQ pending in interrupt set 0. Try set 1. + +#_lwzi %r4, %r4, OCB_ONISR1 + cntlzw %r3, %r4 + addi %r3, %r3, 32 + + .endm + +/// Redirect the .hwmacro_irq_cfg_bitmaps macro to call our ppe specific implementation +/// This is called from the ppe42_exceptions.S file. +/// NOTE: The standalone version of PPE doesn't support external interrupts so this +/// does nothing. + .macro .hwmacro_irq_cfg_bitmaps + .endm + + +#endif /* __ASSEMBLER__ */ + +#endif /* __PPE_COMMON_H__ */ diff --git a/pk/ppe/ppe_init.c b/pk/ppe/ppe_init.c new file mode 100644 index 00000000..09feea67 --- /dev/null +++ b/pk/ppe/ppe_init.c @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe_init.c +/// \brief PK initialization for PPE +/// +/// The entry points in this routine are used during initialization. This +/// code space can be deallocated and reassigned after application +/// initialization if required. + +#include "pk.h" + +/// PPE environment initial setup. +/// +/// This is setup common to all PPE HW Macro applications. This setup takes place +/// during boot, before main() is called. + +void +__hwmacro_setup(void) +{ + //async_initialize(); +} diff --git a/pk/ppe42/Makefile b/pk/ppe42/Makefile new file mode 100644 index 00000000..5bb2b566 --- /dev/null +++ b/pk/ppe42/Makefile @@ -0,0 +1,26 @@ +# This Makefile is designed to be invoked with the -I argument set to +# the location of the "pk.mk" for the build + +include img_defs.mk +include pkppe42files.mk + +ifeq "$(PK_TIMER_SUPPORT)" "1" +PPE42_OBJECTS += ${PPE42-TIMER-C-SOURCES:.c=.o} ${PPE42-TIMER-S-SOURCES:.S=.o} +endif + +ifeq "$(PK_THREAD_SUPPORT)" "1" +PPE42_OBJECTS += ${PPE42-THREAD-C-SOURCES:.c=.o} ${PPE42-THREAD-S-SOURCES:.S=.o} +endif + +OBJS := $(addprefix $(OBJDIR)/, $(PPE42_OBJECTS)) + +all: $(OBJS) + +$(OBJS) $(OBJS:.o=.d): | $(OBJDIR) + +$(OBJDIR): + mkdir -p $(OBJDIR) + +ifneq ($(MAKECMDGOALS),clean) +include $(OBJS:.o=.d) +endif diff --git a/pk/ppe42/div64.S b/pk/ppe42/div64.S new file mode 100644 index 00000000..69de53b3 --- /dev/null +++ b/pk/ppe42/div64.S @@ -0,0 +1,261 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file div64.S +/// \brief Unsigned 64/64 bit division +/// +/// This is IBM code, originally part of OS Open. The code has been slightly +/// modified from its original form, both to be compatible with PK and to +/// change the function prototype slightly. +/// +/// The code was provided by Matt Tyrlik in Raleigh. + +/* @#START#@ +** +** PSCN (Power Service and Control Network) +** Cage Controller OS Open Code +** +** (C) Copyright International Business Machines Corporation 2002 +** All Rights Reserved +** Licensed Material - Program Property of I B M +** Refer to copyright instructions: Form G120-2083 +** +** Module: +** div64.s +** +** Description: +** Divide 64 bit unsigned values on 32 bit CPU +** div64(uint64_t dividen, uint64_t divisor, +** uint64_t *quotient, uint64_t *remainder) +** +** Original source from: +** "The PowerPC Compiler Writer's Guide", pp62-65 by +** Steve Hoxey, Faraydon Karim, Bill Hay, Hank Warray, +** published by Warthman Associates, 240 Hamilton Avenue, +** Palo Alto, CA 94301, USA, 1996 for IBM. +** ISBN 0-9649654-0-2. +** +** This version checks for divisor equal to zero. +** +** Environment: +** OS Open (XCOFF) +** +** Linkage: +** AIX 4.3.3 +** +** @author +** Thomas Richter +** +** History: +** Date Author Description +** ----------------------------------------------------------------------------- +** 23-Sep-02 Richter Created +** +** @#END#@*/ + + .nolist +#include "pk.h" + .list + + .set r0, 0 + .set r1, 1 + .set r3, 3 + .set r4, 4 + .set r5, 5 + .set r6, 6 + .set r7, 7 + .set r8, 8 + .set r9, 9 + .set r10, 10 + .set r30, 30 + .set r31, 31 + + .global_function __ppe42_udiv64 + + /* + ** Code comment notation: + ** msw = most-significant (high-order) word, i.e. bits 0..31 + ** lsw = least-significant (low-order) word, i.e. bits 32..63 + ** LZ = Leading Zeroes + ** SD = Significant Digits + ** + ** R3:R4 = Input parameter, dividend. + ** R5:R6 = Input parameter, divisor. + ** R7 = Output parameter, pointer to quotient. + ** R8 = Output parameter, pointer to remainder. + ** + ** Pointer arguments point to a uint64_t. + ** + ** Division is achieved using a shift/rotate/substract algorithsm + ** described above. + ** The registers are used as follows: + ** R3:R4 = dividend (upper 32bits:lower 32bits) + ** R5:R6 = divisor (upper 32bits:lower 32bits) + ** + ** R7:R8 = temporary 64 bit register (upper 32bits:lower 32bits) + ** count the number of leading 0s in the dividend + ** + ** Here is the description from the book. The dividend is placed + ** in the low order part of a 4 (32bit) register sequence named + ** tmp-high:tmp-low:dividend-high:dividend:low or tmp:dvd for short. + ** + ** Each iteration includes the following steps: + ** 1. Shift tmp:dvd by one bit to the left. + ** 2. Subtract the divisor from tmp. This is a 64 bit operation. + ** 3. If result is greater than or equal, place result in tmp and + ** set the low order bit of dividend + ** 4. If result is negative, do not modify tmp and + ** clear the low order bit of dividend + ** 5. If the number of iterations is less than the width of the + ** dividend, goto step 1. + ** + ** Now the algorithm can be improved by reducing the number of + ** iterations to be executed. + ** 1. Calculate the leading zeroes of the dividend. + ** 2. Calculate the leading zeroes of the divisor. + ** 3. Calculate the significant ones of the dividend. + ** 4. Calculate the significant ones of the divisor. + ** + ** Initial tmp := dvd >> (dvd.SD - dvs.SD) + ** Initial dvd := dvd << (dvd.LZ + dvs.SD) + ** Loops: dvd.SD - dvs.SD. + ** + ** Warning: Special care must be taken if dvd.LZ == dvs.LZ. The code + ** below does so by reducing the number of dvs.SD by one. This leads + ** to the loop being executed 1 more time than really necessary, + ** but avoids to check for the case when dvd.LZ == dvs.LZ. + ** This case (dvd.LZ == dvs.LZ) only checks for the number of leading + ** zeroes, but does not check if dividend is really greater than the + ** divisor. + ** Consider 16/17, both have an LZ value of 59. The code sets dvs.LZ + ** 60. This resutls in dvs.SD to 4, thus one iteration after which + ** tmp is the remainder 16. + */ + +__ppe42_udiv64: // PK + + /* push R30 & R31 onto the stack */ + stwu r1, -16(r1) + stvd r30, 8(r1) + + /* Save result pointers on volatile spare registers */ + ori r31, r8, 0 /* Save remainder address */ + ori r30, r7, 0 /* Save quotient address */ + + /* count the number of leading 0s in the dividend */ + cmpwi cr0, r3, 0 /* dvd.msw == 0? */ + cntlzw r0, r3 /* R0 = dvd.msw.LZ */ + cntlzw r9, r4 /* R9 = dvd.lsw.LZ */ + bne cr0, lab1 /* if(dvd.msw == 0) dvd.LZ = dvd.msw.LZ */ + addi r0, r9, 32 /* dvd.LZ = dvd.lsw.LZ + 32 */ +lab1: + /* count the number of leading 0s in the divisor */ + cmpwi cr0, r5, 0 /* dvd.msw == 0? */ + cntlzw r9, r5 /* R9 = dvs.msw.LZ */ + cntlzw r10, r6 /* R10 = dvs.lsw.LZ */ + bne cr0, lab2 /* if(dvs.msw == 0) dvs.LZ = dvs.msw.LZ */ + cmpwi cr0, r6, 0 /* dvd.lsw == 0? */ + beq cr0, lab10 /* dvs.msw == 0 */ + addi r9, r10, 32 /* dvs.LZ = dvs.lsw.LZ + 32 */ + +lab2: + /* Determine shift amounts to minimize the number of iterations */ + cmpw cr0, r0, r9 /* Compare dvd.LZ to dvs.LZ */ + subfic r10, r0, 64 /* R10 = dvd.SD */ + bgt cr0, lab9 /* if(dvs > dvd) quotient = 0 */ + addi r9, r9, 1 /* See comment above. ++dvs.LZ (or --dvs.SD) */ + subfic r9, r9, 64 /* R9 = dvs.SD */ + add r0, r0, r9 /* (dvd.LZ + dvs.SD) = left shift of dvd for */ + /* initial dvd */ + subf r9, r9, r10 /* (dvd.SD - dvs.SD) = right shift of dvd for */ + /* initial tmp */ + mtctr r9 /* Number of iterations = dvd.SD - dvs.SD */ + + /* R7:R8 = R3:R4 >> R9 */ + cmpwi cr0, r9, 32 /* compare R9 to 32 */ + addi r7, r9, -32 + blt cr0, lab3 /* if(R9 < 32) jump to lab3 */ + srw r8, r3, r7 /* tmp.lsw = dvd.msw >> (R9 - 32) */ + addi r7, r0, 0 /* tmp.msw = 0 */ + b lab4 + +lab3: + srw r8, r4, r9 /* R8 = dvd.lsw >> R9 */ + subfic r7, r9, 32 + slw r7,r3,r7 /* R7 = dvd.msw << 32 - R9 */ + or r8, r8,r7 /* tmp.lsw = R8 | R7 */ + srw r7,r3,r9 /* tmp.msw = dvd.msw >> R9 */ +lab4: + /* R3:R4 = R3:R4 << R0 */ + cmpwi cr0, r0, 32 /* Compare R0 to 32 */ + addic r9, r0, -32 + blt cr0, lab5 /* if(R0 < 32) jump to lab5 */ + slw r3, r4, r9 /* dvd.msw = dvd.lsw << R9 */ + addi r4, r0, 0 /* dvd.lsw = 0 */ + b lab6 + +lab5: + slw r3, r3, r0 /* r3 = dvd.msw << r0 */ + subfic r9, r0, 32 + srw r9, r4, r9 /* r9 = dvd.lsw >> 32 - r0 */ + or r3, r3, r9 /* dvd.msw = r3 | r9 */ + slw r4, r4, r0 /* dvd.lsw = dvd.lsw << r0 */ +lab6: + /* Restoring division shift and subtract loop */ + addi r10, r0, -1 /* r10 = -1 */ + addic r7, r7, 0 /* Clear carry bit before loop starts */ +lab7: + /* + ** tmp:dvd is considered one large register + ** each portion is shifted left 1 bit by adding it to itself + ** adde sums the carry from the previous and creates a new carry + */ + adde r4, r4, r4 /* Shift dvd.lsw left 1 bit */ + adde r3, r3, r3 /* Shift dvd.msw to left 1 bit */ + adde r8, r8, r8 /* Shift tmp.lsw to left 1 bit */ + adde r7, r7, r7 /* Shift tmp.msw to left 1 bit */ + subfc r0, r6, r8 /* tmp.lsw - dvs.lsw */ + subfe. r9, r5, r7 /* tmp.msw - dvs.msw */ + blt cr0, lab8 /* if(result < 0) clear carry bit */ + or r8, r0, r0 /* Move lsw */ + or r7, r9, r9 /* Move msw */ + addic r0, r10, 1 /* Set carry bit */ + +lab8: + bdnz lab7 + + /* Write quotient and remainder */ + adde r4, r4, r4 /* quo.lsw (lsb = CA) */ + adde r3, r3, r3 /* quo.msw (lsb from lsw) */ + stw r4, 4(r30) + stw r3, 0(r30) + stw r8, 4(r31) /* rem.lsw */ + stw r7, 0(r31) /* rem.msw */ + b lab11 + +lab9: + /* Qoutient is 0, divisor > dividend */ + addi r0, r0, 0 + stw r3, 0(r31) /* Store remainder */ + stw r4, 4(r31) + stw r0, 0(r30) /* Set quotient to zero */ + stw r0, 4(r30) + b lab11 + +lab10: + /* Divisor is 0 */ + addi r0, r0, -1 + stw r0, 0(r31) /* Set remainder to zero */ + stw r0, 4(r31) + stw r0, 0(r30) /* Set quotient to zero */ + stw r0, 4(r30) + +lab11: + //pop r30 & r31 from stack + lvd r30, 8(r1) + lwz r1, 0(r1) + blr + .epilogue __ppe42_udiv64 diff --git a/pk/ppe42/pk_port_types.h b/pk/ppe42/pk_port_types.h new file mode 100644 index 00000000..1536b77b --- /dev/null +++ b/pk/ppe42/pk_port_types.h @@ -0,0 +1,41 @@ +#ifndef __PK_PORT_TYPES_H__ +#define __PK_PORT_TYPES_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_port_types.h +/// \brief Type definitions required by the PK port. +/// +/// \todo GCC provides a portable version of cntlzw called __builtin_clz(). +/// We should make the PK priority queues portable by using this facility. +/// +/// \todo I think that if more of the port-dependent types were moved here, we +/// could break the circular dependencies in some of the header inclusion and +/// simplify the way the PK/port/chip headers are included. + +/// An PkIrqId is an integer in the range of valid interrupts defined by the +/// interrupt controller. + +typedef uint8_t PkIrqId; + +/// PK requires the port to define the type PkThreadQueue, which is a +/// priority queue (where 0 is the highest priority). This queue must be able +/// to handle PK_THREADS + 1 priorities (the last for the idle thread). The +/// port must also define methods for clearing, insertion, deletion and min +/// (with assumed legal priorities). The min operation returns PK_THREADS if +/// the queue is empty. (Or a queue could be initialized with the PK_THREADS +/// entry always present - PK code never tries to delete the idle thread from +/// a thread queue). +/// +/// These queues are used both for the run queue and the pending queue +/// associated with every semaphore. +/// +/// On PPE42 with 32 threads (implied), this is a job for a uint32_t and +/// cntlzw(). + +typedef uint32_t PkThreadQueue; + +#endif /* __PK_PORT_TYPES_H__ */ diff --git a/pk/ppe42/pkppe42files.mk b/pk/ppe42/pkppe42files.mk new file mode 100644 index 00000000..3399ca7e --- /dev/null +++ b/pk/ppe42/pkppe42files.mk @@ -0,0 +1,45 @@ +# @file pkppe42files.mk +# +# @brief mk for including ppe42 object files +# +# @page ChangeLogs Change Logs +# @section pkppe42files.mk +# @verbatim +# +# +# Change Log ****************************************************************** +# Flag Defect/Feature User Date Description +# ------ -------------- ---------- ------------ ----------- +# +# @endverbatim +# +########################################################################## +# Include Files +########################################################################## + + + +########################################################################## +# Object Files +########################################################################## +PPE42-C-SOURCES = ppe42_core.c \ + ppe42_init.c \ + ppe42_irq_core.c\ + ppe42_gcc.c\ + +PPE42-S-SOURCES = ppe42_boot.S \ + ppe42_exceptions.S\ + div64.S\ + ppe42_timebase.S + +PPE42-TIMER-C-SOURCES = +PPE42-TIMER-S-SOURCES = + +PPE42-THREAD-C-SOURCES += +PPE42-THREAD-S-SOURCES += ppe42_thread_init.S + + +PPE42_OBJECTS += $(PPE42-C-SOURCES:.c=.o) $(PPE42-S-SOURCES:.S=.o) + + + diff --git a/pk/ppe42/ppe42.h b/pk/ppe42/ppe42.h new file mode 100644 index 00000000..affd7f51 --- /dev/null +++ b/pk/ppe42/ppe42.h @@ -0,0 +1,795 @@ +#ifndef __PPE42_H__ +#define __PPE42_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42.h +/// \brief PPE42 port header for PK + +// Macros to define where declared code is actually compiled + +#ifdef __PPE42_C__ +#define IF__PPE42_CORE_C__(x) x +#define UNLESS__PPE42_CORE_C__(x) +#else +#define IF__PPE42_CORE_C__(x) +#define UNLESS__PPE42_CORE_C__(x) x +#endif + +#ifdef __PPE42_IRQ_CORE_C__ +#define IF__PPE42_IRQ_CORE_C__(x) x +#define UNLESS__PPE42_IRQ_CORE_C__(x) +#else +#define IF__PPE42_IRQ_CORE_C__(x) +#define UNLESS__PPE42_IRQ_CORE_C__(x) x +#endif + +#ifdef HWMACRO_GPE +#include "gpe.h" +#elif defined(HWMACRO_SBE) +#include "sbe.h" +#elif defined(HWMACRO_CME) +#include "cme.h" +#elif defined(HWMACRO_PPE) +#include "ppe.h" +#else +#error "Macro Type not specified. Are you building from the correct directory?" +#endif + + +#include "ppe42_asm.h" +#include "ppe42_gcc.h" +#include "ppe42_spr.h" +#include "ppe42_msr.h" + + +///start + +/// The synchronization macros defined here all create a compiler +/// memory barrier that will cause GCC to flush/invalidate all memory data +/// held in registers before the macro. This is consistent with other systems, +/// e.g., the PowerPC Linux kernel, and is the safest way to define these +/// macros. + + +// Condition register fields + +#define CR_LT(n) (0x80000000u >> (4 * (n))) +#define CR_GT(n) (0x40000000u >> (4 * (n))) +#define CR_EQ(n) (0x20000000u >> (4 * (n))) +#define CR_SO(n) (0x10000000u >> (4 * (n))) + + +#ifndef __ASSEMBLER__ + +#include "stdint.h" + +/// ssize_t is defined explictly rather than bringing in all of <unistd.h> +#ifndef __ssize_t_defined +#define __ssize_t_defined +typedef int ssize_t; +#endif + +/// A memory barrier +#define barrier() asm volatile ("" : : : "memory") + +/// Ensure In-order Execution of Input/Output +#define eieio() asm volatile ("sync" : : : "memory") + +/// Memory barrier +#define sync() asm volatile ("sync" : : : "memory") + +/// Instruction barrier +#define isync() asm volatile ("sync" : : : "memory") + +/// CouNT Leading Zeros Word +#define cntlzw(x) \ +({uint32_t __x = (x); \ + uint32_t __lzw; \ + asm volatile ("cntlzw %0, %1" : "=r" (__lzw) : "r" (__x)); \ + __lzw;}) + +/// CouNT Leading Zeros : uint32_t +static inline int +cntlz32(uint32_t x) { + return cntlzw(x); +} + +/// CouNT Leading Zeros : uint64_t +static inline int +cntlz64(uint64_t x) { + if (x > 0xffffffff) { + return cntlz32(x >> 32); + } else { + return 32 + cntlz32(x); + } +} + + +/// 32-bit population count +static inline int +popcount32(uint32_t x) +{ + return __builtin_popcount(x); +} + + +/// 64-bit population count +static inline int +popcount64(uint64_t x) +{ + return __builtin_popcountll(x); +} + + +// NB: Normally we wouldn't like to force coercion inside a macro because it +// can mask programming errors, but for the MMIO macros the addresses are +// typically manifest constants or 32-bit unsigned integer expressions so we +// embed the coercion to avoid warnings. + +/// 8-bit MMIO Write +#define out8(addr, data) \ +do {*(volatile uint8_t *)(addr) = (data);} while(0) + +/// 8-bit MMIO Read +#define in8(addr) \ +({uint8_t __data = *(volatile uint8_t *)(addr); __data;}) + +/// 16-bit MMIO Write +#define out16(addr, data) \ +do {*(volatile uint16_t *)(addr) = (data);} while(0) + +/// 16-bit MMIO Read +#define in16(addr) \ +({uint16_t __data = *(volatile uint16_t *)(addr); __data;}) + +/// 32-bit MMIO Write +#define out32(addr, data) \ +do {*(volatile uint32_t *)(addr) = (data);} while(0) + +/// 32-bit MMIO Read +#define in32(addr) \ +({uint32_t __data = *(volatile uint32_t *)(addr); __data;}) + +/// 64-bit MMIO Write +#define out64(addr, data) \ + do { \ + uint64_t __data = (data); \ + volatile uint32_t *__addr_hi = (uint32_t *)(addr); \ + volatile uint32_t *__addr_lo = __addr_hi + 1; \ + *__addr_hi = (__data >> 32); \ + *__addr_lo = (__data & 0xffffffff); \ + } while(0) + +/// 64-bit MMIO Read +#define in64(addr) \ + ({ \ + uint64_t __data; \ + volatile uint32_t *__addr_hi = (uint32_t *)(addr); \ + volatile uint32_t *__addr_lo = __addr_hi + 1; \ + __data = *__addr_hi; \ + __data = (__data << 32) | *__addr_lo; \ + __data;}) + +#endif /* __ASSEMBLER__ */ + +#include "ppe42_irq.h" + +#ifndef __ASSEMBLER__ + +/// Store revision information as a (global) string constant +#define REVISION_STRING(symbol, rev) const char* symbol = rev; + +#else // __ASSEMBLER__ + +/// Store revision information as a global string constant + .macro .revision_string, symbol:req, rev:req + .pushsection .rodata + .balign 4 + .global \symbol +\symbol\(): + .asciz "\rev" + .balign 4 + .popsection + .endm + +#endif // __ASSEMBLER__ + + + +#include "ppe42_context.h" + +// PPE42 stack characteristics for PK. The pre-pattern pattern is selected +// to be easily recognizable yet be an illegal instruction. + +#define PK_STACK_DIRECTION -1 +#define PK_STACK_PRE_DECREMENT 1 +#define PK_STACK_ALIGNMENT 8 +#define PK_STACK_TYPE unsigned int +#define PK_STACK_PATTERN 0x03abcdef + +// Kernel data structure offsets for assembler code + +#define PK_THREAD_OFFSET_SAVED_STACK_POINTER 0 +#define PK_THREAD_OFFSET_STACK_LIMIT 4 +#define PK_THREAD_OFFSET_STACK_BASE 8 + +// PK boot loader panic codes + +#define PPE42_BOOT_VECTORS_NOT_ALIGNED 0x00405001 + +// Interrupt handler panic codes + +#define PPE42_DEFAULT_IRQ_HANDLER 0x00405010 +#define PPE42_DEFAULT_SPECIAL_HANDLER 0x00405011 +#define PPE42_PHANTOM_INTERRUPT 0x00405012 +#define PPE42_PROGRAM_HALT 0x00405013 + + +// Exception handling invariant panic codes + +#define PPE42_IRQ_FULL_EXIT_INVARIANT 0x00405020 +#define PPE42_IRQ_FAST2FULL_INVARIANT 0x00405021 + + +// API error panic codes + + +// Application-overrideable definitions + +/// The default thread machine context has MSR[CE], MSR[EE] and MSR[ME] set, +/// and all other MSR bits cleared. +/// +/// The default definition allows critical, non-critical and machine check +/// exceptions. Debug interrupts are not enabled by default. This definition +/// can be overriden by the application. If MMU protection is enabled then +/// the IR/DR bits are also modeably set. + +#ifndef PK_THREAD_MACHINE_CONTEXT_DEFAULT +#define PK_THREAD_MACHINE_CONTEXT_DEFAULT \ + (MSR_UIE | MSR_EE | MSR_ME) + +#endif + + +#ifndef __ASSEMBLER__ + +/// The PK kernel default panic sequence for C code +/// +/// By default a kernel panic from C code forces external debug mode then +/// generates a \c trap instruction followed by the error code. The \a code +/// argument must be a compile-time integer immediate. This definition can be +/// overriden by the application. +/// +/// The OCC may be running in internal debug mode for various reasons, and +/// TRAP-ing in internal debug mode would lead to an infinite loop in the +/// default Program Interrupt handler - which itself would be a TRAP (since +/// that's the default implementation of PK_PANIC(). Therefore by default +/// the panic is implemented as a special code sequence that forces the core +/// into external debug mode before issuing a TRAP which will halt the core. +/// To preserve the state we use the special global variables +/// __pk_panic_save_dbcr0 and __pk_panic_save_r3 defined in ppe42_core.c. +/// The original value of DBCR0 is destroyed, but can be recovered from the +/// global. In the end %r3 is reloaded from temporary storage and will be +/// unchanged at the halt. +/// +/// Note that there is a small chance that an interrupt will fire and +/// interrupt this code before the halt - in general there is no way around +/// this. +/// +/// The Simics environment does not model Debug events correctly. It executes +/// the TRAP as an illegal instruction and branches to the Program Interrupt +/// handler, destroying the contents of SRR0 and SRR1. Therefore we always +/// insert a special Simics magic breakpoint (which is an effective NOP) +/// before the hardware trap. The special-form magic instruction is +/// recognized by our Simics support scripts which decode the kernel state and +/// try to help the user interpret what happened based on the TRAP code. + +#ifndef PK_PANIC + +/*#define PK_PANIC(code) \ + do { \ + barrier(); \ + asm volatile ("stw %r3, __pk_panic_save_r3@sda21(0)"); \ + asm volatile ("mfdbcr0 %r3"); \ + asm volatile ("stw %r3, __pk_panic_save_dbcr0@sda21(0)"); \ + asm volatile ("lwz %r3, __pk_panic_dbcr0@sda21(0)"); \ + asm volatile ("mtdbcr0 %r3"); \ + asm volatile ("isync"); \ + asm volatile ("lwz %r3, __pk_panic_save_r3@sda21(0)"); \ + asm volatile ("rlwimi 1,1,0,0,0"); \ + asm volatile ("trap"); \ + asm volatile (".long %0" : : "i" (code)); \ + } while (0) +*/ +#define PK_PANIC(code) \ + do { \ + barrier(); \ + asm volatile ("b ."); \ + } while (0) + +// These variables are used by the PK_PANIC() definition above to save and +// restore state. __pk_panic_dbcr0 is the value loaded into DBCR0 to force +// traps to halt the OCC and freeze the timers. + +//#ifdef __PPE42_CORE_C__ +//uint32_t __pk_panic_save_r3; +//uint32_t __pk_panic_save_dbcr0; +//uint32_t __pk_panic_dbcr0 = DBCR0_EDM | DBCR0_TDE | DBCR0_FT; +//#endif + +#endif // PK_PANIC + +/// This is the Simics 'magic breakpoint' instruction. +/// +/// Note that this form does not include a memory barrier, as doing so might +/// change the semantics of the program. There is an alternative form +/// SIMICS_MAGIC_BREAKPOINT_BARRIER that does include a barrier. + +//#define SIMICS_MAGIC_BREAKPOINT asm volatile ("rlwimi 0,0,0,0,0") + +/// This is the Simics 'magic breakpoint' instruction including a memory +/// barrier. +/// +/// Note that the memory barrier guarantees that all variables held in +/// registers are flushed to memory before the breakpoint, however this might +/// change the semantics of the program. There is an alternative form of +/// SIMICS_MAGIC_BREAKPOINT that does not include a barrier. If the idea is +/// to use the breakpoint for tracing code execution in Simics, the barrier +/// form may be preferred so that variable values will be visible in memory. + +/*#define SIMICS_MAGIC_BREAKPOINT_BARRIER \ + asm volatile ("rlwimi 0,0,0,0,0" : : : "memory") +*/ + +#else // __ASSEMBLER__ + +/// This is the Simics 'magic breakpoint' instruction. An assembler macro +/// form is also provided for use within macros. + +//#define SIMICS_MAGIC_BREAKPOINT rlwimi 0,0,0,0,0 + +// .macro _simics_magic_breakpoint +// rlwimi 0,0,0,0,0 +// .endm + +/// The PK kernel panic default panic sequence for assembler code +/// +/// By default a kernel panic from assembler forces external debug mode then +/// generates a \c trap instruction followed by the error code. The \a code +/// argument must be a compile-time integer immediate. This definition can be +/// overriden by the application. +/// +/// See the comments for the non-ASSEMBLER version for further details. Note +/// that the code space reserved for exception handlers is only 8 +/// instructions, so in the assembler context we don't save DBCR0 as doing so +/// would require 10. + +#ifndef PK_PANIC + +#define PK_PANIC(code) _pk_panic code + + .macro _pk_panic, code + b . + .endm + +#endif // PK_PANIC + +#endif // __ASSEMBLER__ + + +// Application-overridible definitions for the PK boot loader + +/// In order to enable the default kernel panic (a trap) to halt the machine, +/// the Debug Control Register 0 (DBCR0) is initialized in externel debug +/// mode, with the Trap Debug Event enabled so that the trap will not cause a +/// program exception, and the FT bit set so that the timers will freeze. +/// This definition can be overridden by the application. +/// +/// NB: It is expected that a reliable production system will redefine all of +/// the 'panic' macros and the default DBCR0 setup. + +#ifndef PPE42_DBCR_INITIAL +#define PPE42_DBCR_INITIAL DBCR_TRAP +#endif + +/// This is the value of the MSR used during initialization. Once PK threads +/// are started (with \c pk_start_threads()), all machine contexts derive +/// from the default thread context \c +/// PK_THREAD_MACHINE_CONTEXT_DEFAULT. This definition can be overriden by +/// the application. +/// +/// The default is to enable machine checks only. + +#ifndef PPE42_MSR_INITIAL +#define PPE42_MSR_INITIAL MSR_ME +#endif + +/// The \a argc argument passed to \c main(). This definition can be overriden +/// by the application. + +#ifndef PPE42_ARGC_INITIAL +#define PPE42_ARGC_INITIAL 0 +#endif + +/// The \a argv argument passed to \c main(). This definition can be overriden +/// by the application. + +#ifndef PPE42_ARGV_INITIAL +#define PPE42_ARGV_INITIAL 0 +#endif + +/// Optionally trap the reset for the debugger, which means that the PPE42 +/// will simply spin at the symbol \c __reset_trap after a chip reset. Set R0 +/// to a non-zero value in the debugger to continue execution. This definition +/// can be overriden by the application. + +#ifndef PPE42_RESET_TRAP +#define PPE42_RESET_TRAP 0 +#endif + +// PPE42 doesn't support the system call instruction so we use this illegal +// instruction to force a program exception which then emulates the system call +// instruction. +#define PPE42_SC_INST 0x44000002 + +#ifndef __ASSEMBLER__ + +/// The PPE42 PK machine context is simply the MSR, a 32-bit integer. + +typedef uint32_t PkMachineContext; + +/// Disable interrupts and return the current +/// context. +/// +/// \param context A pointer to an PkMachineContext, this is the context that +/// existed before interrupts were disabled. Typically this +/// context is restored at the end of a critical section. +/// +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_ARGUMENT_INTERRUPT An illegal priority was specified. + +UNLESS__PPE42_CORE_C__(extern) +inline int +pk_interrupt_disable(PkMachineContext *context) +{ + *context = mfmsr(); + + wrteei(0); + + return PK_OK; +} + +/// Set the machine context. +/// +/// \param context A pointer to an PkMachineContext +/// +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_ARGUMENT_CONTEXT_SET A null pointer was provided as +/// the \a context argument or an illegal machine context was specified. + +UNLESS__PPE42_CORE_C__(extern) +inline int +pk_machine_context_set(PkMachineContext *context) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(context == 0, PK_INVALID_ARGUMENT_CONTEXT_SET); + } + + mtmsr(*context); + + return PK_OK; +} + +/// Get the machine context. +/// +/// \param context A pointer to an PkMachineContext. +/// +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_INVALID_ARGUMENT_CONTEXT_GET A null pointer was provided as +/// the \a context argument. + +UNLESS__PPE42_CORE_C__(extern) +inline int +pk_machine_context_get(PkMachineContext *context) +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_IF(context == 0, PK_INVALID_ARGUMENT_CONTEXT_GET); + } + + *context = mfmsr(); + + return PK_OK; +} + +/// The PK kernel thread context switch - PPE42 uses the 'sc' illegal instruction +/// to force a program exception to occur. +#define __pk_switch() asm volatile (".long 0x44000002") + + +/// In the PowerPC EABI all initial stack frames require 8 bytes - the 4 bytes +/// at the SP are zeroed to indicate the end of the stack, and the 4 bytes +/// behind the SP are for the initial subroutine's LR. + +static inline void +__pk_stack_create_initial_frame(PkAddress *stack, size_t *size) \ +{ + *stack -= 8; + *size -= 8; + *((PK_STACK_TYPE *)(*stack)) = 0; +} + +/// The PK Kernel Context for PPE42 +/// +/// The PK portable kernel does not define how the kernel keeps track of +/// whether PK is running, interrupt levels, and other debug +/// information. Instead it defines an API that the port must provide to the +/// portable kernel. +/// +/// In the PPE42 port, the kernel context is maintained in USPRG0. This +/// 32-bit value is treated as 5 distinct fields as indicated in the structure +/// definition. For certain tests it's also helpful to look at the two +/// interrupt counters as a single 0/non-0 field. +typedef union { + + uint32_t value; + + struct { + + /// The critical interrupt nesting level. If this field is non-zero, + /// then interrupt priority and preemption rules guarantee that a + /// critical interrupt handler is running, and the \c irq field will + /// contain the PkIrqId of the currently active critical interrupt. + unsigned reserved : 8; + + /// The non-critical interrupt nesting level. If this field is + /// non-zero and the \c critical_interrupts field is 0, then interrupt + /// priority and preemption rules guarantee that a noncritical + /// interrupt handler is running, and the \c irq field will contain + /// the PkIrqId of the currently active noncritical interrupt. + unsigned noncritical_interrupts : 8; + + /// The PkIrqId of the currently running (or last run) handler. If + /// either of the interrupt nesting levels are non-0, then this is the + /// PkIrqId of the IRQ that is currently executing. + unsigned irq : 8; + + /// A flag indicating that PK is in thread mode after a call of + /// pk_start_threads(). + unsigned thread_mode : 1; + + /// The priority of the currently running thread. In an interrupt + /// context, this is the priority of the thread that was interrupted. + unsigned thread_priority : 7; + + } fields; + + struct { + + unsigned also_ignore : 8; + + /// Used as a 0/non-0 flag for interrupt context. + unsigned interrupt_context : 8; + + /// Ignore + unsigned ignore : 16; + + } merged_fields; + +} __PkKernelContext; + +// These APIs are provided to the PK portable kernel by the port. + +/// PK threads have been started by a call of pk_start_threads(). + +#define __pk_kernel_mode_thread() \ + ({ \ + __PkKernelContext __ctx; \ + __ctx.value = mfspr(SPRN_SPRG0); \ + __ctx.fields.thread_mode;}) + + +/// PK is executing in a thread context (not an interrupt handler). + +#define __pk_kernel_context_thread() \ + ({ \ + __PkKernelContext __ctx; \ + __ctx.value = mfspr(SPRN_SPRG0); \ + __ctx.fields.thread_mode && !__ctx.merged_fields.interrupt_context;}) + + +/// PK is executing an interrupt handler of any priority. + +#define __pk_kernel_context_any_interrupt() \ + ({ \ + __PkKernelContext __ctx; \ + __ctx.value = mfspr(SPRN_SPRG0); \ + __ctx.merged_fields.interrupt_context;}) + + +/// PK is executing a non-critical interrupt handler. + +#define __pk_kernel_context_noncritical_interrupt() \ + ({ \ + __PkKernelContext __ctx; \ + __ctx.value = mfspr(SPRN_SPRG0); \ + __ctx.fields.noncritical_interrupts && \ + !__ctx.fields.critical_interrupts;}) + + +/// Return the noncritical interrupt nesting level + +#define __pk_noncritical_level() \ + ({ \ + __PkKernelContext __ctx; \ + __ctx.value = mfspr(SPRN_SPRG0); \ + __ctx.fields.noncritical_interrupts; }) + + +// PK requires the port to define the type PkThreadQueue, which is a +// priority queue (where 0 is the highest priority). This queue must be able +// to handle PK_THREADS + 1 priorities (the last for the idle thread) The +// port must also define methods for clearing, insertion, deletion and min +// (with assumed legal priorities). The min operation returns PK_THREADS if +// the queue is empty (or a queue could be initialized with that entry always +// present - PK code never tries to delete the idle thread from a thread +// queue). +// +// These queues are used both for the run queue and the pending queue +// associated with every semaphore. +// +// On PPE42 with 32 threads (implied), this is a job for a uint32_t and +// cntlzw(). + +static inline void +__pk_thread_queue_clear(volatile PkThreadQueue *queue) +{ + *queue = 0; +} + +static inline void +__pk_thread_queue_insert(volatile PkThreadQueue *queue, PkThreadPriority priority) +{ + *queue |= (0x80000000u >> priority); +} + +static inline void +__pk_thread_queue_delete(volatile PkThreadQueue *queue, PkThreadPriority priority) +{ + *queue &= ~(0x80000000u >> priority); +} + +static inline PkThreadPriority +__pk_thread_queue_min(volatile PkThreadQueue *queue) +{ + return cntlzw(*queue); +} + +static inline int +__pk_thread_queue_member(volatile PkThreadQueue *queue, PkThreadPriority priority) +{ + return ((*queue >> (31 - priority)) & 1); +} + +static inline void +__pk_thread_queue_union(volatile PkThreadQueue *queue0, + volatile PkThreadQueue *queue1) +{ + *queue0 |= *queue1; +} + +static inline int +__pk_thread_queue_count(volatile PkThreadQueue* queue) +{ + return __builtin_popcount(*queue); +} + + +/// This macro is used to call __pk_start_threads() using the kernel stack, +/// in a critical section. + +#define __pk_call_pk_start_threads() \ + do { \ + PkMachineContext ctx; \ + pk_critical_section_enter(&ctx); \ + asm volatile ("mr 1, %0; mtlr %1; blrl" : : \ + "r" (__pk_noncritical_stack), \ + "r" (__pk_start_threads)); \ + PK_PANIC(PK_START_THREADS_RETURNED); \ + } while (0) + + +#endif /* __ASSEMBLER__ */ + +/// The __PkKernelContext 'thread_mode' bit as a flag + +#define PPE42_THREAD_MODE 0x80 + + +#ifndef __ASSEMBLER__ + +/// Code breakpoints for PPE42 +/// +/// This macro inserts a special PPE42-only breakpoint into the object code +/// at the place the macro invocation appears. This facility is designed for +/// VBU/VPO procedure debugging. This type of breakpoint may not be required +/// on real hardware as we will then have the full power of RISCWatch, gdb, +/// etc. Once inserted into the code, code breakpoints can be enabled or +/// disabled by manipulating the global variable _code_breakpoint_enable, +/// which defaults to 1. +/// +/// The code breakpoint is implemented as a setup routine and a teardown +/// routine, executed in an critical section. The actual break +/// will occur at the address of the call of the teardown routine, in the +/// context of the calling code. The setup routine saves the state of DBCR0/1 +/// and IAC4, then programs the DBCR for an external debug mode, IAC4 +/// breakpoint. The IAC4 breakpoint is set for the address of the call of the +/// teardown routine. The teardown routine simply restores the state of the +/// debug registers that existed before the code breakpoint. +/// +/// Once hit, restarting from the break requires clearing IAC4 and restarting +/// instructions: +/// +/// \code +/// +/// putspr pu.occ iac4 0 +/// cipinstruct pu.occ start +/// +/// \endcode +/// +/// The above restart processes is also encapsulated as the p8_tclEcmd +/// procedure 'unbreakOcc'. +/// +/// In code built for the Simics environment (i.e., with the preprocessor +/// macro SIMICS_ENVIRONMENT=1) this macro simply expands into +/// SIMICS_MAGIC_BREAKPOINT, and simulation can be continued from the break as +/// normal. This Simics magic breakpoint is also under the control of +/// _code_breakpoint_enable. In code not built with SIMICS_ENVIROMENT=1, note +/// that the CODE_BREAKPOINT is ignored by the Simics PPE42 model as it does +/// not model debug events. + +//#if defined(SIMICS_ENVIRONMENT) && (SIMICS_ENVIRONMENT != 0) +/*#define CODE_BREAKPOINT \ + do { \ + if (_code_breakpoint_enable) { \ + SIMICS_MAGIC_BREAKPOINT; \ + } \ + } while (0) +#else +#define CODE_BREAKPOINT \ + do { \ + if (_code_breakpoint_enable) { \ + PkMachineContext __ctx; \ + pk_critical_section_enter(&__ctx); \ + _code_breakpoint_prologue(); \ + _code_breakpoint_epilogue(); \ + pk_critical_section_exit(&__ctx); \ + } \ + } while (0) +#endif +*/ + +//void +//_code_breakpoint_prologue(void); + +//void +//_code_breakpoint_epilogue(void); + +//extern uint32_t _code_breakpoint_enable; + +#endif // __ASSEMBLER__ + + +#endif /* __PPE42_H__ */ diff --git a/pk/ppe42/ppe42_asm.h b/pk/ppe42/ppe42_asm.h new file mode 100644 index 00000000..4a2e3aec --- /dev/null +++ b/pk/ppe42/ppe42_asm.h @@ -0,0 +1,420 @@ +#ifndef __PPE42_ASM_H__ +#define __PPE42_ASM_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_asm.h +/// \brief Generic assembler macros for 32-bit PPE42 + +// Doxygen is confused by assembler; the best I know how to make it +// work is to put all of the documentation at the beginning like below +// and effectively comment out the code using Doxygen cond/endcond. + +/// \page ppe42_asm Generic assembler macros for 32-bit PPE42 +/// +/// +/// \section _lxzi _l<w,h,b>zi - Load register and Zero from Immediate address +/// +/// These macros encapsulate the 2-instruction sequence required to +/// load from a 32-bit immediate address. +/// +/// \arg \c dreg A register to receive the load data. +/// \arg \c areg A register to hold the immediate address. This can \e +/// not be register 0. Note that if \a areg != \a dreg +/// then \a areg will contain the address at the end of +/// the macro sequence. +/// \arg \c addr A 32-bit immediate address, which may be either an +/// absolute or relocatable expression. +/// +/// Forms: +/// +/// \b _lbzi \a dreg, \a areg, \a addr - Load Byte and Zero from Immediate address \n +/// \b _lhzi \a dreg, \a areg, \a addr - Load Halfword and Zero from Immediate address \n +/// \b _lwzi \a dreg, \a areg, \a addr - Load Word and Zero from Immediate address \n +/// +/// +/// \section _stxi _st<w,h,b>i - STore register to Immediate address +/// +/// These macros encapsulate the 2-instruction sequence required to +/// store to a 32-bit immediate address. +/// +/// \arg \c dreg The register to store. +/// \arg \c areg A register to hold the immediate address. This can \e +/// not be register 0, and can not be the same as \a dreg. +/// Note that \a areg will contain the address at the end of +/// the macro sequence. +/// \arg \c addr A 32-bit immediate address, which may be either an +/// absolute or relocatable expression. +/// +/// Forms: +/// +/// \b _stbi \a dreg, \a areg, \a addr - STore Byte to Immediate address \n +/// \b _sthi \a dreg, \a areg, \a addr - STore Halfword to Immediate address \n +/// \b _stwi \a dreg, \a areg, \a addr - STore Word to Immediate address \n +/// +/// +/// \section _lstzsd _<l,st><w,h,b><z>sd - Load/STore register from/to Small Data area +/// +/// These macros encapulate the small data area relocations for access +/// to storage in the small data sections .sbss, .sdata, .sbss2 and +/// .sdata2. Use of these macros implies small data area support in +/// the compile environment (for variables shared between compiled and +/// assembled code) and initialization code that sets up the small data +/// area registers R13 (and optionally R2). +/// +/// The relocations generated by this macro will work for both SVR4 ABI +/// and EABI environments. In particular, for EABI environments +/// the link editor will insert offsets to either R13 or R2 depending +/// on the section of the symbol. +/// +/// \arg \c dreg The register to load or store. +/// \arg \c addr A 32-bit immediate address, assumed to be a +/// relocatable address in one of the small data sections. +/// +/// Forms: +/// +/// \b _lbzsd \a dreg, \a addr - Load Byte and Zero from Small Data area \n +/// \b _lhzsd \a dreg, \a addr - Load Halfword and Zero from Small Data area \n +/// \b _lwzsd \a dreg, \a addr - Load Word and Zero from Small Data area \n +/// \b _stbsd \a dreg, \a addr - STore Byte to Small Data area \n +/// \b _sthsd \a dreg, \a addr - STore Halfword to Small Data area \n +/// \b _stwsd \a dreg, \a addr - STore Word to Small Data area \n +/// +/// +/// \section _liw _liw<a> - Load Immediate Word (Absolute) +/// +/// These macros encapsulate the two instructions required to load a +/// 32-bit immediate value into a register. If the immediate is an +/// absolute expression, then the \c 'a' form may be able to optimize +/// to a single instruction depending on whether only the high- or +/// low-order bits of the immediate are non-zero. +/// +/// Forms: +/// +/// \b _liw \a rd, \a imm - Load register \a rd with the 32-bit immediate \a imm \n +/// \b _liwa \a rd, \a imm - Load register \a rd with the 32-bit absolute immediate \a imm \n +/// +/// +/// \section _oriwa _oriwa - OR Immediate Word Absolute +/// +/// This macro encapsulates the logical OR of a 32-bit immediate with a +/// register. The immediate value must be an absolute expression. +/// +/// The PowerPC has instructions for OR-ing 16-bit immediates into the +/// upper (\c oris) and lower (\c ori) portions of a register. This +/// macro optimizes the generated code based on which bits (if any) of +/// the absolte immediate are non-zero. +/// +/// This special macro is only provided for the OR function. For other +/// logical operations and recording forms it is necessary in general +/// to first load the 32-bit immediate into a register (e.g., with \c +/// _liwa) then perform the logical operation. +/// +/// \arg \c rd The destination register; at the end will contain \c rs +/// OR \a imm +/// \arg \c rs The source register. +/// \arg \c imm 32-bit absolute expression. +/// +/// Forms: +/// +/// \b _oriwa \a rd, \a rs, \a imm - \a rd gets \a rs OR \a imm \n +/// +/// +/// \section _incr64_fast - 64-bit increment for fast interrupt handlers +/// +/// This macros implements 64-bit counter update in fast interrupt handlers +/// which are forbidden from using the carry-bit in the XER (without +/// saving/restoring it.) +/// +/// \arg \c rs Scratch register +/// \arg \c ra Register containing the counter address at entry +/// +/// \a rs and \a ra must be unique. At the end of the macro the count +/// is updated to memory and \a ra is unmodified. +/// +/// +/// \section _setclear_bits Set/Clear/Copy Bits from Immediate Positions +/// +/// There are situations where it is easier/faster to clear individual bits +/// and bit fields, set bits or copy fields, based on immediate bit numbers +/// and locations, rather than loading masks, since setting up a mask +/// requires 2 instruction in general, whereas these macros generate a single +/// instruction. +/// +/// \arg \c rd - The destination register +/// \arg \c rs - The source register +/// \arg \c n - An immediate size of a bit field, in the range 0 to 32 +/// \arg \c b - An immediate big-endian bit number in the range 0 to 31 +/// +/// Forms: +/// +/// \b _clrfield \a rd, \a rs, \a n, \a b - Clear an \a n bit field from \a rs +/// to \a rd starting from bit \a b \n +/// \b _clrbit \a rd, \a rs, \a b - Clear bit \a b \n +/// \b _setbit \a rd, \a rs, \a b - Set bit \a b \n +/// \b _copyfield \a rd, \a rs, \a n, \a b - Copy an n-bit field from \a rs to +/// \a rd starting from bit \a b \n +/// +/// +/// \section pseudo_ops Assembler Pseudo-Ops Macros +/// +/// Several macros define new 'pseudo-ops'. +/// +/// \subsection cache_align .cache_align +/// +/// The \c .cache_align pseudo-op is used to force alignment on a +/// cache-line boundary. It requires a preprocessor symbol definition for +/// \c LOG_CACHE_LINE_SIZE +/// +/// Forms: +/// +/// \b .cache_align \n +/// +/// +/// \subsection global_function Local and Global Functions +/// +/// The \c .function and \c .global_function pseudo-ops define function +/// symbols in the \c .text section. +/// +/// Forms: +/// +/// \b .function \a symbol - Define a local function \a symbol \n +/// \b .global_function \a symbol - Define a global function \a symbol \n +/// +/// +/// \subsection epilogue .epilogue +/// +/// The \c .epilogue pseudo-op adds size and type information for +/// functions defined in assembler. +/// +/// \arg \c symbol - Assembler epilogue for the function \a symbol. +/// +/// Forms: +/// +/// \b .epilogue \a symbol \n +/// +/// +/// \cond + +#ifdef __ASSEMBLER__ + +### **************************************************************************** +### TODO: shouldnt these be supported by the assembler?? +### **************************************************************************** +# .macro lis treg, imm +# addis \treg, 0, imm +# .endm + +# .macro li treg, imm +# addi \treg, 0, imm +# .endm + +# .macro beq target +# bc 12, 2, \target +# .endm + +### **************************************************************************** +### _l<b,h,w>zi +### _st<b,h,w>i +### **************************************************************************** + + .macro _lbzi dreg, areg, addr + lis \areg, \addr@ha + .ifc \areg, \dreg + lbz \dreg, \addr@l(\areg) + .else + lbzu \dreg, \addr@l(\areg) + .endif + .endm + + .macro _lhzi dreg, areg, addr + lis \areg, \addr@ha + .ifc \areg, \dreg + lhz \dreg, \addr@l(\areg) + .else + lhzu \dreg, \addr@l(\areg) + .endif + .endm + + .macro _lwzi dreg, areg, addr + lis \areg, \addr@ha + .ifc \areg, \dreg + lwz \dreg, \addr@l(\areg) + .else + lwzu \dreg, \addr@l(\areg) + .endif + .endm + + .macro _stbi dreg, areg, addr + .ifc \areg, \dreg + .err + .endif + lis \areg, \addr@ha + stbu \dreg, \addr@l(\areg) + .endm + + .macro _sthi dreg, areg, addr + .ifc \areg, \dreg + .err + .endif + lis \areg, \addr@ha + sthu \dreg, \addr@l(\areg) + .endm + + .macro _stwi dreg, areg, addr + .ifc \areg, \dreg + .err + .endif + lis \areg, \addr@ha + stwu \dreg, \addr@l(\areg) + .endm + + +### **************************************************************************** +### _l<b,h,w>zsd +### _st<b,h,w>sd +### **************************************************************************** + + .macro _lbzsd dreg, addr + lbz \dreg, \addr@sda21(0) + .endm + + .macro _lhzsd dreg, addr + lhz \dreg, \addr@sda21(0) + .endm + + .macro _lwzsd dreg, addr + lwz \dreg, \addr@sda21(0) + .endm + + .macro _stbsd dreg, addr + stb \dreg, \addr@sda21(0) + .endm + + .macro _sthsd dreg, addr + sth \dreg, \addr@sda21(0) + .endm + + .macro _stwsd dreg, addr + stw \dreg, \addr@sda21(0) + .endm + + +### **************************************************************************** +### _liw<a> +### _oriwa +### **************************************************************************** + + .macro _liw rd, imm + lis \rd, \imm@h + ori \rd, \rd, \imm@l + .endm + + .macro _liwa rd, imm + .if (\imm & 0xffff0000) + lis \rd, \imm@h + .if (\imm & 0xffff) + ori \rd, \rd, \imm@l + .endif + .else + li \rd, \imm@l + .endif + .endm + + .macro _oriwa rd, rs, imm + .if (\imm & 0xffff0000) + oris \rd, \rs, \imm@h + .if (\imm & 0xffff) + ori \rd, \rd, \imm@l + .endif + .else + ori \rd, \rs, \imm@l + .endif + .endm + +### **************************************************************************** +### _incr64_fast +### **************************************************************************** + + .macro _incr64_fast, rs:req, ra:req + + lwz \rs, 4(\ra) + addi \rs, \rs, 1 + cmpwi \rs, 0 + stw \rs, 4(\ra) + bne 233643278f + + lwz \rs, 0(\ra) + addi \rs, \rs, 1 + stw \rs, 0(\ra) +233643278: + + .endm + +### **************************************************************************** +### _clrfield +### _clrbit +### _setbit +### _copyfield +### **************************************************************************** + + .macro _clrfield, rd, rs, n, b + rlwinm \rd, \rs, 0, (\b + \n) & 0x1f, (\b - 1) & 0x1f + .endm + + .macro _clrbit, rd, rs, b + _clrfield \rd, \rs, 1, \b + .endm + + .macro _setbit, rd, rs, b + .ifle \b - 15 + oris \rd, \rs, 1 << (15 - \b) + .else + ori \rd, \rs, 1 << (31 - \b) + .endif + .endm + + .macro _copyfield, rd, rs, n, b + rlwimi \rd, \rs, 0, \b , (\b + \n - 1) + .endm + +### **************************************************************************** +### .cache_align +### .<global_>function +### .epilogue +### **************************************************************************** + + .set _log_cache_line_size, LOG_CACHE_LINE_SIZE + + .macro .cache_align + .align _log_cache_line_size + .endm + + .macro .function symbol + .text + .align 2 + .endm + + .macro .global_function symbol + .text + .align 2 + .global \symbol + .endm + + .macro .epilogue symbol + .type \symbol, @function + .size \symbol, . - \symbol + .endm + +#endif /* __ASSEMBLER__ */ + +/// \endcond + +// Local Variables: +// mode:asm +// End: + +#endif /* __PPE42_ASM_H__ */ diff --git a/pk/ppe42/ppe42_boot.S b/pk/ppe42/ppe42_boot.S new file mode 100644 index 00000000..7e8ecee6 --- /dev/null +++ b/pk/ppe42/ppe42_boot.S @@ -0,0 +1,169 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_boot.S +/// \brief PK bootloader for PPE42 + + .nolist +#include "pk.h" + .list + +### PK Bootloader for PPE42 +### +### This is the basic restart initialization of the processor. +### Parts of this code were derived from examples in the IBM OSopen +### OpenBIOS for the 405GP written by James Burke. +### +### This code does not really do very much, just makes sure that there will +### be a reasonable state in the machine when control is turned over to +### the PK application. Any core setup that requires SPR access will be done +### here. All other setup is expected to take place in system-specific +### routines. +### +### From the PowerPC 405-S Embedded Core User's manual: +### +### "In general, the contents of SPRs are undefined after a core, chip or +### system reset. Some SPRs retain the content they had before the reset +### occurred." +### +### Registers fully reset: +### DBCR1 - Data compares disabled +### DCWR - Data cache write-through disabled +### ESR - No exception syndromes +### MSR - No exceptions/interrupts are allowed +### +### Registers partially reset: +### CCR0 = 0x00700000 - Sets ICU and DCU PLB Priority +### DBCR0 [EDM] = 0 - External debug mode disabled +### [RST] = 0 - No reset action +### DBSR [MRR] = x - x indicates most recent reset action +### SGR = 0xffffffff - Storage is guarded +### TCR [WRC] = 0 - Watchdog timer reset disabled +### TSR [WRS] = x - x is a copy of TCR[WRC] Watchdog reset status +### [PIS] = x - undefined + + .global_function __pk_boot + .global __reset_trap + +__pk_boot: + + ## Trap the reset for the debugger. Set R0 to a non-zero value in the + ## debugger to continue. + + .if PPE42_RESET_TRAP + li %r0, 0 +__reset_trap: + cmpwi %r0, 0 + beq __reset_trap + .endif + + ## Set up PowerPC EABI constant registers. These registers are never + ## again touched by the PK kernel or the application (if they are + ## behaving). + + _liw %r2, _SDA2_BASE_ + _liw %r13, _SDA_BASE_ + + ## Clear the timer control register. This masks all timer interrupts. + + li %r3, 0 + mttcr %r3 + + ## The stack pointer is initialized for use by the remainder of the + ## initialization, including the application main(). The linker script + ## defines the initial stack area. + ## + ## Stacks are always 8-byte aligned. A '0' is stored at the + ## stack pointer to indicate the end of the stack chain. Stack frames + ## always consist of at least 8 bytes - the backchain pointer and the + ## slot above the backchain pointer for the callee's LR. + + _liw %r1, _PK_INITIAL_STACK + _clrfield %r1, %r1, 3, 29 # 8-byte align + li %r3, 0 + stwu %r3, -8(%r1) + + ## SPRG0 (__PkKernelContext) is initialized to 0 + ## indicating that the PK kernel is not in thread mode, and no + ## interrupts are active. + + li %r3, 0 + mtsprg0 %r3 + + ## Set up the initial value of Debug Control Register 0. Note that + ## DBCR1 is specified to be cleared at reset. VBU simulation requested + ## an option that this register not be modified so that they could + ## completely control debug behavior from reset of the PPE42. + +#ifndef NO_INIT_DBCR0 + _liwa %r3, PPE42_DBCR_INITIAL + mtdbcr %r3 +#endif + + ## The exception vector prefix is set - it must be 512 byte aligned. + ## NOTE: for PPE42, the IVPR is read only, but can be changed through scoms + + #_liw %r3, __vectors + #andi. %r4, %r3, 0x01ff + #beq 1f + #_pk_panic PPE42_BOOT_VECTORS_NOT_ALIGNED +#1: + #mtivpr %r3 + #sync + + ## The MSR to be used during the rest of intialization is + ## established. This MSR should NOT enable critical or non-critical + ## interrupts, but could enable machine check exceptions. + + _liwa %r3, PPE42_MSR_INITIAL + mtmsr %r3 + sync + +#ifdef PK_BOOT_FROM_ROM + + ## NB: I don't think the old linker scripts were necessarily the most + ## optimal. We need to revisit this if we actually do ROM boots in PK + ## Version 2. Not sure the comments are correct. + + ## Data is copied from the initial ROM image to the RAM. The + ## address symbols are defined in the linker command file. The linker + ## will have zeroed this area in the ROM image. + + liw %r3, __pk_ram_lma - 4 # src + liw %r4, __pk_ram_vma - 4 # dest + liw %r5, __pk_ram_size + liw %r6, 2 + srw %r5, %r5, %r6 # Number of word transfers + mtctr %r5 + +copy_loop: + lwzu %r5, 4(%r3) + stwu %r5, 4(%r4) + bdnz copy_loop + +#endif /* PK_BOOT_FROM_ROM */ + + + ## Call the system setup code. + + bl __ppe42_system_setup + + ## Call the application. If for some reason we return from + ## the call of the application we call an alternate entry point of the + ## idle thread. + ## + ## An initial argc/argv can be passed into main(). argc is expected to + ## be a 32-bit immediate integer, and argv is expected to be a 32-bit + ## absolute or relocatable expression. + + _liwa %r3, PPE42_ARGC_INITIAL + _liw %r4, PPE42_ARGV_INITIAL + bl __pk_main + + b __pk_idle_thread_from_bootloader + + .epilogue __pk_boot + diff --git a/pk/ppe42/ppe42_cache.h b/pk/ppe42/ppe42_cache.h new file mode 100644 index 00000000..01031b41 --- /dev/null +++ b/pk/ppe42/ppe42_cache.h @@ -0,0 +1,102 @@ +#ifndef __PPE42_CACHE_H__ +#define __PPE42_CACHE_H__ + +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_cache.h +/// \brief PowerPC-lite (PPE) cache management header for PK +/// +/// The data cache flush/invalidate macros defined here create a compiler +/// memory barrier that will cause GCC to flush/invalidate all memory data +/// held in registers before the macro. + +#ifndef __ASSEMBLER__ + +/// Determine cache-alignment of a pointer or byte-count +#define cache_aligned(x) \ + ((((unsigned long)(x)) & (POW2_32(LOG_CACHE_LINE_SIZE) - 1)) == 0) + +/// Cache-align a pointer or byte count. If the 'direction' is <= 0 then we +/// round down, else round up. +#define cache_align(x, direction) \ + ({ \ + unsigned long __x = (unsigned long)(x); \ + unsigned long __r; \ + if ((direction) <= 0) { \ + __r = __x & ~(((unsigned long)CACHE_LINE_SIZE) - 1); \ + } else { \ + if (__x % CACHE_LINE_SIZE) { \ + __r = __x + (CACHE_LINE_SIZE - (__x % CACHE_LINE_SIZE)); \ + } \ + } \ + (void *)__r; \ + }) + +/// Data Cache Block Flush +#define dcbf(p) asm volatile ("dcbf 0, %0" : : "r" (p) : "memory") + +/// Data Cache Block Touch +#define dcbt(p) asm volatile ("dcbt 0, %0" : : "r" (p) : "memory") + +/// Data Cache Block Invalidate (Privileged) +#define dcbi(p) asm volatile ("dcbi 0, %0" : : "r" (p) : "memory") + +void +dcache_invalidate_all(void); + +void +dcache_flush_all(void); + +void +dcache_invalidate(void *p, size_t bytes); + +void +dcache_flush(void *p, size_t bytes); + +/// Invalidate a line in the D-cache +/// +/// \param p An address withing the cache line to be invalidated. +/// +/// The dcache_invalidate_line() API is used to invalidate a single cache line +/// containing the address \a p. Note that invalidation is a destructive +/// operation that may cause the loss of information. It is the caller's +/// responsibility to insure that no useful data is inadverdently invalidated. +/// D-cache invalidation is more-or-less a no-op for data either not in the +/// cache or marked as non-cacheable. +/// +/// This API always issues a sync() after the invalidation. + +static inline void +dcache_invalidate_line(void *p) +{ + dcbi(p); + sync(); +} + +/// Flush and invalidate a line from the D-cache +/// +/// \param p An address within the cache line to be flushed. +/// +/// The dcache_flush_line() API can be used as a shortcut to flush and +/// invalidate a single cache line. Note that flushing is not a destructive +/// operation in the sense that no information is lost, however the caller +/// must make sure that the entirity of the data to be flushed is contained in +/// the line that includes the address \a p. D-cache flush is more-or-less a +/// no-op for data either not in the cache or marked as non-cacheable. +/// +/// This API always issues a sync() after the flush. + +static inline void +dcache_flush_line(void *p) +{ + dcbf(p); + sync(); +} + +#endif /* __ASSEMBLER__ */ + +#endif /* __PPE42_CAHE_H__ */ diff --git a/pk/ppe42/ppe42_context.h b/pk/ppe42/ppe42_context.h new file mode 100644 index 00000000..f13ec8a7 --- /dev/null +++ b/pk/ppe42/ppe42_context.h @@ -0,0 +1,455 @@ +#ifndef __PPE42_CONTEXT_H__ +#define __PPE42_CONTEXT_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_context.h +/// \brief PPE42 Machine and Thread context for PK + +/// \page ppe42_machine_context PPE42 Assembler Macros for PK Machine +/// Context (Critical Sections) +/// +/// \section _pk_enter_critical \b _pk_critical_section_enter/exit +/// +/// These macro encapsulates the instruction sequences required to enter and +/// exit critical sections, along with the machine context save for later +/// exiting the critical section. +/// +/// \arg \c ctxreg A register that will hold (holds) the machine context (MSR) +/// prior to entering the critical section (to be restored) for \c +/// _pk_critical_section_enter (\c _pk_critical_section_exit). +/// +/// \arg \c scrreg A scratch register required for the computation of +/// \c _pk_critical_section_enter. +/// +/// Forms: +/// +/// \b _pk_critical_section_enter \a priority, \a ctxreg, \a scrreg - Enter a +/// critical section \n +/// \b _pk_critical_section_exit \a ctxreg - Exit a critical section + +#ifdef __ASSEMBLER__ + + .set _msr_ee_bit, MSR_EE_BIT + + .macro _pk_critical_section_enter ctxreg, scrreg + mfmsr \ctxreg + wrteei 0 + .endm + + .macro _pk_critical_section_exit ctxreg + mtmsr \ctxreg + .endm + +// **************************************************************************** +// PK context save/restore macros for 32-bit Embedded PowerPC +// **************************************************************************** + +// All stack frames are 8-byte aligned in conformance with the EABI. PK +// never saves or restores GPR2 or GPR13. GPR13 is constant in (E)ABI +// applications - the base of the read-write small data area. GPR2 is +// system-reserved in ABI applications, and is the base for read-only small data +// in EABI applications. + +// A fair amount of complexity is involved in handling the non-critical and +// critical interrupt levels, and the emphasis on performance of fast-mode +// interrupt handlers. Several different approaches and philosophies could +// have been implemented - this is only one. In this implementation +// critical/non-critical interrupt levels are treated more or less the same, +// and the interrupt priority is just that - a kind of preemption priority. +// Critical interrupt handling does have a little less overhead because it +// does not have a thread scheduling step at the end. + +// A full context save takes place in 3 or 4 steps. Thread switches always do +// steps 1, 2 and 3. +// 1. The fast context that is always saved in response to every interrupt; +// 1a. The optional save/update of the kernel context for interrupts. +// 2. The (volatile - fast) context that is saved if an interrupt handler +// switches from fast-mode to full-mode. +// 3. The non-volatile context that is saved when a thread is switched out. + +// USPRG0 holds the __PkKernelContext structure (defined in ppe42.h) that +// represents the current kernel context. The layout is as follows: +// +// Bits Meaning +// ============== +// 0:7 The critical interrupt count +// 8:15 The non-critical interrupt count +// 16:23 The IRQ currently being processed +// 24 The 'thread_mode' flag +// 25:31 The thread priority of the running thread +// +// When PK is initialized USPRG0 is initialized to 0. When thread-mode is +// entered (by pk_start_threads()) bit 24 is set to 1. In order to support +// PgP/OCC firmware, once initialized (with pk_initialize()) PK can simply +// handle interrupts, reverting back to the non-thread-mode idle loop when +// there's nothing to do. +// +// Note that it would require a serious error for the interrupt counts to ever +// equal or exceed 2**8 as this would imply runaway reentrancy and stack +// overflow. In fact it is most likely an error if an interrupt handler is +// ever re-entered while active. + +// Registers SRR2 and SRR3 are always saved in IRQ context because +// __pk_irq_fast2full must save the (volatile - fast) context to provide +// working registers before it can look at USPRG0 to determine critical +// vs. non-critical context. However, when restoring a non-critical interrupt +// or thread these registers need not be restored. SRR2 and SRR3 are never +// saved or restored for thread context switches, because threads always +// operate at noncritical level. + +// When MMU protection is enabled, relocation/protection is re-established +// immediately upon entry to the interrupt handler, before any memory +// operations (load/store) take place. This requires using SPRG0 and SPGR4 +// for temporary storage for noncritical/critical handlers respectively in +// accordance with the PK conventions for SPRGn usage by fast-mode +// interrupts. + + ## ------------------------------------------------------------ + ## Unused registers for embedded PPE42` + ## ------------------------------------------------------------ + + ## Registers GPR2 and GPR13 are never saved or restored. In ABI and + ## EABI applications these registers are constant. + + .set UNUSED_GPR2, 0x2 # Dedicated; EABI read-only small data area + .set UNUSED_GPR13, 0xd # Dedicated; (E)ABI read-write small data area + + ## ------------------------------------------------------------ + ## Flags for context push/pop + ## ------------------------------------------------------------ + + .set PK_THREAD_CONTEXT, 0 + .set PK_IRQ_CONTEXT, 1 + + ## ------------------------------------------------------------ + ## The PK fast context layout for Embedded PPE42 + ## ------------------------------------------------------------ + + .set PK_FAST_CTX_GPR1, 0x00 # Dedicated; Stack pointer + .set PK_FAST_CTX_HANDLER_LR, 0x04 # Slot for handler to store LR + .set PK_FAST_CTX_GPR3, 0x08 # Volatile; Parameter; Return Value + .set PK_FAST_CTX_GPR4, 0x0c # Volatile; Parameter + .set PK_FAST_CTX_GPR5, 0x10 # Volatile; Parameter + .set PK_FAST_CTX_GPR6, 0x14 # Volatile; Parameter + .set PK_FAST_CTX_CR, 0x18 # Condition register + .set PK_FAST_CTX_LR, 0x1c # Link register SPRN 0x008 + .set PK_FAST_CTX_KERNEL_CTX, 0x20 # Saved __PkKernelContext for IRQ + + .set PK_FAST_CTX_SIZE, 0x28 # Must be 8-byte aligned + + ## ------------------------------------------------------------ + ## The PK (volatile - fast) context layout for Embedded PPE42 + ## ------------------------------------------------------------ + + .set PK_VOL_FAST_CTX_GPR1, 0x00 # Dedicated; Stack pointer + .set PK_VOL_FAST_CTX_HANDLER_LR, 0x04 # Slot for handler to store LR + .set PK_VOL_FAST_CTX_GPR7, 0x08 # Volatile; Parameter + .set PK_VOL_FAST_CTX_GPR8, 0x0c # Volatile; Parameter + .set PK_VOL_FAST_CTX_GPR9, 0x10 # Volatile; Parameter + .set PK_VOL_FAST_CTX_GPR10, 0x14 # Volatile; Parameter + .set PK_VOL_FAST_CTX_XER, 0x18 # Fixed-point exception register SPRN 0x001 + .set PK_VOL_FAST_CTX_CTR, 0x1c # Count register SPRN 0x009 + .set PK_VOL_FAST_CTX_SRR0, 0x20 # Save/restore register 0 SPRN 0x01a + .set PK_VOL_FAST_CTX_SRR1, 0x24 # Save/restore register 1 SPRN 0x01b + .set PK_VOL_FAST_CTX_GPR0, 0x28 # Volatile; Language specific + + .set PK_VOL_FAST_CTX_SIZE, 0x30 # Must be 8-byte aligned + + ## ------------------------------------------------------------ + ## The PK non-volatile context layout for Embedded PowerPC + ## ------------------------------------------------------------ + + ## The 'preferred form' for stmw is for the LSB of R31 to fall into the + ## end of a 16-byte aligned block. + + .set PK_NON_VOL_CTX_GPR1, 0x00 # Dedicated; Stack Pointer + .set PK_NON_VOL_CTX_HANDLER_LR, 0x04 # Slot for handler to store LR + .set PK_NON_VOL_CTX_GPR28, 0x08 # Non-volatile + .set PK_NON_VOL_CTX_GPR29, 0x0c # Non-volatile + .set PK_NON_VOL_CTX_GPR30, 0x10 # Non-volatile + .set PK_NON_VOL_CTX_GPR31, 0x14 # Non-volatile + + .set PK_NON_VOL_CTX_SIZE, 0x18 # Must be 8-byte aligned + + ## ------------------------------------------------------------ + ## Save/restore the fast context + ## + ## 11 Instructions, 8 Loads/Stores : If MMU is disabled + ## 17 Instructions, 8 Loads/Stores : If MMU is enabled + ## ------------------------------------------------------------ + ## + ## Without MMU support, an EIEIO is always executed at the entry point + ## to gauarantee that all memory operations (especially MMIO + ## operations) have completed prior to execution of the interrupt + ## handler. + ## + ## If MMU support is enabled, address translation is re-established + ## immediately at the entry of each interrupt, prior to performing any + ## loads or stores. PK currently only supports using the MMU for + ## protection, not for address translation. Therfore it is 'legal' + ## to change translation modes a with an MTMSR followed by an + ## ISYNC. This is much simpler then the complex instruction sequence + ## that would be required if we had to set up RFI/RFCI sequences to + ## change the execution context at this point. + ## + ## Note that since we are not really doing address translation, it + ## would also be in keeping with the 'fast interrupt' idea to defer + ## reenabling translation (protection) until the fast-to-full sequence + ## was executed for full-mode interrupts, and run fast-mode interrupts + ## unprotected. However here we chose to run all interrupts with MMU + ## protection. + ## + ## Unfortunately the simple MTMSR;ISYNC sequence exposes a serious bug + ## in the PPE42 core that causes the stack-pointer store instruction + ## to generate a seemingly random, *real-mode* address in certain cases + ## when this instruction in a noncritical interrupt prologue is + ## interrupted by a critical interrupt. This bug is described in + ## HW239446. The workaround is to follow the ISYNC sith a SYNC - which + ## eliminates the problem for reasons still unknown. On the bright side + ## this SYNC might also serve the same purpose as the EIEIO in the + ## non-MMU case, guaranteeing that all MMIO has completed prior to the + ## interrupt handler. However without the initial EIEIO we still + ## experience failures, so this seemingly redundant instruction also + ## remains in place. This requirement is assumed to be related to the + ## HW239446 issue. + + .macro _pk_fast_ctx_push + + stwu %r1, -PK_FAST_CTX_SIZE(%r1) + + stvd %d3, PK_FAST_CTX_GPR3(%r1) + stvd %d5, PK_FAST_CTX_GPR5(%r1) + + mfcr %r3 + mflr %r4 + + stvd %d3, PK_FAST_CTX_CR(%r1) + + .endm + + + .macro _pk_fast_ctx_pop + + lvd %d3, PK_FAST_CTX_CR(%r1) + + mtcr0 %r3 + mtlr %r4 + + lvd %d3, PK_FAST_CTX_GPR3(%r1) + lvd %d5, PK_FAST_CTX_GPR5(%r1) + + lwz %r1, 0(%r1) + + .endm + + ## ------------------------------------------------------------ + ## Save/update the kernel context in response to an interrupt. This is + ## not part of the fast context save because for external interupts the + ## IRQ is not determined until later. + ## ------------------------------------------------------------ + + ## The kernel context is saved, then updated with the currently active + ## IRQ in bits 16:23. The correct interrupt count is incremented and + ## the context is returned to SPRG0. + + .macro _save_update_kernel_context irqreg, ctxreg + + #PK_TRACE_NONCRITICAL_IRQ_ENTRY \irqreg, \ctxreg + + mfsprg0 \ctxreg + stw \ctxreg, PK_FAST_CTX_KERNEL_CTX(%r1) + #rlwimi \ctxreg, \irqreg, 24, 9, 15 //set the irq # + rlwimi \ctxreg, \irqreg, 8, 16, 23 //set the irq # + #oris \ctxreg, \ctxreg, 0x4000 //set the 'processing interrupt' PI bit + addis \ctxreg, \ctxreg, 0x0001 //increment the irq count + mtsprg0 \ctxreg + + .endm + + ## ------------------------------------------------------------ + ## Fast-mode context pop and RF(C)I. This is only used by + ## interrupt handlers - the thread context switch has its own + ## code to handle updating USPRG0 for thread mode. + ## ------------------------------------------------------------ + + .macro _pk_fast_ctx_pop_exit + + .if PK_KERNEL_TRACE_ENABLE + bl __pk_trace_noncritical_irq_exit + .endif + + lwz %r3, PK_FAST_CTX_KERNEL_CTX(%r1) + mtsprg0 %r3 + _pk_fast_ctx_pop + rfi + + .endm + + ## ------------------------------------------------------------ + ## Save/restore the (volatile - fast) context + ## + ## Thread - 15 Instructions, 11 Loads/Stores + ## IRQ - 19(15) Instructions, 13(11) Loads/Stores + ## ------------------------------------------------------------ + + .macro _pk_vol_fast_ctx_push + + stwu %r1, -PK_VOL_FAST_CTX_SIZE(%r1) + + stw %r0, PK_VOL_FAST_CTX_GPR0(%r1) + stvd %d7, PK_VOL_FAST_CTX_GPR7(%r1) + stvd %d9, PK_VOL_FAST_CTX_GPR9(%r1) + + mfxer %r7 + mfctr %r8 + mfsrr0 %r9 + mfsrr1 %r10 + + stvd %d7, PK_VOL_FAST_CTX_XER(%r1) + stvd %d9, PK_VOL_FAST_CTX_SRR0(%r1) + + .endm + + + .macro _pk_vol_fast_ctx_pop + + lvd %d7, PK_VOL_FAST_CTX_XER(%r1) + lvd %d9, PK_VOL_FAST_CTX_SRR0(%r1) + + mtxer %r7 + mtctr %r8 + mtsrr0 %r9 + mtsrr1 %r10 + + lwz %r0, PK_VOL_FAST_CTX_GPR0(%r1) + lvd %d7, PK_VOL_FAST_CTX_GPR7(%r1) + lvd %d9, PK_VOL_FAST_CTX_GPR9(%r1) + + lwz %r1, 0(%r1) + + .endm + + ## ------------------------------------------------------------ + ## Save/restore the non-volatile context on the stack + ## + ## 2 Instructions, 19 Loads/Stores + ## ------------------------------------------------------------ + + .macro _pk_non_vol_ctx_push + + stwu %r1, -PK_NON_VOL_CTX_SIZE(%r1) + stvd %d28, PK_NON_VOL_CTX_GPR28(%r1) + stvd %d30, PK_NON_VOL_CTX_GPR30(%r1) + + .endm + + + .macro _pk_non_vol_ctx_pop + + lvd %d28, PK_NON_VOL_CTX_GPR28(%r1) + lvd %d30, PK_NON_VOL_CTX_GPR30(%r1) + lwz %r1, 0(%r1) + + .endm + +#else /* __ASSEMBLER__ */ + +/// PK thread context layout as a C structure. +/// +/// This is the structure of the stack area pointed to by +/// thread->saved_stack_pointer when a thread is fully context-switched out. + +typedef struct { + + uint32_t r1_nv; + uint32_t link_nv; + uint32_t r28; + uint32_t r29; + uint32_t r30; + uint32_t r31; + uint32_t r1_vf; + uint32_t link_vf; + uint32_t r7; + uint32_t r8; + uint32_t r9; + uint32_t r10; + uint32_t xer; + uint32_t ctr; + uint32_t srr0; + uint32_t srr1; + uint32_t r0; + uint32_t pad; + uint32_t r1; + uint32_t link_fast; + uint32_t r3; + uint32_t r4; + uint32_t r5; + uint32_t r6; + uint32_t cr; + uint32_t lr; + uint32_t sprg0; + +} PkThreadContext; + +/// PK thread context of an interrupted thread (full-mode handler) +/// +/// When a thread is interrupted by a full-mode interrupt handler, this is the +/// layout of the stack area pointed to by either __pk_saved_sp_noncritical +/// or __pk_saved_sp_critical. + +typedef struct { + + uint32_t r1_vf; + uint32_t link_vf; + uint32_t r7; + uint32_t r8; + uint32_t r9; + uint32_t r10; + uint32_t xer; + uint32_t ctr; + uint32_t srr0; + uint32_t srr1; + uint32_t r0; + uint32_t pad; + uint32_t r1; + uint32_t link_fast; + uint32_t r3; + uint32_t r4; + uint32_t r5; + uint32_t r6; + uint32_t cr; + uint32_t lr; + uint32_t sprg0; + +} PkThreadContextFullIrq; + + +/// PK thread context of an interrupted thread (fast-mode handler) +/// +/// When a thread is interrupted by a fast-mode interrupt handler, this is the +/// layout of the stack area pointed to by R1 - unless the fast-mode interrupt +/// handler extends the stack. + +typedef struct { + + uint32_t r1; + uint32_t link_fast; + uint32_t r3; + uint32_t r4; + uint32_t r5; + uint32_t r6; + uint32_t cr; + uint32_t lr; + uint32_t sprg0; + +} PkThreadContextFastIrq; + +#endif /* __ASSEMBLER__ */ + +#endif /* __PPE42_CONTEXT_H__ */ + + diff --git a/pk/ppe42/ppe42_core.c b/pk/ppe42/ppe42_core.c new file mode 100644 index 00000000..f82b3bf6 --- /dev/null +++ b/pk/ppe42/ppe42_core.c @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_core.c +/// \brief The final bits of PK runtime code required to complete the PPE42 +/// port. +/// +/// The entry points in this file are considered 'core' routines that will +/// always be present during runtime in any PK application. + +#define __PPE42_CORE_C__ + +#include "pk.h" + +typedef union +{ + uint64_t value; + struct + { + uint32_t dec_start; + uint32_t dec_change_tag; + }; +}ppe42_timebase_data_t; + +ppe42_timebase_data_t ppe42_tb_data = {0}; +PkTimebase ppe42_64bit_timebase = 0; + + +#if 0 +/// Get the emulated 64-bit timebase +/// + +PkTimebase +pk_timebase_get(void) +{ + PkTimebase tb; + uint32_t dec_start; + uint32_t dec; + uint32_t time_since_last_update; + PkMachineContext ctx; + + pk_critical_section_enter(&ctx); + + tb = ppe42_64bit_timebase; + dec_start = ppe42_dec_start; + dec = mfspr(SPRN_DEC); + + pk_critical_section_exit(&ctx); + + time_since_last_update = dec_start - dec; + + return (tb + time_since_last_update); +} +#endif + +/// Set the 64-bit timebase in an critical section +/// +/// It is assumed that the caller knows what they are doing; e.g., is aware of +/// what may happen when time warps as a result of this call. + +void +pk_timebase_set(PkTimebase timebase) +{ + PkMachineContext ctx; + + pk_critical_section_enter(&ctx); + + ppe42_64bit_timebase = timebase; + ppe42_tb_data.dec_start = 0; + ppe42_tb_data.dec_change_tag++; + + //This will cause TSR[DIS] to be set on the next timer tick. + mtspr(SPRN_DEC, 0); + + pk_critical_section_exit(&ctx); +} + + +/// Enable interrupt preemption +/// +/// This API can only be called from an interrupt context. Threads will +/// always be preempted by interrupts unless they explicitly disable +/// interrupts with the \c pk_interrupt_disable() API. It is legal to call +/// this API redundantly. +/// +/// Be careful when enabling interrupt handler preemption that the interrupt +/// being handled does not/can not trigger again, as this could rapidly lead +/// to stack overflows. +/// +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_ILLEGAL_CONTEXT The API call was not made from an interrupt +/// context. + +int +pk_interrupt_preemption_enable() +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_UNLESS_ANY_INTERRUPT_CONTEXT(); + } + + wrteei(1); + + return PK_OK; +} + + +/// Disable interrupt preemption +/// +/// This API can only be called from an interrupt context. Threads will +/// always be preempted by interrupts unless they explicitly disable +/// interrupts with the \c pk_interrupt_disable() API. It is legal to call +/// this API redundantly. +/// +/// Return values other then PK_OK (0) are errors; see \ref pk_errors +/// +/// \retval 0 Successful completion +/// +/// \retval -PK_ILLEGAL_CONTEXT The API call was not made from an interrupt +/// context. + +int +pk_interrupt_preemption_disable() +{ + if (PK_ERROR_CHECK_API) { + PK_ERROR_UNLESS_ANY_INTERRUPT_CONTEXT(); + } + + wrteei(0); + + return PK_OK; +} + + +#if PK_TIMER_SUPPORT + +// The tickless kernel timer mechanism for PPE42 +// +// This routine must be called from a critical section. +// +// Tickless timeouts are provided by programming the PIT timer based on when +// the next timeout will occur. If the timeout is for the end of time there's +// nothing to do - PK does not use auto-reload mode so no more PIT interrupts +// will be arriving. Otherwise, if the timeout is longer than the 32-bit PIT +// timer can handle, we simply schedule the timeout for 2**32 - 1 and +// __pk_timer_handler() will keep rescheduling it until it finally occurs. +// If the \a timeout is in the past, we schedule the PIT interrupt for 1 tick +// in the future in accordance with the PK specification. + +void +__pk_schedule_hardware_timeout(PkTimebase timeout) +{ + PkTimebase now; + uint32_t new_dec; + PkMachineContext ctx; + uint32_t dec; + + if (timeout != PK_TIMEBASE_MAX) { + + pk_critical_section_enter(&ctx); + + now = pk_timebase_get(); + + if (timeout <= now) { + new_dec = 1; + } else if ((timeout - now) > 0xffff0000) { + new_dec = 0xffff0000; + } else { + new_dec = timeout - now; + } + + //read and write the DEC back-to-back so that we loose as little time + //as possible + dec = mfspr(SPRN_DEC); + mtspr(SPRN_DEC, new_dec); + + //update our 64bit accumulator with how much time has advanced since + //we last changed it. + ppe42_64bit_timebase += ppe42_tb_data.dec_start - dec; + + //update our start time so we know how much time has advanced since + //this update of the accumulator + ppe42_tb_data.dec_start = new_dec; + ppe42_tb_data.dec_change_tag++; + + pk_critical_section_exit(&ctx); + } +} + + + +#endif /* PK_TIMER_SUPPORT */ + +#undef __PPE42_CORE_C__ diff --git a/pk/ppe42/ppe42_exceptions.S b/pk/ppe42/ppe42_exceptions.S new file mode 100644 index 00000000..9b3afa0a --- /dev/null +++ b/pk/ppe42/ppe42_exceptions.S @@ -0,0 +1,580 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_exceptions.S +/// \brief PPE42 exception vector area. +/// +/// \cond + + .nolist +#include "pk.h" + .list + +## declare and initializes global variables that hold external irq config data +## Each PPE macro type (GPE, CME, and SBE) will have it's own implementation of this macro +## defined in (gpe, cme, sbe)_common.h + .hwmacro_irq_cfg_bitmaps + +### **************************************************************************** +### .vectors - This section contains all ppe42 exception vectors +### +### **************************************************************************** + + .section .vectors, "ax", @progbits + + .global __vectors + +__vectors: + + ############################################################ + # 0x0000 : Machine Check + ############################################################ + + ### Unmaskable interrupts (including program interrupts) are promoted + ### to machine check interrupts if MSR[UIE] = 0 and MSR[ME] = 1. + ### If the machine check was caused by a program interrupt it + ### will be forwarded to the program exception handler. +__machine_check: + + PPE42_MACHINE_CHECK_HANDLER + + ############################################################ + # 0x0040 : System Reset + ############################################################ + .global __system_reset + .org __vectors + 0x0040 +__system_reset: + b __pk_boot + + ############################################################ + # 0x0060 : Data Storage Interrupt + ############################################################ + + .org __vectors + 0x0060 +__data_storage: + + PPE42_DATA_STORAGE_HANDLER + + ############################################################ + # 0x0080 : Instruction Storage Interrupt + ############################################################ + + .org __vectors + 0x0080 +__instruction_storage: + + PPE42_INSTRUCTION_STORAGE_HANDLER + + + ############################################################ + # 0x00A0 : External Interrupt + ############################################################ + + .org __vectors + 0x00A0 +__external_interrupt_vector: + _pk_fast_ctx_push + + b __external_interrupt + + ############################################################ + # 0x00C0 : Alignment Exception + ############################################################ + + .org __vectors + 0x00C0 +__alignment_exception: + + PPE42_ALIGNMENT_HANDLER + + + ############################################################ + # 0x00E0 : Program Interrupt + ############################################################ + + .org __vectors + 0x00E0 + + ### Program exceptions are utilized for emulating the system call + ### instruction (0x44000002) which is used for doing context + ### switches between threads. They can also be used by the code + ### to signal an exception in an error scenario. +__program_exception: + _pk_fast_ctx_push + + b program_exception_handler + + + ############################################################ + # 0x0100 : DEC Interrupts + ############################################################ + + .org __vectors + 0x0100 +__dec_interrupt: + _pk_fast_ctx_push + li %r3, PPE42_IRQ_DEC + b dec_handler + + ############################################################ + # 0x0120 : FIT Interrupts + ############################################################ + .org __vectors + 0x0120 +__fit_interrupt: + + #b fit_handler + b . + + ############################################################ + # 0x0140 : Watchdog Interrupts + ############################################################ + .org __vectors + 0x0140 +__watchdog_interrupt: + + #b watchdog_handler + b . + + + + + + .global __pk_irq_fast2full +__pk_irq_fast2full: + + ## Convert a fast-mode to a full-mode interrupt by saving the + ## (volatile - fast) context, and switching to the appropriate system + ## stack. + + ## Entry invariants: + ## 1. The SP/stack must be exactly as it was when the fast-mode + ## handler was entered. + ## 2. No changes have been made to the MSR - the interrupt level must + ## remain disabled. + ## 3. The handler owns the fast context and has not modified the other + ## register context. This routine can only use the (volatile - + ## fast) register context. + + ## 41 (linear) instructions plus alignmenmt + + ## Start by pushing the (volatile - fast) context. Technically we also + ## need to save the CR as our contract with the handler is not to + ## disturb any of its register state. + + _pk_vol_fast_ctx_push + mfcr %r10 + mfsprg0 %r8 + + ## At entry here the (volatile - fast) context has been pushed, + ## R8 has SPRG0 and R10 contains the saved CR. + + ## Note that it would violate a kernel/API invariant if this routine + ## were entered from outside an interrupt context. + +fast2full_noncritical: + + ## switch to the kernel stack if we haven't already done so. (SPRG0[RI] = 0) + #bb1wi %r8, RI_BIT, 1f //branches if the RI_BIT is '1' + + extrwi %r9, %r8, 8, 8 + cmpwi %r9, 1 + bne 1f + + _stwsd %r1, __pk_saved_sp_noncritical + _lwzsd %r1, __pk_noncritical_stack + +1: + + .if (PK_ERROR_CHECK_KERNEL | PK_ERROR_CHECK_API) + #bb1wi %r8, PI_BIT, 2f //branches if PI_BIT is '1' + cmpwi %r9, 0 + bne 2f + _pk_panic PPE42_IRQ_FAST2FULL_INVARIANT +2: + .endif + + mtcr0 %r10 + blr + + .global __pk_irq_full_mode_exit +__pk_irq_full_mode_exit: + + ## Exit a full-mode handler. + + ## Entry invariants: + ## 1. The SP/stack must be in exactly the same state it was left in at + ## the exit of __pk_irq_fast2full. + ## 2. It is assumed the the preemption rules of PK have been followed + ## - in particular that critical handlers have not enabled + ## non-critical interrupts. + + ## We can freely modify the volatile context here - the handler is done + ## and we will restore the interrupted volatile context. + + ## 22 linear instructions + + ## If the critical count is non-zero, then the PK preemption rules + ## guarantee that we are exiting from a critical interrupt + ## handler. This test is safe to make even if critical interrupts are + ## enabled, because the variable is set exactly once in a critical + ## section. + + mfsprg0 %r3 + + ## Exiting a full-mode non-critical handler is more complex than the + ## critical case, because the handler may have made a new + ## highest-priority thread runnable and we may need to go through a + ## delayed scheduling step. + + ## Note that the idle thread is treated as a special case. The idle + ## thread has no permanent register context. To avoid having to + ## allocate a stack area for the idle thread, the idle thread + ## 'uses' the non-critical stack. When the idle thread is interrupted + ## the (redundant) context is pushed, but is then effectively lost. + ## Whenever we restore the idle thread we simply reenter the idle + ## thread entry point. + + ## At entry: + ## 1. R3 holds the value of SPRG0 (__PkKernelContext) + + ## 33 linear instructions. + +full_exit_noncritical: + + ## Enter a critical section for the return from interrupt, in the event + ## that the handler enabled preemption. + + _pk_critical_section_enter %r4, %r5 + + ## If the non-critical count is > 1 then this is a nested interrupt + ## and we can simply pop the context and RFI. + + extrwi. %r4, %r3, 8, 8 + + ## If SPRG0[RI] = 1 then this is a recursive interrupt + ## and we can simply pop the context and RFI. Note that it would + ## violate a kernel/API invariant if this routine were entered from + ## outside an interrupt context (interrupt level == 0). + + .if (PK_ERROR_CHECK_KERNEL | PK_ERROR_CHECK_API) + #bb1wi %r3, PI_BIT, 1f //branch if the PI bit is set + bne 1f + _pk_panic PPE42_IRQ_FULL_EXIT_INVARIANT +1: + .endif + + cmpwi %r4, 1 + bne exit_noncritical_without_switch + + ## Otherwise, restore the saved stack pointer and continue. + + _lwzsd %r1, __pk_saved_sp_noncritical + + ## If we are not in thread mode (i.e., we took an interrupt in an + ## interupt-only configuration of PK or after pk_initialize() but + ## before pk_start_threads) simply pop the context and RFI - in this + ## case we'll most likely be returning to main() or the non-thread-mode + ## idle thread. + + andi. %r4, %r3, PPE42_THREAD_MODE + beq exit_noncritical_without_switch + + ## Now, check for a delayed context switch. If none is pending, we can + ## exit (after a check for the idle thread special case). + + _lwzsd %r3, __pk_delayed_switch + cmpwi %r3, 0 + bne noncritical_switch + + _lwzsd %r3, __pk_current_thread + cmpwi %r3, 0 + beq __pk_idle_thread + +exit_noncritical_without_switch: + _pk_vol_fast_ctx_pop + b fast_exit_noncritical + + ## The non-critical interrupt activated a delayed context switch. The + ## C-level code has taken care of the scheduling decisions - we simply + ## need to implement them here. + +noncritical_switch: + + ## Clear the delayed switch flag and go to the context switch code to + ## finish the switch. + + li %r3, 0 + _stwsd %r3, __pk_delayed_switch + + b thread_save_non_volatile_and_switch + + + + + + ## The idle thread has no permanent register context. The idle thread + ## entry point is re-entered whenever the idle thread is scheduled. + + .global __pk_idle_thread + .global __pk_idle_thread_from_bootloader + +__pk_idle_thread: + + ## The idle thread 'uses' the non-critical stack. Any register context + ## pushed here is redundant and is wiped out/ignored every time the + ## idle thread is re-scheduled. + + ## The idle thread simply establishes a default machine context and + ## enters the wait-enable state. The idle thread is always entered + ## with non-critical interrupts disabled. + ## + ## The kernel context is initialized to indicate that the idle thread + ## is running - the idle thread priority is PK_THREADS, and the + ## 'thread-mode' bit is asserted as well. + ## + ## This loop can also be called from the PK bootloader if main() + ## returns - in which case we don't muck with the SPRG0 or the stack + ## pointer. + + li %r3, (PK_THREADS | PPE42_THREAD_MODE) + mtsprg0 %r3 + _lwzsd %r1, __pk_noncritical_stack + +__pk_idle_thread_from_bootloader: + + #li %r3, PK_THREADS + #PK_TRACE_THREAD_SWITCH %r3, %r4 + _lwzsd %r3, __pk_thread_machine_context_default + _oriwa %r3, %r3, MSR_WE + mtmsr %r3 + b . + + ## pk_halt() is implemented on the ppe42 by writing a value of 0x3 to + ## the RST field of the DBCR. + .global pk_halt +pk_halt: + lis %r31, 0x3000 + mtdbcr %r31 + .long 0 + + +dec_handler: + + ## The portable timer handler of PK is a full-mode handler with the prototype: + ## void (*pk_timer_handler)(void). + ## + ## To support the portable specification, the kernel clears the + ## interrupt by writing the DIS back into the TSR before calling the + ## handler. The timer handler does not take any arguments. + + _save_update_kernel_context %r3, %r4 + + _liwa %r3, TSR_DIS + mttsr %r3 + + _pk_irq_fast2full __pk_timer_handler + + + + + + ## Exit traces are moved here because the code area (0x100 bytes) + ## reserved for individual interrupts is overflowing when tracing is + ## enabled. This is kind of a hack: We know that this trace only + ## occurs when we're about to exit the fast context, at a place + ## where we can use any of the fast registers. +#if 0 +__pk_trace_noncritical_irq_exit: + #PK_TRACE_NONCRITICAL_IRQ_EXIT %r3, %r4 + blr +#endif + +program_exception_handler: + ## first check if exception was caused by an illegal 'sc' instruction + mfspr %r3, SPRN_EDR + _liw %r4, PPE42_SC_INST + cmpwbeq %r3, %r4, __sc_helper + _pk_panic PPE42_ILLEGAL_INSTRUCTION + + ## SRR0 is currently pointing to the 'sc' instruction. We need to advance it + ## to the next instruction so that we don't end up in an endless loop (something + ## that the ppc sc instruction does automatically). +__sc_helper: + mfsrr0 %r3 + addi %r3, %r3, 4 + mtsrr0 %r3 + +__system_call: + + ## The program exception is used by PK as a handy way to start a + ## context switch, as the continuation address and MSR of the thread to + ## be swapped out are saved in SRR0 and SRR1. + + ## Non-critical interrupts are disabled at entry. + + ## Begin by saving the volatile context of the current thread. + ## NOTE: fast context has already been saved prior to branching here. + + _pk_vol_fast_ctx_push + +thread_save_non_volatile_and_switch: + + ## Finish the thread context save by pushing the non-volatile context + ## and saving the resulting stack pointer in the thread structure. If + ## the current thread is the idle thread this step is bypassed. + + ## This symbol is also used as an entry point by the non-critical + ## interrupt handler - non-critical interrupts are disabled here. + + _lwzsd %r3, __pk_current_thread + cmpwi %r3, 0 + beq __pk_next_thread_resume + + _pk_non_vol_ctx_push + stw %r1, PK_THREAD_OFFSET_SAVED_STACK_POINTER(%r3) + + ## The next thread becomes the current thread, and we switch to its + ## stack - unless the new thread is the idle thread, in which case it + ## (the idle thread) is simply resumed. + + .global __pk_next_thread_resume +__pk_next_thread_resume: + + _lwzsd %r3, __pk_next_thread + _stwsd %r3, __pk_current_thread + + cmpwi %r3, 0 + beq __pk_idle_thread + + lwz %r1, PK_THREAD_OFFSET_SAVED_STACK_POINTER(%r3) + + ## Restore the thread context and resume the new thread. The kernel + ## context in thread mode is simply the thread priority OR'ed with the + ## thread-mode flag. All other fields are cleared. + + _pk_non_vol_ctx_pop + _pk_vol_fast_ctx_pop + + _lbzsd %r3, __pk_next_priority + #PK_TRACE_THREAD_SWITCH %r3, %r4 + ori %r3, %r3, PPE42_THREAD_MODE + mtsprg0 %r3 + + _pk_fast_ctx_pop + rfi + +fit_handler: + + ## The FIT handler is user defined, and is a fast-mode handler. By + ## convention the kernel clears the interrupt by writing the FIS back + ## into the TSR. + + _pk_fast_ctx_push + + _lwzsd %r3, __ppe42_fit_arg + li %r4, PPE42_IRQ_FIT + + _save_update_kernel_context %r4, %r6 + + _liwa %r6, TSR_FIS + mttsr %r6 + + _lwzsd %r6, __ppe42_fit_routine + mtlr %r6 + blrl + + b fast_exit_noncritical + +watchdog_handler: + ## Watchdog setup is described in the PK Specification. + ## The kernel clears TSR[WIS] prior to calling the handler. + ## The watchdog handler is a critical, fast-mode handler. + + _pk_fast_ctx_push + + li %r3, PPE42_IRQ_WATCHDOG + + _save_update_kernel_context %r3, %r6 + + _liwa %r6, TSR_WIS + mttsr %r6 + + _lwzsd %r6, __ppe42_watchdog_routine + mtlr %r6 + blrl + + b . + + +#if 0 +debug_handler: + + ## PK does nothing upon reception of the debug interrupt other + ## than calling the handler (if non-0). The debug handler is a + ## fast-mode handler. + + _pk_fast_ctx_push + + li %r3, PPE42_IRQ_DEBUG + + _save_update_kernel_context %r3, %r6 + + _lwzsd %r6, __ppe42_debug_routine + cmpwi %r6, 0 + mtlr %r6 + beq debug_exit + blrl + +debug_exit: + b fast_exit_critical +#endif + +__external_interrupt: + + ## The non-critical interrupt handler entry point is re-entrant - A + ## handler may allow preemption, which could cause another entry here. + + ## Entry invariants: + ## 1. Non-critical interupts are disabled; + ## 2. The SP points to a thread stack or the non-critical stack. + + ## Since fast-mode handlers can not use PK services or alter the + ## machine context, the exit of a fast mode handler is a simple RF(C)I. + + ## Begin by pushing the fast context on the current stack. + + ## _pk_fast_ctx_push was called prior to branching here. No need to call it here. + + ## Load the base address for the external interrupt table + + ## TODO: This is HW Macro specific code that is responsible for finding the + ## IRQ # and storing it in r4 (phantom IRQ's are assigned a value of EXTERNAL_IRQS). + + hwmacro_get_ext_irq + + ## An active or phantom IRQ was found. + ## R4 has the IRQ number. + ## The IRQ is converted into a pointer to an 8-byte handler + ## structure, and the handler is dispatched. The call is made with the + ## parameters: + + ## R3 = private + ## R4 = irq + +external_irq_found: + + _save_update_kernel_context %r4, %r5 + slwi %r3, %r4, 3 //multiply the irq# by 8 + _liw %r6, __ppe42_irq_handlers + lwzx %r5, %r6, %r3 + addi %r3, %r3, 4 + lwzx %r3, %r6, %r3 + mtlr %r5 + blrl + + ## Pop the stack/RFI when (if) it returns here. + +fast_exit_noncritical: + + _pk_fast_ctx_pop_exit + + + +/// \endcond diff --git a/pk/ppe42/ppe42_gcc.c b/pk/ppe42/ppe42_gcc.c new file mode 100644 index 00000000..97c2979c --- /dev/null +++ b/pk/ppe42/ppe42_gcc.c @@ -0,0 +1,305 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_gcc.h +/// \brief 32-bit PowerPC functions expected by GCC +/// +/// GCC expects certain built-in functions to be defined in the environment. +/// Since PK applications are statically linked, we must define these +/// functions ourselves to avoid a static link with the GCC libraries, which +/// would legaly require us to distribute (at least) the binary forms of PK +/// applications. +/// +/// We obviously had to look at some GCC library code to understand the +/// specifications of these routines. However, all of the code here is new - +/// no structure definitions or lines of executable code were copied from the +/// GCC sources. + +#include "pk.h" +#include "ppe42_gcc.h" + +/// A 64-bit logical right shift. +/// +/// Note that shifts with negative shift counts or shifts with shift counts +/// longer than 63 bits are undefined. + +uint64_t +__lshrdi3(uint64_t x, int i) +{ + Uint64 input, result; + + if (i == 0) { + return x; + } + + input.value = x; + + if (i >= 32) { + result.word[0] = 0; + result.word[1] = input.word[0] >> (i - 32); + } else { + result.word[0] = input.word[0] >> i; + result.word[1] = (input.word[1] >> i) | (input.word[0] << (32 - i)); + } + + return result.value; +} + + +/// A 64 bit arithmetic left shift. +/// +/// Note that shifts with negative shift counts or shifts with shift counts +/// longer than 63 bits are undefined. + +uint64_t +__ashldi3(uint64_t x, int i) +{ + Uint64 input, result; + + if (i == 0) { + return x; + } + + input.value = x; + + if (i >= 32) { + result.word[1] = 0; + result.word[0] = input.word[1] << (i - 32); + } else { + result.word[1] = input.word[1] << i; + result.word[0] = (input.word[0] << i) | (input.word[1] >> (32 - i)); + } + + return result.value ; + +} + + +/// A 64 bit arithmetic right shift. +/// +/// Note that shifts with negative shift counts or shifts with shift counts +/// longer than 63 bits are undefined. + +uint64_t +__ashrdi3(uint64_t x, int i) +{ + Int64 input, result; + + if (i == 0) { + return x; + } + + input.value = x; + + if (i >= 32) { + result.word[0] = input.word[0] >> 31; + result.word[1] = input.word[0] >> (i - 32); + } else { + result.word[0] = input.word[0] >> i; + result.word[1] = + (((uint32_t)input.word[1]) >> i) | + (input.word[0] << (32 - i)); + } + + return result.value ; + +} + + +/// 32-bit Population count + +// This is a well-known divide-and-conquer algorithm, e.g. look on Wikipedia +// under "Hamming Weight". The idea is to compute sums of adjacent bit +// segments in parallel, in place. + +int +__popcountsi2(uint32_t x) +{ + uint32_t m1 = 0x55555555; + uint32_t m2 = 0x33333333; + uint32_t m4 = 0x0f0f0f0f; + x -= (x >> 1) & m1; /* Sum pairs of bits */ + x = (x & m2) + ((x >> 2) & m2);/* Sum 4-bit segments */ + x = (x + (x >> 4)) & m4; /* Sum 8-bit segments */ + x += x >> 8; /* Sum 16-bit segments */ + return (x + (x >> 16)) & 0x3f; /* Final sum */ +} + + +/// 64-bit Population count + +int +__popcountdi2(uint64_t x) +{ + return __popcountsi2(x >> 32) + __popcountsi2(x & 0xffffffff); +} + + +// 64-bit divides +// +// For the unsigned case, note that divide by 0 returns quotient = remainder = +// 0. +// +// For the signed case, in general we perform the division on the absolute +// values and fix the signs of the quotient and remainder at the end. +// +// For the signed case, the convention in other libraries seems to be to +// ignore the case of the most-negative integer. Although it seems "wrong" to +// return the wrong answer when the right answer can be easily computed, in +// the interest of code size we follow the convention here and ignore the most +// negative integer. +// +// The assembler routine __ppe42_udiv64() assembles to ??? bytes. The full C +// routine __ppc_sdiv64 compiles to ??? bytes with the most-negative checks, +// but only ??? bytes as configured here. + +// For the signed cases, we need to handle the special case that the dividend +// or divisor is the most negative integer. +// +// If the dividend is the most negative integer, then dividing this integer by +// -1 would overflow as a positive quotient, so we set quotient and remainder +// to 0 in this case. For divide by 1, the quotient is the most negative +// integer. Otherwise we adjust the dividend by the absolute value of the +// divisor, then fix up the quotient later by adding or subtracting 1. +// +// If the divisor is the most negative integer, then the quotient is always 0 +// unless the dividend is also the most negative integer, in which case the +// quotient is 1 and the remainder is 0. +// + +uint64_t +__udivdi3(uint64_t u, uint64_t v) +{ + uint64_t quotient, remainder; + + __ppe42_udiv64(u, v, "ient, &remainder); + return quotient; +} + + +uint64_t +__umoddi3(uint64_t u, uint64_t v) +{ + uint64_t quotient, remainder; + + __ppe42_udiv64(u, v, "ient, &remainder); + return remainder; +} + + +#if 0 +#define INT64_T_MIN ((int64_t)(0x8000000000000000ull)) +#endif + +void +__ppe42_sdiv64(int64_t u, int64_t v, + int64_t *quotient, int64_t *remainder) +{ + int q_negate, r_negate; + uint64_t uu, uv; +#if 0 + int fixup = 0; +#endif + + q_negate = (u < 0) ^ (v < 0); + r_negate = (u < 0); + uu = (u < 0 ? -u : u); + uv = (v < 0 ? -v : v); + +#if 0 + if (u == INT64_T_MIN) { + if (v == -1) { + *quotient = 0; + *remainder = 0; + return; + } else if (v == 1) { + *quotient = INT64_T_MIN; + *remainder = 0; + return; + } else if (v == INT64_T_MIN) { + *quotient = 1; + *remainder = 0; + return; + } else { + fixup = 1; + u += (v < 0 ? -v : v); + } + } else if (v == INT64_T_MIN) { + *quotient = 0; + *remainder = u; + return; + } +#endif + + __ppe42_udiv64(uu, uv, (uint64_t *)quotient, (uint64_t *)remainder); + +#if 0 + if (fixup) { + *quotient += 1; + } +#endif + if (q_negate) { + *quotient = -(*quotient); + } + if (r_negate) { + *remainder = -(*remainder); + } +} + + +int64_t +__divdi3(int64_t u, int64_t v) +{ + int64_t quotient, remainder; + + __ppe42_sdiv64(u, v, "ient, &remainder); + return quotient; +} + + +int64_t +__moddi3(int64_t u, int64_t v) +{ + int64_t quotient, remainder; + + __ppe42_sdiv64(u, v, "ient, &remainder); + return remainder; +} + + +/// 64-bit unsigned compare as a function, returning 0 (<), 1 (==) or 2 (>). + +int +__ucmpdi2(uint64_t i_a, uint64_t i_b) +{ + Uint64 a, b; + int rv; + + a.value = i_a; + b.value = i_b; + + if (a.word[0] < b.word[0]) { + rv = 0; + } else if (a.word[0] > b.word[0]) { + rv = 2; + } else if (a.word[1] < b.word[1]) { + rv = 0; + } else if (a.word[1] > b.word[1]) { + rv = 2; + } else { + rv = 1; + } + + return rv; +} + + + + + + + + diff --git a/pk/ppe42/ppe42_gcc.h b/pk/ppe42/ppe42_gcc.h new file mode 100644 index 00000000..8c4179cf --- /dev/null +++ b/pk/ppe42/ppe42_gcc.h @@ -0,0 +1,72 @@ +#ifndef __PPE42_GCC_H__ +#define __PPE42_GCC_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_gcc.h +/// \brief 32-bit functions expected by GCC + +#ifndef __ASSEMBLER__ + +#include <stdint.h> + +/// A 64-bit unsigned integer type + +typedef union { + uint64_t value; + uint32_t word[2]; +} Uint64; + +/// A 64-bit signed integer type + +typedef union { + int64_t value; + int32_t word[2]; +} Int64; + +uint64_t +__lshrdi3(uint64_t x, int i); + +uint64_t +__ashldi3(uint64_t x, int i); + +uint64_t +__ashrdi3(uint64_t x, int i); + +int +__popcountsi2(uint32_t x); + +int +__popcountdi2(uint64_t x); + +/// Unsigned 64/64 bit divide, returning quotient and remainder via pointers. + +void +__ppe42_udiv64(uint64_t u, uint64_t v, uint64_t *q, uint64_t *r); + +/// Signed 64/64 bit divide, returning quotient and remainder via pointers. + +void +__ppe42_sdiv64(int64_t u, int64_t v, int64_t *q, int64_t *r); + +uint64_t +__udivdi3(uint64_t u, uint64_t v); + +int64_t +__divdi3(int64_t u, int64_t v); + +int64_t +__moddi3(int64_t u, int64_t v); + +uint64_t +__umoddi3(uint64_t u, uint64_t v); + +int +__ucmpdi2(uint64_t a, uint64_t b); + +#endif /* __ASSEMBLER__ */ + +#endif /* __PPE42_GCC_H__ */ diff --git a/pk/ppe42/ppe42_init.c b/pk/ppe42/ppe42_init.c new file mode 100644 index 00000000..2e4a2ad2 --- /dev/null +++ b/pk/ppe42/ppe42_init.c @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_init.c +/// \brief PPE42 initialization routines +/// +/// The entry points in this file are routines that are typically used during +/// initialization, and their code space could be deallocated and recovered if +/// no longer needed by the application after initialization. + +#include "pk.h" +#include "pk_trace.h" + +// Note that __ppe42_system_setup() is called from the PK bootloader early +// in the initialization, at a point before the aplication has enabled +// critical or external interruts. + +// This function is expected to be defined by the macro specific code (GPE, CME, SBE) +void __hwmacro_setup(void); + + +void +__ppe42_system_setup() +{ + //Only do this if the application hasn't provided a static table definition +#ifndef STATIC_IRQ_TABLE + PkIrqId irq; + + // Initialize the interrupt vectors. + for (irq = 0; irq < EXTERNAL_IRQS; irq++) { + __ppe42_irq_handlers[irq].handler = __ppe42_default_irq_handler; + } + + //NOTE: EXTERNAL_IRQS is the phantom interrupt assigned irq + __ppe42_irq_handlers[irq].handler = __ppe42_phantom_irq_handler; + + // Initialize special interrupt handlers +/* + __ppe42_fit_routine = __ppe42_default_irq_handler; + __ppe42_fit_arg = 0; + + __ppe42_watchdog_routine = __ppe42_default_irq_handler; + __ppe42_watchdog_arg = 0; + + __ppe42_debug_routine = __ppe42_default_irq_handler; + __ppe42_debug_arg = 0; +*/ +#endif /*STATIC_IRQ_TABLE*/ + + // Set the DEC to decrement on every cycle and enable the DEC interrupt. Clear the status + // of all timers for good measure. + + //andc_spr(SPRN_TCR, TCR_DS); + //or_spr(SPRN_TCR, TCR_DIE); + + //Use dec_timer signal for decrementer + or_spr(SPRN_TCR, TCR_DIE | TCR_DS); + or_spr(SPRN_TSR, TSR_ENW | TSR_WIS | TSR_DIS | TSR_FIS); + +#if PK_TIMER_SUPPORT +#if PK_TRACE_SUPPORT +extern PkTraceBuffer g_pk_trace_buf; + //set the ppe instance id + g_pk_trace_buf.instance_id = (uint16_t)(mfspr(SPRN_PIR) & PIR_PPE_INSTANCE_MASK); +#endif /* PK_TRACE_SUPPORT */ +#endif /* PK_TIMER_SUPPORT */ + + //call macro-specific setup + __hwmacro_setup(); +} + + diff --git a/pk/ppe42/ppe42_irq.h b/pk/ppe42/ppe42_irq.h new file mode 100644 index 00000000..3c45a863 --- /dev/null +++ b/pk/ppe42/ppe42_irq.h @@ -0,0 +1,326 @@ +#ifndef __PPE42_IRQ_H__ +#define __PPE42_IRQ_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_irq.h +/// \brief PPE42 interrupt handling for PK +/// +/// Interrupt handling protocols and interrupt controller programming are +/// inherently non-portable, however PK defines APIs that may be useful among +/// different machines. +/// + + +// Define pseudo-IRQ numbers for PPE42 built-in interrupts. These numbers +// will appear in bits 16:23 of SPRG0 (__PkKernelContext) when the handlers +// are active + +#define PPE42_EXC_MACHINE_CHECK 0x50 +#define PPE42_EXC_DATA_STORAGE 0x53 +#define PPE42_EXC_INSTRUCTION_STORAGE 0x54 +#define PPE42_EXC_ALIGNMENT 0x56 +#define PPE42_EXC_PROGRAM 0x57 +#define PPE42_IRQ_DEC 0x58 +#define PPE42_IRQ_FIT 0x59 +#define PPE42_IRQ_WATCHDOG 0x5A + + +// Unhandled exceptions default to a kernel panic, but the application can +// override these definition. Note that the exception area only allocates 32 +// bytes (8 instructions) to an unhandled exception, so any redefinition +// would most likely be a branch to an application-defined handler. + +#ifndef PPE42_MACHINE_CHECK_HANDLER +#define PPE42_MACHINE_CHECK_HANDLER PK_PANIC(0x0200) +#endif + +#ifndef PPE42_DATA_STORAGE_HANDLER +#define PPE42_DATA_STORAGE_HANDLER PK_PANIC(0x0300) +#endif + +#ifndef PPE42_INSTRUCTION_STORAGE_HANDLER +#define PPE42_INSTRUCTION_STORAGE_HANDLER PK_PANIC(0x0400) +#endif + +#ifndef PPE42_ALIGNMENT_HANDLER +#define PPE42_ALIGNMENT_HANDLER PK_PANIC(0x0600) +#endif + + +//////////////////////////////////////////////////////////////////////////// +// PK API +//////////////////////////////////////////////////////////////////////////// + +#ifndef __ASSEMBLER__ + +/// An IRQ handler takes 2 arguments: +/// \arg \c arg - Private handler data installed by \c ssx_irq_setup() or +/// \c ssx_irq_handler_set(). +/// \arg \c irq - The IRQ id; to enable a generic handler to manipulate +/// its own interrupt status . + +typedef void (*PkIrqHandler)(void* arg, PkIrqId irq); + +/// Declare a subroutine as an IRQ handler + +#define PK_IRQ_HANDLER(f) void f(void* arg, PkIrqId irq) + +int pk_irq_setup(PkIrqId irq, + int polarity, + int trigger); + +int pk_irq_handler_set(PkIrqId irq, + PkIrqHandler handler, + void* arg); + +void pk_irq_enable(PkIrqId irq); +void pk_irq_disable(PkIrqId irq); +void pk_irq_statusclear(PkIrqId irq); + +PK_IRQ_HANDLER(__ppe42_default_irq_handler); +PK_IRQ_HANDLER(__ppe42_phantom_irq_handler); + + +int +ppe42_fit_setup(int tcr_fp, PkIrqHandler handler, void* arg); + + +/// The address of the optional FIT interrupt handler + +UNLESS__PPE42_IRQ_CORE_C__(extern) +volatile +PkIrqHandler __ppe42_fit_routine; + + +/// The private data of the optional FIT interrupt handler + +UNLESS__PPE42_IRQ_CORE_C__(extern) +volatile +void* __ppe42_fit_arg; + + +int +ppe42_watchdog_setup(int tcr_wp, int tcr_wrc, + PkIrqHandler handler, void* arg); + + +/// The address of the optional Watchdog interrupt handler + +UNLESS__PPE42_IRQ_CORE_C__(extern) +volatile +PkIrqHandler __ppe42_watchdog_routine; + + +/// The private data of the optional Watchdog interrupt handler + +UNLESS__PPE42_IRQ_CORE_C__(extern) +volatile +void* __ppe42_watchdog_arg; + + +int +ppe42_debug_setup(PkIrqHandler handler, void* arg); + + +/// The address of the optional Debug interrupt handler + +UNLESS__PPE42_IRQ_CORE_C__(extern) +volatile +PkIrqHandler __ppe42_debug_routine; + + +/// The private data of the optional Watchdog interrupt handler + +UNLESS__PPE42_IRQ_CORE_C__(extern) +volatile +void* __ppe42_debug_arg; + + +// Note: Why PK_IRQ_FAST2FULL (below) is implemented so strangely. +// +// I am adamant that I want to have a a macro in the 'C' environment to create +// these bridge functions. However the limitations of the C preprocessor and +// the intelligence of the GCC 'asm' facility consipre against a +// straightforward solution. The only way that I was able to find to get +// naked assembly code into the output stream is to use 'asm' with simple +// strings - I couldn't make it work with any kind of argument, as 'asm' would +// reinterpret the arguments and resulting assembler code in various ways. +// +// There is another alternative that I tried wherby I created a subroutine +// call and then filled in the subroutine body with 'asm' code. However, the +// subroutine wrapper that GCC creates only works for PowerPC fast-mode +// handlers if GCC is invoked with optimization, which ensures that the +// wrapper doesn't touch the stack pointer or other registers. True, we'll +// always use optimization, but I did not want to have to make this +// requirement for using this macro. + +/// This macro creates a 'bridge' handler that converts the initial fast-mode +/// IRQ dispatch into a call of a full-mode IRQ handler. The full-mode +/// handler is defined by the user (presumably as a \c C subroutine) and has +/// the same prototype (type PkIrqHandler) as the fast handler. +/// +/// \param fast_handler This will be the global function name of the fast +/// IRQ handler created by this macro. This is the symbol +/// that should be passed in as the \a handler argument +/// of \c pk_irq_setup() and \c pk_irq_handler_set(). +/// +/// \param full_handler This is the name of the user-defined full-mode +/// handler which is invoked through this bridge. +/// +/// \e BUG \e ALERT : Beware of passing the \c full_handler to IRQ setup +/// APIs. This won't be caught by the compiler (because the \c full_handler +/// has the correct prototype) and will lead to nasty bugs. Always pass in +/// the \c fast_handler symbol to IRQ setup APIS. +/// +/// The code stream injected into the GCC assembler output in response to +/// +/// PK_IRQ_FAST2FULL(fast_handler, full_handler) +/// +/// is (comments added for clarification) : +/// +/// \code +/// .text +/// .global fast_handler +/// .align 5 # Hard-coded PPE42 cache-line alignment +/// fast_handler = . # Can't macro expand LABEL: - this is equivalent +/// bl __pk_irq_fast2full # The fast-mode to full-mode conversion sequence +/// bl full_handler +/// b __pk_irq_full_mode_exit +/// \endcode +/// +/// The macro also declares the prototype of the fast handler: +/// +/// \code +/// PK_IRQ_HANDLER(fast_handler); +/// \endcode +/// + +#define PK_IRQ_FAST2FULL(fast_handler, full_handler) \ + PK_IRQ_HANDLER(fast_handler); \ + __PK_IRQ_FAST2FULL(.global fast_handler, fast_handler = ., bl full_handler) + +#define __PK_IRQ_FAST2FULL(global, label, call) \ +asm(".text"); \ +asm(#global); \ +asm(".align 5"); \ +asm(#label); \ +asm("bl __pk_irq_fast2full"); \ +asm(#call); \ +asm("b __pk_irq_full_mode_exit"); + +#endif /* __ASSEMBLER__ */ + +// It's hard to be portable and get all of the definitions and headers in the +// correct order. We need to bring in the system IRQ header here. + +#ifdef HWMACRO_GPE +#include "gpe_irq.h" +#endif + +/// \page ppe42_irq_macros_page PPE42 PK IRQ Assembler Macros +/// +/// +/// \section fast2full_asm Fast-Mode to Full-Mode Handler Conversion +/// +/// This macro produces the calling sequence required to convert a +/// fast-mode interrupt handler to a full-mode interrupt handler. The +/// full-mode handler is implemented by another subroutine. The +/// requirements for invoking this macro are: +/// +/// \li The stack pointer and stack must be exactly as they were when the +/// fast-mode handler was entered. +/// +/// \li No changes have been made to the MSR - the interrupt level must +/// remain disabled. +/// +/// \li The handler owns the fast context and has not modified the other +/// register context. The conversion process will not modify any +/// register in the fast context (other than the LR used for +/// subroutine linkage). +/// +/// The final condition above means that the \a full_handler will +/// begin with the fast-mode context exactly as it was (save for LR) +/// at conversion, including the contents of GPR3-7 (the first 5 +/// PowerPC ABI paramater passing registers) and the entire CR. +/// +/// Forms: +/// +/// \c _pk_irq_fast2full \a full_handler +/// \cond + +#ifdef __ASSEMBLER__ + + .macro _pk_irq_fast2full full_handler + bl __pk_irq_fast2full + bl \full_handler + b __pk_irq_full_mode_exit + .endm + +#endif /* __ASSEMBLER__ */ + +/// \endcond + +#ifndef __ASSEMBLER__ + + +/// This structure holds the interrupt handler routine addresses and private +/// data. Assembler code assumes the given structure layout, so any changes +/// to this structure will need to be reflected down into the interrupt +/// dispatch assembler code. + +typedef struct { + PkIrqHandler handler; + void *arg; +} Ppe42IrqHandler; + + +#ifdef STATIC_IRQ_TABLE + +#define IRQ_HANDLER(func, arg) \ + {func, arg}, + +#define IRQ_HANDLER_DEFAULT \ + {__ppe42_default_irq_handler, 0}, + +#define EXTERNAL_IRQ_TABLE_END \ + {__ppe42_phantom_irq_handler, 0}\ +}; + +#define EXTERNAL_IRQ_TABLE_START \ + Ppe42IrqHandler __ppe42_irq_handlers[EXTERNAL_IRQS + 1] = \ +{ + +#else + +#define EXTERNAL_IRQ_TABLE_START + +#define IRQ_HANDLER(func, arg) + +#define IRQ_HANDLER_DEFAULT + +#define EXTERNAL_IRQ_TABLE_END + +#endif /*STATIC_IRQ_TABLE*/ + +/// Interrupt handlers for real (implemented interrupts) plus one for the phantom interrupt handler +extern Ppe42IrqHandler __ppe42_irq_handlers[EXTERNAL_IRQS + 1]; + + +/// The 'phantom interrupt' handler +/// +/// A 'phantom' interrupt occurs when the interrupt handling code in the +/// kernel is entered, but no interrupt is found pending in the controller. +/// This is considered a serious bug, as it indictates a short window +/// condition where a level-sensitive interrupt has been asserted and then +/// quickly deasserted before it can be handled. + +UNLESS__PPE42_IRQ_CORE_C__(extern) +Ppe42IrqHandler __ppe42_phantom_irq; + +#endif /* __ASSEMBLER__ */ + +#endif /* __PPE42_IRQ_H__ */ diff --git a/pk/ppe42/ppe42_irq_core.c b/pk/ppe42/ppe42_irq_core.c new file mode 100644 index 00000000..8e8f29f5 --- /dev/null +++ b/pk/ppe42/ppe42_irq_core.c @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_irq_core.c +/// \brief Core IRQ routines required of any PPE42 configuration of PK +/// +/// This file is mostly only a placeholder - where 'extern inline' API +/// functions and 'extern' variables are realized. A couple of default +/// handlers are also installed here. The entry points in this file are +/// considered 'core' routines that will always be present at runtime in any +/// PK application. + +#define __PPE42_IRQ_CORE_C__ + +#include "pk.h" + +#ifndef STATIC_IRQ_TABLE +Ppe42IrqHandler __ppe42_irq_handlers[EXTERNAL_IRQS + 1]; +#endif + +/// This function is installed by default for interrupts not explicitly set up +/// by the application. These interrupts should never fire. + +void +__ppe42_default_irq_handler(void* arg, PkIrqId irq) +{ + PK_PANIC(PPE42_DEFAULT_IRQ_HANDLER); +} + + +/// This function is installed by default to handle the case that the +/// interrupt dispatch code is entered in response to an external critical or +/// non-critical interrupt, but no interrupt is found pending in the interrupt +/// controller. This should never happen, as it would indicate that a +/// 'glitch' occurred on the external interrupt input +/// to the PPE42 core. + +void __ppe42_phantom_irq_handler(void* arg, PkIrqId irq) +{ + PK_PANIC(PPE42_PHANTOM_INTERRUPT); +} + + +#undef __PPE42_IRQ_CORE_C__ diff --git a/pk/ppe42/ppe42_msr.h b/pk/ppe42/ppe42_msr.h new file mode 100644 index 00000000..26e8c5e0 --- /dev/null +++ b/pk/ppe42/ppe42_msr.h @@ -0,0 +1,81 @@ +#ifndef __PPE42_MSR_H__ +#define __PPE42_MSR_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_msr.h +/// \brief Everything related to the PPE42 Machine State Register +/// +/// All of the macros defined here that \e modify the MSR create a compiler +/// memory barrier that will cause GCC to flush/invalidate all memory data +/// held in registers before the macro. This is consistent with other systems, +/// e.g., the PowerPC Linux kernel, and is the safest way to define these +/// macros as it guarantess for example that kernel data structure updates +/// have completed before exiting a critical section. + +#define MSR_SEM 0x7f000000 /* SIB Error Mask */ +#define MSR_IS0 0x00800000 /* Instance-Specific Field 0 */ +#define MSR_SIBRC 0x00700000 /* Last SIB return code */ +#define MSR_LP 0x00080000 /* Low Priority */ +#define MSR_WE 0x00040000 /* Wait State Enable */ +#define MSR_IS1 0x00020000 /* Instance-Specific Field 1 */ +#define MSR_UIE 0x00010000 /* Unmaskable Interrupt Enable */ +#define MSR_EE 0x00008000 /* External Interrupt Enable */ +#define MSR_ME 0x00001000 /* Machine Check Exception Enable */ +#define MSR_IPE 0x00000100 /* Imprecise Mode Enable */ +#define MSR_SIBRCA 0x000000ff /* SIB Return Code Accumulator */ + +//#define MSR_CE_BIT 14 +#define MSR_EE_BIT 16 +//#define MSR_IR_BIT 26 +//#define MSR_DR_BIT 27 + +#ifndef __ASSEMBLER__ + +/// Move From MSR + +#define mfmsr() \ + ({uint32_t __msr; \ + asm volatile ("mfmsr %0" : "=r" (__msr)); \ + __msr;}) + + +/// Move to MSR + +#define mtmsr(value) \ + asm volatile ("mtmsr %0" : : "r" (value) : "memory") + + +/// Read-Modify-Write the MSR with OR (Set MSR bits). This operation is only +/// guaranteed atomic in a critical section. + +#define or_msr(x) \ + mtmsr(mfmsr() | (x)) + + +/// Read-Modify-Write the MSR with AND complement (Clear MSR bits). This +/// operation is only guaranteed atomic in a critical section. + +#define andc_msr(x) \ + mtmsr(mfmsr() & ~(x)) + + +/// Write MSR[EE] with an immediate value (0/1) +/// +/// Note that the immediate value \a i must be a compile-time constant. + +#define wrteei(i) \ + asm volatile ("wrteei %0" : : "i" (i) : "memory") + + +/// Write MSR[EE] from the EE bit of another MSR + +#define wrtee(other_msr) \ + asm volatile ("wrtee %0" : : "r" (other_msr) : "memory") + +#endif /* __ASSEMBLER__ */ + +#endif /* __PPE42_MSR_H__ */ diff --git a/pk/ppe42/ppe42_scom.c b/pk/ppe42/ppe42_scom.c new file mode 100644 index 00000000..e9fe2e6c --- /dev/null +++ b/pk/ppe42/ppe42_scom.c @@ -0,0 +1,241 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2015 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_scom.c +/// \brief Lowest level PK SCOM definitions. +/// +/// Currently these SCOM functions are only optimized for functionality, not +/// speed. Speed optimization will be done when we have full compiler support +/// for the low-level stvd and lvd SCOM OPs. +/// +/// A FAPI-lite SCOM can call these PK SCOM functions. +/// +/// Comment: +/// - No need to poll for SCOM completion, nor return error code of SCOM fails. +/// A SCOM fail will cause the GPE to hang if configured to do so. But do we +/// necessarily have to do this? Wouldn't a gentle recovery from a SCOM fail +/// be preferred? + +#include "pk.h" +#include "ppe42_scom.h" + + +uint32_t putscom_abs(uint32_t i_address, uint64_t i_data) +{ + + // CMO-Declaring variables tied to specific registers enables us to protect + // the SCOM data and address variables used in the new stvd and lvd 64-bit + // scom instructions. This protection is needed since the new instructions' + // operands are not yet properly considered by the compiler. + // Note that l_dataH is used to represent the "beginning", i.e. High-order, + // part of the 64-bit data in d8 (i.e., r8+r9). + uint32_t register l_dataH asm("r8")=0; + uint32_t register l_dataL asm("r9")=0; + uint32_t register l_addr_eff asm("r10")=0; + uint32_t register l_scratch asm("r31")=0; + + l_addr_eff = i_address; + l_dataH = (uint32_t)(i_data>>32); + l_dataL = (uint32_t)(i_data); + + // CMO-The following sequence forces usage of l_dataH/L and l_addr_eff + // and thus the population of them as well. + // Further note that unless l_dataH/L are placed right before the following + // sequence, more specifically, if they're placed at the top of putscom(), + // r8, or l_dataH, might be overwritten in the if(chiplet_id) section. + // Secondly, we test l_addr_eff for non-zero through the CR0 register + // (which was populated in the "mr." instruction.) This is to convince the + // compiler that we actually used l_addr_eff for something. + // At present the test result causes no action except to execute the stvd + // instruction in either case. + asm volatile ( \ + "mr. %0, %1 \n" \ + : "=r"(l_scratch) \ + : "r"(l_dataH) ); + asm volatile ( \ + "mr. %0, %1 \n" \ + : "=r"(l_scratch) \ + : "r"(l_dataL) ); + asm volatile ( \ + "mr. %0, %1 \n" \ + : "=r"(l_scratch) \ + : "r"(l_addr_eff) ); + asm volatile ( \ + "beq 0x4 \n" ); + + // CMO-This instruction is not fully supported by the compiler (as of + // 20150108): + // - Works: It is correctly translated into the proper OP code + // format. + // - Works not: The compiler does not seem to recognize the usage + // of the two l_xyz variables in that it doesn't + // know prior to this command that the registers that + // contain the values of l_xyz need to be protected + // up to this point. Thus, we are forced to use those + // two l_xyz variables in some dummy instructions just + // before this point in order to fake protection. + asm volatile ( \ + "stvd %[data], 0(%[effective_address]) \n" \ + : [data]"=r"(l_dataH) \ + : [effective_address]"r"(l_addr_eff) ); + + // CMO-TBD + // Check PIB response code in 0x00001007(17:19) + // Translate PIB rc to PK rc + // Does this rc get reset to zero on success? + // Do we need to check this rc prior to issuing the SCOM? + + return 0; +} + +uint32_t _putscom( uint32_t i_chiplet_id, uint32_t i_address, uint64_t i_data) +{ + uint32_t l_rc=0; + uint32_t l_cid=0; + uint32_t l_addr_eff=0; + + if (i_chiplet_id) + { + // Accommodate two different ways of supplying the chiplet ID: + // 0xNN000000: Only bits in high-order two nibbles : Valid + // 0x000000NN: Only bits in low-order two nibbles : Valid + // + if ((i_chiplet_id & 0xFF000000) == i_chiplet_id) + { + // Valid: Chiplet ID in high-order two nibbles. + l_cid = i_chiplet_id; + } + else if ((i_chiplet_id & 0x000000FF) == i_chiplet_id) + { + // Valid: Chiplet ID in low-order two nibbles. Convert to high-order. + l_cid = i_chiplet_id << 24; + } + else + { + // Invalid: Invalid type of chiplet ID + PK_TRACE("putscom() : Invalid value of i_chiplet_id (=0x%08X)",i_chiplet_id); + return 1; //CMO-improve Return sensible rc here. + } + + l_addr_eff = (i_address & 0x00FFFFFF) | l_cid; + } + else + { + // Chiplet ID is zero. Accept address as is. + // This is useful for PIB addresses and non-EX chiplets, and even for + // EX chiplets if the fully qualified EX chiplet addr is already known. + l_addr_eff = i_address; + + } + + l_rc = putscom_abs(l_addr_eff, i_data); + + + return l_rc; +} + + +uint32_t getscom_abs( uint32_t i_address, uint64_t *o_data) +{ + + // CMO-Declaring variables tied to specific registers enables us to protect + // the SCOM data and address variables used in the new stvd and lvd 64-bit + // data instructions. This protection is needed since the new instructions + // are not yet properly considered by the compiler. + // Note that l_dataH is used to represent the "beginning", i.e. High-order, + // part of the 64-bit data in d8 (i.e., r8+r9). + uint32_t register l_dataH asm("r8")=0; + uint32_t register l_dataL asm("r9")=0; + uint32_t register l_addr_eff asm("r10")=0; + uint32_t register l_scratch asm("r31")=0; + + + l_addr_eff = i_address; + + // CMO-The following sequence forces usage of l_addr_eff and thus the + // population of it as well. + // Secondly, we test l_addr_eff for non-zero through the CR0 register + // (which was populated in the "mr." instruction.) This is to convince the + // compiler that we actually used l_addr_eff for something. + // At present the test result causes no action except to execute the lvd + // instruction in either case. + asm volatile ( \ + "mr. %0, %1 \n" \ + : "=r"(l_scratch) \ + : "r"(l_addr_eff) ); + asm volatile ( \ + "beq 0x4 \n" ); + + asm volatile ( \ + "lvd %[data], 0(%[effective_address]) \n" \ + : [data]"=r"(l_dataH) \ + : [effective_address]"r"(l_addr_eff) ); + + // CMO-The following sequence moves the read data, in l_dataH/L, into the + // 64-bit o_data location. + asm volatile ( \ + "stw %0, 0(%1) \n" \ + : "=r"(l_dataH) \ + : "r"(o_data) ); + asm volatile ( \ + "stw %0, 4(%1) \n" \ + : "=r"(l_dataL) \ + : "r"(o_data) ); + + // CMO-TBD + // Check PIB response code in 0x00001007(17:19) + // Translate PIB rc to PK rc + + return 0; +} + +uint32_t _getscom( uint32_t i_chiplet_id, uint32_t i_address, uint64_t *o_data) +{ + uint32_t l_rc=0; + uint32_t l_cid=0; + uint32_t l_addr_eff=0; + + if (i_chiplet_id) + { + // Accommodate two different ways of supplying the chiplet ID: + // 0xNN000000: Only bits in high-order two nibbles : Valid + // 0x000000NN: Only bits in low-order two nibbles : Valid + // + if ((i_chiplet_id & 0xFF000000) == i_chiplet_id) + { + // Valid: Chiplet ID in high-order two nibbles. + l_cid = i_chiplet_id; + } + else if ((i_chiplet_id & 0x000000FF) == i_chiplet_id) + { + // Valid: Chiplet ID in low-order two nibbles. Convert to high-order. + l_cid = i_chiplet_id << 24; + } + else + { + // Invalid: Invalid type of chiplet ID + PK_TRACE("getscom() : Invalid value of i_chiplet_id (=0x%08X)",i_chiplet_id); + return 1; //CMO-improve Return sensible rc here. + } + + l_addr_eff = (i_address & 0x00FFFFFF) | l_cid; + } + else + { + // Chiplet ID is zero. Accept address as is. + // This is useful for PIB addresses and non-EX chiplets, and even for + // EX chiplets if the fully qualified EX chiplet addr is already known. + l_addr_eff = i_address; + } + + l_rc = getscom_abs(l_addr_eff, o_data); + + // CMO-TBD + // Check PIB response code in 0x00001007(17:19) + // Translate PIB rc to PK rc + + return l_rc; +} diff --git a/pk/ppe42/ppe42_scom.h b/pk/ppe42/ppe42_scom.h new file mode 100644 index 00000000..0d57e5d4 --- /dev/null +++ b/pk/ppe42/ppe42_scom.h @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2015 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_scom.h +/// \brief Include file for PK SCOMs +/// + +#ifndef __PK_SCOM_H__ +#define __PK_SCOM_H__ + +/// SCOM operations return non-zero error codes that may or may not indicate +/// an actual error, depending on which SCOM is begin accessed. This error +/// code will appear in the MSR[SIBRC] field, bits[9:11] right after the +/// SCOM OP returns. The error code value increases with the severity of the +/// error. +#define PCB_ERROR_NONE 0 +#define PCB_ERROR_RESOURCE_OCCUPIED 1 +#define PCB_ERROR_CHIPLET_OFFLINE 2 +#define PCB_ERROR_PARTIAL_GOOD 3 +#define PCB_ERROR_ADDRESS_ERROR 4 +#define PCB_ERROR_CLOCK_ERROR 5 +#define PCB_ERROR_PACKET_ERROR 6 +#define PCB_ERROR_TIMEOUT 7 + +/// @brief putscom with absolute address +/// @param [in] i_address Fully formed SCOM address +/// @param [in] i_data uint64_t data to be written +/// +/// @retval On PPE42 platform, unmasked errors will take machine check interrupts +uint32_t putscom_abs( uint32_t i_address, uint64_t i_data); + +/// @brief getscom with absolute address +/// @param [in] i_address Fully formed SCOM address +/// @param [in] *o_data Pointer to uint64_t data read +/// +/// @retval On PPE42 platform, unmasked errors will take machine check interrupts +uint32_t getscom_abs( uint32_t i_address, uint64_t *o_data); + + +/// @brief Implementation of PPE putscom functionality +/// @param [in] i_chiplet Chiplet ID (@todo Should only be right justified) +/// @param [in] i_address Base SCOM address +/// @param [in] i_data uint64_t data to be written +/// +/// @retval On PPE42 platform, unmasked errors will take machine check interrupts +uint32_t _putscom( uint32_t i_chiplet, uint32_t i_address, uint64_t i_data); + + +/// @brief Implementation of PPE getscom functionality +/// @param [in] i_chiplet Chiplet ID (@todo Should only be right justified) +/// @param [in] i_address Base SCOM address +/// @param [in] i_data Pointer to uint64_t data read +/// +/// @retval On PPE42 platform, unmasked errors will take machine check interrupts +uint32_t _getscom( uint32_t i_chiplet, uint32_t i_address, uint64_t *o_data); + + +/// Macro to abstract the underlying putscom function so that it might be replaced +/// later with different implementations. Used directly by low level PPE calls +/// but also used by the FAPI2 API implementation +/* +#define putscom (_m_chiplet, _m_address, _m_data) { \ + _putscom( _m_chiplet, _m_address, _m_data); \ +}; +*/ +extern inline uint32_t putscom(uint32_t i_chiplet, uint32_t i_address, uint64_t i_data) +{ + return _putscom(i_chiplet, i_address, i_data); +} + +/// Macro to abstract the underlying getscom function so that it might be replaced +/// later with different implementations. Used directly by low level PPE calls +/// but also used by the FAPI2 API implementation +/* +#define getscom (i_chiplet, i_address, o_data) { \ + _getscom( i_chiplet, i_address, o_data); \ +}; +*/ +extern inline uint32_t getscom(uint32_t i_chiplet, uint32_t i_address, uint64_t *o_data) +{ + return _getscom(i_chiplet, i_address, o_data); +} + +#endif // __PK_SCOM_H__ diff --git a/pk/ppe42/ppe42_spr.h b/pk/ppe42/ppe42_spr.h new file mode 100644 index 00000000..b9a189e0 --- /dev/null +++ b/pk/ppe42/ppe42_spr.h @@ -0,0 +1,173 @@ +#ifndef __PPE42_SPR_H__ +#define __PPE42_SPR_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file ppe42_spr.h +/// \brief Everything related to PPE42-specific SPRs + +/// \defgroup ppe42_sprs PPE42 SPRs +/// +/// These are the documented SPRs of the PPE42. Most of these SPRs are +/// available in RISCWatch and eCmd using the defined names (minus SPRN_). In +/// some cases RISCWatch/eCMD use different names, which appear in square +/// brackets in the brief comments for each register. RISCWatch/eCMD also +/// allow CR, MSR and IAR (Instruction Address Register) to be accessed as +/// SPRs. +/// +/// @{ + + #define SPRN_XER 0x001 /// Fixed-point exception register + #define SPRN_LR 0x008 /// Link register + #define SPRN_CTR 0x009 /// Count register + #define SPRN_DEC 0x016 /// Decrementer + #define SPRN_SRR0 0x01a /// Save/restore register 0 + #define SPRN_SRR1 0x01b /// Save/restore register 1 + #define SPRN_EDR 0x03d /// Error Data Register + #define SPRN_ISR 0x03e /// Interrupt Status Register + #define SPRN_IVPR 0x03f /// Interrupt Vector Prefix Register + #define SPRN_SPRG0 0x110 /// SPR general register 0 + #define SPRN_PIR 0x11e /// Processor Identification Register + #define SPRN_PVR 0x11f /// Processor version register + #define SPRN_DBCR 0x134 /// Debug Control Register + #define SPRN_DACR 0x13c /// Debug Address Compare Register + #define SPRN_TSR 0x150 /// Timer Status Register + #define SPRN_TCR 0x154 /// Timer Control Register + +/* DBCR - Debug Control Register */ + +#define DBCR_RST 0x30000000 /* Reset: 01=Soft Reset, 10=Hard Reset, 11=Halt */ +#define DBCR_TRAP 0x01000000 /* Trap Instruction Enable */ +#define DBCR_IACE 0x00800000 /* Instruction Address Compare Enable */ +#define DBCR_DACE 0x000c0000 /* Data Address Compare Enable: 01=store, 10=load, 11=both */ + +/* TCR - Timer Control Register */ + +#define TCR_WP_MASK 0xc0000000 /* Watchdog timer select bits */ +#define TCR_WP_0 0x00000000 /* WDT uses timer 0 */ +#define TCR_WP_1 0x40000000 /* WDT uses timer 1 */ +#define TCR_WP_2 0x80000000 /* WDT uses timer 2 */ +#define TCR_WP_3 0xc0000000 /* WDT uses timer 3 */ +#define TCR_WRC_MASK 0x30000000 /* Watchdog Reset Control mask */ +#define TCR_WRC_NONE 0x00000000 /* WDT results in no action */ +#define TCR_WRC_SOFT 0x10000000 /* WDT results in Soft reset */ +#define TCR_WRC_HARD 0x20000000 /* WDT results in Hard reset */ +#define TCR_WRC_HALT 0x30000000 /* WDT results in Halt */ +#define TCR_WIE 0x08000000 /* Watchdog Interrupt Enable */ +#define TCR_DIE 0x04000000 /* Decrementer Interrupt Enable */ +#define TCR_FP_MASK 0x03000000 /* FIT Timer Select bits*/ +#define TCR_FP_0 0x00000000 /* FIT uses timer 0 */ +#define TCR_FP_1 0x01000000 /* FIT uses timer 1 */ +#define TCR_FP_2 0x02000000 /* FIT uses timer 2 */ +#define TCR_FP_3 0x03000000 /* FIT uses timer 3 */ +#define TCR_FIE 0x00800000 /* FIT Interrupt Enable */ +#define TCR_DS 0x00400000 /* Decrementer timer select: 0=every cycle, 1=use dec_timer input signal */ + +#ifndef __ASSEMBLER__ + +typedef union { + uint32_t value; + struct { + unsigned int wp : 2; + unsigned int wrc : 2; + unsigned int wie : 1; + unsigned int die : 1; + unsigned int fp : 2; + unsigned int fie : 1; + unsigned int ds : 1; + unsigned int reserved : 22; + } fields; +} Ppe42TCR; + +#endif /* __ASSEMBLER__ */ + +/* TSR - Timer Status Register */ + +#define TSR_ENW 0x80000000 /* Enable Next Watchdog */ +#define TSR_WIS 0x40000000 /* Watchdog Interrupt Status */ +#define TSR_WRS_MASK 0x30000000 /* Watchdog Reset Status */ +#define TSR_WRS_NONE 0x00000000 /* No watchdog reset has occurred */ +#define TSR_WRS_SOFT 0x10000000 /* Soft reset was forced by the watchdog */ +#define TSR_WRS_HARD 0x20000000 /* Hard reset was forced by the watchdog */ +#define TSR_WRS_HALT 0x30000000 /* Halt was forced by the watchdog */ +#define TSR_DIS 0x08000000 /* Decrementer Interrupt Status */ +#define TSR_FIS 0x04000000 /* FIT Interrupt Status */ + +/* PIR - Processor Identification Register */ +#define PIR_PPE_TYPE_MASK 0x000000E0 +#define PIR_PPE_TYPE_GPE 0x00000020 +#define PIR_PPE_TYPE_CME 0x00000040 +#define PIR_PPE_INSTANCE_MASK 0x0000001F + +#ifndef __ASSEMBLER__ + +/// Move From SPR +/// +/// Note that \a sprn must be a compile-time constant. + +#define mfspr(sprn) \ + ({uint32_t __value; \ + asm volatile ("mfspr %0, %1" : "=r" (__value) : "i" (sprn)); \ + __value;}) + + +/// Move to SPR +/// +/// Note that \a sprn must be a compile-time constant. + +#define mtspr(sprn, value) \ + ({uint32_t __value = (value); \ + asm volatile ("mtspr %0, %1" : : "i" (sprn), "r" (__value)); \ + }) + + +/// Read-Modify-Write an SPR with OR (Set SPR bits) +/// +/// Note that \a sprn must be a compile-time constant. This operation is only +/// guaranteed atomic in a critical section. + +#define or_spr(sprn, x) \ + mtspr(sprn, mfspr(sprn) | (x)) + + +/// Read-Modify-Write an SPR with AND complement (Clear SPR bits) +/// +/// Note that \a sprn must be a compile-time constant. This operation is only +/// guaranteed atomic in a critical section. + +#define andc_spr(sprn, x) \ + mtspr(sprn, mfspr(sprn) & ~(x)) + +#endif /* __ASSEMBLER__ */ + +#ifdef __ASSEMBLER__ + + /// \cond + + // Use this macro to define new mt<spr> and mf<spr> instructions that + // may not exist in the assembler. + + .macro _sprinstrs, name, num + .macro mt\name, reg + mtspr \num, \reg + .endm + .macro mf\name, reg + mfspr \reg, \num + .endm + .endm + + _sprinstrs dbcr, SPRN_DBCR + _sprinstrs tcr, SPRN_TCR + _sprinstrs tsr, SPRN_TSR + _sprinstrs sprg0, SPRN_SPRG0 + _sprinstrs ivpr, SPRN_IVPR + _sprinstrs dec, SPRN_DEC + + /// \endcond + +#endif /* __ASSEMBLER__ */ + +#endif /* __PPE42_SPR_H__ */ diff --git a/pk/ppe42/ppe42_thread_init.S b/pk/ppe42/ppe42_thread_init.S new file mode 100644 index 00000000..6e6c34fe --- /dev/null +++ b/pk/ppe42/ppe42_thread_init.S @@ -0,0 +1,108 @@ +/// \file ppe42_thread_init.S +/// \brief PPE42-specific thread initialization +/// +/// The entry points in this file are routines that are typically used during +/// initialization, and their code space could be deallocated and recovered if +/// no longer needed by the application after initialization. + + .nolist +#include "pk.h" + .list + +/// \fn void __pk_thread_context_initialize(PkThread *thread, PkThreadRoutine thread_routine, void *private) +/// \brief Create the initial thread context on the stack +/// +/// The non-reserved GPRs are prepatterned with 0x0000\<rn\>\<rn\> where \<rn\> is +/// the register number (as decimal). The initial context is set up with the +/// thread running in the default machine context, and when the thread is +/// switched in it will begin executing at the entry point of the thread +/// routine with the \c private parameter in R3. The LR is initialized such +/// that when the thread returns, it will return to the entry point of \c +/// pk_complete(). +#ifdef DOXYGEN_ONLY +void +__pk_thread_context_initialize(PkThread *thread, + PkThreadRoutine thread_routine, + void *private); +#endif +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \cond + + .global_function __pk_thread_context_initialize + +__pk_thread_context_initialize: + + ## R3 = thread (param) + ## R4 = thread_routine (param) + ## R5 = private (param) + ## R6 = thread stack pointer (computed) + ## R7 = scratch + + .macro _gpr_init, prefix, reg, val + li %r7, \val + stw %r7, \prefix\reg(%r6) + .endm + + ## Initialize a fast context on the thread stack. The CR is cleared, + ## the LR = pk_complete(), R3 has the private parameter. + + lwz %r6, PK_THREAD_OFFSET_SAVED_STACK_POINTER(%r3) + + stwu %r6, -PK_FAST_CTX_SIZE(%r6) + + li %r7, 0 + stw %r7, PK_FAST_CTX_CR(%r6) + + _liw %r7, pk_complete + stw %r7, PK_FAST_CTX_LR(%r6) + + stw %r5, PK_FAST_CTX_GPR3(%r6) + + _gpr_init PK_FAST_CTX_GPR, 4, 0x0404 + _gpr_init PK_FAST_CTX_GPR, 5, 0x0505 + _gpr_init PK_FAST_CTX_GPR, 6, 0x0606 + + ## Initialize the (volatile - fast) context on the thread stack. XER + ## and CTR are clear, SRR0 = thread_routine, SRR1 = default machine + ## context. + + stwu %r6, -PK_VOL_FAST_CTX_SIZE(%r6) + + li %r7, 0 + stw %r7, PK_VOL_FAST_CTX_XER(%r6) + stw %r7, PK_VOL_FAST_CTX_CTR(%r6) + + stw %r4, PK_VOL_FAST_CTX_SRR0(%r6) + + _lwzsd %r7, __pk_thread_machine_context_default + stw %r7, PK_VOL_FAST_CTX_SRR1(%r6) + + _gpr_init PK_VOL_FAST_CTX_GPR, 0, 0x0000 + _gpr_init PK_VOL_FAST_CTX_GPR, 7, 0x0707 + _gpr_init PK_VOL_FAST_CTX_GPR, 8, 0x0808 + _gpr_init PK_VOL_FAST_CTX_GPR, 9, 0x0909 + _gpr_init PK_VOL_FAST_CTX_GPR, 10, 0x1010 + + ## Initialize the non-volatile context on the thread stack. + + stwu %r6, -PK_NON_VOL_CTX_SIZE(%r6) + + _gpr_init PK_NON_VOL_CTX_GPR, 28, 0x2828 + _gpr_init PK_NON_VOL_CTX_GPR, 29, 0x2929 + _gpr_init PK_NON_VOL_CTX_GPR, 30, 0x3030 + _gpr_init PK_NON_VOL_CTX_GPR, 31, 0x3131 + + ## Initialization is done - the stack pointer is stored back in the + ## thread. + + stw %r6, PK_THREAD_OFFSET_SAVED_STACK_POINTER(%r3) + blr + + .epilogue __pk_thread_context_initialize + +/// \endcond diff --git a/pk/ppe42/ppe42_timebase.S b/pk/ppe42/ppe42_timebase.S new file mode 100644 index 00000000..16c59529 --- /dev/null +++ b/pk/ppe42/ppe42_timebase.S @@ -0,0 +1,82 @@ +/// \file ppe42_timebase.S +/// \brief PPE42-specific 64 bit timebase emulation +/// + .nolist +#include "pk.h" + .list + +/// \fn PkTimebase pk_timebase_get(void) +/// \brief Returns a 64 bit timebase +/// +#ifdef DOXYGEN_ONLY +PkTimebase +pk_timebase_get(void); +#endif +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \cond + .global ppe42_64bit_timebase + .global ppe42_tb_data + .global_function pk_timebase_get + +//Use the DEC for our timebase until we have a real timebase register (uses 9 instructions) +pk_timebase_get: + + //load the decrementer start time and change tag + lvd %r5, ppe42_tb_data@sda21(0) + + //load 64bit timebase accumulator + lvd %r3, ppe42_64bit_timebase@sda21(0) + + + //load the current decrementer value + mfdec %r0 + + //load the change tag again (should already be in the cache) + lwz %r7, ppe42_tb_data+4@sda21(0) + + //loop until the change tag is the same + //cmplwbne %r6, %r7, pk_timebase_get + cmplw %r6, %r7 + bne pk_timebase_get + + //calculate how much time has passed since the decrementer was started and store in r6 + subf %r6, %r0, %r5 + + //add the 32bit difference to the 64bit timebase accumulator + addc %r4, %r6, %r4 + addze %r3, %r3 + + blr + +//enable this once we have a local timebase register in the model +#if 0 + +// use the local timebase register to keep more accurate time with just 6 instructions +// in the common case and 7 otherwise. +pk_timebase_get: + + //load the timebase local 32bit register address + lwz r5, timebase_local_reg@sda21(0) + + //load the 64bit timebase accumulator + lvd r3, ppe42_64bit_timebase@sda21(0) + + //read the local timebase register + lwz r5, 0(r5) + + //increment the upper 32 bits if the lower 32 bits have flipped + cmplwbgt r5, r4, update_lower_32 + addi r3, 1 //update upper 32 + +update_lower_32: + //replace the lower 32bits with what we read from the local timebase register + mr r4, r6 + + blr +#endif +/// \endcond diff --git a/pk/trace/Makefile b/pk/trace/Makefile new file mode 100644 index 00000000..69e95ebd --- /dev/null +++ b/pk/trace/Makefile @@ -0,0 +1,26 @@ +# This Makefile is designed to be invoked with the -I argument set to +# the location of the "pk.mk" for the build + +include img_defs.mk +include pktracefiles.mk + +ifeq "$(PK_TIMER_SUPPORT)" "1" +PKTRACE_OBJECTS += ${PKTRACE-TIMER-C-SOURCES:.c=.o} ${PKTRACE-TIMER-S-SOURCES:.S=.o} +endif + +ifeq "$(PK_THREAD_SUPPORT)" "1" +PKTRACE_OBJECTS += ${PKTRACE-THREAD-C-SOURCES:.c=.o} ${PKTRACE-THREAD-S-SOURCES:.S=.o} +endif + +OBJS := $(addprefix $(OBJDIR)/, $(PKTRACE_OBJECTS)) + +all: $(OBJS) + +$(OBJS) $(OBJS:.o=.d): | $(OBJDIR) + +$(OBJDIR): + mkdir -p $(OBJDIR) + +ifneq ($(MAKECMDGOALS),clean) +include $(OBJS:.o=.d) +endif diff --git a/pk/trace/pk_trace.h b/pk/trace/pk_trace.h new file mode 100644 index 00000000..14a7278b --- /dev/null +++ b/pk/trace/pk_trace.h @@ -0,0 +1,276 @@ +#ifndef __PK_TRACE_H__ +#define __PK_TRACE_H__ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_trace.h +/// \brief Macros and declarations for the PK Firmware Tracing Facility. +/// + +#include <stdint.h> + +#define PK_TRACE_VERSION 1 + +#ifndef PK_TRACE_SZ +#define PK_TRACE_SZ 256 +#endif + +//Fail compilation if size is not a power of 2 +#if ((PK_TRACE_SZ - 1) & PK_TRACE_SZ) +#error "PK_TRACE_SZ is not a power of two!!!" +#endif + +//Fail compilation if size is smaller than 64 bytes +#if (PK_TRACE_SZ < 64) +#error "PK_TRACE_SZ must be at least 64 bytes!!!" +#endif + +//Mask for calculating offsets into the trace circular buffer +#define PK_TRACE_CB_MASK (PK_TRACE_SZ - 1) + +#define STRINGIFY_HELPER(x) #x +#define STRINGIFY(x) STRINGIFY_HELPER(x) + +#define PPE_IMG_STRING STRINGIFY(IMAGE_NAME) + +#ifdef PK_TRACE_HASH_PREFIX +#if (PK_TRACE_HASH_PREFIX > 0xffff) +#error PK_TRACE_HASH_PREFIX must be defined as a 16 bit constant value +#endif +#endif //PK_TRACE_HASH_PREFIX + +//This provides a 128ns tick (assuming a 32ns clock period) +//and 4 different format values +#define PK_TRACE_TS_BITS 30 + +#define PK_TRACE_FORMAT_BITS (32 - PK_TRACE_TS_BITS) + +#define PK_TRACE_TS_MASK (0xfffffffful << PK_TRACE_FORMAT_BITS) +#define PK_TRACE_FORMAT_MASK (~PK_TRACE_TS_MASK) + +#define PK_GET_TRACE_FORMAT(w32) (PK_TRACE_FORMAT_MASK & w32) +#define PK_GET_TRACE_TIME(w32) (PK_TRACE_TS_MASK & w32) + +//Set the trace timer period to be the maximum +//32 bit time minus 2 seconds (assuming a 32ns tick) +//This allows for up to 1 second of interrupt latency + +//1 second for PK_TRACE_MTBT while only requiring a trace +//every 135 seconds in order to maintain the 64bit timebase. +#define PK_TRACE_TIMER_PERIOD (0xfffffffful - 62500000) + +//The Maximum Time Between Traces. In order to reduce the time that interrupts +//are disabled for tracing, reading of the time stamp is not done atomically +//with alocating an entry in the circular buffer. This means that the +//timestamps might not appear in order in the trace buffer. This is a +//problem because our calculation of the 64 bit timebase uses the unsigned +//difference of the current 32bit timestamp and the previous one and if they +//are out of order it will result in a very large difference. To solve this +//problem, any time that the parser code sees a very large difference (larger +//than PK_TRACE_MTBT) it will treat it as a negative number. +#define PK_TRACE_MTBT (0xfffffffful - 31250000) + +#define PK_TRACE_MAX_PARMS 4 + +//The trace version needs to change if this changes. +#define PK_TRACE_MAX_BINARY 256 + +//clip the largest binary trace according to the trace buffer size. +//(The trace version does not need to change if this changes as long +// as it remains less than PK_TRACE_MAX_BINARY) +#if (PK_TRACE_SZ <= 256) +#define PK_TRACE_CLIPPED_BINARY_SZ PK_TRACE_SZ / 2 +#else +#define PK_TRACE_CLIPPED_BINARY_SZ PK_TRACE_MAX_BINARY +#endif + +//Trace formats that are supported +typedef enum +{ + PK_TRACE_FORMAT_EMPTY, + PK_TRACE_FORMAT_TINY, + PK_TRACE_FORMAT_BIG, + PK_TRACE_FORMAT_BINARY, +}PkTraceFormat; //pk_trace_format_t; + +//This combines the timestamp and the format bits into a +//single 32 bit word. +typedef union +{ + struct + { + uint32_t timestamp : PK_TRACE_TS_BITS; + uint32_t format : PK_TRACE_FORMAT_BITS; + }; + uint32_t word32; +}PkTraceTime; //pk_trace_time_t; + +//PK trace uses a 16 bit string format hash value +typedef uint16_t PkTraceHash; //pk_trace_hash_t; + +//The constant 16 bit hash value is combined with a +//16 bit parameter value when doing a tiny trace +typedef union +{ + struct + { + PkTraceHash string_id; + uint16_t parm; + }; + uint32_t word32; +}PkTraceTinyParms; //pk_trace_tiny_parms_t; + +//A tiny trace fits within a single 8 byte word. This includes +//the timestamp, format bits, hash id, and a 16 bit parameter. +typedef union +{ + struct + { + PkTraceTinyParms parms; + PkTraceTime time_format; + }; + uint64_t word64; +}PkTraceTiny; //pk_trace_tiny_t; + +//Larger traces that require a 32 bit parameter or more than one +//parameter use the big trace format. The number of parms and +//the 'complete' flag are combined with the hash id. 'complete' +//is set to 0 initially and set to one only after all of the trace +//data has been written. +typedef union +{ + struct + { + PkTraceHash string_id; + uint8_t complete; + uint8_t num_parms; + }; + uint32_t word32; +}PkTraceBigParms; //pk_trace_big_parms_t; + +typedef union +{ + struct + { + PkTraceBigParms parms; + PkTraceTime time_format; + }; + uint64_t word64; +}PkTraceBig; //pk_trace_big_t; + +//Binary traces are handled in a similar fashion to big traces, except +//that instead of having a number of parameters, we have number of bytes. +typedef union +{ + struct + { + PkTraceHash string_id; + uint8_t complete; + uint8_t num_bytes; + }; + uint32_t word32; +}PkTraceBinaryParms; //pk_trace_binary_parms_t; + +typedef union +{ + struct + { + PkTraceBinaryParms parms; + PkTraceTime time_format; + }; + uint64_t word64; +}PkTraceBinary; //pk_trace_binary_t; + +//This is a generic structure that can be used to retrieve data +//for tiny, big, and binary formatted entries. +typedef union +{ + struct + { + PkTraceHash string_id; + union + { + uint16_t parm16; + struct + { + uint8_t complete; + uint8_t bytes_or_parms_count; + }; + }; + PkTraceTime time_format; + }; + uint64_t word64; +}PkTraceGeneric; //pk_trace_generic_t; + +//This is a format that might be used in the future for tracing +//a 64 bit timestamp so that we don't fill up the buffer with periodic +//timer traces. It is not currently used. +#if 0 +typedef union +{ + struct + { + uint32_t upper32; + PkTraceTime time_format; + }; + uint64_t word64; +}PkTraceTime64; //pk_trace_time64_t; +#endif + +//It would probably be more accurate to call this a footer since it +//actually resides at the highest address of each trace entry. These eight +//bytes contain information that allow us to walk the trace buffer from the +//most recent entry to the oldest entry. +typedef union +{ + PkTraceGeneric generic; + PkTraceBinary binary; + PkTraceBig big; + PkTraceTiny small; +}PkTraceEntryFooter; //pk_trace_entry_header_t; + + +//This is the data that is updated (in the buffer header) every time we add +//a new entry to the buffer. +typedef union +{ + struct + { + uint32_t tbu32; + uint32_t offset; + }; + uint64_t word64; +}PkTraceState; //pk_trace_state_t; + +#define PK_TRACE_IMG_STR_SZ 16 + +//Header data for the trace buffer that is used for parsing the data. +//Note: pk_trace_state_t contains a uint64_t which is required to be +//placed on an 8-byte boundary according to the EABI Spec. This also +//causes cb to start on an 8-byte boundary. +typedef struct +{ + //these values are needed by the parser + uint16_t version; + uint16_t rsvd; + char image_str[PK_TRACE_IMG_STR_SZ]; + uint16_t instance_id; + uint16_t partial_trace_hash; + uint16_t hash_prefix; + uint16_t size; + uint32_t max_time_change; + uint32_t hz; + uint32_t pad; + + //updated with each new trace entry + PkTraceState state; + + //circular trace buffer + uint8_t cb[PK_TRACE_SZ]; +}PkTraceBuffer; //pk_trace_buffer_t; + +extern PkTraceBuffer g_pk_trace_buf; + +#endif /* __PK_TRACE_H__ */ diff --git a/pk/trace/pk_trace_big.c b/pk/trace/pk_trace_big.c new file mode 100644 index 00000000..4ba212da --- /dev/null +++ b/pk/trace/pk_trace_big.c @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_trace_big.c +/// \brief PK Trace function that supports up to four 32-bit parameters +/// +/// The pk_trace_big function is only called (via some macro magic) if the +/// caller passes in a single parameter (not including the format string) +/// that is larger than 16 bits to the PK_TRACE(...) macro. +/// + +#include "pk.h" +#include "pk_trace.h" + +#if (PK_TRACE_SUPPORT && PK_TIMER_SUPPORT) +void pk_trace_big(uint32_t i_hash_and_count, + uint64_t i_parm1, uint64_t i_parm2) +{ + PkTraceBig footer; + PkTraceBig* footer_ptr; + PkTraceState state; + uint64_t* ptr64; + uint64_t tb64; + PkMachineContext ctx; + uint32_t parm_size; + uint32_t cur_offset; + uint32_t footer_offset; + + //fill in the footer data + tb64 = pk_timebase_get(); + footer.parms.word32 = i_hash_and_count; //this has the parm count and hash + state.tbu32 = tb64 >> 32; + footer.time_format.word32 = tb64 & 0x00000000ffffffffull; + footer.time_format.format = PK_TRACE_FORMAT_BIG; + + //round up to 8 byte boundary + if(footer.parms.num_parms <= 2) + { + parm_size = 8; + } + else + { + parm_size = 16; + } + + //*****The following operations must be done atomically***** + pk_critical_section_enter(&ctx); + + //load in the offset in the cb for the entry we are adding + cur_offset = g_pk_trace_buf.state.offset; + + //Find the offset for the footer (at the end of the entry) + footer_offset = cur_offset + parm_size; + + //calculate the address of the footer + ptr64 = (uint64_t*)&g_pk_trace_buf.cb[footer_offset & PK_TRACE_CB_MASK]; + + //calculate the offset for the next entry in the cb + state.offset = footer_offset + sizeof(PkTraceBig); + + //update the cb state (tbu and offset) + g_pk_trace_buf.state.word64 = state.word64; + + //write the data to the circular buffer including the + //timesamp, string hash, and 16bit parameter + *ptr64 = footer.word64; + + //*******************exit the critical section*************** + pk_critical_section_exit(&ctx); + + + //write parm values to the circular buffer + footer_ptr = (PkTraceBig*)ptr64; + ptr64 = (uint64_t*)&g_pk_trace_buf.cb[cur_offset & PK_TRACE_CB_MASK]; + *ptr64 = i_parm1; + if(parm_size > 8) + { + ptr64 = (uint64_t*)&g_pk_trace_buf.cb[(cur_offset + 8) & PK_TRACE_CB_MASK]; + *ptr64 = i_parm2; + } + + //Mark the trace entry update as being completed + footer_ptr->parms.complete = 1; + +} + +#endif + + diff --git a/pk/trace/pk_trace_binary.c b/pk/trace/pk_trace_binary.c new file mode 100644 index 00000000..58b417a3 --- /dev/null +++ b/pk/trace/pk_trace_binary.c @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_trace_binary.c +/// \brief PK Trace function for dumping memory contents +/// +/// The pk_trace_binary function is called by the PK_TRACE_BINARY() macro. +/// + + +#include "pk.h" +#include "pk_trace.h" + +#if (PK_TRACE_SUPPORT && PK_TIMER_SUPPORT) +void pk_trace_binary(uint32_t i_hash_and_size, void* bufp) +{ + PkTraceBinary footer; + PkTraceBinary* footer_ptr; + PkTraceState state; + uint64_t* ptr64; + uint64_t tb64; + PkMachineContext ctx; + uint32_t data_size; + uint32_t cb_offset; + uint32_t footer_offset; + uint8_t* dest; + uint8_t* src; + uint32_t index; + + //fill in the footer data + tb64 = pk_timebase_get(); + footer.parms.word32 = i_hash_and_size; //this has the size and hash + state.tbu32 = tb64 >> 32; + footer.time_format.word32 = tb64 & 0x00000000ffffffffull; + footer.time_format.format = PK_TRACE_FORMAT_BINARY; + + //round up to 8 byte boundary + data_size = (footer.parms.num_bytes + 7) & ~0x00000007ul; + + //limit data size + if(data_size > PK_TRACE_CLIPPED_BINARY_SZ) + { + data_size = PK_TRACE_CLIPPED_BINARY_SZ; + } + + //*****The following operations must be done atomically***** + pk_critical_section_enter(&ctx); + + //load in the offset in the cb for the entry we are adding + cb_offset = g_pk_trace_buf.state.offset; + + //Find the offset for the footer (at the end of the entry) + footer_offset = cb_offset + data_size; + + //calculate the address of the footer + ptr64 = (uint64_t*)&g_pk_trace_buf.cb[footer_offset & PK_TRACE_CB_MASK]; + + //calculate the offset for the next entry in the cb + state.offset = footer_offset + sizeof(PkTraceBinary); + + //update the cb state (tbu and offset) + g_pk_trace_buf.state.word64 = state.word64; + + //write the footer data to the circular buffer including the + //timesamp, string hash and data size + *ptr64 = footer.word64; + + //*******************exit the critical section*************** + pk_critical_section_exit(&ctx); + + //write data to the circular buffer + for(src = bufp, index = 0; + index < data_size; + index++) + { + dest = &g_pk_trace_buf.cb[(cb_offset + index) & PK_TRACE_CB_MASK]; + *dest = *(src++); + } + + //Mark the trace entry update as being completed + footer_ptr = (PkTraceBinary*)ptr64; + footer_ptr->parms.complete = 1; + +} + +#endif + + diff --git a/pk/trace/pk_trace_core.c b/pk/trace/pk_trace_core.c new file mode 100644 index 00000000..3f416c75 --- /dev/null +++ b/pk/trace/pk_trace_core.c @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2014 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file pk_trace_core.c +/// \brief PK Trace core data and code. +/// +/// This file includes the minimal code/data required to do minimal tracing. +/// This includes the periodic timer initialization and the pk_trace_tiny +/// function. The pk_trace_tiny function is called by the PK_TRACE() macro +/// when there is one or less parameters (not including the format string) +/// and the parameter size is 16 bits or smaller. +/// + +#include "pk.h" +#include "pk_trace.h" + +void pk_trace_timer_callback(void* arg); + +#if (PK_TRACE_SUPPORT && PK_TIMER_SUPPORT) + +//Static initialization of the trace timer +PkTimer g_pk_trace_timer = { + .deque.next = 0, + .deque.previous = 0, + .timeout = 0, + .period = 0, + .callback = pk_trace_timer_callback, + .arg = 0, + .options = PK_TIMER_CALLBACK_PREEMPTIBLE, +}; + +//Static initialization of the pk trace buffer +PkTraceBuffer g_pk_trace_buf = +{ + .version = PK_TRACE_VERSION, + .image_str = PPE_IMG_STRING, + .hash_prefix = PK_TRACE_HASH_PREFIX, + .partial_trace_hash = trace_adal_hash("PARTIAL TRACE ENTRY. HASH_ID = %d", PK_TRACE_HASH_PREFIX), + .size = PK_TRACE_SZ, + .max_time_change = PK_TRACE_MTBT, + .hz = 500000000, //default value. Actual value is set in pk_init.c + .state.word64 = 0, + .cb = {0} +}; + +//Needed for buffer extraction in simics for now +PkTraceBuffer* g_pk_trace_buf_ptr = &g_pk_trace_buf; + +// Creates an 8 byte entry in the trace buffer that includes a timestamp, +// a format string hash value and a 16 bit parameter. +// +// i_parm has the hash value combined with the 16 bit parameter +void pk_trace_tiny(uint32_t i_parm) +{ + PkTraceTiny footer; + PkTraceState state; + uint64_t* ptr64; + uint64_t tb64; + PkMachineContext ctx; + + //fill in the footer data + footer.parms.word32 = i_parm; + tb64 = pk_timebase_get(); + state.tbu32 = tb64 >> 32; + footer.time_format.word32 = tb64 & 0x00000000ffffffffull; + + footer.time_format.format = PK_TRACE_FORMAT_TINY; + + //The following operations must be done atomically + pk_critical_section_enter(&ctx); + + //load the current byte count and calculate the address for this + //entry in the cb + ptr64 = (uint64_t*)&g_pk_trace_buf.cb[g_pk_trace_buf.state.offset & PK_TRACE_CB_MASK]; + + //calculate the offset for the next entry in the cb + state.offset = g_pk_trace_buf.state.offset + sizeof(PkTraceTiny); + + //update the cb state (tbu and offset) + g_pk_trace_buf.state.word64 = state.word64; + + //write the data to the circular buffer including the + //timesamp, string hash, and 16bit parameter + *ptr64 = footer.word64; + + //exit the critical section + pk_critical_section_exit(&ctx); +} + + +// This function is called periodically in order to ensure that the max ticks +// between trace entries is no more than what will fit inside a 32bit value. +void pk_trace_timer_callback(void* arg) +{ + +#if 0 + PkTraceTime64_t footer; + PkTraceState state; + uint64_t* ptr64; + PkMachineContext ctx; + +#define TIMESTAMP64_EXISTS 0x80000000 + + //If the timestamp64 flag is not set then we need another 64 bit timestamp + if(!(g_pk_trace_buf.state.tbu32 & TIMESTAMP64_EXISTS)) + { + + //fill in the footer data + footer.word64 = pk_timebase_get(); + footer.time_format.format = PK_TRACE_FORMAT_TIME64; + + state.tbu32 = footer.upper32 | TIMESTAMP64_EXISTS; + + //The following operations must be done atomically + pk_critical_section_enter(&ctx); + + //load the current byte count and calculate the address for this + //entry in the cb + ptr64 = (uint64_t*)&g_pk_trace_buf.cb[g_pk_trace_buf.state.offset & PK_TRACE_CB_MASK]; + + //calculate the offset for the next entry in the cb + state.offset = g_pk_trace_buf.state.offset + sizeof(PkTraceTiny); + + //update the cb state (tbu and offset) + g_pk_trace_buf.state.word64 = state.word64; + + //write the 64bit timestamp to the buffer + *ptr64 = footer.word64; + + //exit the critical section + pk_critical_section_exit(&ctx); + + } + +#else + + // doing it this way requires less code, but it also means that the + // trace can fill up with these traces over time. + + PK_TRACE("PERIODIC TIMESTAMPING TRACE"); +#endif + + // restart the timer + pk_timer_schedule(&g_pk_trace_timer, + PK_TRACE_TIMER_PERIOD, + 0); +} + +#endif diff --git a/pk/trace/pktracefiles.mk b/pk/trace/pktracefiles.mk new file mode 100644 index 00000000..e3aede76 --- /dev/null +++ b/pk/trace/pktracefiles.mk @@ -0,0 +1,39 @@ +# @file pkppe42files.mk +# +# @brief mk for including ppe42 object files +# +# @page ChangeLogs Change Logs +# @section pkppe42files.mk +# @verbatim +# +# +# Change Log ****************************************************************** +# Flag Defect/Feature User Date Description +# ------ -------------- ---------- ------------ ----------- +# +# @endverbatim +# +########################################################################## +# Include Files +########################################################################## + + + +########################################################################## +# Object Files +########################################################################## +PKTRACE-C-SOURCES = pk_trace_core.c pk_trace_big.c pk_trace_binary.c + +PKTRACE-S-SOURCES = + +PKTRACE-TIMER-C-SOURCES = +PKTRACE-TIMER-S-SOURCES = + +PKTRACE-THREAD-C-SOURCES += +PKTRACE-THREAD-S-SOURCES += + + +PKTRACE_OBJECTS += $(PKTRACE-C-SOURCES:.c=.o) $(PKTRACE-S-SOURCES:.S=.o) + + + |