/* * 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 #include #include #include #include #include #include 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 to the specified interrupt * . 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 * is invoked when interrupt is asserted. The 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 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 . An interrupt vector will be allocated to * satisfy the specified . * * The specified 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 , 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 * will then be invoked with the single . When the ISR returns, a * context switch may occur. * * On some platforms 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 * 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 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 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 and/or . 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 * * This routine scans the _interrupt_vectors_allocated[] array for a free vector * that satisfies the specified . 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 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 */