/* * hal_apic.c - Advanced Programmable Interrupt Controller * * Copyright (c) 2017 Maxime Villard * * This file is part of ALMOS-MKH. * * ALMOS-MKH is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2.0 of the License. * * ALMOS-MKH is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ALMOS-MKH.; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* -------------------------------------------------------------------------- */ #define PIC1_CMD 0x0020 #define PIC1_DATA 0x0021 #define PIC2_CMD 0x00a0 #define PIC2_DATA 0x00a1 static void hal_pic_init() { /* * Disable the PIC (8259A). We are going to use IOAPIC instead. */ out8(PIC1_DATA, 0xff); out8(PIC2_DATA, 0xff); } /* -------------------------------------------------------------------------- */ uint64_t pit_ticks_base __in_kdata = 0; #define PIT_FREQUENCY 1193182 #define HZ 100 /* 1/HZ = 10ms */ #define PIT_TIMER0 0x40 #define PIT_CMD 0x43 # define CMD_BINARY 0x00 /* Use Binary counter values */ # define CMD_BCD 0x01 /* Use Binary Coded Decimal counter values */ # define CMD_MODE0 0x00 /* Interrupt on Terminal Count */ # define CMD_MODE1 0x02 /* Hardware Retriggerable One-Shot */ # define CMD_MODE2 0x04 /* Rate Generator */ # define CMD_MODE3 0x06 /* Square Wave */ # define CMD_MODE4 0x08 /* Software Trigerred Strobe */ # define CMD_MODE5 0x0a /* Hardware Trigerred Strobe */ # define CMD_LATCH 0x00 /* latch counter for reading */ # define CMD_LSB 0x10 /* LSB, 8 bits */ # define CMD_MSB 0x20 /* MSB, 8 bits */ # define CMD_16BIT 0x30 /* LSB and MSB, 16 bits */ # define CMD_COUNTER0 0x00 # define CMD_COUNTER1 0x40 # define CMD_COUNTER2 0x80 # define CMD_READBACK 0xc0 void hal_pit_init() { /* Initialize PIT clock 0 to the maximum counter value, 65535. */ out8(PIT_CMD, CMD_COUNTER0|CMD_MODE2|CMD_16BIT); out8(PIT_TIMER0, 0xFF); out8(PIT_TIMER0, 0xFF); } uint64_t hal_pit_timer_read() { static uint16_t last; uint8_t lo, hi; uint16_t ctr; uint64_t ticks; /* Read the current timer counter. */ out8(PIT_CMD, CMD_COUNTER0|CMD_LATCH); lo = in8(PIT_TIMER0); hi = in8(PIT_TIMER0); ctr = (hi << 8) | lo; /* If the counter has wrapped, assume we're into the next tick. */ if (ctr > last) pit_ticks_base += 0xFFFF; last = ctr; ticks = pit_ticks_base + (0xFFFF - ctr); return ticks; } /* -------------------------------------------------------------------------- */ #define BAUDRATE 19200 #define BAUDRATE_DIV (115200 / BAUDRATE) #define RS232_COM1_BASE 0x3F8 #define RS232_DATA 0x00 #define RS232_IER 0x01 # define IER_RD 0x01 # define IER_TBE 0x02 # define IER_ER_BRK 0x04 # define IER_RS232IN 0x08 #define RS232_DIVLO 0x00 /* when DLAB = 1 */ #define RS232_DIVHI 0x01 /* when DLAB = 1 */ #define RS232_IIR 0x02 #define RS232_LCR 0x03 # define LCR_DATA5 0x00 # define LCR_DATA6 0x01 # define LCR_DATA7 0x02 # define LCR_DATA8 0x03 # define LCR_TWOSTOP 0x04 # define LCR_PARITY 0x08 # define LCR_EVEN 0x10 # define LCR_STICK 0x20 # define LCR_DLAB 0x80 #define RS232_MCR 0x04 # define MCR_DTR 0x01 # define MCR_RTS 0x02 # define MCR_ELL 0x04 # define MCR_IR 0x40 #define RS232_LSR 0x05 # define LSR_DR 0x01 # define LSR_OVR 0x02 # define LSR_PE 0x04 # define LSR_FE 0x08 # define LSR_BRK 0x10 # define LSR_TBE 0x20 # define LSR_TE 0x40 #define RS232_MSR 0x06 # define MSR_DCTS 0x01 # define MSR_DDSR 0x02 # define MSR_DRI 0x04 # define MSR_DDCD 0x08 # define MSR_CTS 0x10 # define MSR_DSR 0x20 # define MSR_RI 0x40 # define MSR_DCD 0x80 #define RS232_SCRATCH 0x07 static bool_t hal_com_received() { return (in8(RS232_COM1_BASE + RS232_LSR) & LSR_DR) != 0; } static bool_t hal_com_transmit_empty() { return (in8(RS232_COM1_BASE + RS232_LSR) & LSR_TBE) != 0; } char hal_com_read() { while (!hal_com_received()); return in8(RS232_COM1_BASE + RS232_DATA); } void hal_com_send(char c) { uint8_t mcr = in8(RS232_COM1_BASE + RS232_MCR); out8(RS232_COM1_BASE + RS232_MCR, mcr | MCR_RTS); while (!hal_com_transmit_empty()); out8(RS232_COM1_BASE + RS232_DATA, c); out8(RS232_COM1_BASE + RS232_MCR, mcr); } static void hal_com_init() { /* Disable all interrupts */ out8(RS232_COM1_BASE + RS232_IER, 0x00); /* Set baudrate */ out8(RS232_COM1_BASE + RS232_LCR, LCR_DLAB); out8(RS232_COM1_BASE + RS232_DIVLO, BAUDRATE_DIV); out8(RS232_COM1_BASE + RS232_DIVHI, 0); /* 8bits, no parity, one stop bit */ out8(RS232_COM1_BASE + RS232_LCR, LCR_DATA8); /* Enable IRQs, DTR set, and also DSR */ out8(RS232_COM1_BASE + RS232_IER, IER_RD|IER_RS232IN); out8(RS232_COM1_BASE + RS232_MCR, MCR_DTR|MCR_IR); out8(RS232_COM1_BASE + RS232_MSR, MSR_DSR); } /* -------------------------------------------------------------------------- */ size_t ioapic_pins __in_kdata = 0; paddr_t ioapic_pa __in_kdata = 0; vaddr_t ioapic_va __in_kdata = 0; #define IRQ_TIMER 0x00 #define IRQ_KEYBOARD 0x01 #define IRQ_COM2 0x03 #define IRQ_COM1 0x04 #define IRQ_FLOPPY 0x06 #define IRQ_ATA0 0x0e #define IRQ_ATA1 0x0f #define IOREGSEL 0x00 #define IOWIN 0x10 #define IOAPICID 0x00 #define IOAPICVER 0x01 #define IOAPICARB 0x02 #define IOREDTBL 0x10 # define IOREDTBL_DEL_FIXED 0x000 # define IOREDTBL_DEL_LOPRI 0x100 # define IOREDTBL_DEL_SMI 0x200 # define IOREDTBL_DEL_NMI 0x400 # define IOREDTBL_DEL_INIT 0x500 # define IOREDTBL_DEL_EXTINT 0x700 # define IOREDTBL_DEM_PHYS 0x000 # define IOREDTBL_DEM_LOGIC 0x800 # define IOREDTBL_DES_SHIFT 56 # define IOREDTBL_MSK 0x10000 void hal_ioapic_write(uint8_t reg, uint32_t val) { *((volatile uint32_t *)((uint8_t *)ioapic_va + IOREGSEL)) = reg; *((volatile uint32_t *)((uint8_t *)ioapic_va + IOWIN)) = val; } uint32_t hal_ioapic_read(uint8_t reg) { *((volatile uint32_t *)((uint8_t *)ioapic_va + IOREGSEL)) = reg; return *((volatile uint32_t *)((uint8_t *)ioapic_va + IOWIN)); } void hal_ioapic_disable_entry(uint8_t index) { const uint64_t data = IOREDTBL_MSK; hal_ioapic_write(IOREDTBL + index * 2, (uint32_t)(data & 0xFFFFFFFF)); hal_ioapic_write(IOREDTBL + index * 2 + 1, (uint32_t)(data >> 32)); } void hal_ioapic_set_entry(uint8_t index, uint8_t vec, uint8_t dest) { const uint64_t data = ((uint64_t)dest << IOREDTBL_DES_SHIFT) | IOREDTBL_DEM_PHYS | IOREDTBL_DEL_FIXED | vec; hal_ioapic_write(IOREDTBL + index * 2, (uint32_t)(data & 0xFFFFFFFF)); hal_ioapic_write(IOREDTBL + index * 2 + 1, (uint32_t)(data >> 32)); } static void hal_ioapic_init() { uint32_t ver; size_t i; ioapic_va = hal_gpt_bootstrap_valloc(1); // XXX: should be shared hal_gpt_enter(ioapic_va, ioapic_pa, PG_V|PG_KW|PG_NX|PG_N); ver = hal_ioapic_read(IOAPICVER); ioapic_pins = ((ver >> 16) & 0xFF) + 1; /* Explicitly disable (mask) each vector */ for (i = 0; i < ioapic_pins; i++) { hal_ioapic_disable_entry(i); } x86_printf("IOAPICPINS: #%z\n", ioapic_pins); /* Now, enable the com1 port and the keyboard */ hal_ioapic_set_entry(IRQ_COM1, IOAPIC_COM1_VECTOR, 0); hal_ioapic_set_entry(IRQ_KEYBOARD, IOAPIC_KEYBOARD_VECTOR, 0); } /* -------------------------------------------------------------------------- */ paddr_t lapic_pa __in_kdata = 0; vaddr_t lapic_va __in_kdata = 0; void hal_lapic_write(uint32_t reg, uint32_t val) { *((volatile uint32_t *)((uint8_t *)lapic_va + reg)) = val; } uint32_t hal_lapic_read(uint32_t reg) { return *((volatile uint32_t *)((uint8_t *)lapic_va + reg)); } uint32_t hal_lapic_gid() { return hal_lapic_read(LAPIC_ID) >> LAPIC_ID_SHIFT; } /* * Use the PIT, which has a standard clock frequency, to determine the CPU's * exact bus frequency. */ static void hal_lapic_calibrate() { uint64_t pittick, lapictick0, lapictick1; uint32_t lapicticks, lapicstart; /* Initialize the LAPIC timer to the maximum value */ hal_lapic_write(LAPIC_ICR_TIMER, 0xFFFFFFFF); /* Initialize the PIT */ hal_pit_init(); pittick = hal_pit_timer_read() + 1; while (hal_pit_timer_read() < pittick) { /* Wait until start of a PIT tick */ } /* Read base count from LAPIC */ lapictick0 = hal_lapic_read(LAPIC_CCR_TIMER); while (hal_pit_timer_read() < pittick + (PIT_FREQUENCY + HZ/2) / HZ) { /* Wait 1/HZ sec = 10ms */ } /* Read final count from LAPIC */ lapictick1 = hal_lapic_read(LAPIC_CCR_TIMER); /* Total number of LAPIC ticks per 1/HZ tick */ lapicticks = (lapictick1 - lapictick0); /* Finally, calibrate the timer, an interrupt each 1s. */ lapicstart = - (lapicticks * 100); hal_lapic_write(LAPIC_ICR_TIMER, lapicstart); } /* * We have 8 interrupt sources: * - Spurious * - APIC Timer (TMR) * - Local Interrupt 0 (LINT0) * - Local Interrupt 1 (LINT1) * - Performance Monitor Counters (PMC) * - Thermal Sensors (THM) * - APIC internal error (ERR) * - Extended (Implementation dependent) * Only the Spurious and APIC Timer interrupts are enabled. */ static void hal_lapic_init() { lapic_va = hal_gpt_bootstrap_valloc(1); // XXX: should be shared if ((rdmsr(MSR_APICBASE) & APICBASE_PHYSADDR) != lapic_pa) { x86_panic("APICBASE and ACPI don't match!\n"); } wrmsr(MSR_APICBASE, lapic_pa | APICBASE_EN); hal_gpt_enter(lapic_va, lapic_pa, PG_V|PG_KW|PG_NX|PG_N); hal_lapic_write(LAPIC_TPR, 0); hal_lapic_write(LAPIC_EOI, 0); hal_lapic_write(LAPIC_SVR, LAPIC_SVR_ENABLE|VECTOR_APIC_SPURIOU); /* Explicitly disable (mask) each vector */ hal_lapic_write(LAPIC_LVT_TMR, LAPIC_TMR_M); hal_lapic_write(LAPIC_LVT_LINT0, LAPIC_LINT_M); hal_lapic_write(LAPIC_LVT_LINT1, LAPIC_LINT_M); hal_lapic_write(LAPIC_LVT_PMC, LAPIC_PMC_M); hal_lapic_write(LAPIC_LVT_THM, LAPIC_THM_M); hal_lapic_write(LAPIC_LVT_ERR, LAPIC_ERR_M); /* Now, enable the timer in repeated mode. */ hal_lapic_write(LAPIC_LVT_TMR, LAPIC_TMR_TM|LAPIC_TMR_M); hal_lapic_write(LAPIC_DCR_TIMER, LAPIC_DCRT_DIV1); hal_lapic_calibrate(); hal_lapic_write(LAPIC_LVT_TMR, LAPIC_TMR_TM|LAPIC_TIMER_VECTOR); } /* -------------------------------------------------------------------------- */ void hal_apic_init() { /* Disable the PIC */ hal_pic_init(); /* Enable the LAPIC */ hal_lapic_init(); /* Enable the IOAPIC */ hal_ioapic_init(); /* Enable the Serial Port */ hal_com_init(); hal_com_send('p'); hal_com_send('d'); hal_com_send('\n'); }