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

#include <arch/mmio.h>
#include <bootstate.h>
#include <amdblocks/reset.h>
#include <console/console.h>
#include <cpu/amd/msr.h>
#include <types.h>
#include "psp_def.h"

#define PSB_STATUS_OFFSET	0x10994

#define FUSE_PLATFORM_SECURE_BOOT_EN	BIT(24)

#define PSB_TEST_STATUS_MASK		0xff
#define PSB_FUSING_READY_MASK		BIT(8)

/* PSB Test Status and Error Codes (doc#56654) */
#define PSB_TEST_STATUS_PASS			0x00
#define PSB_TEST_STATUS_FUSE_READ_ERR		0x3e
#define PSB_TEST_STATUS_BIOS_KEY_BAD_USAGE	0x81
#define PSB_TEST_STATUS_BIOS_RTM_SIG_NOENT	0x82
#define PSB_TEST_STATUS_BIOS_RTM_COPY_ERR	0x83
#define PSB_TEST_STATUS_BIOS_RTM_BAD_SIG	0x84
#define PSB_TEST_STATUS_BIOS_KEY_BAD_SIG	0x85
#define PSB_TEST_STATUS_PLATFORM_BAD_ID		0x86
#define PSB_TEST_STATUS_BIOS_COPY_BIT_UNSET	0x87
#define PSB_TEST_STATUS_BIOS_CA_BAD_SIG		0x8a
#define PSB_TEST_STATUS_BIOS_CA_BAD_USAGE	0x8b
#define PSB_TEST_STATUS_BIOS_KEY_BAD_REVISION	0x8c

#define FUSE_STATUS_SUCCESS	0x00
#define FUSE_STATUS_NOT_ALLOWED	0x09
#define FUSE_STATUS_FUSING_ERR	0x0a
#define FUSE_STATUS_BOOT_DONE	0x0b

static const char *psb_test_status_to_string(u32 status)
{
	switch (status) {
	case PSB_TEST_STATUS_PASS:
		return "Psb Test Status PASS";
	case PSB_TEST_STATUS_FUSE_READ_ERR:
		return "Error reading fuse info";
	case PSB_TEST_STATUS_BIOS_KEY_BAD_USAGE:
		return "OEM BIOS signing key usage flag violation";
	case PSB_TEST_STATUS_BIOS_RTM_SIG_NOENT:
		return "BIOS RTM signature entry not found";
	case PSB_TEST_STATUS_BIOS_RTM_COPY_ERR:
		return "BIOS copy to DRAM failed";
	case PSB_TEST_STATUS_BIOS_RTM_BAD_SIG:
		return "BIOS RTM signature verification failed";
	case PSB_TEST_STATUS_BIOS_KEY_BAD_SIG:
		return "OEM BIOS signing key failed signature verification";
	case PSB_TEST_STATUS_PLATFORM_BAD_ID:
		return "Platform vendor id and/or model id binding violation";
	case PSB_TEST_STATUS_BIOS_COPY_BIT_UNSET:
		return "BIOS copy bit unset for reset image";
	case PSB_TEST_STATUS_BIOS_CA_BAD_SIG:
		return "OEM BIOS signing CA key failed signature verification";
	case PSB_TEST_STATUS_BIOS_CA_BAD_USAGE:
		return "OEM BIOS signing CA key usage flag violation";
	case PSB_TEST_STATUS_BIOS_KEY_BAD_REVISION:
		return "OEM BIOS signing key revision violation";
	default:
		return "Unknown failure";
	}
}

static const char *fuse_status_to_string(u32 status)
{
	switch (status) {
	case FUSE_STATUS_SUCCESS:
		return "PSB Fusing completed successfully";
	case FUSE_STATUS_NOT_ALLOWED:
		return "Fusing not allowed or already done";
	case FUSE_STATUS_FUSING_ERR:
		return "Fuse programming failed";
	case FUSE_STATUS_BOOT_DONE:
		return "Issued after BOOT DONE";
	default:
		return "Unknown failure";
	}
}

static uint32_t get_psb_status(void)
{
	return smn_read32(SMN_PSP_PUBLIC_BASE + PSB_STATUS_OFFSET);
}

/*
 * Request Platform Secure Boot enablement via the PSP if it is not already
 * enabled. Upon receiving this command, the PSP will program all PSB fuses
 * so long as the BIOS signing key token is valid.
 */
static enum cb_err psb_enable(void)
{
	u32 status;
	struct mbox_default_buffer buffer = {
		.header = {
			.size = sizeof(buffer)
		}
	};

	status = get_psb_status();
	printk(BIOS_INFO, "PSB: Status = %x\n", status);

	if (status & FUSE_PLATFORM_SECURE_BOOT_EN) {
		printk(BIOS_DEBUG, "PSB: Already enabled\n");
		return CB_SUCCESS;
	}

	status = soc_read_c2p38();
	printk(BIOS_INFO, "PSB: HSTI = %x\n", status);

	const u32 psb_test_status = status & PSB_TEST_STATUS_MASK;

	if (psb_test_status != PSB_TEST_STATUS_PASS) {
		printk(BIOS_ERR, "PSB: %s\n", psb_test_status_to_string(psb_test_status));
		return CB_ERR;
	}

	if (!(status & PSB_FUSING_READY_MASK)) {
		printk(BIOS_ERR, "PSB: Fusing not allowed\n");
		return CB_ERR;
	}

	printk(BIOS_DEBUG, "PSB: Enable... ");

	const int cmd_status = send_psp_command(MBOX_BIOS_CMD_PSB_AUTO_FUSING, &buffer);

	psp_print_cmd_status(cmd_status, &buffer.header);

	if (cmd_status) {
		printk(BIOS_ERR, "PSB: Fusing request failed: %d\n", cmd_status);
		return CB_ERR;
	}

	const u32 fuse_status = read32(&buffer.header.status);
	if (fuse_status != FUSE_STATUS_SUCCESS) {
		printk(BIOS_ERR, "PSB: %s\n", fuse_status_to_string(fuse_status));
		return CB_ERR;
	}

	printk(BIOS_NOTICE, "PSB: Rebooting\n");
	cold_reset();
}

static void enable_secure_boot(void *unused)
{
	/*
	 * Enable secure boot before loading payload. Psb fusing is done late in
	 * boot process to avoid any fuse access conflicts with other components
	 * which happens during boot process.
	 */
	if (psb_enable() == CB_ERR)
		printk(BIOS_NOTICE, "Enabling PSB failed.\n");
}

BOOT_STATE_INIT_ENTRY(BS_PAYLOAD_LOAD, BS_ON_ENTRY, enable_secure_boot, NULL);