From 8f8ccee0d9e1c3adfb1de484f26f6d9f6ff08708 Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Mon, 23 Apr 2018 09:50:39 +0200 Subject: bsps: Move interrupt controller support to bsps This patch is a part of the BSP source reorganization. Update #3285. --- bsps/i386/shared/irq/elcr.c | 170 +++++++++++++++++ bsps/i386/shared/irq/elcr.h | 37 ++++ bsps/i386/shared/irq/idt.c | 381 +++++++++++++++++++++++++++++++++++++ bsps/i386/shared/irq/irq.c | 405 ++++++++++++++++++++++++++++++++++++++++ bsps/i386/shared/irq/irq_asm.S | 268 ++++++++++++++++++++++++++ bsps/i386/shared/irq/irq_init.c | 191 +++++++++++++++++++ 6 files changed, 1452 insertions(+) create mode 100644 bsps/i386/shared/irq/elcr.c create mode 100644 bsps/i386/shared/irq/elcr.h create mode 100644 bsps/i386/shared/irq/idt.c create mode 100644 bsps/i386/shared/irq/irq.c create mode 100644 bsps/i386/shared/irq/irq_asm.S create mode 100644 bsps/i386/shared/irq/irq_init.c (limited to 'bsps/i386') diff --git a/bsps/i386/shared/irq/elcr.c b/bsps/i386/shared/irq/elcr.c new file mode 100644 index 0000000000..f72e36c33f --- /dev/null +++ b/bsps/i386/shared/irq/elcr.c @@ -0,0 +1,170 @@ +/*- + * Copyright (c) 2004 John Baldwin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef __rtems__ +__FBSDID("$FreeBSD$"); +#endif /* __rtems__ */ + +/* + * The ELCR is a register that controls the trigger mode and polarity of + * EISA and ISA interrupts. In FreeBSD 3.x and 4.x, the ELCR was only + * consulted for determining the appropriate trigger mode of EISA + * interrupts when using an APIC. However, it seems that almost all + * systems that include PCI also include an ELCR that manages the ISA + * IRQs 0 through 15. Thus, we check for the presence of an ELCR on + * every machine by checking to see if the values found at bootup are + * sane. Note that the polarity of ISA and EISA IRQs are linked to the + * trigger mode. All edge triggered IRQs use active-hi polarity, and + * all level triggered interrupts use active-lo polarity. + * + * The format of the ELCR is simple: it is a 16-bit bitmap where bit 0 + * controls IRQ 0, bit 1 controls IRQ 1, etc. If the bit is zero, the + * associated IRQ is edge triggered. If the bit is one, the IRQ is + * level triggered. + */ + +#ifndef __rtems__ +#include +#include +#include +#include +#endif /* __rtems__ */ + +#ifdef __rtems__ +#include +#include "i386_io.h" +#include +#include "elcr.h" +#endif /* __rtems__ */ + +#define ELCR_PORT 0x4d0 +#define ELCR_MASK(irq) (1 << (irq)) + +static int elcr_status; +#ifdef __rtems__ +static +#endif /* __rtems__ */ +int elcr_found; + +#ifdef __rtems__ +#undef printf +#define printf printk +#define bootverbose 1 +#define KASSERT(...) +#endif /* __rtems__ */ + +/* + * Check to see if we have what looks like a valid ELCR. We do this by + * verifying that IRQs 0, 1, 2, and 13 are all edge triggered. + */ +int +elcr_probe(void) +{ + int i; + + elcr_status = inb(ELCR_PORT) | inb(ELCR_PORT + 1) << 8; + if ((elcr_status & (ELCR_MASK(0) | ELCR_MASK(1) | ELCR_MASK(2) | + ELCR_MASK(8) | ELCR_MASK(13))) != 0) + return (ENXIO); + if (bootverbose) { + printf("ELCR Found. ISA IRQs programmed as:\n"); + for (i = 0; i < 16; i++) + printf(" %2d", i); + printf("\n"); + for (i = 0; i < 16; i++) + if (elcr_status & ELCR_MASK(i)) + printf(" L"); + else + printf(" E"); + printf("\n"); + } +#ifndef __rtems__ + if (resource_disabled("elcr", 0)) + return (ENXIO); +#endif /* __rtems__ */ + elcr_found = 1; + return (0); +} + +/* + * Returns 1 for level trigger, 0 for edge. + */ +enum intr_trigger +elcr_read_trigger(u_int irq) +{ +#ifdef __rtems__ + if (!elcr_found) + return INTR_TRIGGER_EDGE; +#endif /* __rtems__ */ + KASSERT(elcr_found, ("%s: no ELCR was found!", __func__)); + KASSERT(irq <= 15, ("%s: invalid IRQ %u", __func__, irq)); + if (elcr_status & ELCR_MASK(irq)) + return (INTR_TRIGGER_LEVEL); + else + return (INTR_TRIGGER_EDGE); +} + +/* + * Set the trigger mode for a specified IRQ. Mode of 0 means edge triggered, + * and a mode of 1 means level triggered. + */ +void +elcr_write_trigger(u_int irq, enum intr_trigger trigger) +{ + int new_status; + +#ifdef __rtems__ + if (!elcr_found) + return; +#endif /* __rtems__ */ + KASSERT(elcr_found, ("%s: no ELCR was found!", __func__)); + KASSERT(irq <= 15, ("%s: invalid IRQ %u", __func__, irq)); + if (trigger == INTR_TRIGGER_LEVEL) + new_status = elcr_status | ELCR_MASK(irq); + else + new_status = elcr_status & ~ELCR_MASK(irq); + if (new_status == elcr_status) + return; + elcr_status = new_status; + if (irq >= 8) + outb(ELCR_PORT + 1, elcr_status >> 8); + else + outb(ELCR_PORT, elcr_status & 0xff); +} + +void +elcr_resume(void) +{ +#ifdef __rtems__ + if (!elcr_found) + return; +#endif /* __rtems__ */ + + KASSERT(elcr_found, ("%s: no ELCR was found!", __func__)); + outb(ELCR_PORT, elcr_status & 0xff); + outb(ELCR_PORT + 1, elcr_status >> 8); +} diff --git a/bsps/i386/shared/irq/elcr.h b/bsps/i386/shared/irq/elcr.h new file mode 100644 index 0000000000..a006d4f149 --- /dev/null +++ b/bsps/i386/shared/irq/elcr.h @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Chris Johns + * + * Header for the FreeBSD ported elcr.c + */ + +#ifndef _IRQ_ELCR_H_ +#define _IRQ_ELCR_H_ + +#include + +enum intr_trigger { + INTR_TRIGGER_EDGE, + INTR_TRIGGER_LEVEL +}; + +/* + * Check to see if we have what looks like a valid ELCR. We do this by + * verifying that IRQs 0, 1, 2, and 13 are all edge triggered. + */ +int elcr_probe(void); + +/* + * Returns 1 for level trigger, 0 for edge. + */ +enum intr_trigger elcr_read_trigger(u_int irq); + +/* + * Set the trigger mode for a specified IRQ. Mode of 0 means edge triggered, + * and a mode of 1 means level triggered. + */ +void elcr_write_trigger(u_int irq, enum intr_trigger trigger); + +void elcr_resume(void); + + +#endif diff --git a/bsps/i386/shared/irq/idt.c b/bsps/i386/shared/irq/idt.c new file mode 100644 index 0000000000..d3adbc4f05 --- /dev/null +++ b/bsps/i386/shared/irq/idt.c @@ -0,0 +1,381 @@ +/* + * cpu.c - This file contains implementation of C function to + * instantiate IDT entries. More detailled information can be found + * on Intel site and more precisely in the following book : + * + * Pentium Processor family + * Developper's Manual + * + * Volume 3 : Architecture and Programming Manual + * + * Copyright (C) 1998 Eric Valette (valette@crf.canon.fr) + * Canon Centre Recherche France. + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#include +#include +#include + +/* + * This locking is not enough if IDT is changed at runtime + * and entry can be changed for vector which is enabled + * at change time. But such use is broken anyway. + * Protect code only against concurrent changes. + * Even that is probably unnecessary if different + * entries are changed concurrently. + */ +RTEMS_INTERRUPT_LOCK_DEFINE( static, rtems_idt_access_lock, "rtems_idt_access_lock" ); + +static rtems_raw_irq_connect_data* raw_irq_table; +static rtems_raw_irq_connect_data default_raw_irq_entry; +static interrupt_gate_descriptor default_idt_entry; +static rtems_raw_irq_global_settings* local_settings; + +void create_interrupt_gate_descriptor (interrupt_gate_descriptor* idtEntry, + rtems_raw_irq_hdl hdl) +{ + idtEntry->low_offsets_bits = (((unsigned) hdl) & 0xffff); + idtEntry->segment_selector = i386_get_cs(); + idtEntry->fixed_value_bits = 0; + idtEntry->gate_type = 0xe; + idtEntry->privilege = 0; + idtEntry->present = 1; + idtEntry->high_offsets_bits = ((((unsigned) hdl) >> 16) & 0xffff); +} + +rtems_raw_irq_hdl get_hdl_from_vector(rtems_vector_offset index) +{ + uint32_t hdl; + interrupt_gate_descriptor* idt_entry_tbl; + unsigned limit; + + i386_get_info_from_IDTR (&idt_entry_tbl, &limit); + + /* Convert limit into number of entries */ + limit = (limit + 1) / sizeof(interrupt_gate_descriptor); + + if(index >= limit) { + return 0; + } + + hdl = (idt_entry_tbl[index].low_offsets_bits | + (idt_entry_tbl[index].high_offsets_bits << 16)); + return (rtems_raw_irq_hdl) hdl; +} + +int i386_set_idt_entry (const rtems_raw_irq_connect_data* irq) +{ + interrupt_gate_descriptor* idt_entry_tbl; + unsigned limit; + rtems_interrupt_lock_context lock_context; + + i386_get_info_from_IDTR (&idt_entry_tbl, &limit); + + /* Convert limit into number of entries */ + limit = (limit + 1) / sizeof(interrupt_gate_descriptor); + + if (irq->idtIndex >= limit) { + return 0; + } + /* + * Check if default handler is actually connected. If not issue an error. + * You must first get the current handler via i386_get_current_idt_entry + * and then disconnect it using i386_delete_idt_entry. + * RATIONALE : to always have the same transition by forcing the user + * to get the previous handler before accepting to disconnect. + */ + if (get_hdl_from_vector(irq->idtIndex) != default_raw_irq_entry.hdl) { + return 0; + } + + rtems_interrupt_lock_acquire(&rtems_idt_access_lock, &lock_context); + + raw_irq_table [irq->idtIndex] = *irq; + create_interrupt_gate_descriptor (&idt_entry_tbl[irq->idtIndex], irq->hdl); + if (irq->on) + irq->on(irq); + + rtems_interrupt_lock_release(&rtems_idt_access_lock, &lock_context); + return 1; +} + +void _CPU_ISR_install_vector (uint32_t vector, + proc_ptr hdl, + proc_ptr * oldHdl) +{ + interrupt_gate_descriptor* idt_entry_tbl; + unsigned limit; + interrupt_gate_descriptor new; + rtems_interrupt_lock_context lock_context; + + i386_get_info_from_IDTR (&idt_entry_tbl, &limit); + + /* Convert limit into number of entries */ + limit = (limit + 1) / sizeof(interrupt_gate_descriptor); + + if (vector >= limit) { + return; + } + rtems_interrupt_lock_acquire(&rtems_idt_access_lock, &lock_context); + * ((unsigned int *) oldHdl) = idt_entry_tbl[vector].low_offsets_bits | + (idt_entry_tbl[vector].high_offsets_bits << 16); + + create_interrupt_gate_descriptor(&new, hdl); + idt_entry_tbl[vector] = new; + + rtems_interrupt_lock_release(&rtems_idt_access_lock, &lock_context); +} + +int i386_get_current_idt_entry (rtems_raw_irq_connect_data* irq) +{ + interrupt_gate_descriptor* idt_entry_tbl; + unsigned limit; + + i386_get_info_from_IDTR (&idt_entry_tbl, &limit); + + /* Convert limit into number of entries */ + limit = (limit + 1) / sizeof(interrupt_gate_descriptor); + + if (irq->idtIndex >= limit) { + return 0; + } + raw_irq_table [irq->idtIndex].hdl = get_hdl_from_vector(irq->idtIndex); + + *irq = raw_irq_table [irq->idtIndex]; + + return 1; +} + +int i386_delete_idt_entry (const rtems_raw_irq_connect_data* irq) +{ + interrupt_gate_descriptor* idt_entry_tbl; + unsigned limit; + rtems_interrupt_lock_context lock_context; + + i386_get_info_from_IDTR (&idt_entry_tbl, &limit); + + /* Convert limit into number of entries */ + limit = (limit + 1) / sizeof(interrupt_gate_descriptor); + + if (irq->idtIndex >= limit) { + return 0; + } + /* + * Check if handler passed is actually connected. If not issue an error. + * You must first get the current handler via i386_get_current_idt_entry + * and then disconnect it using i386_delete_idt_entry. + * RATIONALE : to always have the same transition by forcing the user + * to get the previous handler before accepting to disconnect. + */ + if (get_hdl_from_vector(irq->idtIndex) != irq->hdl){ + return 0; + } + rtems_interrupt_lock_acquire(&rtems_idt_access_lock, &lock_context); + + idt_entry_tbl[irq->idtIndex] = default_idt_entry; + + if (irq->off) + irq->off(irq); + + raw_irq_table[irq->idtIndex] = default_raw_irq_entry; + raw_irq_table[irq->idtIndex].idtIndex = irq->idtIndex; + + rtems_interrupt_lock_release(&rtems_idt_access_lock, &lock_context); + + return 1; +} + +/* + * Caution this function assumes the IDTR has been already set. + */ +int i386_init_idt (rtems_raw_irq_global_settings* config) +{ + unsigned limit; + unsigned i; + rtems_interrupt_lock_context lock_context; + interrupt_gate_descriptor* idt_entry_tbl; + + i386_get_info_from_IDTR (&idt_entry_tbl, &limit); + + /* Convert limit into number of entries */ + limit = (limit + 1) / sizeof(interrupt_gate_descriptor); + + if (config->idtSize != limit) { + return 0; + } + /* + * store various accelarators + */ + raw_irq_table = config->rawIrqHdlTbl; + local_settings = config; + default_raw_irq_entry = config->defaultRawEntry; + + rtems_interrupt_lock_acquire(&rtems_idt_access_lock, &lock_context); + + create_interrupt_gate_descriptor (&default_idt_entry, default_raw_irq_entry.hdl); + + for (i=0; i < limit; i++) { + interrupt_gate_descriptor new; + create_interrupt_gate_descriptor (&new, raw_irq_table[i].hdl); + idt_entry_tbl[i] = new; + if (raw_irq_table[i].hdl != default_raw_irq_entry.hdl) { + raw_irq_table[i].on(&raw_irq_table[i]); + } + else { + raw_irq_table[i].off(&raw_irq_table[i]); + } + } + rtems_interrupt_lock_release(&rtems_idt_access_lock, &lock_context); + + return 1; +} + +int i386_get_idt_config (rtems_raw_irq_global_settings** config) +{ + *config = local_settings; + return 1; +} + +uint32_t i386_raw_gdt_entry (uint16_t segment_selector_index, + segment_descriptors* sd) +{ + uint16_t gdt_limit; + uint16_t tmp_segment = 0; + segment_descriptors* gdt_entry_tbl; + uint8_t present; + + i386_get_info_from_GDTR (&gdt_entry_tbl, &gdt_limit); + + if (segment_selector_index >= (gdt_limit+1)/8) { + /* index to GDT table out of bounds */ + return 0; + } + if (segment_selector_index == 0) { + /* index 0 is not usable */ + return 0; + } + + /* put prepared descriptor into the GDT */ + present = sd->present; + sd->present = 0; + gdt_entry_tbl[segment_selector_index].present = 0; + RTEMS_COMPILER_MEMORY_BARRIER(); + gdt_entry_tbl[segment_selector_index] = *sd; + RTEMS_COMPILER_MEMORY_BARRIER(); + gdt_entry_tbl[segment_selector_index].present = present; + sd->present = present; + /* + * Now, reload all segment registers so that the possible changes takes effect. + */ + __asm__ volatile( "movw %%ds,%0 ; movw %0,%%ds\n\t" + "movw %%es,%0 ; movw %0,%%es\n\t" + "movw %%fs,%0 ; movw %0,%%fs\n\t" + "movw %%gs,%0 ; movw %0,%%gs\n\t" + "movw %%ss,%0 ; movw %0,%%ss" + : "=r" (tmp_segment) + : "0" (tmp_segment) + ); + return 1; +} + +void i386_fill_segment_desc_base(uint32_t base, + segment_descriptors* sd) +{ + sd->base_address_15_0 = base & 0xffff; + sd->base_address_23_16 = (base >> 16) & 0xff; + sd->base_address_31_24 = (base >> 24) & 0xff; +} + +void i386_fill_segment_desc_limit(uint32_t limit, + segment_descriptors* sd) +{ + sd->granularity = 0; + if (limit > 65535) { + sd->granularity = 1; + limit /= 4096; + } + sd->limit_15_0 = limit & 0xffff; + sd->limit_19_16 = (limit >> 16) & 0xf; +} + +/* + * Caution this function assumes the GDTR has been already set. + */ +uint32_t i386_set_gdt_entry (uint16_t segment_selector_index, uint32_t base, + uint32_t limit) +{ + segment_descriptors gdt_entry; + memset(&gdt_entry, 0, sizeof(gdt_entry)); + + i386_fill_segment_desc_limit(limit, &gdt_entry); + i386_fill_segment_desc_base(base, &gdt_entry); + /* + * set up descriptor type (this may well becomes a parameter if needed) + */ + gdt_entry.type = 2; /* Data R/W */ + gdt_entry.descriptor_type = 1; /* Code or Data */ + gdt_entry.privilege = 0; /* ring 0 */ + gdt_entry.present = 1; /* not present */ + + /* + * Now, reload all segment registers so the limit takes effect. + */ + return i386_raw_gdt_entry(segment_selector_index, &gdt_entry); +} + +uint16_t i386_next_empty_gdt_entry () +{ + uint16_t gdt_limit; + segment_descriptors* gdt_entry_tbl; + /* initial amount of filled descriptors */ + static uint16_t segment_selector_index = NUM_SYSTEM_GDT_DESCRIPTORS - 1; + + segment_selector_index += 1; + i386_get_info_from_GDTR (&gdt_entry_tbl, &gdt_limit); + if (segment_selector_index >= (gdt_limit+1)/8) { + return 0; + } + return segment_selector_index; +} + +uint16_t i386_cpy_gdt_entry(uint16_t segment_selector_index, + segment_descriptors* struct_to_fill) +{ + uint16_t gdt_limit; + segment_descriptors* gdt_entry_tbl; + + i386_get_info_from_GDTR (&gdt_entry_tbl, &gdt_limit); + + if (segment_selector_index >= (gdt_limit+1)/8) { + return 0; + } + + *struct_to_fill = gdt_entry_tbl[segment_selector_index]; + return segment_selector_index; +} + +segment_descriptors* i386_get_gdt_entry(uint16_t segment_selector_index) +{ + uint16_t gdt_limit; + segment_descriptors* gdt_entry_tbl; + + i386_get_info_from_GDTR (&gdt_entry_tbl, &gdt_limit); + + if (segment_selector_index >= (gdt_limit+1)/8) { + return 0; + } + return &gdt_entry_tbl[segment_selector_index]; +} + +uint32_t i386_limit_gdt_entry(segment_descriptors* gdt_entry) +{ + uint32_t lim = (gdt_entry->limit_15_0 + (gdt_entry->limit_19_16<<16)); + if (gdt_entry->granularity) { + return lim*4096+4095; + } + return lim; +} diff --git a/bsps/i386/shared/irq/irq.c b/bsps/i386/shared/irq/irq.c new file mode 100644 index 0000000000..ac08f1f7be --- /dev/null +++ b/bsps/i386/shared/irq/irq.c @@ -0,0 +1,405 @@ +/* + * This file contains the implementation of the function described in irq.h + */ + +/* + * Copyright (c) 2009 embedded brains GmbH + * Copyright (C) 1998 valette@crf.canon.fr + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#include +#include +#include +#include + +#include +#include +#include + + +#include "elcr.h" + +RTEMS_INTERRUPT_LOCK_DEFINE( static, rtems_i8259_access_lock, "rtems_i8259_access_lock" ); + +/* + * pointer to the mask representing the additionnal irq vectors + * that must be disabled when a particular entry is activated. + * They will be dynamically computed from teh prioruty table given + * in BSP_rtems_irq_mngt_set(); + * CAUTION : this table is accessed directly by interrupt routine + * prologue. + */ +static rtems_i8259_masks irq_mask_or_tbl[BSP_IRQ_LINES_NUMBER]; + +/* + * Stats of interrupts dispatched. + */ +static uint32_t irq_count[BSP_IRQ_VECTOR_NUMBER] = {0}; +static uint32_t spurious_count; + +/* + * Edge or level trigger interrupts. + */ +static enum intr_trigger irq_trigger[BSP_IRQ_LINES_NUMBER]; + +/*-------------------------------------------------------------------------+ +| Cache for 1st and 2nd PIC IRQ line's mssk (enabled or disabled) register. ++--------------------------------------------------------------------------*/ +/* + * lower byte is interrupt mask on the master PIC. + * while upper bits are interrupt on the slave PIC. + * This cache is initialized in ldseg.s + */ +static rtems_i8259_masks i8259a_imr_cache = 0xFFFB; +static rtems_i8259_masks i8259a_in_progress = 0; + +static inline +void BSP_i8259a_irq_update_master_imr( void ) +{ + rtems_i8259_masks mask = i8259a_in_progress | i8259a_imr_cache; + outport_byte( PIC_MASTER_IMR_IO_PORT, mask & 0xff ); +} + +static inline +void BSP_i8259a_irq_update_slave_imr( void ) +{ + rtems_i8259_masks mask = i8259a_in_progress | i8259a_imr_cache; + outport_byte( PIC_SLAVE_IMR_IO_PORT, ( mask >> 8 ) & 0xff ); +} + +/* + * Print the stats. + */ +uint32_t BSP_irq_count_dump(FILE *f) +{ + uint32_t tot = 0; + int i; + if ( !f ) + f = stdout; + fprintf(f,"SPURIOUS: %9"PRIu32"\n", spurious_count); + for ( i = 0; i < BSP_IRQ_VECTOR_NUMBER; i++ ) { + char type = '-'; + if (i < BSP_IRQ_LINES_NUMBER) + type = irq_trigger[i] == INTR_TRIGGER_EDGE ? 'E' : 'L'; + tot += irq_count[i]; + fprintf(f,"IRQ %2u: %c %9"PRIu32"\n", i, type, irq_count[i]); + } + return tot; +} + +/* + * Is the IRQ valid? + */ +static inline bool BSP_i8259a_irq_valid(const rtems_irq_number irqLine) +{ + return ((int)irqLine >= BSP_IRQ_VECTOR_LOWEST_OFFSET) && + ((int)irqLine <= BSP_IRQ_MAX_ON_i8259A); +} + +/* + * Read the IRR register. The default. + */ +static inline uint8_t BSP_i8259a_irq_int_request_reg(uint32_t ioport) +{ + uint8_t isr; + inport_byte(ioport, isr); + return isr; +} + +/* + * Read the ISR register. Keep the default of the IRR. + */ +static inline uint8_t BSP_i8259a_irq_in_service_reg(uint32_t ioport) +{ + uint8_t isr; + outport_byte(ioport, PIC_OCW3_SEL | PIC_OCW3_RR | PIC_OCW3_RIS); + inport_byte(ioport, isr); + outport_byte(ioport, PIC_OCW3_SEL | PIC_OCW3_RR); + return isr; +} + +/*-------------------------------------------------------------------------+ +| Function: BSP_irq_disable_at_i8259a +| Description: Mask IRQ line in appropriate PIC chip. +| Global Variables: i8259a_imr_cache, i8259a_in_progress +| Arguments: vector_offset - number of IRQ line to mask. +| Returns: 0 is OK. ++--------------------------------------------------------------------------*/ +static int BSP_irq_disable_at_i8259a(const rtems_irq_number irqLine) +{ + unsigned short mask; + rtems_interrupt_lock_context lock_context; + + rtems_interrupt_lock_acquire(&rtems_i8259_access_lock, &lock_context); + + mask = 1 << irqLine; + i8259a_imr_cache |= mask; + + if (irqLine < 8) + { + BSP_i8259a_irq_update_master_imr(); + } + else + { + BSP_i8259a_irq_update_slave_imr(); + } + + rtems_interrupt_lock_release(&rtems_i8259_access_lock, &lock_context); + + return 0; +} + +/*-------------------------------------------------------------------------+ +| Function: BSP_irq_enable_at_i8259a +| Description: Unmask IRQ line in appropriate PIC chip. +| Global Variables: i8259a_imr_cache, i8259a_in_progress +| Arguments: irqLine - number of IRQ line to mask. +| Returns: Nothing. ++--------------------------------------------------------------------------*/ +static int BSP_irq_enable_at_i8259a(const rtems_irq_number irqLine) +{ + unsigned short mask; + rtems_interrupt_lock_context lock_context; + uint8_t isr; + uint8_t irr; + + rtems_interrupt_lock_acquire(&rtems_i8259_access_lock, &lock_context); + + mask = 1 << irqLine; + i8259a_imr_cache &= ~mask; + + if (irqLine < 8) + { + isr = BSP_i8259a_irq_in_service_reg(PIC_MASTER_COMMAND_IO_PORT); + irr = BSP_i8259a_irq_int_request_reg(PIC_MASTER_COMMAND_IO_PORT); + BSP_i8259a_irq_update_master_imr(); + } + else + { + isr = BSP_i8259a_irq_in_service_reg(PIC_SLAVE_COMMAND_IO_PORT); + irr = BSP_i8259a_irq_int_request_reg(PIC_SLAVE_COMMAND_IO_PORT); + BSP_i8259a_irq_update_slave_imr(); + } + + if (((isr ^ irr) & mask) != 0) + printk("i386: isr=%x irr=%x\n", isr, irr); + + rtems_interrupt_lock_release(&rtems_i8259_access_lock, &lock_context); + + return 0; +} /* mask_irq */ + +/*-------------------------------------------------------------------------+ +| Function: BSP_irq_ack_at_i8259a +| Description: Signal generic End Of Interrupt (EOI) to appropriate PIC. +| Global Variables: None. +| Arguments: irqLine - number of IRQ line to acknowledge. +| Returns: Nothing. ++--------------------------------------------------------------------------*/ +static int BSP_irq_ack_at_i8259a(const rtems_irq_number irqLine) +{ + uint8_t slave_isr = 0; + + if (irqLine >= 8) { + outport_byte(PIC_SLAVE_COMMAND_IO_PORT, PIC_EOI); + slave_isr = BSP_i8259a_irq_in_service_reg(PIC_SLAVE_COMMAND_IO_PORT); + } + + /* + * Only issue the EOI to the master if there are no more interrupts in + * service for the slave. i8259a data sheet page 18, The Special Fully Nested + * Mode, b. + */ + if (slave_isr == 0) + outport_byte(PIC_MASTER_COMMAND_IO_PORT, PIC_EOI); + + return 0; + +} /* ackIRQ */ + +/* + * ------------------------ RTEMS Irq helper functions ---------------- + */ + +static rtems_irq_prio irqPrioTable[BSP_IRQ_LINES_NUMBER]={ + /* + * actual priorities for each interrupt source: + * 0 means that only current interrupt is masked + * 255 means all other interrupts are masked + * The second entry has a priority of 255 because + * it is the slave pic entry and is should always remain + * unmasked. + */ + 0,0, + 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static void compute_i8259_masks_from_prio (void) +{ + rtems_interrupt_lock_context lock_context; + unsigned int i; + unsigned int j; + + rtems_interrupt_lock_acquire(&rtems_i8259_access_lock, &lock_context); + + /* + * Always mask at least current interrupt to prevent re-entrance + */ + for (i=0; i < BSP_IRQ_LINES_NUMBER; i++) { + * ((unsigned short*) &irq_mask_or_tbl[i]) = (1 << i); + for (j = 0; j < BSP_IRQ_LINES_NUMBER; j++) { + /* + * Mask interrupts at i8259 level that have a lower priority + */ + if (irqPrioTable [i] > irqPrioTable [j]) { + * ((unsigned short*) &irq_mask_or_tbl[i]) |= (1 << j); + } + } + } + + rtems_interrupt_lock_release(&rtems_i8259_access_lock, &lock_context); +} + +static inline bool bsp_interrupt_vector_is_valid(rtems_vector_number vector) +{ + return BSP_i8259a_irq_valid((const rtems_irq_number) vector); +} + +void bsp_interrupt_vector_enable(rtems_vector_number vector) +{ + bsp_interrupt_assert(bsp_interrupt_is_valid_vector(vector)); + BSP_irq_enable_at_i8259a(vector); +} + +void bsp_interrupt_vector_disable(rtems_vector_number vector) +{ + bsp_interrupt_assert(bsp_interrupt_is_valid_vector(vector)); + BSP_irq_disable_at_i8259a(vector); +} + +rtems_status_code bsp_interrupt_facility_initialize(void) +{ + int i; + + /* + * set up internal tables used by rtems interrupt prologue + */ + compute_i8259_masks_from_prio(); + + /* + * must enable slave pic anyway + */ + BSP_irq_enable_at_i8259a(2); + + /* + * Probe the ELCR. + */ + elcr_probe(); + + for (i = 0; i < BSP_IRQ_LINES_NUMBER; i++) + irq_trigger[i] = elcr_read_trigger(i); + + return RTEMS_SUCCESSFUL; +} + +/* + * Global so the asm handler can call it. + */ +void BSP_dispatch_isr(int vector); + +void BSP_dispatch_isr(int vector) +{ + rtems_interrupt_lock_context lock_context; + rtems_i8259_masks in_progress_save = 0; + + if (vector < BSP_IRQ_VECTOR_NUMBER) { + /* + * Hardware? + */ + if (vector <= BSP_IRQ_MAX_ON_i8259A) { + + rtems_interrupt_lock_acquire_isr(&rtems_i8259_access_lock, &lock_context); + + /* + * See if this is a spurious interrupt. + */ + if ((vector == 7 || vector == 15)) { + /* + * Only check it there no handler for 7 or 15. + */ + if (bsp_interrupt_handler_is_empty(vector)) { + /* + * Read the ISR register to see if IRQ 7/15 is really pending. + */ + uint8_t isr = BSP_i8259a_irq_in_service_reg(PIC_MASTER_COMMAND_IO_PORT); + if ((isr & (1 << 7)) == 0) { + ++spurious_count; + rtems_interrupt_lock_release_isr(&rtems_i8259_access_lock, &lock_context); + return; + } + } + } + + /* + * Save the current cached value for the IMR. It will have the bit for this + * vector clear. + */ + if (vector <= BSP_IRQ_MAX_ON_i8259A) { + in_progress_save = i8259a_in_progress; + i8259a_in_progress |= irq_mask_or_tbl[vector]; + BSP_i8259a_irq_update_master_imr(); + BSP_i8259a_irq_update_slave_imr(); + } + + /* + * Do not use auto-EOI as some slave PIC do not work correctly. + */ + BSP_irq_ack_at_i8259a(vector); + + rtems_interrupt_lock_release_isr(&rtems_i8259_access_lock, &lock_context); + } + + /* + * Count the interrupt. + */ + irq_count[vector]++; + + RTEMS_COMPILER_MEMORY_BARRIER(); + /* + * Allow nesting. + */ + __asm__ __volatile__("sti"); + + bsp_interrupt_handler_dispatch(vector); + + /* + * Disallow nesting. + */ + __asm__ __volatile__("cli"); + + RTEMS_COMPILER_MEMORY_BARRIER(); + + if (vector <= BSP_IRQ_MAX_ON_i8259A) { + + rtems_interrupt_lock_acquire_isr(&rtems_i8259_access_lock, &lock_context); + + /* + * Put the mask back but keep this vector masked if the trigger type is + * level. The driver or a thread level interrupt server needs to enable it + * again. + */ + if (vector <= BSP_IRQ_MAX_ON_i8259A) { + i8259a_in_progress = in_progress_save; + BSP_i8259a_irq_update_master_imr(); + BSP_i8259a_irq_update_slave_imr(); + } + + rtems_interrupt_lock_release_isr(&rtems_i8259_access_lock, &lock_context); + } + } +} diff --git a/bsps/i386/shared/irq/irq_asm.S b/bsps/i386/shared/irq/irq_asm.S new file mode 100644 index 0000000000..181293351e --- /dev/null +++ b/bsps/i386/shared/irq/irq_asm.S @@ -0,0 +1,268 @@ +/* + * This file contains the implementation of the function described in irq.h + */ + +/* + * Copyright (C) 1998 valette@crf.canon.fr + * + * COPYRIGHT (c) 1989-2011. + * On-Line Applications Research Corporation (OAR). + * + * The license and distribution terms for this file may be + * found in found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#include +#include +#include +#include +#include + +#include /* to establish dependency on prototype */ + +#ifndef CPU_STACK_ALIGNMENT +#error "Missing header? CPU_STACK_ALIGNMENT is not defined here" +#endif + +/* Stack frame we use for intermediate storage */ +#define ARG_OFF 0 +#define MSK_OFF 4 /* not used any more */ +#define EBX_OFF 8 /* ebx */ +#define EBP_OFF 12 /* code restoring ebp/esp relies on */ +#define ESP_OFF 16 /* esp being on top of ebp! */ +#ifdef __SSE__ +/* need to be on 16 byte boundary for SSE, add 12 to do that */ +#define FRM_SIZ (20+12+512) +#define SSE_OFF 32 +#else +#define FRM_SIZ 20 +#endif + + BEGIN_CODE + +SYM (_ISR_Handler): + /* + * Before this was point is reached the vectors unique + * entry point did the following: + * + * 1. saved scratch registers registers eax edx ecx" + * 2. put the vector number in ecx. + * + * BEGINNING OF ESTABLISH SEGMENTS + * + * WARNING: If an interrupt can occur when the segments are + * not correct, then this is where we should establish + * the segments. In addition to establishing the + * segments, it may be necessary to establish a stack + * in the current data area on the outermost interrupt. + * + * NOTE: If the previous values of the segment registers are + * pushed, do not forget to adjust SAVED_REGS. + * + * NOTE: Make sure the exit code which restores these + * when this type of code is needed. + */ + + /***** ESTABLISH SEGMENTS CODE GOES HERE ******/ + + /* + * END OF ESTABLISH SEGMENTS + */ + + /* + * Establish an aligned stack frame + * original sp + * saved ebx + * saved ebp + * saved irq mask + * vector arg to BSP_dispatch_isr <- aligned SP + */ + movl esp, eax + subl $FRM_SIZ, esp + andl $ - CPU_STACK_ALIGNMENT, esp + movl ebx, EBX_OFF(esp) + movl eax, ESP_OFF(esp) + movl ebp, EBP_OFF(esp) + + /* + * GCC versions starting with 4.3 no longer place the cld + * instruction before string operations. We need to ensure + * it is set correctly for ISR handlers. + */ + cld + +#ifdef __SSE__ + /* NOTE: SSE only is supported if the BSP enables fxsave/fxrstor + * to save/restore SSE context! This is so far only implemented + * for pc386!. + */ + + /* We save SSE here (on the task stack) because we possibly + * call other C-code (besides the ISR, namely _Thread_Dispatch()) + */ + /* don't wait here; a possible exception condition will eventually be + * detected when the task resumes control and executes a FP instruction + fwait + */ + fxsave SSE_OFF(esp) + fninit /* clean-slate FPU */ + movl $0x1f80, ARG_OFF(esp) /* use ARG_OFF as scratch space */ + ldmxcsr ARG_OFF(esp) /* clean-slate MXCSR */ +#endif + + /* + * Now switch stacks if necessary + */ + +PUBLIC (ISR_STOP) +ISR_STOP: +.check_stack_switch: + movl esp, ebp /* ebp = previous stack pointer */ + +#ifdef RTEMS_SMP + call SYM(_CPU_SMP_Get_current_processor) + sall $PER_CPU_CONTROL_SIZE_LOG2, eax + addl $SYM(_Per_CPU_Information), eax + movl eax, ebx +#else + movl $SYM(_Per_CPU_Information), ebx +#endif + + /* is this the outermost interrupt? */ + cmpl $0, PER_CPU_ISR_NEST_LEVEL(ebx) + jne nested /* No, then continue */ + movl PER_CPU_INTERRUPT_STACK_HIGH(ebx), esp + + /* + * We want to insure that the old stack pointer is in ebp + * By saving it on every interrupt, all we have to do is + * movl ebp->esp near the end of every interrupt. + */ + +nested: + incl PER_CPU_ISR_NEST_LEVEL(ebx) /* one nest level deeper */ + incl PER_CPU_THREAD_DISPATCH_DISABLE_LEVEL(ebx) /* disable + multitasking */ + /* + * ECX is preloaded with the vector number; store as arg + * on top of stack. Note that _CPU_Interrupt_stack_high + * was adjusted in _CPU_Interrupt_stack_setup() (score/rtems/cpu.h) + * to make sure there is space. + */ + + movl ecx, ARG_OFF(esp) /* store vector arg in stack */ + call BSP_dispatch_isr + + movl ARG_OFF(esp), ecx /* grab vector arg from stack */ + + /* + * Restore stack. This moves back to the task stack + * when all interrupts are unnested. + */ + movl ebp, esp + + decl PER_CPU_ISR_NEST_LEVEL(ebx) /* one less ISR nest level */ + /* If interrupts are nested, */ + /* then dispatching is disabled */ + + decl PER_CPU_THREAD_DISPATCH_DISABLE_LEVEL(ebx) + /* unnest multitasking */ + /* Is dispatch disabled */ + jne .exit /* Yes, then exit */ + + cmpb $0, PER_CPU_DISPATCH_NEEDED(ebx) + /* Is task switch necessary? */ + jne .schedule /* Yes, then call the scheduler */ + jmp .exit /* No, exit */ + +.schedule: + /* + * the scratch registers have already been saved and we are already + * back on the thread system stack. So we can call _Thread_Dispatch + * directly + */ + call _Thread_Dispatch + /* + * fall through exit to restore complete contex (scratch registers + * eip, CS, Flags). + */ +.exit: + +#ifdef __SSE__ + fwait + fxrstor SSE_OFF(esp) +#endif + + /* restore ebx, ebp and original esp */ + addl $EBX_OFF, esp + popl ebx + popl ebp + popl esp + + /* + * BEGINNING OF DE-ESTABLISH SEGMENTS + * + * NOTE: Make sure there is code here if code is added to + * load the segment registers. + * + */ + + /******* DE-ESTABLISH SEGMENTS CODE GOES HERE ********/ + + /* + * END OF DE-ESTABLISH SEGMENTS + */ + popl edx + popl ecx + popl eax + iret + +#define DISTINCT_INTERRUPT_ENTRY(_vector) \ + .p2align 4 ; \ + PUBLIC (rtems_irq_prologue_ ## _vector ) ; \ +SYM (rtems_irq_prologue_ ## _vector ): \ + pushl eax ; \ + pushl ecx ; \ + pushl edx ; \ + movl $ _vector, ecx ; \ + jmp SYM (_ISR_Handler) ; + +DISTINCT_INTERRUPT_ENTRY(0) +DISTINCT_INTERRUPT_ENTRY(1) +DISTINCT_INTERRUPT_ENTRY(2) +DISTINCT_INTERRUPT_ENTRY(3) +DISTINCT_INTERRUPT_ENTRY(4) +DISTINCT_INTERRUPT_ENTRY(5) +DISTINCT_INTERRUPT_ENTRY(6) +DISTINCT_INTERRUPT_ENTRY(7) +DISTINCT_INTERRUPT_ENTRY(8) +DISTINCT_INTERRUPT_ENTRY(9) +DISTINCT_INTERRUPT_ENTRY(10) +DISTINCT_INTERRUPT_ENTRY(11) +DISTINCT_INTERRUPT_ENTRY(12) +DISTINCT_INTERRUPT_ENTRY(13) +DISTINCT_INTERRUPT_ENTRY(14) +DISTINCT_INTERRUPT_ENTRY(15) +DISTINCT_INTERRUPT_ENTRY(16) + + /* + * routine used to initialize the IDT by default + */ + +PUBLIC (default_raw_idt_handler) +PUBLIC (raw_idt_notify) + +SYM (default_raw_idt_handler): + pusha + cld + mov esp, ebp + andl $ - CPU_STACK_ALIGNMENT, esp + call raw_idt_notify + mov ebp, esp + popa + iret + +END_CODE + +END diff --git a/bsps/i386/shared/irq/irq_init.c b/bsps/i386/shared/irq/irq_init.c new file mode 100644 index 0000000000..fdf4801a5a --- /dev/null +++ b/bsps/i386/shared/irq/irq_init.c @@ -0,0 +1,191 @@ +/* irq_init.c + * + * This file contains the implementation of rtems initialization + * related to interrupt handling. + * + * Copyright (c) 2009 embedded brains GmbH + * CopyRight (C) 1998 valette@crf.canon.fr + * + * COPYRIGHT (c) 2011. + * On-Line Applications Research Corporation (OAR). + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#include + +#include + +#include +#include +#include + +/* + * rtems prologue generated in irq_asm.S + */ +extern void rtems_irq_prologue_0(void); +extern void rtems_irq_prologue_1(void); +extern void rtems_irq_prologue_2(void); +extern void rtems_irq_prologue_3(void); +extern void rtems_irq_prologue_4(void); +extern void rtems_irq_prologue_5(void); +extern void rtems_irq_prologue_6(void); +extern void rtems_irq_prologue_7(void); +extern void rtems_irq_prologue_8(void); +extern void rtems_irq_prologue_9(void); +extern void rtems_irq_prologue_10(void); +extern void rtems_irq_prologue_11(void); +extern void rtems_irq_prologue_12(void); +extern void rtems_irq_prologue_13(void); +extern void rtems_irq_prologue_14(void); +extern void rtems_irq_prologue_15(void); +extern void rtems_irq_prologue_16(void); +/* + * default vectors + */ +extern void default_raw_idt_handler(void); + +/* + * default raw on/off function + */ +static void raw_nop_func(const struct __rtems_raw_irq_connect_data__ *unused) +{ +} + +/* + * default raw isOn function + */ +static int raw_not_connected( + const struct __rtems_raw_irq_connect_data__ *unused +) +{ + return 0; +} + +static rtems_raw_irq_connect_data idtHdl[IDT_SIZE]; + +static rtems_raw_irq_hdl rtemsIrq[BSP_IRQ_VECTOR_NUMBER] = { + rtems_irq_prologue_0, + rtems_irq_prologue_1, + rtems_irq_prologue_2, + rtems_irq_prologue_3, + rtems_irq_prologue_4, + rtems_irq_prologue_5, + rtems_irq_prologue_6, + rtems_irq_prologue_7, + rtems_irq_prologue_8, + rtems_irq_prologue_9, + rtems_irq_prologue_10, + rtems_irq_prologue_11, + rtems_irq_prologue_12, + rtems_irq_prologue_13, + rtems_irq_prologue_14, + rtems_irq_prologue_15, + rtems_irq_prologue_16, +}; + +static rtems_raw_irq_connect_data defaultRawIrq = { + 0, /* vectorIdex */ + default_raw_idt_handler, /* hdl */ + raw_nop_func, /* on */ + raw_nop_func, /* off */ + raw_not_connected /* isOn */ +}; + +static interrupt_gate_descriptor idtEntry; + +static rtems_raw_irq_global_settings raw_initial_config; + + +/* + * This method is called from irq_asm.S and cannot be static. + */ +void raw_idt_notify(void) +{ + printk("raw_idt_notify has been called \n"); +} + +void rtems_irq_mngt_init(void) +{ + int i; + interrupt_gate_descriptor* idt_entry_tbl; + unsigned int limit; + rtems_interrupt_level level; + + i386_get_info_from_IDTR(&idt_entry_tbl, &limit); + + /* Convert into number of entries */ + limit = (limit + 1)/sizeof(interrupt_gate_descriptor); + + if(limit != IDT_SIZE) { + printk("IDT table size mismatch !!! System locked\n"); + while(1); + } + + /* + * The interrupt management can be initialized only once + * during system bootup and that should happen on boot + * CPU so there is no need to synchronize with others CPUs. + */ + rtems_interrupt_local_disable(level); + + /* + * Init the complete IDT vector table with defaultRawIrq value + */ + for (i = 0; i < IDT_SIZE ; i++) { + idtHdl[i] = defaultRawIrq; + idtHdl[i].idtIndex = i; + } + + raw_initial_config.idtSize = IDT_SIZE; + raw_initial_config.defaultRawEntry = defaultRawIrq; + raw_initial_config.rawIrqHdlTbl = idtHdl; + + if (!i386_init_idt (&raw_initial_config)) { + /* + * put something here that will show the failure... + */ + printk("Unable to initialize IDT!!! System locked\n"); + while (1); + } + /* + * Patch the entry that will be used by RTEMS for interrupt management + * with RTEMS prologue. + */ + for (i = 0; i < BSP_IRQ_VECTOR_NUMBER; i++) { + create_interrupt_gate_descriptor(&idtEntry, rtemsIrq[i]); + idt_entry_tbl[i + BSP_ASM_IRQ_VECTOR_BASE] = idtEntry; + } + /* + * At this point we have completed the initialization of IDT + * with raw handlers. We must now initialize the higher level + * interrupt management. + */ + + /* + * Init initial Interrupt management config + */ + bsp_interrupt_initialize(); + + /* + * #define DEBUG + */ +#ifdef DEBUG + { + /* + * following adresses should be the same + */ + unsigned tmp; + + printk("idt_entry_tbl = %x Interrupt_descriptor_table addr = %x\n", + idt_entry_tbl, &Interrupt_descriptor_table); + tmp = (unsigned) get_hdl_from_vector (BSP_ASM_IRQ_VECTOR_BASE + BSP_PERIODIC_TIMER); + printk("clock isr address from idt = %x should be %x\n", + tmp, (unsigned) rtems_irq_prologue_0); + } + printk("i8259s_cache = %x\n", * (unsigned short*) &i8259s_cache); + BSP_wait_polled_input(); +#endif +} -- cgit v1.2.3