#include "kvm/fdt.h"
#include "kvm/kvm.h"
#include "kvm/kvm-cpu.h"
#include "kvm/util.h"

#include "arm-common/gic.h"
#include "arm-common/pmu.h"

#ifdef CONFIG_ARM64
static int set_pmu_attr(struct kvm *kvm, int vcpu_idx,
			struct kvm_device_attr *attr)
{
	int ret, fd;

	fd = kvm->cpus[vcpu_idx]->vcpu_fd;

	ret = ioctl(fd, KVM_HAS_DEVICE_ATTR, attr);
	if (!ret) {
		ret = ioctl(fd, KVM_SET_DEVICE_ATTR, attr);
		if (ret)
			pr_err("PMU KVM_SET_DEVICE_ATTR failed (%d)\n", ret);
	} else {
		pr_err("Unsupported PMU on vcpu%d\n", vcpu_idx);
	}

	return ret;
}

void pmu__generate_fdt_nodes(void *fdt, struct kvm *kvm)
{
	const char compatible[] = "arm,armv8-pmuv3";
	int irq = KVM_ARM_PMUv3_PPI;
	int i, ret;

	u32 cpu_mask = (((1 << kvm->nrcpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) \
		       & GIC_FDT_IRQ_PPI_CPU_MASK;
	u32 irq_prop[] = {
		cpu_to_fdt32(GIC_FDT_IRQ_TYPE_PPI),
		cpu_to_fdt32(irq - 16),
		cpu_to_fdt32(cpu_mask | IRQ_TYPE_LEVEL_HIGH),
	};

	if (!kvm->cfg.arch.has_pmuv3)
		return;

	if (!kvm__supports_extension(kvm, KVM_CAP_ARM_PMU_V3)) {
		pr_info("PMU unsupported\n");
		return;
	}

	for (i = 0; i < kvm->nrcpus; i++) {
		struct kvm_device_attr pmu_attr;

		pmu_attr = (struct kvm_device_attr){
			.group	= KVM_ARM_VCPU_PMU_V3_CTRL,
			.addr	= (u64)(unsigned long)&irq,
			.attr	= KVM_ARM_VCPU_PMU_V3_IRQ,
		};

		ret = set_pmu_attr(kvm, i, &pmu_attr);
		if (ret)
			return;

		pmu_attr = (struct kvm_device_attr){
			.group	= KVM_ARM_VCPU_PMU_V3_CTRL,
			.attr	= KVM_ARM_VCPU_PMU_V3_INIT,
		};

		ret = set_pmu_attr(kvm, i, &pmu_attr);
		if (ret)
			return;
	}

	_FDT(fdt_begin_node(fdt, "pmu"));
	_FDT(fdt_property(fdt, "compatible", compatible, sizeof(compatible)));
	_FDT(fdt_property(fdt, "interrupts", irq_prop, sizeof(irq_prop)));
	_FDT(fdt_end_node(fdt));
}
#else
void pmu__generate_fdt_nodes(void *fdt, struct kvm *kvm) { }
#endif
