// #========================================================================
// #
// #    hal_arch.S
// #
// #    M68K support for contexts, exceptions and interrupts
// #
// #========================================================================
//=============================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2003, 2004, 2005, 2006, 2008 Free Software Foundation, Inc.
//
// eCos is free software; you can redistribute it and/or modify it under    
// the terms of the GNU General Public License as published by the Free     
// Software Foundation; either version 2 or (at your option) any later      
// version.                                                                 
//
// eCos is distributed in the hope that it will be useful, but WITHOUT      
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or    
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License    
// for more details.                                                        
//
// You should have received a copy of the GNU General Public License        
// along with eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//============================================================================
//###DESCRIPTIONBEGIN####
//
// Author(s):     bartv
// Date:          2003-06-04
//
//###DESCRIPTIONEND####
//========================================================================

        .file   "hal_arch.S"

#include <pkgconf/system.h>                
#include <pkgconf/hal.h>
#include <pkgconf/hal_m68k.h>
#ifdef CYGPKG_KERNEL
        // For instrumentation options
# include <pkgconf/kernel.h>
#endif                
#include <cyg/hal/arch.inc>

// ----------------------------------------------------------------------------
// The system startup and/or interrupt stack, unless the platform HAL
// provides its own.
//
// The default behaviour is a separate interrupt stack via common HAL
// configuration options, with the interrupt stack reused as the
// startup stack. If the use of an interrupt stack is disabled then
// a separate startup stack is needed. Either the startup stack, the
// interrupt stack, or both can be provided by the platform HAL, for
// example to place the stack in an area of memory that is not otherwise
// readily usable.                
                
#if defined(_HAL_M68K_INSTANTIATE_STARTUP_STACK_) || defined(_HAL_M68K_INSTANTIATE_INTERRUPT_STACK_)
        .section        .bss
        .balign         4
# ifdef _HAL_M68K_INSTANTIATE_STARTUP_STACK_
        .global         _hal_m68k_startup_stack_base_
_hal_m68k_startup_stack_base_:        
# endif
# ifdef _HAL_M68K_INSTANTIATE_INTERRUPT_STACK_
        .global         _hal_m68k_interrupt_stack_base_
_hal_m68k_interrupt_stack_base_:        
# endif
# ifdef CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK
        .rept   CYGNUM_HAL_COMMON_INTERRUPTS_STACK_SIZE
        .byte   0
        .endr
        .balign 4
# else
        .rept   CYGNUM_HAL_M68K_STARTUP_STACK_SIZE
        .byte   0
        .endr
        .balign 4
# endif
# ifdef _HAL_M68K_INSTANTIATE_STARTUP_STACK_
        .global _hal_m68k_startup_stack_
_hal_m68k_startup_stack_:        
# endif
# ifdef _HAL_M68K_INSTANTIATE_INTERRUPT_STACK_
        .global _hal_m68k_interrupt_stack_
_hal_m68k_interrupt_stack_:        
# endif                
        
#endif
        
// ----------------------------------------------------------------------------
// M68K calling conventions.
//
// The stack pointer always points at the first word of the stack that
// is currently in use. Pushing additional data requires a predecrement.
//        
// Frame pointers are not required but the compiler usually defaults to
// using them, to make debugging easier. This assembler code typically
// does not use a frame pointer.        
//
// All arguments are pushed onto the stack, never in registers. D0 is
// used for return values.        
//
// According to CALL_USED_REGISTERS in gcc/config/m68k/m68k.h,
// d0/d1/a0/a1/fp0/fp1 are caller-save, i.e. must be saved in an interrupt
// handler but can be ignored in a function called from C. The fp status
// registers are also caller-save.
//
// d2-d7/a2-a6/fp2-fp7 are callee-save registers, i.e. must be saved by
// a C function. fpsr is not callee-save, a C function can perform floating
// point operations and hence clobber the status. Similarly fpiar is not
// callee-save. fpcr is 
//
// Floating point support is only available on some variants. There can also
// be other hardware units such as multiply-accumulators which may have state
// that should be saved.

// ----------------------------------------------------------------------------
// Context switch support.
//
// The context consists of generic integer registers, possibly floating
// point registers, possibly another hardware unit, and space for pc/sr
// at the end in a variant-specific format.        
                
        .equ    hal_context_integer_offset,     0
        .equ    hal_context_integer_d0_offset,  0
        .equ    hal_context_integer_d2_offset,  (2 * 4)
        .equ    hal_context_integer_a0_offset,  (8 * 4)
        .equ    hal_context_integer_a2_offset,  ((8+2) * 4)
        .equ    hal_context_integer_size,       (15 * 4)
        
        .equ    hal_context_fpu_offset,         (hal_context_integer_offset + hal_context_integer_size)
#ifdef CYGIMP_HAL_M68K_FPU_SAVE
        .equ    hal_context_fpu_fpsr_offset,    (hal_context_fpu_offset + 0)
        .equ    hal_context_fpu_fpiar_offset,   (hal_context_fpu_offset + 4)
        .equ    hal_context_fpu_fp0_offset,     (hal_context_fpu_offset + 8)
        .equ    hal_context_fpu_fp2_offset,     (hal_context_fpu_fp0_offset + (2 * 12))
        .equ    hal_context_fpu_size,           ((2 * 4) + (8 * 12))
#else
        .equ    hal_context_fpu_size,           0        
#endif

        .equ    hal_context_other_offset,       (hal_context_fpu_offset + hal_context_fpu_size)
#ifdef HAL_CONTEXT_OTHER_SIZE
        .equ    hal_context_other_size,         HAL_CONTEXT_OTHER_SIZE
#else
        .equ    hal_context_other_size,         0                
#endif
        
        .equ    hal_context_pcsr_offset,        (hal_context_other_offset + hal_context_other_size)
#ifdef HAL_CONTEXT_PCSR_SIZE
        .equ    hal_context_pcsr_size,          HAL_CONTEXT_PCSR_SIZE
#else
        .equ    hal_context_pcsr_size,          8
#endif
        .equ    hal_context_size,               (hal_context_pcsr_offset + hal_context_pcsr_size)

// The offset to be used when switching to a new thread. For some variants
// this is 0 because the rte instruction will pop the entire pcsr part of
// the structure. For other variants some additional data may have to be popped.
#ifdef HAL_CONTEXT_PCSR_RTE_ADJUST
        .equ    hal_context_rte_adjust,         (hal_context_pcsr_offset + HAL_CONTEXT_PCSR_RTE_ADJUST)
#else
        .equ    hal_context_rte_adjust,         hal_context_pcsr_offset
#endif                

                
// Definitions for saving/restoring FPU context, if the FPU is part of the
// saved context. It can be assumed that %sp points at the thread context.
// FIXME: this code should probably use fsave/frestore to ensure that
// floating point operations have completed.        
#ifdef CYGIMP_HAL_M68K_FPU_SAVE

        .macro  hal_context_fpu_save_caller areg=%sp
        fmove.l         %fpsr, hal_context_fpu_fpsr_offset(\areg)
        fmove.l         %fpiar, hal_context_fpu_fpiar_offset(\areg)
        fmovem.x        %fp0-%fp1, hal_context_fpu_fp0_offset(\areg)
        .endm

        .macro  hal_context_fpu_load_caller areg=%sp
        fmove.l         hal_context_fpu_fpsr_offset(\areg), %fpisr
        fmove.l         hal_context_fpu_fpiar_offset(\areg), %fpiar
        fmovem.x        hal_context_fpu_fp0_offset(\areg),%fp0-%fp1
        .endm

        .macro  hal_context_fpu_save_callee areg=%sp
        fmovem.x        %fp2-%fp7, hal_context_fpu_fp2_offset(\areg)
        .endm

        .macro  hal_context_fpu_load_callee areg=%sp
        fmovem.x        hal_context_fpu_fp2_offset(\areg), %fp2-%fp7
        .endm

        .macro  hal_context_fpu_save_all areg=%sp
        fmove.l         %fpsr, hal_context_fpu_fpsr_offset(\areg)
        fmove.l         %fpiar, hal_context_fpu_fpiar_offset(\areg)
        fmovem.x        %fp0-%fp7, hal_context_fpu_fp0_offset(\areg)
        .endm

        .macro  hal_context_fpu_load_all areg=%sp
        fmove.l         hal_context_fpu_fpsr_offset(\areg), %fpsr
        fmove.l         hal_context_fpu_fpiar_offset(\areg), %fpiar
        fmovem.x        hal_context_fpu_fp0_offset(\areg), %fp0-%fp7
        .endm                         
#else
        .macro hal_context_fpu_save_caller areg=%sp
        .endm
        .macro hal_context_fpu_load_caller areg=%sp
        .endm
        .macro hal_context_fpu_save_callee areg=%sp
        .endm
        .macro hal_context_fpu_load_callee areg=%sp
        .endm
        .macro hal_context_fpu_save_all areg=%sp
        .endm
        .macro hal_context_fpu_load_all areg=%sp
        .endm
#endif

// No-op defaults for saving/restoring the OTHER context. If there is in fact
// something to be done then the variant HAL should have defined
// HAL_CONTEXT_OTHER_SIZE
#ifndef HAL_CONTEXT_OTHER_SIZE
        .macro hal_context_other_save_caller areg=%sp
        .endm
        .macro hal_context_other_load_caller areg=%sp
        .endm
        .macro hal_context_other_save_callee areg=%sp
        .endm
        .macro hal_context_other_load_callee areg=%sp
        .endm
        .macro hal_context_other_save_all areg=%sp
        .endm
        .macro hal_context_other_load_all areg=%sp
        .endm
#endif                

// ----------------------------------------------------------------------------
// LOAD_CONTEXT is called to start a new thread, usually only during system
// startup.
//
// void hal_thread_load_context(void* sp)

        FUNC_START(hal_thread_load_context)
        move.l  4(%sp),%sp
        hal_context_other_load_all
        hal_context_fpu_load_all
        movem.l hal_context_integer_offset(%sp),%d0-%d7/%a0-%a6
        add.l   #hal_context_rte_adjust, %sp
        rte

// ----------------------------------------------------------------------------
// void hal_thread_switch_context(void** from, void* to)

        FUNC_START(hal_thread_switch_context)
        
        // Space for the saved thread context. The PC is already on the
        // stack.  "from" is on the stack immediately after the context,
        // then "to".
        mov.l   4(%sp),%a0
        sub.l   # (hal_context_size - 4), %sp
        mov.l   %sp, 0(%a0)

#ifdef CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM        
        // Save all the callee-save integer registers, so that they become
        // available to the other macros.
        movem.l %d2-%d7, hal_context_integer_d2_offset(%sp)
        movem.l %a2-%a6, hal_context_integer_a2_offset(%sp)

        // The status register must be saved here, since loading a thread
        // context always involves an rte instruction. The details are
        // variant-specific.
        hal_context_pcsr_save_sr %sp,0,%d0

        // Now the fpu and other contexts can be saved. All registers
        // are available.
        hal_context_fpu_save_callee
        hal_context_other_save_callee
#else
        movem.l %d0-%d7/%a0-%a6, hal_context_integer_offset(%sp)
        hal_context_pcsr_save_sr %sp,0,%d0
        hal_context_fpu_save_all
        hal_context_other_save_all
#endif                

        // All thread state has now been saved, so switch to the new one.
        mov.l   (hal_context_size+4)(%sp),%sp

#ifdef CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
        hal_context_other_load_callee
        hal_context_fpu_load_callee
        movem.l hal_context_integer_d2_offset(%sp),%d2-%d7
        movem.l hal_context_integer_a2_offset(%sp),%a2-%a6
#else
        hal_context_other_load_all
        hal_context_fpu_load_all
        movem.l hal_context_integer_offset(%sp),%d0-%d7/%a0-%a6
#endif        

        add.l   #hal_context_rte_adjust, %sp
        rte                        
        
// ----------------------------------------------------------------------------
// setjmp()/longjmp(). See hal_arch.h for details.
//
// int  hal_m68k_setjmp(hal_jmp_buf);
// void hal_m68k_longjmp(hal_jmp_buf, val)       
        
        FUNC_START_WEAK(hal_m68k_setjmp)
        lea.l       4(%sp),%a1                  // return stack pointer. longjmp() does an indirect jmp, not an rts
        move.l      0(%a1),%a0                  // the jmp_buf structure
        move.l      0(%sp),0(%a0)               // return pc
        move.l      %a1,4(%a0)
        movem.l     %d2-%d7/%a2-%a6,8(%a0)      // 11 longs, occupying offsets 8 to 51
#ifdef CYGINT_HAL_M68K_VARIANT_FPU
        fmovem.x    %fp2-%fp7,52(%a0)
#endif                
        clr.l   %d0                             // setjmp() always returns 0
        rts

        FUNC_START_WEAK(hal_m68k_longjmp)
        move.l      8(%sp),%d0                  // val argument, and hence return value
        move.l      4(%sp),%a0                  // The jmp_buf structure
        movem.l     8(%a0),%d2-%d7/%a2-%a6
#ifdef CYGINT_HAL_M68K_VARIANT_FPU
        fmovem.x    52(%a0),%fp2-%fp7
#endif
        move.l      0(%a0),%a1                  // The return pc
        move.l      4(%a0),%sp
        jmp         (%a1)        

// ----------------------------------------------------------------------------
// Synchronous exceptions. Interrupts are disabled because that could confuse
// some code like gdb stubs (breakpoints also involve synchronous exceptions).
// Full state is saved on the current stack, then usually we switch to the
// interrupt stack and handle the rest of the exception there. That avoids 
// having to worry about gdb stubs stack requirements in every thread context.                
//
// It is assumed that on entry there is pc/sr context information already
// on the stack. Some or all of this will have been provided by the hardware.
// Other bits, e.g. the specific exception number, may have been pushed by
// software that then jumped here. The hardware may also have pushed some
// additional state which has already been removed or stashes elsewhere, 
// to keep things simple.
        
        .extern hal_m68k_exception_handler        

        FUNC_START(hal_m68k_exception_vsr)
        mov.w   #0x2700, %sr
        sub.l   #hal_context_pcsr_offset, %sp

        // If CYGDBG_HAL_COMMON_INTERRUPTS_SAVE_MINIMUM_CONTEXT is enabled
        // then only caller-save registers need to be saved here, but
        // exceptions should be rare and the additional info may prove
        // useful to higher-level code.
        movem.l %d0-%d7/%a0-%a6, hal_context_integer_offset(%sp)
        hal_context_fpu_save_all
        hal_context_other_save_all

        // Remember the current stack pointer in a callee-save register,
        // which have all been saved anyway. That makes it easier to restore
        // the stack later.
        move.l  %sp,%a2

#ifdef CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK        
        // Do we need to switch to the interrupt stack?
        cmpa.l  _HAL_M68K_INTERRUPT_STACK_, %sp
        jbgt    1f
        cmpa.l  _HAL_M68K_INTERRUPT_STACK_BASE_, %sp
        jbge    2f
1:      
        mova.l  _HAL_M68K_INTERRUPT_STACK_, %sp
2:
#endif
        // Zero out the frame pointer to encourage GDB to backtrace correctly
        suba.l  %a6, %a6
        
        // Now call into C with a single argument, the HAL_SavedRegisters
        mov.l   %a2,-(%sp)
        jbsr    hal_m68k_exception_handler

        // We can switch back to the right stack by re-using a2, irrespective
        // of whether or not we switched to the interrupt stack.
        move.l  %a2,%sp
        
        // Restore the entire state. In theory only the callee-save registers
        // should have changed, but some others may have been manipulated by
        // gdb.
        hal_context_other_load_all
        hal_context_fpu_load_all
        movem.l hal_context_integer_offset(%sp), %d0-%d7/%a0-%a6
        add.l   #hal_context_rte_adjust, %sp
        rte
                
// ----------------------------------------------------------------------------
// Interrupt handling.
//
// The current stack may be a thread stack or the interrupt stack.
// On some variants this code will be called directly by the hardware.
// On others there will have been a trampoline doing some stack
// manipulation and then jumping here. We assume that the stack is
// properly aligned and that the pc/sr part has been correctly saved.

        FUNC_START(hal_m68k_interrupt_vsr)

#ifndef CYGSEM_HAL_COMMON_INTERRUPTS_ALLOW_NESTING
        // If nesting is not of interest then just disable all interrupts.
        // Otherwise interrupt nesting is controlled via the M68K IPL
        move.w  #0x2700, %sr
#endif        

        sub.l   # hal_context_pcsr_offset, %sp
#ifdef CYGDBG_HAL_COMMON_INTERRUPTS_SAVE_MINIMUM_CONTEXT
        // Even for a minimum context the callee-save a2 register
        // is saved. This makes it easier to restore the stack
        // to the right place after the ISR call, allowing for
        // the optional interrupt stack.
        movem.l %d0-%d1,hal_context_integer_d0_offset(%sp)
        movem.l %a0-%a2,hal_context_integer_a0_offset(%sp)
        hal_context_fpu_save_caller %sp
        hal_context_other_save_caller %sp
#else
        movem.l %d0-%d7/%a0-%a6, hal_context_integer_d0_offset(%sp)
        hal_context_fpu_save_all %sp
        hal_context_other_save_all %sp
#endif                

#if defined(CYGDBG_HAL_DEBUG_GDB_CTRLC_SUPPORT) || defined(CYGDBG_HAL_DEBUG_GDB_BREAK_SUPPORT)
        // Let gdb stubs know which thread was interrupted, in case this
        // interrupt was a ctrl-C.
        .extern hal_saved_interrupt_state
        move.l  %sp, hal_saved_interrupt_state
#endif
        
        // d0/d1/a0/a1/a2 are now available. 
        // We want to extract the ISR vector while it is readily accessible. 
        // The details depend the particular M68K variant. The result should
        // be the vector << 2 to facilitate indexing. On some processors that
        // actually requires less code than getting the vector itself.
        hal_context_extract_isr_vector_shl2 %sp,0,%d0
        
#if defined(CYGPKG_KERNEL_INSTRUMENT) && defined(CYGDBG_KERNEL_INSTRUMENT_INTR)
        // We need to call cyg_instrument(INTR.RAISE, vector, intr_number)
        // The actual interrupt number is not readily available, it depends
        // on the variant and possibly the platform, so just instrument 0
        // instead. The vector is preserved in callee-save a2 since it may
        // take several instructions to refetch it.
        movea.l %d0, %a2
        lsr.l   #2, %d0
        move.l  #0, -(%sp)
        move.l  %d0, -(%sp)
        move.l  #0x0301, -(%sp)
        jbsr    cyg_instrument
        add.l   #12, %sp
        move.l  %a2, %d0
#endif  

        // %d0 holds the ISR vector << 2. d1/a0/a1/a2 are available.

#ifdef CYGFUN_HAL_COMMON_KERNEL_SUPPORT                 
        // Increment the scheduler lock. In theory the ISR should not look
        // at this so it could be deferred till just before the interrupt_end()
        // call, but this provides protection against badly written ISR's.
        //
        // This is not sufficient on SMP systems. The test of the scheduler lock 
        // below also needs attention.
        .extern cyg_scheduler_sched_lock
        addq.l  #1, cyg_scheduler_sched_lock
#endif        
        
        // At the end of the VSR we need to call interrupt_end().
        // The last two arguments are cyg_hal_interrupt_objects[vec]
        // and the saved registers. These are pushed now, while
        // vec is readily available, at the cost of a slight increase
        // in interrupt latency but saving some instructions later.
        move.l  %sp,-(%sp)
        lea     cyg_hal_interrupt_objects,%a0
        move.l  0(%a0,%d0), -(%sp)

        // We want to be able to restore the stack pointer to this location,
        // irrespective of whether or not an interrupt stack is being used.
        // Store it in a callee-save register.
        movea.l %sp, %a2
        
#ifdef CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK
        // Switch to the interrupt stack if we are not already running there.
        cmpa.l  _HAL_M68K_INTERRUPT_STACK_, %sp
        jbgt    1f
        cmpa.l  _HAL_M68K_INTERRUPT_STACK_BASE_, %sp
        jbge    2f
1:
        movea.l _HAL_M68K_INTERRUPT_STACK_, %sp
2:
#endif
        // FIXME: Some variation of the following should be considered to ensure that
        // GDB can't get permanently lost when doing a backtrace from within an ISR.
#if 0 && defined(CYGPKG_INFRA_DEBUG) && !defined(CYGDBG_HAL_COMMON_INTERRUPTS_SAVE_MINIMUM_CONTEXT)
        // Zero out the frame pointer to encourage GDB to backtrace correctly
        // But only if explicitly debugging so that we don't adversely affect
        // critical ISR latency
        move.w  #0,%a6
#endif

        // d0 is the isr vector << 2. a2 holds a saved stack pointer. d1/a0/a1 are available.
        // We need to call (*cyg_hal_interrupt_handlers[vec])(vec, cyg_hal_interrupt_data[vec])
        lea     cyg_hal_interrupt_data,%a0
        move.l  0(%a0,%d0),-(%sp)
        lea     cyg_hal_interrupt_handlers,%a0
        move.l  0(%a0,%d0),%a1
        lsr.l   #2,%d0
        move.l  %d0,-(%sp)
        jbsr    (%a1)

        // We now want to return to the right position on the current
        // stack, or to the thread stack if we switched to the interrupt
        // stack.
        movea.l %a2, %sp

        // Next we need to call interrupt_end(). If the scheduler was unlocked
        // at the time of the interrupt then that will run the DSRs, possibly
        // cause a context switch, and eventually return to this VSR. If the
        // scheduler was already locked then interrupt_end() will return to
        // the VSR almost immediately. The IPL level will be restored when
        // the VSR executes the final rte.
        //
        // DSRs should run with interrupts re-enabled. Ideally we want to
        // run them with the IPL level of the interrupted thread, and at
        // the end reset the IPL level to what it is now. That way this
        // VSR gets to return, without risk of unconstrained stack usage.
        // However this is very hard: currently the desired IPL levels
        // cannot be passed on the stack; they cannot be passed via globals
        // either because of the possibility of a context switch.
        //
        // Instead there is a defined IPL level for running all DSRs.
        // HAL_INTERRUPT_STACK_CALL_PENDING_DSRs() sets the IPL level to
        // that value, and restores the IPL level when done. For most
        // applications that will be fine, but it does make it more
        // difficult to manipulate IPL levels on a per-thread level
        // or anything similarly fancy.

        // We need to call interrupt_end(isr_return, cyg_hal_interrupt_objects[vec], saved_state_pointer)
        // The last two have already been pushed onto the stack. d0 holds the return value.
        move.l  %d0,-(%sp)
        jbsr    interrupt_end
        add.l   #12,%sp

#ifdef CYGDBG_HAL_COMMON_INTERRUPTS_SAVE_MINIMUM_CONTEXT
        hal_context_other_load_caller %sp
        hal_context_fpu_load_caller %sp
        movem.l hal_context_integer_a0_offset(%sp),%a0-%a2
        movem.l hal_context_integer_d0_offset(%sp),%d0-%d1
#else
        hal_context_other_load_all %sp
        hal_context_fpu_load_all   %sp
        movem.l hal_context_integer_d0_offset(%sp),%d0-%d7/%a0-%a6
#endif
        add.l   #hal_context_rte_adjust,%sp
        rte                

// ----------------------------------------------------------------------------
// On configurations with an interrupt stack HAL_INTERRUPT_STACK_CALL_PENDING_DSRS()
// is used to run the DSRs on the interrupt stack rather than the thread stack,
// reducing stack size requirements for the latter. It is called only on a thread
// stack. In addition to the stack switch the IPL level is manipulated here,
// ensuring that DSRs run with interrupts enabled.
//
// On other architectures HAL_INTERRUPT_STACK_CALL_PENDING_DSRS() is only called
// when a separate interrupt stack is used. If the interrupt stack is disabled
// then DSRs run with interrupts disabled, which is a bad idea. On the M68K
// HAL_INTERRUPT_STACK_CALL_PENDING_DSRS() is always defined, thus sorting out
// the interrupt level, but only does the stack switch if an interrupt stack        
// is actually being used.                

        .extern hal_m68k_dsr_ipl_level
        .extern cyg_interrupt_call_pending_DSRs

        FUNC_START(hal_interrupt_stack_call_pending_DSRs)
#ifdef CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK        
        // Use callee-save registers to store the current stack and status register
        // Also store the frame pointer so we can zero it to improve GDB debugging backtraces
        move.l  %a2,-(%sp)
        move.l  %d2,-(%sp)
        move.l  %a6,-(%sp)
        movea.l %sp,%a2
        move.w  %sr,%d2
        suba.l  %a6,%a6

        // There is a problem if the configuration contains the kernel but interrupts
        // are enabled while still running the startup code. The kernel's interrupt_end()
        // rather than the driver API's will be used, so this code gets to run. We now
        // get to switch to the interrupt stack while still on the startup stack, which
        // is a shame since the two are the same. This could be easily fixed by checking
        // the current stack as happens in the interrupt_vsr, but at the cost of an
        // extra four instructions in the DSR path. The scenario should probably not
        // arise so those four instructions are left out for now.
        movea.l _HAL_M68K_INTERRUPT_STACK_, %sp
        move.l  hal_m68k_dsr_ipl_level, %d0
        move.w  %d0, %sr
        
        jbsr    cyg_interrupt_call_pending_DSRs

        move.w  %d2, %sr
        movea.l %a2, %sp
        movea.l (%sp)+,%a6
        move.l  (%sp)+,%d2
        movea.l (%sp)+,%a2
        rts
#else
        move.l  %d2, -(%sp)
        move.w  %sr, %d2
        move.l  hal_m68k_dsr_ipl_level, %d0
        move.w  %d0, %sr
        jbsr    cyg_interrupt_call_pending_DSRs
        move.w  %d2, %sr
        move.l  (%sp)+, %d2
        rts
#endif // CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK
        
                
// ----------------------------------------------------------------------------
// A dummy entry point for unused entries in the system exception vector.

        FUNC_START(hal_m68k_rte)
        rte

// ----------------------------------------------------------------------------
// Fast implementations of lsbit_index() and msbit_index()

        FUNC_START(hal_lsbit_index)
        clr.l   %d0                     // result
        mov.l   4(%sp),%d1              // mask
        bne     1f                      // special case for 0
        subq.l  #1,%d0                  // lsbit_index(0) -> -1
        rts
1:              
        tst.w   %d1                     // anything set in the bottom 16 bits?
        bne     2f
        swap    %d1                     // the top 16 bits only are of interest
        moveq.l #16, %d0
2:      
        tst.b   %d1                     // anything set in the bottom 8 bits?
        bne     3f
        addq.l  #8, %d0                 // bottom byte is 0, switch to next byte
        lsr.l    #8, %d1
3:      
        move.l  %d1, %a0                // backup current data, so that we can manipulate it
        and.l   #0x0F, %d1              // anything in the bottom nibble?
        bne     4f
        move.l  %a0, %d1                // nope, restore and use the next nibble
        lsr.l    #4, %d1
        and.l   #0x0F, %d1
        addq.l  #4, %d0
4:      
                                        // The bottom nibble contains a number between 1 and 15
                                        // (not 0, that would have been caught by the test at the top).
        lea     5f, %a0
        mov.b   0(%a0,%d1),%d1          // only zap bottom byte, the other bytes are already 0
        add.l   %d1,%d0
        rts
5:
        // Every odd number has an index of 0. The first entry is irrelevent.
        .byte 0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0

        FUNC_START(hal_msbit_index)
        clr.l   %d0                     // result
        mov.l   4(%sp), %d1             // mask
        bne     1f                      // special case for 0
        subq.l  #1, %d0                 // msbit_index(0) -> -1
        rts
1:
        cmpi.l  #0x0000FFFF,%d1
        bls     2f
        moveq.l #16,%d0
        clr.w   %d1
        swap    %d1
2:      
        cmpi.l  #0x00FF, %d1
        bls     3f
        addq.l  #8, %d0
        lsr.l   #8, %d1
3:      
        cmpi.l  #0x000F, %d1
        bls     4f
        addq.l  #4, %d0
        lsr.l   #4, %d1

4:      
        lea     5f, %a0
        mov.b   0(%a0,%d1), %d1
        add.l   %d1,%d0
        rts

5:
        .byte 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3

// ----------------------------------------------------------------------------
// Profiling support. When code is compiled with -pg the compiler inserts
// calls to mcount() at the start of each function. This can be used to
// build a callgraph of the application. mcount() is tied to the compiler
// internals and does not always use standard calling conventions.
//
// mcount() should call __profile_mcount() with two arguments, caller_pc
// and callee_pc, where callee_pc refers to the function that calls
// mcount() and caller_pc refers to the place where that function is
// called from. callee_pc is readily accessed relative to sp, caller_pc
// can be accessed using the frame pointer. This assumes the code has
// not been built with -fomit-frame-pointer, a safe assumption since
// the compiler disallows combining -pg and -fomit-frame-pointer.        
//
// The m68k compiler appears to implement mcount() a bit differently
// from other targets. It reserves a word for every function entry
// point which presumably is intended to act as the start of a linked
// list chain. On other targets the PC is used to index a hash table
// which contains the start of that linked list. That extra word is
// not used for eCos profiling since it would require customizing the
// profiling package on a per-target basis, so unfortunately the
// memory is wasted.
//
// It is assumed that d0/d1/a0/a1 (the callee-save registers) are
// available. a0 is certainly available because that is used to hold 
// the above per-function word. If the assumption is false then this
// would have to save d0/d1/a1 because the C __profile_mcount() could
// zap them.                
//
// This code is conditional on CYGPKG_PROFILE_GPROF since that provides
// the implementation of __profile_mcount.
#ifdef CYGPKG_PROFILE_GPROF                
        FUNC_START(mcount)
        // __profile_mcount() should be called with interrupts disabled.
        move.w  %sr,%d0
        move.l  %d0,-(%sp)
        // The callee-pc corresponds to the return address on the stack
        move.l  4(%sp),%a0
        move.l  %a0,-(%sp)
        // The caller-pc is accessible relative to the frame pointer a6
        move.l  4(%a6),%a0
        move.l  %a0,-(%sp)
        
        move.w  #0x2700, %sr
        jbsr    __profile_mcount

        move.l  8(%sp),%d0
        add.l   #12, %sp
        move.w  %d0,%sr
        
        rts
#endif
                
        .end
