/*
 * 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 <hal_types.h>
#include <hal_boot.h>
#include <hal_register.h>
#include <hal_segmentation.h>
#include <hal_apic.h>
#include <hal_internal.h>

#include <memcpy.h>
#include <thread.h>
#include <string.h>
#include <process.h>
#include <printk.h>
#include <vmm.h>
#include <core.h>
#include <cluster.h>

/* -------------------------------------------------------------------------- */

#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);
}

/* -------------------------------------------------------------------------- */

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 IOENTRY_DISABLE	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_set_entry(uint8_t index, uint64_t data)
{
	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()
{
	size_t i, pins;
	uint32_t ver;

	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);
	pins = ((ver >> 16) & 0xFF) + 1;

	/* Explicitly disable (mask) each vector */
	for (i = 0; i < pins; i++) {
		hal_ioapic_set_entry(i, IOENTRY_DISABLE);
	}

	/* Now, enable the keyboard */
	hal_ioapic_set_entry(IRQ_KEYBOARD, IOAPIC_KEYBOARD_VECTOR);
}

/* -------------------------------------------------------------------------- */

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;
}

/*
 * 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)
 */
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|LAPIC_SPURIOUS_VECTOR);

	/* 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_write(LAPIC_ICR_TIMER, 1000000000); // XXX 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();
}

