/*
 * Copyright (c) 2007, 2008 University of Tsukuba
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the University of Tsukuba nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Copyright (c) 2010-2014 Yuichi Watanabe
 */

#include <core/acpi.h>
#include <core/assert.h>
#include <core/initfunc.h>
#include <core/printf.h>
#include <core/sleep.h>
#include <core/string.h>
#include <core/vm.h>
#include "acpi.h"
#include "asm.h"
#include "acpi_dsdt.h"
#include "beep.h"
#include "constants.h"
#include "io_io.h"
#include "mm.h"
#include "panic.h"
#include "vm.h"

#define FIND_RSDP_NOT_FOUND	0xFFFFFFFFFFFFFFFFULL
#define RSDP_SIGNATURE		"RSD PTR"
#define RSDP_SIGNATURE_LEN	7
#define ADDRESS_SPACE_ID_MEM	0
#define ADDRESS_SPACE_ID_IO	1
#define SIGNATURE_LEN		4
#define RSDT_SIGNATURE		"RSDT"
#define FACP_SIGNATURE		"FACP"
#define FACS_SIGNATURE		"FACS"
#define PM1_CNT_SLP_TYPX_MASK	0x1C00
#define PM1_CNT_SLP_TYPX_SHIFT	10
#define PM1_CNT_SLP_EN_BIT	0x2000
#define IS_STRUCT_SIZE_OK(l, h, m) \
	((l) >= ((u8 *)&(m) - (u8 *)(h)) + sizeof (m))

#define PARSERS_MAX_NUM		8

#define ACPI_FACP_FLAG_RESET_REG_SUP	0x400

/* #define ACPI_DEBUG */
#ifdef ACPI_DEBUG
#define ACPI_DBG(...)						\
	do {							\
		printf("ACPI: " __VA_ARGS__);			\
	} while (0)
#else
#define ACPI_DBG(...)
#endif

struct acpi_parser {
	char signature[SIGNATURE_LEN];
	acpi_parser_t *func;
};

struct rsdp {
	u8 signature[8];
	u8 checksum;
	u8 oemid[6];
	u8 revision;
	u32 rsdt_address;
} __attribute__ ((packed));

struct rsdt {
	struct acpi_desc_header header;
	u32 entry[];
} __attribute__ ((packed));

struct gas {
	u8 address_space_id;
	u8 register_bit_width;
	u8 register_bit_offset;
	u8 access_size;
	u64 address;
} __attribute__ ((packed));

struct facp {
	struct acpi_desc_header header;
	u32 firmware_ctrl;
	u32 dsdt;
	u8 reserved1;
	u8 preferred_pm_profile;
	u16 sci_int;
	u32 sci_cmd;
	u8 acpi_enable;
	u8 acpi_disable;
	u8 s4bios_req;
	u8 pstate_cnt;
	u32 pm1a_evt_blk;
	u32 pm1b_evt_blk;
	u32 pm1a_cnt_blk;
	u32 pm1b_cnt_blk;
	u32 pm2_cnt_blk;
	u32 pm_tmr_blk;
	u32 gpe0_blk;
	u32 gpe1_blk;
	u8 pm1_evt_len;
	u8 pm1_cnt_len;
	u8 pm2_cnt_len;
	u8 pm_tmr_len;
	u8 gpe0_blk_len;
	u8 gpe1_blk_len;
	u8 gpe1_base;
	u8 cst_cnt;
	u16 p_lvl2_lat;
	u16 p_lvl3_lat;
	u16 flush_size;
	u16 flush_stride;
	u8 duty_offset;
	u8 duty_width;
	u8 day_alrm;
	u8 mon_alrm;
	u8 century;
	u16 iapc_boot_arch;
	u8 reserved2;
	u32 flags;
	struct gas reset_reg;
	u8 reset_value;
	u8 reserved3[3];
	u64 x_firmware_ctrl;
	u64 x_dsdt;
	struct gas x_pm1a_evt_blk;
	struct gas x_pm1b_evt_blk;
	struct gas x_pm1a_cnt_blk;
	struct gas x_pm1b_cnt_blk;
	struct gas x_pm2_cnt_blk;
	struct gas x_pm_tmr_blk;
	struct gas x_gpe0_blk;
	struct gas x_gpe1_blk;
} __attribute__ ((packed));

struct facs {
	u8 signature[4];
	u32 length;
	u8 hardware_signature[4];
	u32 firmware_waking_vector;
	u32 global_lock;
	u32 flags;
	u64 x_firmware_waking_vector;
	u8 version;
	u8 reserved[31];
} __attribute__ ((packed));

static bool rsdp_found = false;
static struct rsdp rsdp_copy;

static struct acpi_parser parsers[PARSERS_MAX_NUM];
static uint parsers_num = 0;

static bool pm1a_cnt_found = false;
static ioport_t pm1a_cnt_ioaddr;
static u64 facs_addr;
static bool reset_reg_found = false;
static ioport_t reset_reg_ioaddr;
static u8 reset_value;

unsigned char acpi_dsdt_system_state[6][5];

static u8
acpi_checksum (void *p, int len)
{
	u8 *q, s;

	s = 0;
	for (q = p; len > 0; len--)
		s += *q++;
	return s;
}

void
acpi_update_checksum(struct acpi_desc_header *header)
{
	u8 sum;

	sum = acpi_checksum(header, header->length);
	header->checksum -= sum;
}

void
acpi_hide_table(struct acpi_desc_header *header)
{
	memset(header->signature, 0, 4);
	acpi_update_checksum(header);
}

static void *
acpi_mapmem (u64 addr, int len)
{
	static void *oldmap;
	static int oldlen = 0;

	if (oldlen)
		unmapmem (oldmap, oldlen);
	oldlen = len;
	oldmap = mapmem (MAPMEM_HPHYS | MAPMEM_WRITE, addr, len);
	if (oldmap == NULL) {
		panic("Failed to map address 0x%llx", addr);
	}
	return oldmap;
}

void
acpi_register_parser(char *signature, acpi_parser_t *func)
{
	struct rsdt *rsdt;
	struct acpi_desc_header *header;
	struct acpi_parser *parser;
	int i, n, len;
	u32 entry;

	if (parsers_num >= PARSERS_MAX_NUM) {
		panic("Too many acpi parser");
	}

	parser = parsers + parsers_num;
	memcpy(parser->signature, signature, SIGNATURE_LEN);
	parser->func = func;
	parsers_num++;

	if (!rsdp_found)
		return;

	rsdt = acpi_mapmem(rsdp_copy.rsdt_address, sizeof *rsdt);
	if (memcmp(rsdt->header.signature, RSDT_SIGNATURE, SIGNATURE_LEN))
		return;
	len = rsdt->header.length;
	rsdt = acpi_mapmem(rsdp_copy.rsdt_address, len);
	if (acpi_checksum(rsdt, len))
		return;
	n = (rsdt->header.length - sizeof rsdt->header) / sizeof entry;
	for (i = 0; i < n; i++) {
		rsdt = acpi_mapmem (rsdp_copy.rsdt_address, len);
		entry = rsdt->entry[i];
		header = acpi_mapmem (entry, sizeof *header);
		if (memcmp(header->signature, parser->signature,
			   SIGNATURE_LEN))
			continue;
		header = acpi_mapmem(entry, header->length);
		if (acpi_checksum (header, header->length))
			continue;
		parser->func(header, header->length);
	}
}

static u64
get_ebda_address (void)
{
	u16 *p;

	p = acpi_mapmem (0x40E, sizeof *p);
	return ((u64)*p) << 4;
}

static u64
find_rsdp_iapc_sub (u64 start, u64 end)
{
	struct rsdp *p;
	u64 i;

	for (i = start; i < end; i += 16) {
		p = acpi_mapmem (i, sizeof *p);
		if (!memcmp (p->signature, RSDP_SIGNATURE, RSDP_SIGNATURE_LEN)
		    && !acpi_checksum (p, sizeof *p))
			return i;
	}
	return FIND_RSDP_NOT_FOUND;
}

static u64
find_rsdp_iapc (void)
{
	u64 ebda;
	u64 rsdp;

	ebda = get_ebda_address ();
	rsdp = find_rsdp_iapc_sub (ebda, ebda + 0x3FF);
	if (rsdp == FIND_RSDP_NOT_FOUND)
		rsdp = find_rsdp_iapc_sub (0xE0000, 0xFFFFF);
	return rsdp;
}

static u64
find_rsdp (void)
{
	return find_rsdp_iapc ();
}

static void
parse_tables(void)
{
	struct rsdt *rsdt;
	struct acpi_desc_header *header;
	struct acpi_parser *parser;
	int i, j, n, len;
	u32 entry;

	if (!rsdp_found)
		return;
	rsdt = acpi_mapmem(rsdp_copy.rsdt_address, sizeof *rsdt);
	if (memcmp(rsdt->header.signature, RSDT_SIGNATURE, SIGNATURE_LEN))
		return;
	len = rsdt->header.length;
	rsdt = acpi_mapmem(rsdp_copy.rsdt_address, len);
	if (acpi_checksum(rsdt, len))
		return;
	n = (rsdt->header.length - sizeof rsdt->header) / sizeof entry;
	for (i = 0; i < n; i++) {
		rsdt = acpi_mapmem (rsdp_copy.rsdt_address, len);
		entry = rsdt->entry[i];
		header = acpi_mapmem (entry, sizeof *header);
		if (acpi_checksum (header, header->length))
			continue;
		for (j = 0; j < parsers_num; j++) {
			parser = parsers + j;
			if (memcmp(header->signature, parser->signature,
				   SIGNATURE_LEN))
				continue;
			header = acpi_mapmem(entry, header->length);
			parser->func(header, header->length);
		}
	}
}

static void
debug_dump (void *p, int len)
{
	u8 *q;
	int i, j;

	q = p;
	for (i = 0; i < len; i += 16) {
		printf ("%08X ", i);
		for (j = 0; j < 16; j++)
			printf ("%02X%c", q[i + j], j == 7 ? '-' : ' ');
		for (j = 0; j < 16; j++)
			printf ("%c", q[i + j] >= 0x20 && q[i + j] <= 0x7E
				? q[i + j] : '.');
		printf ("\n");
	}
}

static void
acpi_poweroff (void)
{
	u32 data, typx;

	if (!pm1a_cnt_found)
		return;
	if (!acpi_dsdt_system_state[5][0])
		return;
	ACPI_DBG("acpi_poweroff\n");
	typx = acpi_dsdt_system_state[5][1] << PM1_CNT_SLP_TYPX_SHIFT;
	/* FIXME: how to handle pm1b_cnt? */
	asm_inl (pm1a_cnt_ioaddr, &data);
	data &= ~PM1_CNT_SLP_TYPX_MASK;
	data |= typx & PM1_CNT_SLP_TYPX_MASK;
	data |= PM1_CNT_SLP_EN_BIT;
	asm_outl (pm1a_cnt_ioaddr, data);
}

static void
acpi_pm1_sleep (u32 v)
{
	struct facs *facs;
#ifdef ACPI_DSDT
	bool m[6];
	int i;
	u8 n;
#endif

	ACPI_DBG("acpi_pm1_sleep 0x%x\n", v);

#ifdef ACPI_DSDT
	n = (v & PM1_CNT_SLP_TYPX_MASK) >> PM1_CNT_SLP_TYPX_SHIFT;
	for (i = 0; i <= 5; i++) {
		if (acpi_dsdt_system_state[i][0] &&
		    acpi_dsdt_system_state[i][1] == n)
			m[i] = true;
		else
			m[i] = false;
	}
	if (!m[2] && !m[3]) {
		ACPI_DBG("acpi_pm1_sleep m[2] %d m[3] %d\n", m[2], m[3]);
		return;
	}
#endif
	facs = acpi_mapmem (facs_addr, sizeof *facs);
	if (IS_STRUCT_SIZE_OK (facs->length, facs,
			       facs->x_firmware_waking_vector))
		facs->x_firmware_waking_vector = 0;
	if (IS_STRUCT_SIZE_OK (facs->length, facs,
			       facs->firmware_waking_vector))
		facs->firmware_waking_vector = 0xFFFF0;
	else
		for (;;); /* FIXME: ERROR */
	/* DEBUG */
	/* the computer is going to sleep */
	/* most devices are suspended */
	/* vmm can't write anything to a screen here */
	/* vmm can't input from/output to a keyboard here */
	/* vmm can beep! */
	beep_on ();
	beep_set_freq (880);
	usleep (500000);
	beep_set_freq (440);
	usleep (500000);
	beep_set_freq (880);
	usleep (500000);
	beep_set_freq (440);
	usleep (500000);
	beep_set_freq (880);
	usleep (500000);
	beep_set_freq (440);
	usleep (500000);
	beep_set_freq (880);

	acpi_poweroff ();
}

static int
acpi_io_monitor (iotype_t type, ioport_t port, void *data)
{
	u32 v;

	if (pm1a_cnt_found && port == pm1a_cnt_ioaddr) {
		switch (type) {
		case IOTYPE_OUTB:
			v = *(u8 *)data;
			break;
		case IOTYPE_OUTW:
			v = *(u16 *)data;
			break;
		case IOTYPE_OUTL:
			v = *(u32 *)data;
			break;
		default:
			goto def;
		}
		if (v & PM1_CNT_SLP_EN_BIT)
			acpi_pm1_sleep (v);
	}
 def:
	return 0; /* pass */
}

static int
acpi_reset_reg (iotype_t type, ioport_t port, void *data)
{
	u32 v;

	switch (type) {
	case IOTYPE_OUTB:
		v = *(u8 *)data;
		break;
	case IOTYPE_OUTW:
		v = *(u16 *)data;
		break;
	case IOTYPE_OUTL:
		v = *(u32 *)data;
		break;
	default:
		return 0; /* pass */
	}

	if (v == reset_value) {
		if (vm_reset() == VMMERR_SUCCESS) {
			return 1; /* emulated */
		}
		printf("ACPI reset of %s ignored\n",
		       vm_get_name());
		return 1; /* emulated */
	}

	return 0; /* pass */
}

static void
acpi_iohook (void)
{
	if (vm_get_id() != 0) {
		return;
	}

	if (pm1a_cnt_found)
		set_iofunc (pm1a_cnt_ioaddr, acpi_io_monitor);

	if (reset_reg_found && reset_reg_ioaddr != 0x64)
		set_iofunc(reset_reg_ioaddr, acpi_reset_reg);
}

static void
get_pm1a_cnt_ioaddr (struct facp *q)
{
	if (IS_STRUCT_SIZE_OK (q->header.length, q, q->x_pm1a_cnt_blk)) {
		if (q->x_pm1a_cnt_blk.address_space_id !=
		    ADDRESS_SPACE_ID_IO)
			panic ("X_PM1a_CNT_BLK is not I/O address");
		if (q->x_pm1a_cnt_blk.address > 0xFFFF) {
			panic ("X_PM1a_CNT_BLK > 0xFFFF");
		}
		pm1a_cnt_ioaddr = q->x_pm1a_cnt_blk.address;
	} else if (IS_STRUCT_SIZE_OK (q->header.length, q, q->pm1a_cnt_blk)) {
		if (q->pm1a_cnt_blk > 0xFFFF) {
			panic ("pm1a_cnt_blk > 0xFFFF");
		}
		pm1a_cnt_ioaddr = q->pm1a_cnt_blk;
	} else {
		panic ("ACPI FACP is too short");
	}
	pm1a_cnt_found = true;
	ACPI_DBG("PM1a control port is 0x%X\n", pm1a_cnt_ioaddr);
}

static void
get_reset_reg_ioaddr (struct facp *facp)
{
	if ((facp->flags & ACPI_FACP_FLAG_RESET_REG_SUP) == 0) {
		printf("ACPI reset register is not supported.\n");
		return;
	}
	if (!IS_STRUCT_SIZE_OK(facp->header.length, facp, facp->reset_value)) {
		printf("FACP length is too short: %d\n", facp->header.length);
		return;
	}
	if (facp->reset_reg.address_space_id != ADDRESS_SPACE_ID_IO) {
		printf("ACPI RESET_REG is not in I/O space: %d\n",
		       facp->reset_reg.address_space_id);
		return;
	}
	if (facp->reset_reg.address > 0xFFFF) {
		printf("ACPI RESET_REG is out of range: 0x%llx\n",
		       facp->reset_reg.address);
		return;
	}
	reset_reg_found = true;
	reset_reg_ioaddr = facp->reset_reg.address;
	reset_value = facp->reset_value;
	ACPI_DBG("RESET_REG 0x%x value 0x%x\n", reset_reg_ioaddr, reset_value);
}

static void
get_facs_addr (struct facp *facp)
{
	if (IS_STRUCT_SIZE_OK (facp->header.length, facp,
			       facp->x_firmware_ctrl))
		facs_addr = facp->x_firmware_ctrl;
	else if (IS_STRUCT_SIZE_OK (facp->header.length, facp,
				    facp->firmware_ctrl))
		facs_addr = facp->firmware_ctrl;
	else
		panic ("ACPI FACP is too short");
}

static acpi_parser_t parse_facp;
static void
parse_facp(void *table, u32 length)
{
	struct facp *facp = (struct facp *)table;

#ifdef ACPI_DSDT
	acpi_dsdt_parse(facp->dsdt);
#endif
	get_pm1a_cnt_ioaddr(facp);
	get_reset_reg_ioaddr(facp);
	get_facs_addr(facp);
	if (0)
		debug_dump(facp, facp->header.length);
}

static void
acpi_init_global (void)
{
	u64 rsdp;
	struct rsdp *p;

	acpi_register_parser(FACP_SIGNATURE, parse_facp);

	rsdp = find_rsdp ();
	if (rsdp == FIND_RSDP_NOT_FOUND) {
		printf ("ACPI RSDP not found.\n");
		return;
	}
	p = acpi_mapmem (rsdp, sizeof *p);
	memcpy (&rsdp_copy, p, sizeof *p);
	rsdp_found = true;

	parse_tables();
}

INITFUNC ("global3", acpi_init_global);
INITFUNC ("setupvm1", acpi_iohook);
