/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <soc/acpi.h>
#include <acpi/acpi_gnvs.h>
#include <cbmem.h>
#include <console/console.h>
#include <soc/hest.h>
#include <intelblocks/nvs.h>

static u64 hest_get_elog_addr(void)
{
	/* The elog address comes from reserved memory */
	struct global_nvs *gnvs;
	gnvs = acpi_get_gnvs();
	if (!gnvs) {
		printk(BIOS_ERR, "Unable to get gnvs\n");
		return 0;
	}

	/* Runtime logging address */
	printk(BIOS_DEBUG, "\t status blk start addr = %llx\n", gnvs->hest_log_addr);
	printk(BIOS_DEBUG, "\t size = %x\n", CONFIG_ERROR_LOG_BUFFER_SIZE);
	return gnvs->hest_log_addr;
}

static u32 acpi_hest_add_ghes(void *current)
{
	ghes_record_t *rec = (ghes_record_t *)current;
	u32 size = sizeof(ghes_record_t);

	/* Fill GHES error source descriptor  */
	memset(rec, 0, size);
	rec->esd.type = HEST_GHES_DESC_TYPE;
	rec->esd.source_id = 0; /* 0 for MCE check exception source */
	rec->esd.enabled = 1;
	rec->esd.related_src_id = 0xffff;
	rec->esd.prealloc_erecords = 1;
	rec->esd.max_section_per_record = 0xf;
	rec->max_raw_data_length = GHES_MAX_RAW_DATA_LENGTH;

	/* Add error_status_address */
	rec->sts_addr.space_id = 0;
	rec->sts_addr.bit_width = 0x40;
	rec->sts_addr.bit_offset = 0;
	rec->sts_addr.access_size = QWORD_ACCESS;

	/* Add notification structure */
	rec->notify.type = NOTIFY_TYPE_SCI;
	rec->notify.length = sizeof(acpi_hest_hen_t);
	rec->err_sts_blk_len = GHEST_ERROR_STATUS_BLOCK_LENGTH;

	/* error status block entries start address */
	if (CONFIG(SOC_ACPI_HEST))
		rec->sts_addr.addr = hest_get_elog_addr();

	return size;
}

static unsigned long acpi_fill_hest(acpi_hest_t *hest)
{
	acpi_header_t *header = &(hest->header);
	void *current;
	current = (void *)(hest);
	void *next = current;
	next = hest + 1;
	next += acpi_hest_add_ghes(next);
	hest->error_source_count += 1;
	header->length += next - current;
	return header->length;
}

unsigned long hest_create(unsigned long current, struct acpi_rsdp *rsdp)
{
	struct global_nvs *gnvs;
	acpi_hest_t *hest;

	/* Reserve memory for Enhanced error logging */
	void *mem = cbmem_add(CMBMEM_ID_ACPI_HEST, CONFIG_ERROR_LOG_BUFFER_SIZE);
	if (!mem) {
		printk(BIOS_ERR, "Unable to allocate HEST memory\n");
		return current;
	}

	printk(BIOS_DEBUG, "HEST memory created: %p\n", mem);
	gnvs = acpi_get_gnvs();
	if (!gnvs) {
		printk(BIOS_ERR, "Unable to get gnvs\n");
		return current;
	}
	gnvs->hest_log_addr = (uintptr_t)mem;
	printk(BIOS_DEBUG, "elog_addr: %llx, size:%x\n", gnvs->hest_log_addr,
		CONFIG_ERROR_LOG_BUFFER_SIZE);

	current = ALIGN(current, 8);
	hest = (acpi_hest_t *)current;
	acpi_write_hest(hest, acpi_fill_hest);
	acpi_add_table(rsdp, (void *)current);
	current += hest->header.length;
	return current;
}
