zephyr/drivers/interrupt_controller/mvic.c

526 lines
13 KiB
C

/*
* 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 <nanokernel.h>
#include <arch/cpu.h>
#include <misc/__assert.h>
#include "board.h"
#include <toolchain.h>
#include <sections.h>
#include <drivers/ioapic.h> /* IO APIC public API declarations. */
#include <drivers/loapic.h> /* 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 */
/* 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 */
*(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER) = LOAPIC_LVT_MASKED;
/* 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)
{
}
/**
*
* @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
*
* This routine is utilized by the platform-provided routined _SysIntVecAllocate()
* which in turn is provided to support the irq_connect() API. Once
* a vector has been allocated, this routine is invoked to update the LVT
* entry associated with <irq> with the vector.
*
* @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)
{
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);
/*
* The following mappings are used:
*
* LVT0 -> LOAPIC_TIMER
*
* It's assumed that LVTs are spaced by LOAPIC_LVT_REG_SPACING bytes
*/
pLvt = (volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER +
(irq * LOAPIC_LVT_REG_SPACING));
/* update the 'vector' bits in the LVT */
oldLevel = irq_lock();
*pLvt = (*pLvt & ~LOAPIC_VECTOR) | vector;
irq_unlock(oldLevel);
}
/**
*
* @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));
}
void loapic_int_vec_trigger(unsigned int vector)
{
uint32_t icr_cmd;
/*
* Bit 14 : level ASSERT (1)
* Bit 18-19: Destination shorthand SELF (01)
* Bit 15 : trigger mode EDGE (0)
*
* Destination mode ignored
*/
icr_cmd = vector | (1 << 14) | (1 << 18);
*(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_ICRLO) = icr_cmd;
}