// 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 #include #include #include #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); }