/* SPDX-License-Identifier: GPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2024 Riku Viitanen <riku.viitanen@protonmail.com> */

/* Dumps information about MXM support on System BIOS.  Based on the
 * MXM 3.0 software spec, may or may not work on older versions. */

#include <stddef.h>

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <libx86.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

#include "mxm_parse.h"


/* Executes an int15h MXM function. Returns 0 on success,
 * 1 on LRMI error and 2 if function isn't supported by System BIOS */
int mxm_exec(struct LRMI_regs *r, uint8_t function, uint16_t cx) {
	r->eax = 0x5f80;
	/* Don't bother with Adapter ID, we're interested in the one
	 * that will be used for booting. */
	r->ebx = (0xff00 | function);
	r->ecx = cx;
	if (!LRMI_int(0x15, r))
		return 1;
	
	if (r->eax != 0x005f)
		return 2;

	return 0;
}

/* id=0x30 to get the MXM Information structure (for v3.0) and
 * id=0x80-0x8F for vendor specific data blocks
 * Return 0: success, p points to data.
 *        1: LRMI error   2: function not supported by System BIOS */
int mxm_get_data(struct LRMI_regs *r, const char **p, uint8_t id) {
	int ret = mxm_exec(r, 1, id);
	if (ret)
		return ret;
	*p = (const char *)LRMI_base_addr() + r->es*16 + r->edi;
	return 0;
}

void print_help(char *pgmname) {
	puts(" -h            print this help message");
	puts(" -i filename   parse filename instead of querying BIOS");
	puts(" -o filename   output binary dump to file");
	puts(" -p            parse the structure. DEFAULT");
	puts(" -x            hexdump the whole structure");
}

int main(int argc, char **argv) {
	printf("mxmdump 0.1 (C) 2024 Riku Viitanen\n");

	int do_hexdump = 0;
	int do_parse = 0;
	int do_vendor = 0;
	int fd_in = 0;
	FILE *file_out = NULL;

	while (1) {
		int o = getopt(argc, argv, "i:ho:pvx");
		if (o == -1) break;
		switch (o) {
		case 'h':
			print_help(*argv);
			exit(0);
		case 'i':
			fd_in = open(optarg, O_RDONLY);
			if (!fd_in) {
				perror("open input");
				exit(EXIT_FAILURE);
			}
			break;
		case 'o':
			file_out = fopen(optarg, "w");
			if (!file_out) {
				perror("fopen output");
				exit(EXIT_FAILURE);
			}
			break;
		case 'p':
			do_parse = 1;
			break;
		case 'v':
			do_vendor = 1;
			break;
		case 'x':
			do_hexdump = 1;
			break;
		default:
			printf("see help: %s -h\n", *argv);
			exit(1);
		}
	}
	if ((!do_hexdump) & (!do_parse) & (!do_vendor))
		do_parse = 1; /* The default */

	struct LRMI_regs r;
	if ((!fd_in && (do_parse || do_hexdump)) || do_vendor) {
		memset(&r, 0, sizeof(r));
		if (!LRMI_init()) {
			fprintf(stderr, "Error: LRMI init failed!\n");
			return 1;
		}
	}

	const char *data;
	uint16_t len;
	if (!fd_in) {
		printf("\nQuery supported specification level...\n");
		switch (mxm_exec(&r, 0, 0x30)) {
		case 1:
			perror("Error: LRMI call failed\n");
			return EXIT_FAILURE;
		case 2:
			fprintf(stderr, "Error: MXM function 0 failed\n");
			return EXIT_FAILURE;
		}

		printf("Specification level supported by System BIOS: %u.%u\n",
		       (r.ebx >> 4) & 0xf, r.ebx & 0xf);
		if ((r.ebx & 0xf0) != 0x30)
			printf("Warning: Only 3.x is tested.\n");

		r.ecx &= 0x3ff;
		for (int i = 1; i < 8; i++) {
			r.ecx = r.ecx >> 1;
			if (r.ecx & 1) {
				printf("Function %u is supported\n", i);
			}
		}

		printf("\nGet MXM System Information Structure...\n");
       		switch (mxm_get_data(&r, &data, 0x30)) {
		case 1:
			perror("Error: LRMI call failed!\n");
			return EXIT_FAILURE;
		case 2:
			fprintf(stderr, "Error: Failed to get MXM data!\n");
			return EXIT_FAILURE;
		}

		printf("Structure located at %p\n", data);

		if (memcmp("MXM_", data, 4)) {
			fprintf(stderr, "Invalid header!\n");
		}
		len = *(data+6)+8;
	} else {
		/* read structure from file */
		errno = 0;
		data = mmap(0, 8, PROT_READ, MAP_PRIVATE, fd_in, 0);
		if (errno) {
			perror("input read failed");
			exit(EXIT_FAILURE);
		}
		len = *(data+6)+8;
		munmap((void *)data, len);
		data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd_in, 0);
	}

	
	if (file_out) {
		fwrite(data, 1, len, file_out);
		fclose(file_out);
	}

	if (do_parse)
		mxm_parse_sis(data);

	if (do_hexdump) {
		if (hexdump(data, len))
			printf("Invalid checksum!\n");
		if (!do_parse)
			printf("Valid checksum\n");
		putchar('\n');
	}

	munmap((void *)data, len);

	/* Read vendor specific structures 0x80-0x8f */
	if (do_vendor) {
		uint8_t id = 0x7f;
		while (id++ < 0x8f) {
			mxm_get_data(&r, &data, id);

			printf("Vendor %04x specific structure 0x%02x at %p\n",
			       r.ebx & 0xffff, id, data);
		}
	}

	return 0;
}
