
BSP support middleware for 'new-exception' style PPC.

T. Straumann, 12/2007

EXPLANATION OF SOME TERMS
=========================

In this README we refer to exceptions and sometimes
to 'interrupts'. Interrupts simply are asynchronous
exceptions such as 'external' exceptions or 'decrementer'
/'timer' exceptions.

Traditionally (in the libbsp/powerpc/shared implementation),
synchronous exceptions are handled entirely in the context
of the interrupted task, i.e., the exception handlers use
the task's stack and leave thread-dispatching enabled,
i.e., scheduling is allowed to happen 'in the middle'
of an exception handler.

Asynchronous exceptions/interrupts, OTOH, use a dedicated
interrupt stack and defer scheduling until after the last
nested ISR has finished.

RATIONALE
=========
The 'new-exception' processing API works at a rather
low level. It provides functions for
installing low-level code (which must be written in
assembly code) directly into the PPC vector area.
It is entirely left to the BSP to implement low-level
exception handlers and to implement an API for
C-level exception handlers and to implement the
RTEMS interrupt API defined in cpukit/include/rtems/irq.h.

The result has been a Darwinian evolution of variants
of this code which is very hard to maintain. Mostly,
the four files

libbsp/powerpc/shared/vectors/vectors.S
  (low-level handlers for 'normal' or 'synchronous'
  exceptions. This code saves all registers on
  the interrupted task's stack and calls a
  'global' C (high-level) exception handler.

libbsp/powerpc/shared/vectors/vectors_init.c
  (default implementation of the 'global' C
  exception handler and initialization of the
  vector table with trampoline code that ends up
  calling the 'global' handler.

libbsp/powerpc/shared/irq/irq_asm.S
  (low-level handlers for 'IRQ'-type or 'asynchronous'
  exceptions. This code is very similar to vectors.S
  but does slightly more: after saving (only
  the minimal set of) registers on the interrupted
  task's stack it disables thread-dispatching, switches
  to a dedicated ISR stack (if not already there which is
  possible for nested interrupts) and then executes the high
  level (C) interrupt dispatcher 'C_dispatch_irq_handler()'.
  After 'C_dispatch_irq_handler()' returns the stack
  is switched back (if not a nested IRQ), thread-dispatching
  is re-enabled, signals are delivered and a context
  switch is initiated if necessary.

libbsp/powerpc/shared/irq/irq.c
  implementation of the RTEMS ('new') IRQ API defined
  in cpukit/include/rtems/irq.h.

have been copied and modified by a myriad of BSPs leading
to many slightly different variants.

THE BSP-SUPORT MIDDLEWARE
=========================

The code in this directory is an attempt to provide the
functionality implemented by the aforementioned files
in a more generic way so that it can be shared by more
BSPs rather than being copied and modified.

Another important goal was eliminating all conditional
compilation which tested for specific CPU models by means
of C-preprocessor symbols (#ifdef ppcXYZ).
Instead, appropriate run-time checks for features defined
in cpuIdent.h are used.

The assembly code has been (almost completely) rewritten
and it tries to address a few problems while deliberately
trying to live with the existing APIs and semantics
(how these could be improved is beyond the scope but
that they could is beyond doubt...):

 - some PPCs don't fit into the classic scheme where
   the exception vector addresses all were multiples of
   0x100 (some vectors are spaced as closely as 0x10).
   The API should not expose vector offsets but only
   vector numbers which can be considered an abstract
   entity. The mapping from vector numbers to actual
   address offsets is performed inside 'raw_exception.c'
 - having to provide assembly prologue code in order to
   hook an exception is cumbersome. The middleware
   tries to free users and BSP writers from this issue
   by dealing with assembly prologues entirely inside
   the middleware. The user can hook ordinary C routines.
 - the advent of BookE CPUs brought interrupts with
   multiple priorities: non-critical and critical 
   interrupts. Unfortunately, these are not entirely
   trivial to deal with (unless critical interrupts
   are permanently disabled [which is still the case:
   ATM rtems_interrupt_enable()/rtems_interrupt_disable()
   only deal with EE]). See separate section titled
   'race condition...' below for a detailed explanation.

STRUCTURE
=========

The middleware uses exception 'categories' or
'flavors' as defined in raw_exception.h.

The middleware consists of the following parts:

   1 small 'prologue' snippets that encode the
     vector information and jump to appropriate
	 'flavored-wrapper' code for further handling.
	 Some PPC exceptions are spaced only
	 16-bytes apart, so the generic
	 prologue snippets are only 16-bytes long.
	 Prologues for synchronuos and asynchronous
	 exceptions differ.

   2 flavored-wrappers which sets up a stack frame
     and do things that are specific for
	 different 'flavors' of exceptions which
	 currently are
	   - classic PPC exception
	   - ppc405 critical exception
	   - bookE critical exception
	   - e500 machine check exception

   Assembler macros are provided and they can be
   expanded to generate prologue templates and
   flavored-wrappers for different flavors
   of exceptions. Currently, there are two prologues
   for all aforementioned flavors. One for synchronous
   exceptions, the other for interrupts.

   3 generic assembly-level code that does the bulk
     of saving register context and calling C-code.

   4 C-code (ppc_exc_hdl.c) for dispatching BSP/user
     handlers.

   5 Initialization code (vectors_init.c). All valid
     exceptions for the detected CPU are determined
	 and a fitting prologue snippet for the exception
	 category (classic, critical, synchronous or IRQ, ...)
	 is generated from a template and the vector number
	 and then installed in the vector area.

	 The user/BSP only has to deal with installing
	 high-level handlers but by default, the standard
	 'C_dispatch_irq_handler' routine is hooked to
	 the external and 'decrementer' exceptions.

   6 RTEMS IRQ API is implemented by 'irq.c'. It
     relies on a few routines to be provided by
	 the BSP.

USAGE
=====
	BSP writers must provide the following routines 
	(declared in irq_supp.h):
	Interrupt controller (PIC) support:
		BSP_setup_the_pic()        - initialize PIC hardware
		BSP_enable_irq_at_pic()    - enable/disable given irq at PIC; IGNORE if
		BSP_disable_irq_at_pic()     irq number out of range!
		C_dispatch_irq_handler()   - handle irqs and dispatch user handlers
		                             this routine SHOULD use the inline
									 fragment

									   bsp_irq_dispatch_list()

									 provided by irq_supp.h
									 for calling user handlers.

	BSP initialization; call

	rtems_status_code sc = ppc_exc_initialize(
	  PPC_INTERRUPT_DISABLE_MASK_DEFAULT,
	  interrupt_stack_begin,
	  interrupt_stack_size
	);
	if (sc != RTEMS_SUCCESSFUL) {
	  BSP_panic("cannot initialize exceptions");
	}
	BSP_rtems_irq_mngt_set();

	Note that BSP_rtems_irq_mngt_set() hooks the C_dispatch_irq_handler()
	to the external and decrementer (PIT exception for bookE; a decrementer
	emulation is activated) exceptions for backwards compatibility reasons.
	C_dispatch_irq_handler() must therefore be able to support these two
	exceptions.
	However, the BSP implementor is free to either disconnect
	C_dispatch_irq_handler() from either of these exceptions, to connect
	other handlers (e.g., for SYSMGMT exceptions) or to hook
	C_dispatch_irq_handler() to yet more exceptions etc. *after*
	BSP_rtems_irq_mngt_set() executed.

	Hooking exceptions:

	The API defined in vectors.h declares routines for connecting
	a C-handler to any exception. Note that the execution environment
	of the C-handler depends on the exception being synchronous or
	asynchronous:

		- synchronous exceptions use the task stack and do not
		  disable thread dispatching scheduling.
		- asynchronous exceptions use a dedicated stack and do
		  defer thread dispatching until handling has (almost) finished.

	By inspecting the vector number stored in the exception frame
	the nature of the exception can be determined: asynchronous 
	exceptions have the most significant bit(s) set.

	Any exception for which no dedicated handler is registered
	ends up being handled by the routine addressed by the
	(traditional) 'globalExcHdl' function pointer.

	Makefile.am:
		- make sure the Makefile.am does NOT use any of the files
			vectors.S, vectors.h, vectors_init.c, irq_asm.S, irq.c
		  from 'libbsp/powerpc/shared' NOR must the BSP implement
		  any functionality that is provided by those files (and
		  now the middleware).

		- (probably) remove 'vectors.rel' and anything related

		- add

		    ../../../libcpu/@RTEMS_CPU@/@exceptions@/bspsupport/vectors.h
		    ../../../libcpu/@RTEMS_CPU@/@exceptions@/bspsupport/irq_supp.h

		  to 'include_bsp_HEADERS'

		- add

		    ../../../libcpu/@RTEMS_CPU@/@exceptions@/exc_bspsupport.rel
		    ../../../libcpu/@RTEMS_CPU@/@exceptions@/irq_bspsupport.rel

		  to 'libbsp_a_LIBADD'

		  (irq.c is in a separate '.rel' so that you can get support
		  for exceptions only).

CAVEATS
=======

On classic PPCs, early (and late) parts of the low-level
exception handling code run with the MMU disabled which mean
that the default caching attributes (write-back) are in effect
(thanks to Thomas Doerfler for bringing this up).
The code currently assumes that the MMU translations
for the task and interrupt stacks as well as some
variables in the data-area MATCH THE DEFAULT CACHING
ATTRIBUTES (this assumption also holds for the old code
in libbsp/powepc/shared/vectors ../irq).

During initialization of exception handling, a crude test
is performed to check if memory seems to have the write-back
attribute. The 'dcbz' instruction should - on most PPCs - cause
an alignment exception if the tested cache-line does not
have this attribute.

BSPs which entirely disable caching (e.g., by physically
disabling the cache(s)) should set the variable
  ppc_exc_cache_wb_check = 0
prior to calling initialize_exceptions(). 
Note that this check does not catch all possible
misconfigurations (e.g., on the 860, the default attribute
is AFAIK [libcpu/powerpc/mpc8xx/mmu/mmu_init.c] set to
'caching-disabled' which is potentially harmful but
this situation is not detected).


RACE CONDITION WHEN DEALING WITH CRITICAL INTERRUPTS
====================================================

   The problematic race condition is as follows:

   Usually, ISRs are allowed to use certain OS
   primitives such as e.g., releasing a semaphore.
   In order to prevent a context switch from happening
   immediately (this would result in the ISR being
   suspended), thread-dispatching must be disabled
   around execution of the ISR. However, on the
   PPC architecture it is neither possible to 
   atomically disable ALL interrupts nor is it
   possible to atomically increment a variable
   (the thread-dispatch-disable level).
   Hence, the following sequence of events could
   occur:
    1) low-priority interrupt (LPI) is taken
    2) before the LPI can increase the 
	   thread-dispatch-disable level or disable
	   high-priority interupts, a high-priority
	   interrupt (HPI) happens
	3) HPI increases dispatch-disable level
	4) HPI executes high-priority ISR which e.g.,
	   posts a semaphore
	5) HPI decreases dispatch-disable level and
	   realizes that a context switch is necessary
	6) context switch is performed since LPI had
	   not gotten to the point where it could
	   increase the dispatch-disable level.
   At this point, the LPI has been effectively
   suspended which means that the low-priority
   ISR will not be executed until the task 
   interupted in 1) is scheduled again!

   The solution to this problem is letting the
   first machine instruction of the low-priority
   exception handler write a non-zero value to 
   a variable in memory:

	ee_vector_offset:  

         stw r1, ee_lock@sdarel(r13)	
         .. save some registers etc..
		 .. increase thread-dispatch-disable-level
		 .. clear 'ee_lock' variable

	After the HPI decrements the dispatch-disable level
	it checks 'ee_lock' and refrains from performing
	a context switch if 'ee_lock' is nonzero. Since
	the LPI will complete execution subsequently it
	will eventually do the context switch.

	For the single-instruction write operation we must
	  a) write a register that is guaranteed to be
	     non-zero (e.g., R1 (stack pointer) or R13
		 (SVR4 short-data area).
	  b) use an addressing mode that doesn't require
	     loading any registers. The short-data area
		 pointer R13 is appropriate.

    CAVEAT: unfortunately, this method by itself
	is *NOT* enough because raising a low-priority
	exception and executing the first instruction
	of the handler is *NOT* atomic. Hence, the following
	could occur:

	 1) LPI is taken
	 2) PC is saved in SRR0, PC is loaded with 
	    address of 'locking instruction'
		  stw r1, ee_lock@sdarel(r13)
     3) ==> critical interrupt happens
	 4) PC (containing address of locking instruction)
	    is saved in CSRR0
     5) HPI is dispatched

	For the HPI to correctly handle this situation
	it does the following:

	
		a) increase thread-dispatch disable level
		b) do interrupt work
		c) decrease thread-dispatch disable level
	    d) if ( dispatch-disable level == 0 )
		 d1) check ee_lock
		 d2) check instruction at *CSRR0
		 d3) do a context switch if necessary ONLY IF
		     ee_lock is NOT set AND *CSRR0 is NOT the
			 'locking instruction'

	this works because the address of 'ee_lock'
	is embedded in the locking instruction
	'stw r1, ee_lock@sdarel(r13)' and because the
	registers r1/r13 have a special purpose
	(stack-pointer, SDA-pointer). Hence it is safe
	to assume that the particular instruction
	'stw r1,ee_lock&sdarel(r13)' never occurs
	anywhere else.

	Another note: this algorithm also makes sure
	that ONLY nested ASYNCHRONOUS interrupts which
	enable/disable thread-dispatching and check if
	thread-dispatching is required before returning
	control engage in this locking protocol. It is
	important that when a critical, asynchronous
	interrupt interrupts a 'synchronous' exception
	(which does not disable thread-dispatching)
	the thread-dispatching operation upon return of
	the HPI is NOT deferred (because the synchronous
	handler would not, eventually, check for a
	dispatch requirement).

	And one more note: We never want to disable
	machine-check exceptions to avoid a checkstop.
	This means that we cannot use enabling/disabling
	this type of exception for protection of critical
	OS data structures.
	Therefore, calling OS primitives from a asynchronous
	machine-check handler is ILLEGAL and not supported.
	Since machine-checks can happen anytime it is not
	legal to test if a deferred context switch should
	be performed when the asynchronous machine-check
	handler returns (since _Context_Switch_is_necessary
	could have been set by a IRQ-protected section of
	code that was hit by the machine-check).
	Note that synchronous machine-checks can legally
	use OS primitives and currently there are no
	asynchronous machine-checks defined.

   Epilogue:

   You have to disable all asynchronous exceptions which may cause a context
   switch before the restoring of the SRRs and the RFI.  Reason:
   
      Suppose we are in the epilogue code of an EE between the move to SRRs and
      the RFI. Here EE is disabled but CE is enabled. Now a CE happens.  The
      handler decides that a thread dispatch is necessary. The CE checks if
      this is possible:
   
         o The thread dispatch disable level is 0, because the EE has already
           decremented it.
         o The EE lock variable is cleared.
         o The EE executes not the first instruction.
   
      Hence a thread dispatch is allowed. The CE issues a context switch to a
      task with EE enabled (for example a task waiting for a semaphore). Now a
      EE happens and the current content of the SRRs is lost.
