#include "bios.h"
#include "stdio.h"
#include "fw_cfg.h"
#include "string.h"
#include "start_info.h"

extern struct hvm_start_info start_info;

struct loader_cmd {
	uint32_t cmd;
	union {
#define CMD_QUIT 0
#define CMD_ALLOC 1
		struct {
			char file[56];
			uint32_t align;
			uint8_t zone;
		} alloc;
#define CMD_PTR 2
		struct {
			char dest[56];
			char src[56];
			uint32_t offset;
			uint8_t size;
		} ptr;
#define CMD_CHECKSUM 3
		struct {
			char file[56];
			uint32_t offset;
			uint32_t start;
			uint32_t len;
		} checksum;
		uint8_t pad[124];
	};
} __attribute__((__packed__));

enum {
	ALLOC_HIGH = 1,
	ALLOC_FSEG = 2
};

static uint8_t *file_address[20];

static inline void *id_to_addr(int fw_cfg_id)
{
	return file_address[fw_cfg_id];
}

static inline void set_file_addr(int fw_cfg_id, void *p)
{
	file_address[fw_cfg_id] = p;
}

static void do_alloc(char *file, uint32_t align, uint8_t zone)
{
	int id = fw_cfg_file_id(file);
	int n = fw_cfg_file_size(id);
	char *p;

	if (id == -1)
		panic();

	if (align < 16)
		align = 16;

	if (zone == ALLOC_FSEG)
		p = malloc_fseg_align(n, align);
	else
		p = malloc_align(n, align);

	set_file_addr(id, p);
	fw_cfg_read_file(id, p, n);

	/* For PVH boot, save the PA where the RSDP is stored */
	if (zone == ALLOC_FSEG) {
		if (!memcmp(p, "RSD PTR ", 8)) {
			start_info.rsdp_paddr = (uintptr_t)id_to_addr(id);
		}
	}
}

static void do_ptr(char *dest, char *src, uint32_t offset, uint8_t size)
{
	char *p, *q;
	int id;
	union {
		long long ll;
		char b[8];
	} data;

	id = fw_cfg_file_id(src);
	p = id_to_addr(id);
	if (!p)
		panic();

	id = fw_cfg_file_id(dest);
	q = id_to_addr(id);
	if (!q)
		panic();

	q += offset;

	/* Assumes little endian */
	data.ll = 0;
	memcpy(&data.b, q, size);
	data.ll += (uintptr_t) p;
	memcpy(q, &data.b, size);
}

static void do_checksum(char *file, uint32_t offset, uint32_t start, uint32_t len)
{
	uint8_t *p;
	int id;
	int n;

	id = fw_cfg_file_id(file);
	p = id_to_addr(id);
	if (!p)
		panic();

	n = fw_cfg_file_size(id);
	if (offset >= n || n < start || len > n - start)
		panic();

	p[offset] -= csum8(&p[start], len);
}

void extract_acpi(void)
{
	int id = fw_cfg_file_id("etc/table-loader");
	int n = fw_cfg_file_size(id);
	struct loader_cmd script[n / sizeof(struct loader_cmd)];
	int i;

	if (!n)
		return;

	fw_cfg_read_file(id, script, n);

	for (i = 0; i < ARRAY_SIZE(script); i++) {
		struct loader_cmd *s = &script[i];
		switch(script[i].cmd) {
		case CMD_ALLOC:
			do_alloc(s->alloc.file, s->alloc.align, s->alloc.zone);
			break;
		case CMD_PTR:
			do_ptr(s->ptr.dest, s->ptr.src, s->ptr.offset, s->ptr.size);
			break;
		case CMD_CHECKSUM:
			do_checksum(s->checksum.file, s->checksum.offset,
				    s->checksum.start, s->checksum.len);
			break;
		case CMD_QUIT:
			return;
		default:
			panic();
		}
	}
}
