/* SPDX-License-Identifier: GPL-2.0-only */

#include <stdint.h>
#include <console/console.h>
#include <console/uart.h>

#include "bmcinfo.h"

typedef struct {
	u32 magic0; // "BMCI"
	u32 magic1; // "nfo0"
	u16 length;
	u16 chksum;
	u8 uuid[16];
	u8 bmcSerial[9]; // as null terminated string
	u8 slot;
	u8 corebootVerbosityLevel;
	u8 relaxSecurity;
	u32 baudrate;
	u8 bootOption;
	u8 hwRev; // Note: Initial implementation ended here
	u8 disableNic1;
	u8 endMarker; // Insert new fields before
} biosBmcInfo_t;

#define BIOSBMCINFO_MAGIC0 0x49434d42
#define BIOSBMCINFO_MAGIC1 0x306f666e

#define BMC_INFO ((biosBmcInfo_t *)CONFIG_BMC_INFO_LOC)

enum biosBmcInfoValidFlag_e {
	BMCINFO_UNTESTED = 0,
	BMCINFO_INVALID,
	BMCINFO_INVALID_WARNED,
	BMCINFO_VALID_NEED_WARN,
	BMCINFO_VALID,
};
static bool bmcinfo_is_valid(size_t minsize)
{
	static enum biosBmcInfoValidFlag_e biosBmcInfoValidFlag;
	const biosBmcInfo_t *bmc_info = BMC_INFO;
	if (biosBmcInfoValidFlag == BMCINFO_UNTESTED) {
		biosBmcInfoValidFlag = BMCINFO_INVALID;
		if ((bmc_info->magic0 == BIOSBMCINFO_MAGIC0)
		 && (bmc_info->magic1 == BIOSBMCINFO_MAGIC1)
		 && (bmc_info->length >= offsetof(biosBmcInfo_t, hwRev))
		 && (bmc_info->length <= 0x1000)) {
			u16 chksum = 0 - (bmc_info->chksum & 0xff)
				- (bmc_info->chksum >> 8);
			int i;
			for (i = 0; i < bmc_info->length ; i++)
				chksum += ((u8 *)bmc_info)[i];
			if (bmc_info->chksum == chksum) {
				if (bmc_info->length >= offsetof(biosBmcInfo_t,
							endMarker))
					biosBmcInfoValidFlag = BMCINFO_VALID;
				else
					biosBmcInfoValidFlag = BMCINFO_VALID_NEED_WARN;
			}
		}
	}
	if (ENV_RAMSTAGE && biosBmcInfoValidFlag == BMCINFO_INVALID) {
		int length = offsetof(biosBmcInfo_t, endMarker);
		printk(BIOS_CRIT, "WARNING bmcInfo struct"
				"is not available please update your BMC.\n");
		biosBmcInfoValidFlag = BMCINFO_INVALID_WARNED;
		printk(BIOS_CRIT, "bmcInfo magic = \"%x-%x\"\n",
				bmc_info->magic0, bmc_info->magic1);
		printk(BIOS_CRIT, "bmcInfo length = %d expected = %d\"\n",
				bmc_info->length, length);
		u16 chksum = 0 - (bmc_info->chksum & 0xff)
			- (bmc_info->chksum >> 8);
		int i;
		for (i = 0; i < bmc_info->length; i++)
			chksum += ((u8 *)bmc_info)[i];
		printk(BIOS_CRIT, "bmcInfo chksum = 0x%x expected = 0x%x\"\n",
				bmc_info->chksum, chksum);
	}
	if (ENV_RAMSTAGE && biosBmcInfoValidFlag == BMCINFO_VALID_NEED_WARN) {
		printk(BIOS_CRIT, "WARNING bmcInfo struct"
				" is incomplete please update your BMC.\n");

		biosBmcInfoValidFlag = BMCINFO_VALID;
	}
	if (biosBmcInfoValidFlag < BMCINFO_VALID_NEED_WARN)
		return false;
	return (bmc_info->length >= minsize);
}

#define IS_BMC_INFO_FIELD_VALID(field) \
	(bmcinfo_is_valid(offsetof(biosBmcInfo_t, field) \
		+ sizeof(((biosBmcInfo_t *)0)->field)))

char *bmcinfo_serial(void)
{
	if (IS_BMC_INFO_FIELD_VALID(bmcSerial))
		return (char *) BMC_INFO->bmcSerial;
	return NULL;
}

u8 *bmcinfo_uuid(void)
{
	if (IS_BMC_INFO_FIELD_VALID(uuid))
		return BMC_INFO->uuid;
	return NULL;
}

int bmcinfo_slot(void)
{
	if (IS_BMC_INFO_FIELD_VALID(slot))
		return BMC_INFO->slot;
	return -1;
}

int bmcinfo_hwrev(void)
{
	if (IS_BMC_INFO_FIELD_VALID(hwRev))
		return BMC_INFO->hwRev;
	return -1;
}

u32 bmcinfo_baudrate(void)
{
	if (IS_BMC_INFO_FIELD_VALID(baudrate))
		return BMC_INFO->baudrate;
	return 0;
}

int bmcinfo_coreboot_verbosity_level(void)
{
	if (IS_BMC_INFO_FIELD_VALID(corebootVerbosityLevel))
		return BMC_INFO->corebootVerbosityLevel & 0xf;
	return BIOS_CRIT;
}

int bmcinfo_fsp_verbosity_level(void)
{
	if (IS_BMC_INFO_FIELD_VALID(corebootVerbosityLevel))
		return BMC_INFO->corebootVerbosityLevel >> 4;
	return 0;
}

int bmcinfo_relax_security(void)
{
	if (IS_BMC_INFO_FIELD_VALID(relaxSecurity))
		return BMC_INFO->relaxSecurity;
	return 0;
}

int bmcinfo_boot_option(void)
{
	if (IS_BMC_INFO_FIELD_VALID(bootOption))
		return BMC_INFO->bootOption;
	return 0;
}

int bmcinfo_disable_nic1(void)
{
	if (IS_BMC_INFO_FIELD_VALID(disableNic1))
		return BMC_INFO->disableNic1;
	return 0;
}

/* Add override functions below */

/* Override default uart baudrate */
unsigned int get_uart_baudrate(void)
{
	int baudrate = bmcinfo_baudrate();
	if (baudrate)
		return baudrate;
	return 115200;
}

#if __CONSOLE_ENABLE__
/* Override default console loglevel */
int get_console_loglevel(void)
{
	return bmcinfo_coreboot_verbosity_level();
}
#endif
