diff options
author | Richard Braun <rbraun@sceen.net> | 2017-10-14 23:45:04 +0200 |
---|---|---|
committer | Richard Braun <rbraun@sceen.net> | 2018-01-04 01:57:38 +0100 |
commit | 9437f135da9fab16180fc64cdd64e2a3bb3d5b7a (patch) | |
tree | 8cd3d9e769c2af24463d58e8ba416aae9de9ce7b /src/i8259.c |
Initial commit
Diffstat (limited to 'src/i8259.c')
-rw-r--r-- | src/i8259.c | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/src/i8259.c b/src/i8259.c new file mode 100644 index 0000000..589e13c --- /dev/null +++ b/src/i8259.c @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2017 Richard Braun. + * Copyright (c) 2017 Jerko Lenstra. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * + * IRQ means Interrupt ReQuest. They're used by external hardware to signal + * the CPU, and in turn the OS, that an external event has happened and + * requires processing. The usual model is shown in the image at + * https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/PIC_Hardware_interrupt_path.svg/300px-PIC_Hardware_interrupt_path.svg.png. + * + * This driver implements IRQ handling on the Intel 8259 PIC. The IBM PC/AT + * actually uses 2 of these PICs for external interrupt handling, as shown + * in https://masherz.files.wordpress.com/2010/08/217.jpg. The public + * interface completely hides this detail and considers all given IRQs + * as logical indexes, used to find the corresponding PIC (master or slave) + * and the local IRQ on that PIC. + * + * 8259 datasheet : + * https://pdos.csail.mit.edu/6.828/2010/readings/hardware/8259A.pdf + */ + +#include <assert.h> +#include <stddef.h> +#include <stdbool.h> +#include <stdint.h> + +#include <lib/macros.h> + +#include "cpu.h" +#include "error.h" +#include "i8259.h" +#include "io.h" + +#define I8259_IRQ_CASCADE 2 /* IRQ used for cascading on the master */ +#define I8259_NR_IRQS 8 + +/* + * Initialization Control Word 1 bits. + */ +#define I8259_ICW1_ICW4 0x01 /* State that a 4th ICW will be sent */ +#define I8259_ICW1_INIT 0x10 /* This bit must be set */ + +/* + * Initialization Control Word 4 bits. + */ +#define I8259_ICW4_8086 0x01 /* 8086 mode, as x86 is still compatible + with the old 8086 processor */ + +#define I8259_OCW2_EOI 0x20 /* End of interrupt control word */ + +enum { + I8259_PIC_ID_MASTER, + I8259_PIC_ID_SLAVE, + I8259_NR_PICS +}; + +/* + * Intel 8259 programmable interrupt controller. + */ +struct i8259_pic { + uint16_t cmd_port; /* Command I/O port of the PIC */ + uint16_t data_port; /* Data I/O port of the PIC */ + uint8_t imr; /* Cached value of the IMR register */ + bool master; /* True if this PIC is the master */ +}; + +/* + * Static instances of PIC objects. + */ +static struct i8259_pic i8259_pics[] = { + [I8259_PIC_ID_MASTER] = { + .cmd_port = 0x20, + .data_port = 0x21, + .imr = 0xff, + .master = true, + }, + [I8259_PIC_ID_SLAVE] = { + .cmd_port = 0xa0, + .data_port = 0xa1, + .imr = 0xff, + .master = false, + }, +}; + +static struct i8259_pic * +i8259_get_pic(unsigned int id) +{ + assert(id < ARRAY_SIZE(i8259_pics)); + return &i8259_pics[id]; +} + +static int +i8259_convert_global_irq(unsigned int irq, struct i8259_pic **pic, + unsigned int *local_irq) +{ + int error; + + if (irq < I8259_NR_IRQS) { + *pic = i8259_get_pic(I8259_PIC_ID_MASTER); + + if (local_irq) { + *local_irq = irq; + } + + error = 0; + } else if (irq < (I8259_NR_IRQS * I8259_NR_PICS)) { + *pic = i8259_get_pic(I8259_PIC_ID_SLAVE); + + if (local_irq) { + *local_irq = irq - I8259_NR_IRQS; + } + + error = 0; + } else { + *local_irq = 0; + error = ERROR_INVAL; + } + + return error; +} + +static void +i8259_pic_write_cmd(const struct i8259_pic *pic, uint8_t byte) +{ + io_write(pic->cmd_port, byte); +} + +static void +i8259_pic_write_data(const struct i8259_pic *pic, uint8_t byte) +{ + io_write(pic->data_port, byte); +} + +static void +i8259_pic_apply_imr(const struct i8259_pic *pic) +{ + io_write(pic->data_port, pic->imr); +} + +static void +i8259_pic_enable_irq(struct i8259_pic *pic, unsigned int irq) +{ + assert(irq < I8259_NR_IRQS); + + pic->imr &= ~(1 << irq); + i8259_pic_apply_imr(pic); +} + +static void +i8259_pic_disable_irq(struct i8259_pic *pic, unsigned int irq) +{ + assert(irq < I8259_NR_IRQS); + + pic->imr |= (1 << irq); + i8259_pic_apply_imr(pic); +} + +static void +i8259_pic_eoi(struct i8259_pic *pic) +{ + io_write(pic->cmd_port, I8259_OCW2_EOI); +} + +void +i8259_setup(void) +{ + struct i8259_pic *master, *slave; + + master = i8259_get_pic(I8259_PIC_ID_MASTER); + slave = i8259_get_pic(I8259_PIC_ID_SLAVE); + + i8259_pic_write_cmd(master, I8259_ICW1_INIT | I8259_ICW1_ICW4); + i8259_pic_write_cmd(slave, I8259_ICW1_INIT | I8259_ICW1_ICW4); + i8259_pic_write_data(master, CPU_IDT_VECT_IRQ_BASE); + i8259_pic_write_data(slave, CPU_IDT_VECT_IRQ_BASE + I8259_NR_IRQS); + i8259_pic_write_data(master, 1 << I8259_IRQ_CASCADE); + i8259_pic_write_data(slave, I8259_IRQ_CASCADE); + i8259_pic_write_data(master, I8259_ICW4_8086); + i8259_pic_write_data(slave, I8259_ICW4_8086); + + i8259_pic_enable_irq(master, I8259_IRQ_CASCADE); + i8259_pic_apply_imr(master); + i8259_pic_apply_imr(slave); +} + +void +i8259_irq_enable(unsigned int irq) +{ + struct i8259_pic *pic; + unsigned int local_irq; + int error; + + error = i8259_convert_global_irq(irq, &pic, &local_irq); + assert(!error); + i8259_pic_enable_irq(pic, local_irq); +} + +void +i8259_irq_disable(unsigned int irq) +{ + struct i8259_pic *pic; + unsigned int local_irq; + int error; + + error = i8259_convert_global_irq(irq, &pic, &local_irq); + assert(!error); + i8259_pic_disable_irq(pic, local_irq); +} + +void +i8259_irq_eoi(unsigned int irq) +{ + struct i8259_pic *pic; + int error; + + assert(!cpu_intr_enabled()); + + error = i8259_convert_global_irq(irq, &pic, NULL); + assert(!error); + + if (!pic->master) { + /* + * The order in which EOI messages are sent (master then slave or the + * reverse) is irrelevant : + * - If the slave is sent the EOI message first, it may raise another + * interrupt right away, in which case it will be pending at the + * master until the latter is sent the EOI message too. + * - If the master is sent the EOI message first, it may raise another + * interrupt right away, in which case it will be pending at the + * processor until interrupts are reenabled, assuming that this + * function is called with interrupts disabled, and that interrupts + * remain disabled until control is returned to the interrupted + * thread. + */ + i8259_pic_eoi(i8259_get_pic(I8259_PIC_ID_MASTER)); + } + + i8259_pic_eoi(pic); +} |