/*
 * Copyright (C) 2018-2022 Intel Corporation.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#ifndef PTDEV_H
#define PTDEV_H
#include <list.h>
#include <asm/lib/spinlock.h>
#include <timer.h>
#include <vacpi.h>


enum intx_ctlr {
	INTX_CTLR_IOAPIC	= 0U,
	INTX_CTLR_PIC
};

#define PTDEV_INTR_MSI		(1U << 0U)
#define PTDEV_INTR_INTX		(1U << 1U)

#define GPU_OPREGION_SIZE	0x5000U
#define GPU_OPREGION_GPA	(VIRT_ACPI_DATA_ADDR  - GPU_OPREGION_SIZE)
#define PCIR_ASLS_CTL		0xfcU /* register offset in PCIe configuration space for Opregion base address */
#define PCIM_ASLS_OPREGION_MASK	0xfffff000U /* opregion need 4KB aligned */

#define INVALID_PTDEV_ENTRY_ID 0xffffU

#define DEFINE_MSI_SID(name, a, b)	\
union source_id (name) = {.msi_id = {.bdf = (a), .entry_nr = (b)} }

#define DEFINE_INTX_SID(name, a, b)	\
union source_id (name) = {.intx_id = {.gsi = (a), .ctlr = (b)} }

union irte_index {
	uint16_t index;
	struct {
		uint16_t index_low:15;
		uint16_t index_high:1;
	} bits __packed;
};


union source_id {
	uint64_t value;
	struct {
		uint16_t bdf;
		uint16_t entry_nr;
		uint32_t reserved;
	} msi_id;
	/*
	 * ctlr indicates if the source of interrupt is IO-APIC or PIC
	 * pin indicates the pin number of interrupt controller determined by ctlr
	 */
	struct {
		enum intx_ctlr ctlr;
		uint32_t gsi;
	} intx_id;
};

/*
 * Macros for bits in union msi_addr_reg
 */

#define	MSI_ADDR_BASE			0xfeeUL	/* Base address for MSI messages */
#define	MSI_ADDR_RH			0x1U	/* Redirection Hint */
#define	MSI_ADDR_DESTMODE_LOGICAL	0x1U	/* Destination Mode: Logical*/
#define	MSI_ADDR_DESTMODE_PHYS		0x0U	/* Destination Mode: Physical*/

union msi_addr_reg {
	uint64_t full;
	struct {
		uint32_t rsvd_1:2;
		uint32_t dest_mode:1;
		uint32_t rh:1;
		uint32_t rsvd_2:8;
		uint32_t dest_field:8;
		uint32_t addr_base:12;
		uint32_t hi_32;
	} bits __packed;
	struct {
		uint32_t rsvd_1:2;
		uint32_t intr_index_high:1;
		uint32_t shv:1;
		uint32_t intr_format:1;
		uint32_t intr_index_low:15;
		uint32_t constant:12;
		uint32_t hi_32;
	} ir_bits __packed;

};

/*
 * Macros for bits in union msi_data_reg
 */

#define MSI_DATA_DELMODE_FIXED		0x0U	/* Delivery Mode: Fixed */
#define MSI_DATA_DELMODE_LOPRI		0x1U	/* Delivery Mode: Low Priority */
#define MSI_DATA_TRGRMODE_EDGE		0x0U	/* Trigger Mode: Edge */
#define MSI_DATA_TRGRMODE_LEVEL		0x1U	/* Trigger Mode: Level */

union msi_data_reg {
	uint32_t full;
	struct {
		uint32_t vector:8;
		uint32_t delivery_mode:3;
		uint32_t rsvd_1:3;
		uint32_t level:1;
		uint32_t trigger_mode:1;
		uint32_t rsvd_2:16;
	} bits __packed;
};

struct msi_info {
	union msi_addr_reg addr;
	union msi_data_reg data;
};

struct ptirq_remapping_info;
typedef void (*ptirq_arch_release_fn_t)(const struct ptirq_remapping_info *entry);

/* entry per each allocated irq/vector
 * it represents a pass-thru device's remapping data entry which collecting
 * information related with its vm and msi/intx mapping & interaction nodes
 * with interrupt handler and softirq.
 */
struct ptirq_remapping_info {
	struct hlist_node phys_link;
	struct hlist_node virt_link;
	uint16_t ptdev_entry_id;
	uint32_t intr_type;
	union source_id phys_sid;
	union source_id virt_sid;
	struct acrn_vm *vm;
	bool active;	/* true=active, false=inactive*/
	uint32_t allocated_pirq;
	uint32_t polarity; /* 0=active high, 1=active low*/
	struct list_head softirq_node;
	struct msi_info vmsi;
	struct msi_info pmsi;
	uint16_t irte_idx;

	uint64_t intr_count;
	struct hv_timer intr_delay_timer; /* used for delay intr injection */
	ptirq_arch_release_fn_t release_cb;
};

static inline bool is_entry_active(const struct ptirq_remapping_info *entry)
{
	return entry->active;
}

extern struct ptirq_remapping_info ptirq_entries[CONFIG_MAX_PT_IRQ_ENTRIES];
extern spinlock_t ptdev_lock;

/**
 * @file ptdev.h
 *
 * @brief public APIs for ptdev
 */

/**
 * @brief ptdev
 *
 * @addtogroup acrn_passthrough
 * @{
 */


/*
 * @brief Find a ptdev entry by sid
 *
 * param[in] intr_type interrupt type of the ptirq entry
 * param[in] sid source id of the ptirq entry
 * param[in] vm vm pointer of the ptirq entry if find the ptdev entry by virtual sid
 *
 * @retval NULL when no ptirq entry match the sid
 * @retval ptirq entry when there is available ptirq entry match the sid
 *
 * @pre: vm must be NULL when lookup by physical sid, otherwise,
 * vm must not be NULL when lookup by virtual sid.
 */
struct ptirq_remapping_info *find_ptirq_entry(uint32_t intr_type,
		const union source_id *sid, const struct acrn_vm *vm);

/**
 * @brief Handler of softirq for passthrough device.
 *
 * When hypervisor receive a physical interrupt from passthrough device, it
 * will enqueue a ptirq entry and raise softirq SOFTIRQ_PTDEV. This function
 * is the handler of the softirq, it handles the interrupt and injects the
 * virtual into VM.
 * The handler is registered by calling @ref ptdev_init during hypervisor
 * initialization.
 *
 * @param[in]    pcpu_id physical cpu id of the soft irq
 *
 */
void ptirq_softirq(uint16_t pcpu_id);
/**
 * @brief Passthrough device global data structure initialization.
 *
 * During the hypervisor cpu initialization stage, this function:
 * - init global spinlock for ptdev (on BSP)
 * - register SOFTIRQ_PTDEV handler (on BSP)
 * - init the softirq entry list for each CPU
 *
 */
void ptdev_init(void);
/**
 * @brief Deactivate and release all ptirq entries for a VM.
 *
 * This function deactivates and releases all ptirq entries for a VM. The function
 * should only be called after the VM is already down.
 *
 * @param[in]    vm acrn_vm on which the ptirq entries will be released
 *
 * @pre VM is already down
 *
 */
void ptdev_release_all_entries(const struct acrn_vm *vm);

/**
 * @brief Dequeue an entry from per cpu ptdev softirq queue.
 *
 * Dequeue an entry from the ptdev softirq queue on the specific physical cpu.
 *
 * @param[in]    pcpu_id physical cpu id
 *
 * @retval NULL when the queue is empty
 * @retval !NULL when there is available ptirq_remapping_info entry in the queue
 *
 */
struct ptirq_remapping_info *ptirq_dequeue_softirq(uint16_t pcpu_id);
/**
 * @brief Allocate a ptirq_remapping_info entry.
 *
 * Allocate a ptirq_remapping_info entry for hypervisor to store the remapping information.
 * The total number of the entries is statically defined as CONFIG_MAX_PT_IRQ_ENTRIES.
 * Appropriate number should be configured on different platforms.
 *
 * @param[in]    vm acrn_vm that the entry allocated for.
 * @param[in]    intr_type interrupt type: PTDEV_INTR_MSI or PTDEV_INTR_INTX
 *
 * @retval NULL when the number of entries allocated is CONFIG_MAX_PT_IRQ_ENTRIES
 * @retval !NULL when the number of entries allocated is less than CONFIG_MAX_PT_IRQ_ENTRIES
 *
 */
struct ptirq_remapping_info *ptirq_alloc_entry(struct acrn_vm *vm, uint32_t intr_type);
/**
 * @brief Release a ptirq_remapping_info entry.
 *
 * @param[in]    entry the ptirq_remapping_info entry to release.
 *
 */
void ptirq_release_entry(struct ptirq_remapping_info *entry);
/**
 * @brief Activate a irq for the associated passthrough device.
 *
 * After activating the ptirq entry, the physical interrupt irq of passthrough device will be handled
 * by the handler  ptirq_interrupt_handler.
 *
 * @param[in]    entry the ptirq_remapping_info entry that will be associated with the physical irq.
 * @param[in]    phys_irq physical interrupt irq for the entry
 *
 * @retval success when return value >=0
 * @retval failure when return value < 0
 *
 */
int32_t ptirq_activate_entry(struct ptirq_remapping_info *entry, uint32_t phys_irq);
/**
 * @brief De-activate a irq for the associated passthrough device.
 *
 * @param[in]    entry the ptirq_remapping_info entry that will be de-activated.
 *
 */
void ptirq_deactivate_entry(struct ptirq_remapping_info *entry);
/**
 * @brief Get the interrupt information and store to the buffer provided.
 *
 * @param[in]    target_vm the VM to get the interrupt information.
 * @param[out]   buffer where interrupt information is stored.
 * @param[in]    buffer_cnt the size of the buffer.
 *
 * @retval the actual size the buffer filled with the interrupt information
 *
 */
uint32_t ptirq_get_intr_data(const struct acrn_vm *target_vm, uint64_t *buffer, uint32_t buffer_cnt);

/**
  * @}
  */

#endif /* PTDEV_H */
