Skip to content

Commit

Permalink
KVM: selftests: Add a minimal library for interacting with an ITS
Browse files Browse the repository at this point in the history
A prerequisite of testing LPI injection performance is of course
instantiating an ITS for the guest. Add a small library for creating an
ITS and interacting with it from the guest.

Signed-off-by: Oliver Upton <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Marc Zyngier <[email protected]>
  • Loading branch information
oupton authored and Marc Zyngier committed Apr 25, 2024
1 parent 232269e commit be26db6
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 1 deletion.
1 change: 1 addition & 0 deletions tools/testing/selftests/kvm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ LIBKVM_x86_64 += lib/x86_64/vmx.c

LIBKVM_aarch64 += lib/aarch64/gic.c
LIBKVM_aarch64 += lib/aarch64/gic_v3.c
LIBKVM_aarch64 += lib/aarch64/gic_v3_its.c
LIBKVM_aarch64 += lib/aarch64/handlers.S
LIBKVM_aarch64 += lib/aarch64/processor.c
LIBKVM_aarch64 += lib/aarch64/spinlock.c
Expand Down
8 changes: 7 additions & 1 deletion tools/testing/selftests/kvm/include/aarch64/gic.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@ enum gic_type {
GIC_TYPE_MAX,
};

#define GICD_BASE_GPA 0x8000000ULL
/*
* Note that the redistributor frames are at the end, as the range scales
* with the number of vCPUs in the VM.
*/
#define GITS_BASE_GPA 0x8000000ULL
#define GICD_BASE_GPA (GITS_BASE_GPA + KVM_VGIC_V3_ITS_SIZE)
#define GICR_BASE_GPA (GICD_BASE_GPA + KVM_VGIC_V3_DIST_SIZE)

/* The GIC is identity-mapped into the guest at the time of setup. */
#define GITS_BASE_GVA ((volatile void *)GITS_BASE_GPA)
#define GICD_BASE_GVA ((volatile void *)GICD_BASE_GPA)
#define GICR_BASE_GVA ((volatile void *)GICR_BASE_GPA)

Expand Down
19 changes: 19 additions & 0 deletions tools/testing/selftests/kvm/include/aarch64/gic_v3_its.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 */

#ifndef __SELFTESTS_GIC_V3_ITS_H__
#define __SELFTESTS_GIC_V3_ITS_H__

#include <linux/sizes.h>

void its_init(vm_paddr_t coll_tbl, size_t coll_tbl_sz,
vm_paddr_t device_tbl, size_t device_tbl_sz,
vm_paddr_t cmdq, size_t cmdq_size);

void its_send_mapd_cmd(void *cmdq_base, u32 device_id, vm_paddr_t itt_base,
size_t itt_size, bool valid);
void its_send_mapc_cmd(void *cmdq_base, u32 vcpu_id, u32 collection_id, bool valid);
void its_send_mapti_cmd(void *cmdq_base, u32 device_id, u32 event_id,
u32 collection_id, u32 intid);
void its_send_invall_cmd(void *cmdq_base, u32 collection_id);

#endif // __SELFTESTS_GIC_V3_ITS_H__
2 changes: 2 additions & 0 deletions tools/testing/selftests/kvm/include/aarch64/vgic.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ void kvm_irq_write_isactiver(int gic_fd, uint32_t intid, struct kvm_vcpu *vcpu);

#define KVM_IRQCHIP_NUM_PINS (1020 - 32)

int vgic_its_setup(struct kvm_vm *vm);

#endif // SELFTEST_KVM_VGIC_H
248 changes: 248 additions & 0 deletions tools/testing/selftests/kvm/lib/aarch64/gic_v3_its.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
// 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"

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, vm_paddr_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(vm_paddr_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(vm_paddr_t coll_tbl, size_t coll_tbl_sz,
vm_paddr_t device_tbl, size_t device_tbl_sz,
vm_paddr_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);
}

#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, vm_paddr_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, 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);
}
18 changes: 18 additions & 0 deletions tools/testing/selftests/kvm/lib/aarch64/vgic.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,21 @@ void kvm_irq_write_isactiver(int gic_fd, uint32_t intid, struct kvm_vcpu *vcpu)
{
vgic_poke_irq(gic_fd, intid, vcpu, GICD_ISACTIVER);
}

int vgic_its_setup(struct kvm_vm *vm)
{
int its_fd = kvm_create_device(vm, KVM_DEV_TYPE_ARM_VGIC_ITS);
u64 attr;

attr = GITS_BASE_GPA;
kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
KVM_VGIC_ITS_ADDR_TYPE, &attr);

kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);

virt_map(vm, GITS_BASE_GPA, GITS_BASE_GPA,
vm_calc_num_guest_pages(vm->mode, KVM_VGIC_V3_ITS_SIZE));

return its_fd;
}

0 comments on commit be26db6

Please sign in to comment.