/* * Copyright (c) 2015 Intel Corporation * * 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 Quark D2000 Interrupt Controller (MVIC) * * This module is based on the standard Local APIC and IO APIC source modules. * This modules combines these modules into one source module that exports the * same APIs defined by the Local APIC and IO APIC header modules. These * routine have been adapted for the Quark D2000 Interrupt Controller which has * a cutdown implementation of the Local APIC & IO APIC register sets. * * The MVIC (Quark D2000 Interrupt Controller) is configured by default * to support 32 external interrupt lines. * Unlike the traditional IA LAPIC/IOAPIC, the interrupt vectors in MVIC are fixed * and not programmable. * The larger the vector number, the higher the priority of the interrupt. * Higher priority interrupts preempt lower priority interrupts. * Lower priority interrupts do not preempt higher priority interrupts. * The MVIC holds the lower priority interrupts pending until the interrupt * service routine for the higher priority interrupt writes to the End of * Interrupt (EOI) register. * After an EOI write, the MVIC asserts the next highest pending interrupt. * * INCLUDE FILES: ioapic.h loapic.h * */ /* includes */ #include #include #include #include "board.h" #include #include #include /* IO APIC public API declarations. */ #include /* Local APIC public API declarations.*/ /* defines */ /* IO APIC direct register offsets */ #define IOAPIC_IND 0x00 /* Index Register - MVIC IOREGSEL register */ #define IOAPIC_DATA 0x10 /* IO window (data) - pc.h */ /* MVIC IOREGSEL register usage defines */ #define MVIC_LOW_NIBBLE_MASK 0x07 #define MVIC_HIGH_NIBBLE_MASK 0x18 /* MVIC Local APIC Vector Table Bits */ #define LOAPIC_VECTOR 0x000000ff /* vectorNo */ /* MVIC Local APIC Spurious-Interrupt Register Bits */ #define LOAPIC_ENABLE 0x100 /* APIC Enabled */ #define LOAPIC_MVIC_ISR 0x110 /* MVIC In-Service Register offset */ /* forward declarations */ static void _mvic_rte_set(unsigned int irq, uint32_t value); static uint32_t _mvic_rte_get(unsigned int irq); static void _mvic_rte_update(unsigned int irq, uint32_t value, uint32_t mask); /* * The functions irq_enable() and irq_disable() are implemented * in the platforms that incorporate this interrupt controller driver due to the * IRQ virtualization imposed by the platform. */ /** * * @brief initialize the MVIC IO APIC and local APIC register sets. * * This routine initializes the Quark D2000 Interrupt Controller (MVIC). * This routine replaces the standard Local APIC / IO APIC init routines. * * @returns: N/A */ int _mvic_init(struct device *unused) { ARG_UNUSED(unused); int32_t ix; /* Interrupt line register index */ uint32_t rteValue; /* value to copy into interrupt line register */ /* * The platform must define the CONFIG_IOAPIC_NUM_RTES macro to indicate the number * of redirection table entries supported by the IOAPIC on the board. * * By default mask all interrupt lines and set default sensitivity to edge. * */ rteValue = IOAPIC_EDGE | IOAPIC_INT_MASK; for (ix = 0; ix < CONFIG_IOAPIC_NUM_RTES; ix++) { _mvic_rte_set(ix, rteValue); } /* enable the MVIC Local APIC */ _loapic_enable(); /* reset the TPR, and TIMER_ICR */ *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TPR) = (int)0x0; *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER_ICR) = (int)0x0; /* program Local Vector Table for the Virtual Wire Mode */ /* lock the MVIC timer interrupt, set which IRQ line should be * used for timer interrupts (this is unlike LOAPIC where the * vector is programmed instead). */ __ASSERT_NO_MSG(CONFIG_LOAPIC_TIMER_IRQ <= 15); *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER) = LOAPIC_LVT_MASKED | CONFIG_LOAPIC_TIMER_IRQ; /* discard a pending interrupt if any */ *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_EOI) = 0; return 0; } /** * * @brief Send EOI (End Of Interrupt) signal to IO APIC * * This routine sends an EOI signal to the IO APIC's interrupting source. * * All line interrupts on Quark D2000 are EOI'ed with local APIC EOI register. * * @param irq INT number to send EOI * * @returns: N/A */ void _ioapic_eoi(unsigned int irq) { _loapic_eoi(); } /** * * @brief Get EOI (End Of Interrupt) information * * This routine returns EOI signalling information for a specific IRQ. * * @param irq INTIN number of interest * @param argRequired ptr to "argument required" result area * @param arg ptr to "argument value" result area * * @returns: address of routine to be called to signal EOI; * as a side effect, also passes back indication if routine requires * an interrupt vector argument and what the argument value should be */ void *_ioapic_eoi_get(unsigned int irq, char *argRequired, void **arg) { /* indicate that an argument to the EOI handler is required */ *argRequired = 1; /* * The parameter to the ioApicIntEoi() routine is the vector programmed * into the redirection table. The platform _SysIntVecAlloc() routine * must invoke _IoApicIntEoiGet() after _IoApicRedVecSet() to ensure the * redirection table contains the desired interrupt vector. * * Vectors fixed on this CPU Arch, no memory location on this CPU * arch with this information. */ *arg = NULL; /* lo eoi always used on this CPU arch. */ return _loapic_eoi; } /** * * @brief Enable a specified APIC interrupt input line * * This routine enables a specified APIC interrupt input line. * * @param irq INTIN number to enable * * @returns: N/A */ void _ioapic_irq_enable(unsigned int irq) { _mvic_rte_update(irq, 0, IOAPIC_INT_MASK); } /** * * @brief disable a specified APIC interrupt input line * * This routine disables a specified APIC interrupt input line. * * @param irq INTIN number to disable * * @returns: N/A */ void _ioapic_irq_disable(unsigned int irq) { _mvic_rte_update(irq, IOAPIC_INT_MASK, IOAPIC_INT_MASK); } /** * * @brief Programs Rte interrupt line register. * * Always mask interrupt as part of programming like standard IOAPIC * version of this routine. * Vector is fixed by this HW and is unused. * Or in flags for trigger bit. * * @param irq Virtualized IRQ * @param vector Vector Number * @param flags Interrupt flags * * @returns: N/A */ void _ioapic_irq_set(unsigned int irq, unsigned int vector, uint32_t flags) { uint32_t rteValue; /* value to copy into Rte register */ ARG_UNUSED(vector); rteValue = IOAPIC_INT_MASK | flags; _mvic_rte_set(irq, rteValue); } /** * * @brief program interrupt vector for specified irq * * Fixed vector on this HW. Nothing to do. * * @param irq Interrupt number * @param vector Vector number * * @returns: N/A */ void _ioapic_int_vec_set(unsigned int irq, unsigned int vector) { ARG_UNUSED(irq); ARG_UNUSED(vector); } /** * * @brief Enable the MVIC Local APIC * * This routine enables the MVIC Local APIC. * * @returns: N/A */ void _loapic_enable(void) { int32_t oldLevel = irq_lock(); /* LOCK INTERRUPTS */ *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_SVR) |= LOAPIC_ENABLE; irq_unlock(oldLevel); /* UNLOCK INTERRUPTS */ } /** * * @brief Disable the MVIC Local APIC. * * This routine disables the MVIC Local APIC. * * @returns: N/A */ void _loapic_disable(void) { int32_t oldLevel = irq_lock(); /* LOCK INTERRUPTS */ *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_SVR) &= ~LOAPIC_ENABLE; irq_unlock(oldLevel); /* UNLOCK INTERRUPTS */ } /** * * @brief Set the vector field in the specified RTE * * Fixed vectors on this HW. Nothing to do. * * @param irq IRQ number of the interrupt * @param vector vector to copy into the LVT * * @returns N/A */ void _loapic_int_vec_set(unsigned int irq, unsigned int vector) { ARG_UNUSED(irq); ARG_UNUSED(vector); } /** * * @brief enable an individual LOAPIC interrupt (IRQ) * * This routine clears the interrupt mask bit in the LVT for the specified IRQ * * @param irq IRQ number of the interrupt * * @returns N/A */ void _loapic_irq_enable(unsigned int irq) { volatile int *pLvt; /* pointer to local vector table */ int32_t oldLevel; /* previous interrupt lock level */ /* * irq is actually an index to local APIC LVT register. * ASSERT if out of range for MVIC implementation. */ __ASSERT_NO_MSG(irq < LOAPIC_IRQ_COUNT); /* * See the comments in _LoApicLvtVecSet() regarding IRQ to LVT mappings * and ths assumption concerning LVT spacing. */ pLvt = (volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER + (irq * LOAPIC_LVT_REG_SPACING)); /* clear the mask bit in the LVT */ oldLevel = irq_lock(); *pLvt = *pLvt & ~LOAPIC_LVT_MASKED; irq_unlock(oldLevel); } /** * * @brief disable an individual LOAPIC interrupt (IRQ) * * This routine clears the interrupt mask bit in the LVT for the specified IRQ * * @param irq IRQ number of the interrupt * * @returns N/A */ void _loapic_irq_disable(unsigned int irq) { volatile int *pLvt; /* pointer to local vector table */ int32_t oldLevel; /* previous interrupt lock level */ /* * irq is actually an index to local APIC LVT register. * ASSERT if out of range for MVIC implementation. */ __ASSERT_NO_MSG(irq < LOAPIC_IRQ_COUNT); /* * See the comments in _LoApicLvtVecSet() regarding IRQ to LVT mappings * and ths assumption concerning LVT spacing. */ pLvt = (volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER + (irq * LOAPIC_LVT_REG_SPACING)); /* set the mask bit in the LVT */ oldLevel = irq_lock(); *pLvt = *pLvt | LOAPIC_LVT_MASKED; irq_unlock(oldLevel); } /** * * @brief read a 32 bit MVIC IO APIC register * * @param irq INTIN number * * @returns register value */ static uint32_t _mvic_rte_get(unsigned int irq) { uint32_t value; /* value */ int key; /* interrupt lock level */ volatile unsigned int *rte; volatile unsigned int *index; unsigned int low_nibble; unsigned int high_nibble; index = (unsigned int *)(CONFIG_IOAPIC_BASE_ADDRESS + IOAPIC_IND); rte = (unsigned int *)(CONFIG_IOAPIC_BASE_ADDRESS + IOAPIC_DATA); /* Set index in the IOREGSEL */ __ASSERT(irq < CONFIG_IOAPIC_NUM_RTES, "INVL"); low_nibble = ((irq & MVIC_LOW_NIBBLE_MASK) << 0x1); high_nibble = ((irq & MVIC_HIGH_NIBBLE_MASK) << 0x2); /* lock interrupts to ensure indirect addressing works "atomically" */ key = irq_lock(); *(index) = high_nibble | low_nibble; value = *(rte); irq_unlock(key); return value; } /** * * @brief write to 32 bit MVIC IO APIC register * * @param irq INTIN number * @param value value to be written * * @returns N/A */ static void _mvic_rte_set(unsigned int irq, uint32_t value) { int key; /* interrupt lock level */ volatile unsigned int *rte; volatile unsigned int *index; unsigned int low_nibble; unsigned int high_nibble; index = (unsigned int *)(CONFIG_IOAPIC_BASE_ADDRESS + IOAPIC_IND); rte = (unsigned int *)(CONFIG_IOAPIC_BASE_ADDRESS + IOAPIC_DATA); /* Set index in the IOREGSEL */ __ASSERT(irq < CONFIG_IOAPIC_NUM_RTES, "INVL"); low_nibble = ((irq & MVIC_LOW_NIBBLE_MASK) << 0x1); high_nibble = ((irq & MVIC_HIGH_NIBBLE_MASK) << 0x2); /* lock interrupts to ensure indirect addressing works "atomically" */ key = irq_lock(); *(index) = high_nibble | low_nibble; *(rte) = (value & IOAPIC_LO32_RTE_SUPPORTED_MASK); irq_unlock(key); } /** * * @brief modify interrupt line register. * * @param irq INTIN number * @param value value to be written * @param mask of bits to be modified * * @returns N/A */ static void _mvic_rte_update(unsigned int irq, uint32_t value, uint32_t mask) { _mvic_rte_set(irq, (_mvic_rte_get(irq) & ~mask) | (value & mask)); } /** * @brief Find the currently executing interrupt vector, if any * * This routine finds the vector of the interrupt that is being processed. * The ISR (In-Service Register) register contain the vectors of the interrupts * in service. And the higher vector is the indentification of the interrupt * being currently processed. * * MVIC ISR registers' offsets: * -------------------- * | Offset | bits | * -------------------- * | 0110H | 32:63 | * -------------------- * * @return The vector of the interrupt that is currently being processed. */ int _loapic_isr_vector_get(void) { /* pointer to ISR vector table */ volatile int *pReg; pReg = (volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_MVIC_ISR); return 32 + (find_msb_set(*pReg) - 1); }