488 lines
15 KiB
C
488 lines
15 KiB
C
/*
|
|
* Copyright (c) 2010-2014 Wind River Systems, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief Interrupt management support for IA-32 arch
|
|
*
|
|
* This module provides routines to manage asynchronous interrupts
|
|
* on the IA-32 architecture.
|
|
*
|
|
* INTERNAL
|
|
* The _idt_base_address symbol is used to determine the base address of the IDT.
|
|
* (It is generated by the linker script, and doesn't correspond to an actual
|
|
* global variable.)
|
|
*
|
|
* Dynamic interrupts are handled by a set of dynamic interrupt stubs defined
|
|
* in intstub.S. Each one pushes a "stub id" onto the stack and calls
|
|
* common_dynamic_handler, which uses the stub id to pull the details
|
|
* about what to do with the dynamic IRQ out of the dyn_irq_list array.
|
|
* This array is populated by calls to irq_connect_dynamic(), which also
|
|
* installs the associated dynamic stub in the IDT.
|
|
*/
|
|
|
|
|
|
#include <nanokernel.h>
|
|
#include <arch/cpu.h>
|
|
#include <nano_private.h>
|
|
#include <misc/__assert.h>
|
|
#include <idtEnt.h>
|
|
#include <misc/printk.h>
|
|
#include <irq.h>
|
|
|
|
extern void _SpuriousIntHandler(void *);
|
|
extern void _SpuriousIntNoErrCodeHandler(void *);
|
|
|
|
/*
|
|
* These 'dummy' variables are used in nanoArchInit() to force the inclusion of
|
|
* the spurious interrupt handlers. They *must* be declared in a module other
|
|
* than the one they are used in to get around garbage collection issues and
|
|
* warnings issued some compilers that they aren't used. Therefore care must
|
|
* be taken if they are to be moved. See nano_private.h for more information.
|
|
*/
|
|
void *_dummy_spurious_interrupt;
|
|
void *_dummy_exception_vector_stub;
|
|
|
|
/*
|
|
* Place the addresses of the spurious interrupt handlers into the intList
|
|
* section. The genIdt tool can then populate any unused vectors with
|
|
* these routines.
|
|
*/
|
|
void *__attribute__((section(".spurIsr"))) MK_ISR_NAME(_SpuriousIntHandler) =
|
|
&_SpuriousIntHandler;
|
|
void *__attribute__((section(".spurNoErrIsr")))
|
|
MK_ISR_NAME(_SpuriousIntNoErrCodeHandler) =
|
|
&_SpuriousIntNoErrCodeHandler;
|
|
|
|
#if CONFIG_DEBUG_IRQS
|
|
/**
|
|
*
|
|
* @brief Dump out the IDT for debugging purposes
|
|
*
|
|
* The IDT has a strange structure which confounds direct examination in
|
|
* a debugger. This function will print out its contents in human-readable
|
|
* form. If unused, gc-sections will strip this function from the binary.
|
|
*/
|
|
void irq_debug_dump_idt(void)
|
|
{
|
|
int i;
|
|
IDT_ENTRY *idt = (IDT_ENTRY *)_idt_base_address;
|
|
|
|
printk("Installed interrupt handlers (spurious omitted):\n");
|
|
for (i = 0; i < CONFIG_IDT_NUM_VECTORS; i++) {
|
|
uint32_t addr = idt[i].offset_low + (idt[i].offset_high << 16);
|
|
|
|
if ((void *)addr == &_SpuriousIntNoErrCodeHandler ||
|
|
(void *)addr == &_SpuriousIntHandler) {
|
|
continue;
|
|
}
|
|
|
|
printk("IDT 0x%x: CS=0x%x ADDR=0x%x DPL=0x%x ",
|
|
i, idt[i].segment_selector, addr,
|
|
idt[i].dpl);
|
|
if (idt[i].present) {
|
|
printk("present ");
|
|
}
|
|
|
|
if (idt[i].gate_size) {
|
|
printk("32-bit ");
|
|
} else {
|
|
printk("16-bit ");
|
|
}
|
|
|
|
switch (idt[i].type) {
|
|
case 0x5:
|
|
printk("task gate");
|
|
break;
|
|
case 0x6:
|
|
printk("IRQ gate");
|
|
break;
|
|
case 0x7:
|
|
printk("trap gate");
|
|
break;
|
|
default:
|
|
printk("Garbage type (0x%x)", idt[i].type);
|
|
break;
|
|
}
|
|
printk("\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
*
|
|
* @brief Connect a routine to an interrupt vector
|
|
*
|
|
* @param vector interrupt vector: 0 to 255 on IA-32
|
|
* @param routine a function pointer to the interrupt routine
|
|
* @param dpl priv level for interrupt-gate descriptor
|
|
*
|
|
* This routine "connects" the specified <routine> to the specified interrupt
|
|
* <vector>. On the IA-32 architecture, an interrupt vector is a value from
|
|
* 0 to 255. This routine merely fills in the appropriate interrupt
|
|
* descriptor table (IDT) with an interrupt-gate descriptor such that <routine>
|
|
* is invoked when interrupt <vector> is asserted. The <dpl> argument specifies
|
|
* the privilege level for the interrupt-gate descriptor; (hardware) interrupts
|
|
* and exceptions should specify a level of 0, whereas handlers for user-mode
|
|
* software generated interrupts should specify 3.
|
|
*
|
|
* @return N/A
|
|
*
|
|
* INTERNAL
|
|
* Unlike nanoCpuExcConnect() and irq_connect_dynamic(), the _IntVecSet() routine
|
|
* is a very basic API that simply updates the appropriate entry in Interrupt
|
|
* Descriptor Table (IDT) such that the specified routine is invoked when the
|
|
* specified interrupt vector is asserted.
|
|
*
|
|
*/
|
|
|
|
void _IntVecSet(unsigned int vector, void (*routine)(void *), unsigned int dpl)
|
|
{
|
|
unsigned long long *pIdtEntry;
|
|
unsigned int key;
|
|
|
|
/*
|
|
* The <vector> parameter must be less than the value of the
|
|
* CONFIG_IDT_NUM_VECTORS configuration parameter, however,
|
|
* explicit validation will not be performed in this primitive.
|
|
*/
|
|
|
|
pIdtEntry = (unsigned long long *)(_idt_base_address + (vector << 3));
|
|
|
|
/*
|
|
* Lock interrupts to protect the IDT entry to which _IdtEntryCreate()
|
|
* will write. They must be locked here because the _IdtEntryCreate()
|
|
* code is shared with the 'gen_idt' host tool.
|
|
*/
|
|
|
|
key = irq_lock();
|
|
_IdtEntCreate(pIdtEntry, routine, dpl);
|
|
|
|
#ifdef CONFIG_MVIC
|
|
/* Some nonstandard interrupt controllers may be doing some IDT
|
|
* caching for performance reasons and need the IDT reloaded if
|
|
* any changes are made to it
|
|
*/
|
|
__asm__ volatile ("lidt _Idt");
|
|
#endif
|
|
|
|
irq_unlock(key);
|
|
}
|
|
|
|
#if ALL_DYN_IRQ_STUBS > 0
|
|
/*
|
|
* _interrupt_vectors_allocated[] is generated by the 'gen_idt' tool. It is
|
|
* initialized to identify which interrupts have been statically connected
|
|
* and which interrupts are available to be dynamically connected at run time.
|
|
* The variable itself is defined in the linker file.
|
|
*/
|
|
extern unsigned int _interrupt_vectors_allocated[];
|
|
|
|
/*
|
|
* Guard against situations when ALL_DYN_IRQ_STUBS is left equal to 0,
|
|
* but irq_connect_dynamic is still used, which causes system failure.
|
|
* If ALL_DYN_IRQ_STUBS is left 0, but irq_connect_dynamic is used, linker
|
|
* generates an error
|
|
*/
|
|
struct dyn_irq_info {
|
|
/** IRQ handler */
|
|
void (*handler)(void *param);
|
|
/** Parameter to pass to the handler */
|
|
void *param;
|
|
};
|
|
|
|
/*
|
|
* Instead of creating a large sparse table mapping all possible IDT vectors
|
|
* to dyn_irq_info, the dynamic stubs push a "stub id" onto the stack
|
|
* which is used by common_dynamic_handler() to fetch the appropriate
|
|
* information out of this much smaller table
|
|
*/
|
|
static struct dyn_irq_info dyn_irq_list[ALL_DYN_IRQ_STUBS];
|
|
static unsigned int next_irq_stub;
|
|
|
|
/* Memory address pointing to where in ROM the code for the dynamic stubs are */
|
|
extern void *_DynIntStubsBegin;
|
|
|
|
/**
|
|
*
|
|
* @brief Connect a C routine to a hardware interrupt
|
|
*
|
|
* @param irq virtualized IRQ to connect to
|
|
* @param priority requested priority of interrupt
|
|
* @param routine the C interrupt handler
|
|
* @param parameter parameter passed to C routine
|
|
* @param flags IRQ flags
|
|
*
|
|
* This routine connects an interrupt service routine (ISR) coded in C to
|
|
* the specified hardware <irq>. An interrupt vector will be allocated to
|
|
* satisfy the specified <priority>.
|
|
*
|
|
* The specified <irq> represents a virtualized IRQ, i.e. it does not
|
|
* necessarily represent a specific IRQ line on a given interrupt controller
|
|
* device. The platform presents a virtualized set of IRQs from 0 to N, where
|
|
* N is the total number of IRQs supported by all the interrupt controller
|
|
* devices on the board. See the platform's documentation for the mapping of
|
|
* virtualized IRQ to physical IRQ.
|
|
*
|
|
* When the device asserts an interrupt on the specified <irq>, a switch to
|
|
* the interrupt stack is performed (if not already executing on the interrupt
|
|
* stack), followed by saving the integer (i.e. non-floating point) thread of
|
|
* the currently executing task, fiber, or ISR. The ISR specified by <routine>
|
|
* will then be invoked with the single <parameter>. When the ISR returns, a
|
|
* context switch may occur.
|
|
*
|
|
* On some platforms <flags> parameter needs to be specified to indicate if
|
|
* the irq is triggered by low or high level or by rising or falling edge.
|
|
*
|
|
* The routine searches for the first available element in the dynamic_stubs
|
|
* array and uses it for the stub.
|
|
*
|
|
* @return the allocated interrupt vector
|
|
*
|
|
* WARNINGS
|
|
* This routine does not perform range checking on the requested <priority>
|
|
* and thus, depending on the underlying interrupt controller, may result
|
|
* in the assignment of an interrupt vector located in the reserved range of
|
|
* the processor.
|
|
*
|
|
* INTERNAL
|
|
* For debug kernels, this routine shall return -1 when there are no
|
|
* vectors remaining in the specified <priority> level.
|
|
*/
|
|
|
|
int _arch_irq_connect_dynamic(unsigned int irq, unsigned int priority,
|
|
void (*routine)(void *parameter), void *parameter,
|
|
uint32_t flags)
|
|
{
|
|
int vector;
|
|
int stub_idx;
|
|
|
|
/*
|
|
* Invoke the interrupt controller routine _SysIntVecAlloc() which will:
|
|
* a) allocate a vector satisfying the requested priority,
|
|
* b) create a new entry in the dynamic stub array
|
|
* c) program the underlying interrupt controller device such that
|
|
* when <irq> is asserted, the allocated interrupt vector will be
|
|
* presented to the CPU.
|
|
*
|
|
* The _SysIntVecAlloc() routine will use the "utility" routine
|
|
* _IntVecAlloc() provided in this module to scan the
|
|
* _interrupt_vectors_allocated[] array for a suitable vector.
|
|
*/
|
|
|
|
vector = _SysIntVecAlloc(irq, priority, flags);
|
|
__ASSERT(vector != -1, "Unable to request a vector for irq %d with priority %d",
|
|
irq, priority);
|
|
|
|
stub_idx = _stub_alloc(&next_irq_stub, ALL_DYN_IRQ_STUBS);
|
|
__ASSERT(stub_idx != -1, "No available interrupt stubs found");
|
|
|
|
dyn_irq_list[stub_idx].handler = routine;
|
|
dyn_irq_list[stub_idx].param = parameter;
|
|
_IntVecSet(vector, _get_dynamic_stub(stub_idx, &_DynIntStubsBegin), 0);
|
|
|
|
return vector;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Common dynamic IRQ handler function
|
|
*
|
|
* This gets called by _DynStubCommon with the stub index supplied as
|
|
* an argument. Look up the required information in dyn_irq_list and
|
|
* execute it.
|
|
*
|
|
* @param stub_idx Index into the dyn_irq_list array
|
|
*/
|
|
void _common_dynamic_irq_handler(uint32_t stub_idx)
|
|
{
|
|
dyn_irq_list[stub_idx].handler(dyn_irq_list[stub_idx].param);
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*
|
|
* @brief Set the handler in an already connected stub
|
|
*
|
|
* This routine is used to modify an already fully constructed interrupt stub
|
|
* to specify a new <routine> and/or <parameter>. This only works with
|
|
* dynamic interrupt stubs.
|
|
*/
|
|
void _irq_handler_set(unsigned int vector, void (*routine)(void *parameter),
|
|
void *parameter)
|
|
{
|
|
int key;
|
|
uint8_t stub_idx;
|
|
|
|
/*
|
|
* Disable IRQs so we can ensure that the associated interrupt
|
|
* doesn't run in an inconsistent state while we're doing this
|
|
*/
|
|
key = irq_lock();
|
|
|
|
stub_idx = _stub_idx_from_vector(vector);
|
|
|
|
__ASSERT(stub_idx < ALL_DYN_IRQ_STUBS, "Bad stub index");
|
|
|
|
dyn_irq_list[stub_idx].handler = routine;
|
|
dyn_irq_list[stub_idx].param = parameter;
|
|
irq_unlock(key);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
* @brief Allocate a free interrupt vector given <priority>
|
|
*
|
|
* This routine scans the _interrupt_vectors_allocated[] array for a free vector
|
|
* that satisfies the specified <priority>. It is a utility function for use
|
|
* only by the interrupt controller's _SysIntVecAlloc() routine.
|
|
*
|
|
* This routine assumes that the relationship between interrupt priority and
|
|
* interrupt vector is :
|
|
*
|
|
* priority = (vector / 16) - 2;
|
|
*
|
|
* Vectors 0 to 31 are reserved for CPU exceptions and do NOT fall under
|
|
* the priority scheme. The first vector used for priority level 0 will be 32.
|
|
*
|
|
* Each interrupt priority level contains 16 vectors, and the prioritization
|
|
* of interrupts within a priority level is determined by the vector number;
|
|
* the higher the vector number, the higher the priority within that priority
|
|
* level.
|
|
*
|
|
* It is also assumed that the interrupt controllers are capable of managing
|
|
* interrupt requests on a per-vector level as opposed to a per-priority level.
|
|
* For example, the local APIC on Pentium4 and later processors, the in-service
|
|
* register (ISR) and the interrupt request register (IRR) are 256 bits wide.
|
|
*
|
|
* @return allocated interrupt vector
|
|
*
|
|
* INTERNAL
|
|
* For debug kernels, this routine shall return -1 when there are no
|
|
* vectors remaining in the specified <priority> level.
|
|
*/
|
|
|
|
int _IntVecAlloc(unsigned int requested_priority)
|
|
{
|
|
unsigned int key;
|
|
unsigned int entryToScan;
|
|
unsigned int fsb; /* first set bit in entry */
|
|
unsigned int search_set;
|
|
int vector_block;
|
|
int vector;
|
|
|
|
static unsigned int mask[2] = {0x0000ffff, 0xffff0000};
|
|
|
|
vector_block = requested_priority + 2;
|
|
|
|
__ASSERT(((vector_block << 4) + 15) <= CONFIG_IDT_NUM_VECTORS,
|
|
"IDT too small (%d entries) to use priority %d",
|
|
CONFIG_IDT_NUM_VECTORS, requested_priority);
|
|
|
|
/*
|
|
* Atomically allocate a vector from the _interrupt_vectors_allocated[]
|
|
* array to prevent race conditions with other tasks/fibers attempting
|
|
* to allocate an interrupt vector.
|
|
*
|
|
* Note: As _interrupt_vectors_allocated[] is initialized by the 'gen_idt'
|
|
* tool, it is critical that this routine use the same algorithm as the
|
|
* 'gen_idt' tool for allocating interrupt vectors.
|
|
*/
|
|
|
|
entryToScan = vector_block >> 1;
|
|
|
|
/*
|
|
* The _interrupt_vectors_allocated[] entry indexed by 'entryToScan' is a
|
|
* 32-bit quantity and thus represents the vectors for a pair of priority
|
|
* levels. Mask out the unwanted priority level and then use find_lsb_set()
|
|
* to scan for an available vector of the requested priority.
|
|
*
|
|
* Note that find_lsb_set() returns bit position from 1 to 32,
|
|
* or 0 if the argument is zero.
|
|
*/
|
|
|
|
key = irq_lock();
|
|
|
|
search_set = mask[vector_block & 1] & _interrupt_vectors_allocated[entryToScan];
|
|
fsb = find_lsb_set(search_set);
|
|
|
|
__ASSERT(fsb != 0, "No remaning vectors for priority level %d",
|
|
requested_priority);
|
|
|
|
/*
|
|
* An available vector of the requested priority was found.
|
|
* Mark it as allocated.
|
|
*/
|
|
|
|
--fsb;
|
|
_interrupt_vectors_allocated[entryToScan] &= ~(1 << fsb);
|
|
|
|
irq_unlock(key);
|
|
|
|
/* compute vector given allocated bit within the priority level */
|
|
|
|
vector = (entryToScan << 5) + fsb;
|
|
|
|
return vector;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @brief Mark interrupt vector as allocated
|
|
*
|
|
* This routine is used to "reserve" an interrupt vector that is allocated
|
|
* or assigned by any means other than _IntVecAllocate(). This marks the vector
|
|
* as allocated so that any future invocations of _IntVecAllocate() will not
|
|
* return that vector.
|
|
*
|
|
* @return N/A
|
|
*
|
|
*/
|
|
|
|
void _IntVecMarkAllocated(unsigned int vector)
|
|
{
|
|
unsigned int entryToSet = vector / 32;
|
|
unsigned int bitToSet = vector % 32;
|
|
unsigned int imask;
|
|
|
|
imask = irq_lock();
|
|
_interrupt_vectors_allocated[entryToSet] &= ~(1 << bitToSet);
|
|
irq_unlock(imask);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @brief Mark interrupt vector as free
|
|
*
|
|
* This routine is used to "free" an interrupt vector that is allocated
|
|
* or assigned using _IntVecAllocate() or _IntVecMarkAllocated(). This marks the
|
|
* vector as available so that any future allocations can return that vector.
|
|
*
|
|
*/
|
|
|
|
void _IntVecMarkFree(unsigned int vector)
|
|
{
|
|
unsigned int entryToSet = vector / 32;
|
|
unsigned int bitToSet = vector % 32;
|
|
unsigned int imask;
|
|
|
|
imask = irq_lock();
|
|
_interrupt_vectors_allocated[entryToSet] |= (1 << bitToSet);
|
|
irq_unlock(imask);
|
|
}
|
|
#endif /* ALL_DYN_IRQ_STUBS > 0 */
|