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

#ifndef CPU_X86_SMM_H
#define CPU_X86_SMM_H

#include <arch/cpu.h>
#include <commonlib/region.h>
#include <device/pci_type.h>
#include <device/resource.h>
#include <types.h>

#define SMM_DEFAULT_BASE 0x30000
#define SMM_DEFAULT_SIZE 0x10000

/* used only by C programs so far */
#define SMM_BASE 0xa0000

#define SMM_ENTRY_OFFSET 0x8000
#define SMM_SAVE_STATE_BEGIN(x) (SMM_ENTRY_OFFSET + (x))

#define APM_CNT		0xb2
#define APM_CNT_NOOP_SMI	0x00
#define APM_CNT_ACPI_DISABLE	0x1e
#define APM_CNT_ACPI_ENABLE	0xe1
#define APM_CNT_ROUTE_ALL_XHCI	0xca
#define APM_CNT_FINALIZE	0xcb
#define APM_CNT_LEGACY		0xcc
#define APM_CNT_MBI_UPDATE	0xeb
#define APM_CNT_SMMINFO		0xec
#define APM_CNT_SMMSTORE	0xed
#define APM_CNT_ELOG_GSMI	0xef
#define APM_STS		0xb3

#define SMM_PCI_RESOURCE_STORE_NUM_RESOURCES 6

/*
 * SMI Transfer Monitor (STM) descriptor reserved in SMM save state.
 */
#if CONFIG(STM)
#define STM_PSD_SIZE ALIGN_UP(sizeof(TXT_PROCESSOR_SMM_DESCRIPTOR), 0x100)
#else
#define STM_PSD_SIZE 0
#endif

/* Send cmd to APM_CNT with HAVE_SMI_HANDLER checking. */
int apm_control(u8 cmd);
u8 apm_get_apmc(void);

void io_trap_handler(int smif);
int mainboard_io_trap_handler(int smif);

void southbridge_smi_set_eos(void);

void global_smi_enable(void);
void global_smi_enable_no_pwrbtn(void);

void cpu_smi_handler(void);
void northbridge_smi_handler(void);
void southbridge_smi_handler(void);

void mainboard_smi_gpi(u32 gpi_sts);
int  mainboard_smi_apmc(u8 data);
void mainboard_smi_sleep(u8 slp_typ);
void mainboard_smi_finalize(void);
int mainboard_set_smm_log_level(void);

void smm_soc_early_init(void);
void smm_soc_exit(void);

/* This is the SMM handler. */
extern unsigned char _binary_smm_start[];
extern unsigned char _binary_smm_end[];

struct smm_pci_resource_info {
	pci_devfn_t pci_addr;
	uint16_t class_device;
	uint8_t class_prog;
	struct resource resources[SMM_PCI_RESOURCE_STORE_NUM_RESOURCES];
};

struct smm_runtime {
	u32 smbase;
	u32 smm_size;
	u32 save_state_size;
	u32 num_cpus;
	u32 gnvs_ptr;
	u32 cbmemc_size;
	void *cbmemc;
#if CONFIG(SMM_PCI_RESOURCE_STORE)
	struct smm_pci_resource_info pci_resources[CONFIG_SMM_PCI_RESOURCE_STORE_NUM_SLOTS];
#endif
	uintptr_t save_state_top[CONFIG_MAX_CPUS];
	int smm_log_level;
} __packed;

struct smm_module_params {
	size_t cpu;
	/* A canary value that has been placed at the end of the stack.
	 * If (uintptr_t)canary != *canary then a stack overflow has occurred.
	 */
	const uintptr_t *canary;
};

/* These parameters are used by the SMM stub code. A pointer to the params
 * is also passed to the C-base handler. */
struct smm_stub_params {
	u32 stack_size;
	u32 stack_top;
	u32 c_handler;
	/* The apic_id_to_cpu provides a mapping from APIC id to CPU number.
	 * The CPU number is indicated by the index into the array by matching
	 * the default APIC id and value at the index. The stub loader
	 * initializes this array with a 1:1 mapping. If the APIC ids are not
	 * contiguous like the 1:1 mapping it is up to the caller of the stub
	 * loader to adjust this mapping. */
	u16 apic_id_to_cpu[CONFIG_MAX_CPUS];
} __packed;

/* smm_handler_t is called with arg of smm_module_params pointer. */
typedef asmlinkage void (*smm_handler_t)(void *);

/* SMM Runtime helpers. */
#if ENV_SMM
extern struct global_nvs *gnvs;
#endif

/* Entry point for SMM modules. */
asmlinkage void smm_handler_start(void *params);

/* Retrieve SMM save state for a given CPU. WARNING: This does not take into
 * account CPUs which are configured to not save their state to RAM. */
void *smm_get_save_state(int cpu);

/* Returns true if the region overlaps with the SMM */
bool smm_region_overlaps_handler(const struct region *r);

/* Returns true if the memory pointed to overlaps with SMM reserved memory. */
static inline bool smm_points_to_smram(const void *ptr, const size_t len)
{
	const struct region r = {(uintptr_t)ptr, len};

	return smm_region_overlaps_handler(&r);
}

/* SMM Module Loading API */

/* The smm_loader_params structure provides direction to the SMM loader:
 * - num_cpus - number of concurrent cpus in handler needing stack
 *                           optional for setting up relocation handler.
 * - cpu_save_state_size - the SMM save state size per cpu
 * - num_concurrent_save_states - number of concurrent cpus needing save state
 *                                space
 * - handler - optional handler to call. Only used during SMM relocation setup.
 * - runtime - this field is a result only. The SMM runtime location is filled
 *             into this field so the code doing the loading can manipulate the
 *             runtime's assumptions. e.g. updating the APIC id to CPU map to
 *             handle sparse APIC id space.
 */
struct smm_loader_params {
	size_t num_cpus;

	size_t cpu_save_state_size;
	size_t num_concurrent_save_states;

	smm_handler_t handler;
};

/* All of these return 0 on success, < 0 on failure. */
int smm_setup_stack(const uintptr_t perm_smbase, const size_t perm_smram_size,
		    const unsigned int total_cpus, const size_t stack_size);
int smm_setup_relocation_handler(struct smm_loader_params *params);
int smm_load_module(uintptr_t smram_base, size_t smram_size, struct smm_loader_params *params);

u32 smm_get_cpu_smbase(unsigned int cpu_num);

/* Backup and restore default SMM region. */
void *backup_default_smm_area(void);
void restore_default_smm_area(void *smm_save_area);

/*
 * Fills in the arguments for the entire SMM region covered by chipset
 * protections. e.g. TSEG.
 */
void smm_region(uintptr_t *start, size_t *size);

static inline void aseg_region(uintptr_t *start, size_t *size)
{
	*start = SMM_BASE;
	*size = SMM_DEFAULT_SIZE; /* SMM_CODE_SEGMENT_SIZE ? */
}

enum {
	/* SMM handler area. */
	SMM_SUBREGION_HANDLER,
	/* SMM cache region. */
	SMM_SUBREGION_CACHE,
	/* Chipset specific area. */
	SMM_SUBREGION_CHIPSET,
	/* Total sub regions supported. */
	SMM_SUBREGION_NUM,
};

/* Fills in the start and size for the requested SMM subregion. Returns
 * 0 on success, < 0 on failure. */
int smm_subregion(int sub, uintptr_t *start, size_t *size);

/* Print the SMM memory layout on console. */
void smm_list_regions(void);

#define SMM_REVISION_OFFSET_FROM_TOP (0x8000 - 0x7efc)
/* Return the SMM save state revision. The revision can be fetched from the smm savestate
   which is always at the same offset downward from the top of the save state. */
uint32_t smm_revision(void);
/* Returns the PM ACPI SMI port. On Intel systems this typically not configurable (APM_CNT, 0xb2).
   On AMD systems it is sometimes configurable. */
uint16_t pm_acpi_smi_cmd_port(void);

const volatile struct smm_pci_resource_info *smm_get_pci_resource_store(void);

void smm_pci_get_stored_resources(const volatile struct smm_pci_resource_info **out_slots,
				  size_t *out_size);
/* Weak handler function to store PCI BARs. */
void smm_mainboard_pci_resource_store_init(struct smm_pci_resource_info *slots, size_t size);
/* Helper function to fill BARs from an array of device pointers. */
bool smm_pci_resource_store_fill_resources(struct smm_pci_resource_info *slots, size_t num_slots,
					   const struct device **devices, size_t num_devices);

void smm_pci_resource_store_init(struct smm_runtime *smm_runtime);

#endif /* CPU_X86_SMM_H */