// SPDX-License-Identifier: GPL-2.0
/*
 * Guest ITS library, generously donated by drivers/irqchip/irq-gic-v3-its.c
 * over in the kernel tree.
 */

#include <linux/kvm.h>
#include <linux/sizes.h>
#include <asm/kvm_para.h>
#include <asm/kvm.h>

#include "kvm_util.h"
#include "vgic.h"
#include "gic.h"
#include "gic_v3.h"
#include "processor.h"

#define GITS_COLLECTION_TARGET_SHIFT 16

static u64 its_read_u64(unsigned long offset)
{
	return readq_relaxed(GITS_BASE_GVA + offset);
}

static void its_write_u64(unsigned long offset, u64 val)
{
	writeq_relaxed(val, GITS_BASE_GVA + offset);
}

static u32 its_read_u32(unsigned long offset)
{
	return readl_relaxed(GITS_BASE_GVA + offset);
}

static void its_write_u32(unsigned long offset, u32 val)
{
	writel_relaxed(val, GITS_BASE_GVA + offset);
}

static unsigned long its_find_baser(unsigned int type)
{
	int i;

	for (i = 0; i < GITS_BASER_NR_REGS; i++) {
		u64 baser;
		unsigned long offset = GITS_BASER + (i * sizeof(baser));

		baser = its_read_u64(offset);
		if (GITS_BASER_TYPE(baser) == type)
			return offset;
	}

	GUEST_FAIL("Couldn't find an ITS BASER of type %u", type);
	return -1;
}

static void its_install_table(unsigned int type, gpa_t base, size_t size)
{
	unsigned long offset = its_find_baser(type);
	u64 baser;

	baser = ((size / SZ_64K) - 1) |
		GITS_BASER_PAGE_SIZE_64K |
		GITS_BASER_InnerShareable |
		base |
		GITS_BASER_RaWaWb |
		GITS_BASER_VALID;

	its_write_u64(offset, baser);
}

static void its_install_cmdq(gpa_t base, size_t size)
{
	u64 cbaser;

	cbaser = ((size / SZ_4K) - 1) |
		 GITS_CBASER_InnerShareable |
		 base |
		 GITS_CBASER_RaWaWb |
		 GITS_CBASER_VALID;

	its_write_u64(GITS_CBASER, cbaser);
}

void its_init(gpa_t coll_tbl, size_t coll_tbl_sz, gpa_t device_tbl,
	      size_t device_tbl_sz, gpa_t cmdq, size_t cmdq_size)
{
	u32 ctlr;

	its_install_table(GITS_BASER_TYPE_COLLECTION, coll_tbl, coll_tbl_sz);
	its_install_table(GITS_BASER_TYPE_DEVICE, device_tbl, device_tbl_sz);
	its_install_cmdq(cmdq, cmdq_size);

	ctlr = its_read_u32(GITS_CTLR);
	ctlr |= GITS_CTLR_ENABLE;
	its_write_u32(GITS_CTLR, ctlr);
}

struct its_cmd_block {
	union {
		u64	raw_cmd[4];
		__le64	raw_cmd_le[4];
	};
};

static inline void its_fixup_cmd(struct its_cmd_block *cmd)
{
	/* Let's fixup BE commands */
	cmd->raw_cmd_le[0] = cpu_to_le64(cmd->raw_cmd[0]);
	cmd->raw_cmd_le[1] = cpu_to_le64(cmd->raw_cmd[1]);
	cmd->raw_cmd_le[2] = cpu_to_le64(cmd->raw_cmd[2]);
	cmd->raw_cmd_le[3] = cpu_to_le64(cmd->raw_cmd[3]);
}

static void its_mask_encode(u64 *raw_cmd, u64 val, int h, int l)
{
	u64 mask = GENMASK_ULL(h, l);
	*raw_cmd &= ~mask;
	*raw_cmd |= (val << l) & mask;
}

static void its_encode_cmd(struct its_cmd_block *cmd, u8 cmd_nr)
{
	its_mask_encode(&cmd->raw_cmd[0], cmd_nr, 7, 0);
}

static void its_encode_devid(struct its_cmd_block *cmd, u32 devid)
{
	its_mask_encode(&cmd->raw_cmd[0], devid, 63, 32);
}

static void its_encode_event_id(struct its_cmd_block *cmd, u32 id)
{
	its_mask_encode(&cmd->raw_cmd[1], id, 31, 0);
}

static void its_encode_phys_id(struct its_cmd_block *cmd, u32 phys_id)
{
	its_mask_encode(&cmd->raw_cmd[1], phys_id, 63, 32);
}

static void its_encode_size(struct its_cmd_block *cmd, u8 size)
{
	its_mask_encode(&cmd->raw_cmd[1], size, 4, 0);
}

static void its_encode_itt(struct its_cmd_block *cmd, u64 itt_addr)
{
	its_mask_encode(&cmd->raw_cmd[2], itt_addr >> 8, 51, 8);
}

static void its_encode_valid(struct its_cmd_block *cmd, int valid)
{
	its_mask_encode(&cmd->raw_cmd[2], !!valid, 63, 63);
}

static void its_encode_target(struct its_cmd_block *cmd, u64 target_addr)
{
	its_mask_encode(&cmd->raw_cmd[2], target_addr >> 16, 51, 16);
}

static void its_encode_collection(struct its_cmd_block *cmd, u16 col)
{
	its_mask_encode(&cmd->raw_cmd[2], col, 15, 0);
}

static u64 procnum_to_rdbase(u32 vcpu_id)
{
	return vcpu_id << GITS_COLLECTION_TARGET_SHIFT;
}

#define GITS_CMDQ_POLL_ITERATIONS	0

static void its_send_cmd(void *cmdq_base, struct its_cmd_block *cmd)
{
	u64 cwriter = its_read_u64(GITS_CWRITER);
	struct its_cmd_block *dst = cmdq_base + cwriter;
	u64 cbaser = its_read_u64(GITS_CBASER);
	size_t cmdq_size;
	u64 next;
	int i;

	cmdq_size = ((cbaser & 0xFF) + 1) * SZ_4K;

	its_fixup_cmd(cmd);

	WRITE_ONCE(*dst, *cmd);
	dsb(ishst);
	next = (cwriter + sizeof(*cmd)) % cmdq_size;
	its_write_u64(GITS_CWRITER, next);

	/*
	 * Polling isn't necessary considering KVM's ITS emulation at the time
	 * of writing this, as the CMDQ is processed synchronously after a write
	 * to CWRITER.
	 */
	for (i = 0; its_read_u64(GITS_CREADR) != next; i++) {
		__GUEST_ASSERT(i < GITS_CMDQ_POLL_ITERATIONS,
			       "ITS didn't process command at offset %lu after %d iterations\n",
			       cwriter, i);

		cpu_relax();
	}
}

void its_send_mapd_cmd(void *cmdq_base, u32 device_id, gpa_t itt_base,
		       size_t itt_size, bool valid)
{
	struct its_cmd_block cmd = {};

	its_encode_cmd(&cmd, GITS_CMD_MAPD);
	its_encode_devid(&cmd, device_id);
	its_encode_size(&cmd, ilog2(itt_size) - 1);
	its_encode_itt(&cmd, itt_base);
	its_encode_valid(&cmd, valid);

	its_send_cmd(cmdq_base, &cmd);
}

void its_send_mapc_cmd(void *cmdq_base, u32 vcpu_id, u32 collection_id, bool valid)
{
	struct its_cmd_block cmd = {};

	its_encode_cmd(&cmd, GITS_CMD_MAPC);
	its_encode_collection(&cmd, collection_id);
	its_encode_target(&cmd, procnum_to_rdbase(vcpu_id));
	its_encode_valid(&cmd, valid);

	its_send_cmd(cmdq_base, &cmd);
}

void its_send_mapti_cmd(void *cmdq_base, u32 device_id, u32 event_id,
			u32 collection_id, u32 intid)
{
	struct its_cmd_block cmd = {};

	its_encode_cmd(&cmd, GITS_CMD_MAPTI);
	its_encode_devid(&cmd, device_id);
	its_encode_event_id(&cmd, event_id);
	its_encode_phys_id(&cmd, intid);
	its_encode_collection(&cmd, collection_id);

	its_send_cmd(cmdq_base, &cmd);
}

void its_send_invall_cmd(void *cmdq_base, u32 collection_id)
{
	struct its_cmd_block cmd = {};

	its_encode_cmd(&cmd, GITS_CMD_INVALL);
	its_encode_collection(&cmd, collection_id);

	its_send_cmd(cmdq_base, &cmd);
}

void its_send_sync_cmd(void *cmdq_base, u32 vcpu_id)
{
	struct its_cmd_block cmd = {};

	its_encode_cmd(&cmd, GITS_CMD_SYNC);
	its_encode_target(&cmd, procnum_to_rdbase(vcpu_id));

	its_send_cmd(cmdq_base, &cmd);
}
