diff options
Diffstat (limited to 'bsps/i386/shared/irq/idt.c')
-rw-r--r-- | bsps/i386/shared/irq/idt.c | 381 |
1 files changed, 381 insertions, 0 deletions
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 <rtems/score/cpu.h> +#include <bsp/irq.h> +#include <bsp/tblsizes.h> + +/* + * 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; +} |