/*
 * pic_apic.c - APIC PIC driver implementation
 *
 * 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_kernel_types.h>
#include <chdev.h>
#include <pic_apic.h>
#include <errno.h>
#include <string.h>
#include <printk.h>

#include <hal_internal.h>
#include <hal_segmentation.h>
#include <hal_apic.h>

extern iopic_input_t iopic_input;

static void
idt_set_seg(struct idt_seg *seg, void *func, int ist, int type, int dpl, int sel)
{
	seg->gd_looffset = (uint64_t)func & 0xffff;
	seg->gd_selector = sel;
	seg->gd_ist = ist;
	seg->gd_type = type;
	seg->gd_dpl = dpl;
	seg->gd_p = 1;
	seg->gd_hioffset = (uint64_t)func >> 16;
	seg->gd_zero = 0;
	seg->gd_xx1 = 0;
	seg->gd_xx2 = 0;
	seg->gd_xx3 = 0;
}

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

extern uint8_t *idtstore;

void pic_apic_init(chdev_t *pic)
{
	/* XXX APIC already initialized earlier */
}

void pic_apic_extend_init(uint32_t *xcu_base)
{
	x86_panic((char *)__func__);
}

void pic_apic_bind_irq(lid_t lid, chdev_t *src_chdev)
{
	struct idt_seg seg, *idt;
	void *isr;
	int slot;

	/* get the source chdev functional type, channel, and direction */
	uint32_t func    = src_chdev->func;
	uint32_t channel = src_chdev->channel;

	/* get external IRQ index */
	uint32_t irq_id;
	if (func == DEV_FUNC_IOC)
		irq_id = iopic_input.ioc[channel];
	else if (func == DEV_FUNC_TXT)
		irq_id = iopic_input.txt[channel];
	else
		assert(false, "unsupported device\n");

	/* get the ISR pointer */
	isr = src_chdev->isr;

	/* create the IDT entry, and register it */
	idt_set_seg(&seg, isr, 0, SDT_SYS386IGT, SEL_KPL,
	    GDT_FIXED_SEL(GDT_KCODE_SEL, SEL_KPL));
	slot = idt_slot_alloc();
	idt = (struct idt_seg *)&idtstore;
	memcpy(&idt[slot], &seg, sizeof(struct idt_seg));

	/* Bind the IRQ line in IOAPIC */
	hal_ioapic_bind_irq(irq_id, slot, lid);
}

void pic_apic_enable_irq(lid_t lid, xptr_t src_chdev_xp)
{
	chdev_t *src_chdev = (chdev_t *)src_chdev_xp;
	uint32_t func = src_chdev->func;
	uint32_t channel = src_chdev->channel;
	uint32_t irq_id;

	/* get external IRQ index */
	if (func == DEV_FUNC_IOC)
		irq_id = iopic_input.ioc[channel];
	else if (func == DEV_FUNC_TXT)
		irq_id = iopic_input.txt[channel];
	else
		assert(false, "unsupported device\n");

	/* enable the line */
	hal_ioapic_enable_irq(irq_id);
}

void pic_apic_disable_irq(lid_t lid, xptr_t src_chdev_xp)
{
	chdev_t *src_chdev = (chdev_t *)src_chdev_xp;
	uint32_t func = src_chdev->func;
	uint32_t channel = src_chdev->channel;
	uint32_t irq_id;

	/* get external IRQ index */
	if (func == DEV_FUNC_IOC)
		irq_id = iopic_input.ioc[channel];
	else if (func == DEV_FUNC_TXT)
		irq_id = iopic_input.txt[channel];
	else
		assert(false, "unsupported device\n");

	/* disable the line */
	hal_ioapic_disable_irq(irq_id);
}

void pic_apic_enable_timer(uint32_t period)
{
	/*
	 * TODO: right now, we don't do anything, because the LAPIC is already
	 * calibrated and launched earlier, mostly for debugging purposes. But it
	 * would be good to launch it here for real.
	 */
}

void pic_apic_enable_ipi( void )
{
	/* TODO */
//	x86_panic((char *)__func__);
}

void pic_apic_send_ipi(cxy_t cxy, lid_t lid)
{
	x86_panic((char *)__func__);
}
