| // Copyright 2024 syzkaller project authors. All rights reserved. |
| // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. |
| |
| // This file provides guest code running inside the ARM64 KVM. |
| |
| #include "common_kvm_syzos.h" |
| #include "kvm.h" |
| #include <linux/kvm.h> |
| #include <stdbool.h> |
| |
| // Compilers will eagerly try to transform the switch statement in guest_main() |
| // into a jump table, unless the cases are sparse enough. |
| // We use prime numbers multiplied by 10 to prevent this behavior. |
| // Remember these constants must match those in sys/linux/dev_kvm_arm64.txt. |
| typedef enum { |
| SYZOS_API_UEXIT = 0, |
| SYZOS_API_CODE = 10, |
| SYZOS_API_MSR = 20, |
| SYZOS_API_SMC = 30, |
| SYZOS_API_HVC = 50, |
| SYZOS_API_IRQ_SETUP = 70, |
| SYZOS_API_MEMWRITE = 110, |
| SYZOS_API_ITS_SETUP = 130, |
| SYZOS_API_ITS_SEND_CMD = 170, |
| SYZOS_API_MRS = 190, |
| SYZOS_API_ERET = 230, |
| SYZOS_API_SVC = 290, |
| SYZOS_API_STOP, // Must be the last one |
| } syzos_api_id; |
| |
| struct api_call_header { |
| uint64 call; |
| uint64 size; |
| }; |
| |
| struct api_call_uexit { |
| struct api_call_header header; |
| uint64 exit_code; |
| }; |
| |
| struct api_call_1 { |
| struct api_call_header header; |
| uint64 arg; |
| }; |
| |
| struct api_call_2 { |
| struct api_call_header header; |
| uint64 args[2]; |
| }; |
| |
| struct api_call_3 { |
| struct api_call_header header; |
| uint64 args[3]; |
| }; |
| |
| struct api_call_code { |
| struct api_call_header header; |
| uint32 insns[]; |
| }; |
| |
| struct api_call_smccc { |
| struct api_call_header header; |
| uint32 func_id; |
| uint64 params[5]; |
| }; |
| |
| struct api_call_irq_setup { |
| struct api_call_header header; |
| uint32 nr_cpus; |
| uint32 nr_spis; |
| }; |
| |
| struct api_call_memwrite { |
| struct api_call_header header; |
| uint64 base_addr; |
| uint64 offset; |
| uint64 value; |
| uint64 len; |
| }; |
| |
| struct api_call_its_send_cmd { |
| struct api_call_header header; |
| uint8 type; |
| uint8 valid; |
| uint32 cpuid; |
| uint32 devid; |
| uint32 eventid; |
| uint32 intid; |
| uint32 cpuid2; |
| }; |
| |
| static void guest_uexit(uint64 exit_code); |
| static void guest_execute_code(uint32* insns, uint64 size); |
| static void guest_handle_mrs(uint64 reg); |
| static void guest_handle_msr(uint64 reg, uint64 val); |
| static void guest_handle_smc(struct api_call_smccc* cmd); |
| static void guest_handle_hvc(struct api_call_smccc* cmd); |
| static void guest_handle_svc(struct api_call_smccc* cmd); |
| static void guest_handle_eret(uint64 unused); |
| static void guest_handle_irq_setup(struct api_call_irq_setup* cmd); |
| static void guest_handle_memwrite(struct api_call_memwrite* cmd); |
| static void guest_handle_its_setup(struct api_call_3* cmd); |
| static void guest_handle_its_send_cmd(struct api_call_its_send_cmd* cmd); |
| |
| typedef enum { |
| UEXIT_END = (uint64)-1, |
| UEXIT_IRQ = (uint64)-2, |
| UEXIT_ASSERT = (uint64)-3, |
| } uexit_code; |
| |
| // Main guest function that performs necessary setup and passes the control to the user-provided |
| // payload. |
| __attribute__((used)) |
| GUEST_CODE static void |
| guest_main(uint64 size, uint64 cpu) |
| { |
| uint64 addr = ARM64_ADDR_USER_CODE + cpu * 0x1000; |
| |
| while (size >= sizeof(struct api_call_header)) { |
| struct api_call_header* cmd = (struct api_call_header*)addr; |
| if (cmd->call >= SYZOS_API_STOP) |
| return; |
| if (cmd->size > size) |
| return; |
| switch (cmd->call) { |
| case SYZOS_API_UEXIT: { |
| struct api_call_uexit* ucmd = (struct api_call_uexit*)cmd; |
| guest_uexit(ucmd->exit_code); |
| break; |
| } |
| case SYZOS_API_CODE: { |
| struct api_call_code* ccmd = (struct api_call_code*)cmd; |
| guest_execute_code(ccmd->insns, cmd->size - sizeof(struct api_call_header)); |
| break; |
| } |
| case SYZOS_API_MRS: { |
| struct api_call_1* ccmd = (struct api_call_1*)cmd; |
| guest_handle_mrs(ccmd->arg); |
| break; |
| } |
| case SYZOS_API_MSR: { |
| struct api_call_2* ccmd = (struct api_call_2*)cmd; |
| guest_handle_msr(ccmd->args[0], ccmd->args[1]); |
| break; |
| } |
| case SYZOS_API_ERET: { |
| struct api_call_1* ccmd = (struct api_call_1*)cmd; |
| guest_handle_eret(ccmd->arg); |
| break; |
| } |
| case SYZOS_API_SMC: { |
| guest_handle_smc((struct api_call_smccc*)cmd); |
| break; |
| } |
| case SYZOS_API_HVC: { |
| guest_handle_hvc((struct api_call_smccc*)cmd); |
| break; |
| } |
| case SYZOS_API_SVC: { |
| guest_handle_svc((struct api_call_smccc*)cmd); |
| break; |
| } |
| case SYZOS_API_IRQ_SETUP: { |
| guest_handle_irq_setup((struct api_call_irq_setup*)cmd); |
| break; |
| } |
| case SYZOS_API_MEMWRITE: { |
| guest_handle_memwrite((struct api_call_memwrite*)cmd); |
| break; |
| } |
| case SYZOS_API_ITS_SETUP: { |
| guest_handle_its_setup((struct api_call_3*)cmd); |
| break; |
| } |
| case SYZOS_API_ITS_SEND_CMD: { |
| guest_handle_its_send_cmd((struct api_call_its_send_cmd*)cmd); |
| break; |
| } |
| } |
| addr += cmd->size; |
| size -= cmd->size; |
| }; |
| guest_uexit((uint64)-1); |
| } |
| |
| // Some ARM chips use 128-byte cache lines. Pick 256 to be on the safe side. |
| #define MAX_CACHE_LINE_SIZE 256 |
| |
| GUEST_CODE static noinline void |
| flush_cache_range(void* addr, uint64 size) |
| { |
| uint64 start = (uint64)addr; |
| uint64 end = start + size; |
| |
| // For self-modifying code, we must clean the D-cache and invalidate the |
| // I-cache for the memory range that was modified. This is the sequence |
| // mandated by the ARMv8-A architecture. |
| |
| // 1. Clean D-cache over the whole range to the Point of Unification. |
| for (uint64 i = start; i < end; i += MAX_CACHE_LINE_SIZE) |
| asm volatile("dc cvau, %[addr]" : : [addr] "r"(i) : "memory"); |
| // 2. Wait for the D-cache clean to complete. |
| asm volatile("dsb sy" : : : "memory"); |
| |
| // 3. Invalidate I-cache over the whole range. |
| for (uint64 i = start; i < end; i += MAX_CACHE_LINE_SIZE) |
| asm volatile("ic ivau, %[addr]" : : [addr] "r"(i) : "memory"); |
| // 4. Wait for the I-cache invalidate to complete. |
| asm volatile("dsb sy" : : : "memory"); |
| |
| // 5. Flush pipeline to force re-fetch of new instruction. |
| asm volatile("isb" : : : "memory"); |
| } |
| |
| GUEST_CODE static noinline void guest_execute_code(uint32* insns, uint64 size) |
| { |
| flush_cache_range(insns, size); |
| volatile void (*fn)() = (volatile void (*)())insns; |
| fn(); |
| } |
| |
| // Perform a userspace exit that can be handled by the host. |
| // The host returns from ioctl(KVM_RUN) with kvm_run.exit_reason=KVM_EXIT_MMIO, |
| // and can handle the call depending on the data passed as exit code. |
| GUEST_CODE static noinline void guest_uexit(uint64 exit_code) |
| { |
| volatile uint64* ptr = (volatile uint64*)ARM64_ADDR_UEXIT; |
| *ptr = exit_code; |
| } |
| |
| #define MSR_REG_OPCODE 0xd5100000 |
| #define MRS_REG_OPCODE 0xd5300000 |
| |
| // Generate an `MSR register, x0` instruction based on the register ID. |
| // Luckily for us, the five operands, Op0, Op1, CRn, CRm, and Op2 are laid out sequentially in |
| // both the register ID and the MSR instruction encoding (see |
| // https://developer.arm.com/documentation/ddi0602/2024-06/Base-Instructions/MSR--register---Move-general-purpose-register-to-System-register-), |
| // so we can just extract the lower 16 bits and put them into the opcode. |
| GUEST_CODE static uint32 reg_to_msr(uint64 reg) |
| { |
| return MSR_REG_OPCODE | ((reg & 0xffff) << 5); |
| } |
| |
| // Generate an `MRS register, x0` instruction based on the register ID. |
| GUEST_CODE static uint32 reg_to_mrs(uint64 reg) |
| { |
| return MRS_REG_OPCODE | ((reg & 0xffff) << 5); |
| } |
| |
| // Host sets TPIDR_EL1 to contain the virtual CPU id. |
| GUEST_CODE static uint32 get_cpu_id() |
| { |
| uint64 val = 0; // Suppress lint warning. |
| asm volatile("mrs %0, tpidr_el1" |
| : "=r"(val)); |
| return (uint32)val; |
| } |
| |
| // Read the value from a system register using an MRS instruction. |
| GUEST_CODE static noinline void |
| guest_handle_mrs(uint64 reg) |
| { |
| uint32 mrs = reg_to_mrs(reg); |
| uint32 cpu_id = get_cpu_id(); |
| // Make sure CPUs use different cache lines for scratch code. |
| uint32* insn = (uint32*)((uint64)ARM64_ADDR_SCRATCH_CODE + cpu_id * MAX_CACHE_LINE_SIZE); |
| insn[0] = mrs; |
| insn[1] = 0xd65f03c0; // RET |
| flush_cache_range(insn, 8); |
| // Make a call to the generated MSR instruction and clobber x0. |
| asm("blr %[pc]\n" |
| : |
| : [pc] "r"(insn) |
| : "x0", "x30"); |
| } |
| |
| GUEST_CODE static noinline void |
| guest_handle_eret(uint64 unused) |
| { |
| asm("eret\n" : : : "memory"); |
| } |
| |
| // Write value to a system register using an MSR instruction. |
| // The word "MSR" here has nothing to do with the x86 MSR registers. |
| GUEST_CODE static noinline void |
| guest_handle_msr(uint64 reg, uint64 val) |
| { |
| uint32 msr = reg_to_msr(reg); |
| uint32 cpu_id = get_cpu_id(); |
| // Make sure CPUs use different cache lines for scratch code. |
| uint32* insn = (uint32*)((uint64)ARM64_ADDR_SCRATCH_CODE + cpu_id * MAX_CACHE_LINE_SIZE); |
| insn[0] = msr; |
| insn[1] = 0xd65f03c0; // RET |
| flush_cache_range(insn, 8); |
| // Put `val` into x0 and make a call to the generated MSR instruction. |
| asm("mov x0, %[val]\nblr %[pc]\n" |
| : |
| : [val] "r"(val), [pc] "r"(insn) |
| : "x0", "x30", "memory"); |
| } |
| |
| // See "SMC Calling Convention", https://documentation-service.arm.com/static/5f8edaeff86e16515cdbe4c6 |
| GUEST_CODE static noinline void guest_handle_smc(struct api_call_smccc* cmd) |
| { |
| asm volatile( |
| "mov x0, %[func_id]\n" |
| "mov x1, %[arg1]\n" |
| "mov x2, %[arg2]\n" |
| "mov x3, %[arg3]\n" |
| "mov x4, %[arg4]\n" |
| "mov x5, %[arg5]\n" |
| // TODO(glider): it could be interesting to pass other immediate values here, although |
| // they are ignored as per the calling convention. |
| "smc #0\n" |
| : // Ignore the outputs for now |
| : [func_id] "r"((uint64)cmd->func_id), |
| [arg1] "r"(cmd->params[0]), [arg2] "r"(cmd->params[1]), |
| [arg3] "r"(cmd->params[2]), [arg4] "r"(cmd->params[3]), |
| [arg5] "r"(cmd->params[4]) |
| : "x0", "x1", "x2", "x3", "x4", "x5", |
| // These registers are not used above, but may be clobbered by the SMC call. |
| "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", |
| "memory"); |
| } |
| |
| GUEST_CODE static noinline void guest_handle_hvc(struct api_call_smccc* cmd) |
| { |
| asm volatile( |
| "mov x0, %[func_id]\n" |
| "mov x1, %[arg1]\n" |
| "mov x2, %[arg2]\n" |
| "mov x3, %[arg3]\n" |
| "mov x4, %[arg4]\n" |
| "mov x5, %[arg5]\n" |
| // TODO(glider): nonzero immediate values are designated for use by hypervisor vendors. |
| "hvc #0\n" |
| : // Ignore the outputs for now |
| : [func_id] "r"((uint64)cmd->func_id), |
| [arg1] "r"(cmd->params[0]), [arg2] "r"(cmd->params[1]), |
| [arg3] "r"(cmd->params[2]), [arg4] "r"(cmd->params[3]), |
| [arg5] "r"(cmd->params[4]) |
| : "x0", "x1", "x2", "x3", "x4", "x5", |
| // These registers are not used above, but may be clobbered by the HVC call. |
| "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", |
| "memory"); |
| } |
| |
| GUEST_CODE static noinline void guest_handle_svc(struct api_call_smccc* cmd) |
| { |
| asm volatile( |
| "mov x0, %[func_id]\n" |
| "mov x1, %[arg1]\n" |
| "mov x2, %[arg2]\n" |
| "mov x3, %[arg3]\n" |
| "mov x4, %[arg4]\n" |
| "mov x5, %[arg5]\n" |
| // TODO(glider): nonzero immediate values are designated for use by hypervisor vendors. |
| "svc #0\n" |
| : // Ignore the outputs for now |
| : [func_id] "r"((uint64)cmd->func_id), |
| [arg1] "r"(cmd->params[0]), [arg2] "r"(cmd->params[1]), |
| [arg3] "r"(cmd->params[2]), [arg4] "r"(cmd->params[3]), |
| [arg5] "r"(cmd->params[4]) |
| : "x0", "x1", "x2", "x3", "x4", "x5", |
| // These registers are not used above, but may be clobbered by the HVC call. |
| "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", |
| "memory"); |
| } |
| |
| // VGICv3 setup and IRQ handling code below. |
| // This code is based on the "Arm Generic Interrupt Controller (GIC) Architecture Specification. |
| // GIC architecture version 3 and version 4" doc (https://developer.arm.com/documentation/ihi0069/latest/) |
| // and KVM selftests in the Linux kernel. |
| |
| // GICv3 Distributor registers. |
| #define GICD_CTLR 0x0000 |
| #define GICD_IGROUPR 0x0080 |
| #define GICD_ISENABLER 0x0100 |
| #define GICD_ICENABLER 0x0180 |
| #define GICD_ICACTIVER 0x0380 |
| #define GICD_IPRIORITYR 0x0400 |
| |
| #define GICD_INT_DEF_PRI_X4 0xa0a0a0a0 |
| #define GICD_CTLR_ARE_NS (1U << 4) |
| #define GICD_CTLR_ENABLE_G1A (1U << 1) |
| #define GICD_CTLR_ENABLE_G1 (1U << 0) |
| |
| #define GICD_CTLR_RWP (1U << 31) |
| |
| // GICv3 Redistributor registers. |
| #define GICR_CTLR GICD_CTLR |
| #define GICR_WAKER 0x0014 |
| #define GICR_PROPBASER 0x0070 |
| #define GICR_PENDBASER 0x0078 |
| |
| #define GICR_CTLR_ENABLE_LPIS (1UL << 0) |
| #define GICR_CTLR_RWP (1UL << 3) |
| |
| #define GICR_IGROUPR0 GICD_IGROUPR |
| #define GICR_ICENABLER0 GICD_ICENABLER |
| #define GICR_ICACTIVER0 GICD_ICACTIVER |
| #define GICR_IPRIORITYR0 GICD_IPRIORITYR |
| |
| #define ICC_SRE_EL1_SRE (1U << 0) |
| #define ICC_PMR_DEF_PRIO 0xff |
| #define ICC_IGRPEN1_EL1_ENABLE (1U << 0) |
| |
| #define GICR_WAKER_ProcessorSleep (1U << 1) |
| #define GICR_WAKER_ChildrenAsleep (1U << 2) |
| |
| // When building with tools/syz-old-env, GCC doesn't recognize the names of ICC registers. |
| // Replace them with generic S3_* names until we get a newer toolchain. |
| #define ICC_SRE_EL1 "S3_0_C12_C12_5" |
| #define ICC_PMR_EL1 "S3_0_C4_C6_0" |
| #define ICC_IGRPEN1_EL1 "S3_0_C12_C12_7" |
| #define ICC_IAR0_EL1 "S3_0_C12_C8_0" |
| #define ICC_IAR1_EL1 "S3_0_C12_C12_0" |
| #define ICC_EOIR0_EL1 "S3_0_C12_C8_1" |
| #define ICC_EOIR1_EL1 "S3_0_C12_C12_1" |
| #define ICC_DIR_EL1 "S3_0_C12_C11_1" |
| |
| GUEST_CODE static __always_inline void __raw_writel(uint32 val, uint64 addr) |
| { |
| asm volatile("str %w0, [%1]" |
| : |
| : "rZ"(val), "r"(addr)); |
| } |
| |
| GUEST_CODE static __always_inline void __raw_writeq(uint64 val, uint64 addr) |
| { |
| asm volatile("str %x0, [%1]" |
| : |
| : "rZ"(val), "r"(addr)); |
| } |
| |
| GUEST_CODE static __always_inline uint32 __raw_readl(uint64 addr) |
| { |
| uint32 val; |
| asm volatile("ldr %w0, [%1]" |
| : "=r"(val) |
| : "r"(addr)); |
| return val; |
| } |
| |
| GUEST_CODE static __always_inline uint64 __raw_readq(uint64 addr) |
| { |
| uint64 val; |
| asm volatile("ldr %x0, [%1]" |
| : "=r"(val) |
| : "r"(addr)); |
| return val; |
| } |
| |
| #define dmb() asm volatile("dmb sy" \ |
| : \ |
| : \ |
| : "memory") |
| |
| #define writel(v, c) ({ dmb(); __raw_writel(v, c); }) |
| #define readl(c) ({ uint32 __v = __raw_readl(c); dmb(); __v; }) |
| #define writeq(v, c) ({ dmb(); __raw_writeq(v, c); }) |
| #define readq(c) ({ uint64 __v = __raw_readq(c); dmb(); __v; }) |
| |
| // TODO(glider): may want to return extra data to the host. |
| #define GUEST_ASSERT(val) \ |
| do { \ |
| if (!(val)) \ |
| guest_uexit(UEXIT_ASSERT); \ |
| } while (0) |
| |
| // Helper to implement guest_udelay(). |
| GUEST_CODE static uint64 read_cntvct(void) |
| { |
| uint64 val; |
| asm volatile("mrs %0, cntvct_el0" |
| : "=r"(val)); |
| return val; |
| } |
| |
| // Wait for roughly @us microseconds. |
| GUEST_CODE static void guest_udelay(uint32 us) |
| { |
| uint64 ticks_per_second = 0; |
| // Have to read the frequency every time, since we don't have static storage. |
| asm volatile("mrs %0, cntfrq_el0" |
| : "=r"(ticks_per_second)); |
| |
| uint64 start = read_cntvct(); |
| |
| // Target counter value for the desired delay. |
| uint64 target = start + (us * ticks_per_second) / 1000000; |
| |
| while (read_cntvct() < target) { |
| } |
| } |
| |
| // Spin for at most one second as long as the register value has bits from mask. |
| GUEST_CODE static void spin_while_readl(uint64 reg, uint32 mask) |
| { |
| volatile unsigned int count = 100000; |
| while (readl(reg) & mask) { |
| GUEST_ASSERT(count--); |
| guest_udelay(10); |
| } |
| } |
| |
| // Wait for the Register Write Pending bit on GICD_CTLR. |
| GUEST_CODE static void gicd_wait_for_rwp() |
| { |
| spin_while_readl(ARM64_ADDR_GICD_BASE + GICD_CTLR, GICD_CTLR_RWP); |
| } |
| |
| GUEST_CODE static uint64 gicr_base_cpu(uint32 cpu) |
| { |
| return ARM64_ADDR_GICR_BASE + cpu * SZ_64K * 2; |
| } |
| |
| GUEST_CODE static uint64 sgi_base_cpu(uint32 cpu) |
| { |
| return gicr_base_cpu(cpu) + SZ_64K; |
| } |
| |
| // Wait for the Register Write Pending bit on GICR_CTLR. |
| GUEST_CODE static void gicr_wait_for_rwp(uint32 cpu) |
| { |
| spin_while_readl(gicr_base_cpu(cpu) + GICR_CTLR, GICR_CTLR_RWP); |
| } |
| |
| // Set up the distributor part. |
| GUEST_CODE static void gicv3_dist_init(int nr_spis) |
| { |
| // Disable the distributor. |
| writel(0, ARM64_ADDR_GICD_BASE + GICD_CTLR); |
| gicd_wait_for_rwp(); |
| |
| // Mark all the SPI interrupts as non-secure Group-1. Also, deactivate and disable them. |
| for (int i = 32; i < nr_spis + 32; i += 32) { |
| writel(~0, ARM64_ADDR_GICD_BASE + GICD_IGROUPR + i / 8); |
| writel(~0, ARM64_ADDR_GICD_BASE + GICD_ICACTIVER + i / 8); |
| writel(~0, ARM64_ADDR_GICD_BASE + GICD_ICENABLER + i / 8); |
| } |
| |
| // Set a default priority for all the SPIs. |
| for (int i = 32; i < nr_spis + 32; i += 4) { |
| writel(GICD_INT_DEF_PRI_X4, |
| ARM64_ADDR_GICD_BASE + GICD_IPRIORITYR + i); |
| } |
| |
| // Wait for the settings to sync-in. |
| gicd_wait_for_rwp(); |
| |
| // Finally, enable the distributor globally with Affinity Routing Enable, Non-Secure. |
| writel(GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1, ARM64_ADDR_GICD_BASE + GICD_CTLR); |
| gicd_wait_for_rwp(); |
| } |
| |
| // https://developer.arm.com/documentation/198123/0302/Configuring-the-Arm-GIC |
| GUEST_CODE static void gicv3_enable_redist(uint32 cpu) |
| { |
| uint64 redist_base_cpu = gicr_base_cpu(cpu); |
| uint32 val = readl(redist_base_cpu + GICR_WAKER); |
| |
| val &= ~GICR_WAKER_ProcessorSleep; |
| writel(val, ARM64_ADDR_GICR_BASE + GICR_WAKER); |
| // Wait until the processor is 'active'. |
| spin_while_readl(ARM64_ADDR_GICR_BASE + GICR_WAKER, GICR_WAKER_ChildrenAsleep); |
| } |
| |
| GUEST_CODE static void gicv3_cpu_init(uint32 cpu) |
| { |
| uint64 sgi_base = sgi_base_cpu(cpu); |
| |
| // It is important that software performs these steps before configuring |
| // the CPU interface, otherwise behavior can be UNPREDICTABLE. |
| gicv3_enable_redist(cpu); |
| |
| // Mark all the SGI and PPI interrupts as non-secure Group-1. Also, deactivate and disable them. |
| writel(~0, sgi_base + GICR_IGROUPR0); |
| writel(~0, sgi_base + GICR_ICACTIVER0); |
| writel(~0, sgi_base + GICR_ICENABLER0); |
| |
| // Set a default priority for all the SGIs and PPIs. |
| for (int i = 0; i < 32; i += 4) { |
| writel(GICD_INT_DEF_PRI_X4, |
| sgi_base + GICR_IPRIORITYR0 + i); |
| } |
| |
| gicr_wait_for_rwp(cpu); |
| |
| // Enable the GIC system register (ICC_*) access. |
| uint64 icc_sre_el1 = 0; |
| asm volatile("mrs %0, " ICC_SRE_EL1 |
| : "=r"(icc_sre_el1)); |
| icc_sre_el1 |= ICC_SRE_EL1_SRE; |
| asm volatile("msr " ICC_SRE_EL1 ", %0" |
| : |
| : "r"(icc_sre_el1)); |
| |
| // Set a default priority threshold. |
| uint64 value = ICC_PMR_DEF_PRIO; |
| asm volatile("msr " ICC_PMR_EL1 ", %0" |
| : |
| : "r"(value)); |
| |
| // Enable non-secure Group-1 interrupts. |
| value = ICC_IGRPEN1_EL1_ENABLE; |
| asm volatile("msr " ICC_IGRPEN1_EL1 ", %0" |
| : |
| : "r"(value)); |
| } |
| |
| // GICv3 reserves interrupts 32-1019 for SPI. |
| #define VGICV3_MIN_SPI 32 |
| #define VGICV3_MAX_SPI 1019 |
| |
| // https://developer.arm.com/documentation/ihi0048/b/Programmers--Model/Distributor-register-descriptions/Interrupt-Set-Enable-Registers--GICD-ISENABLERn |
| GUEST_CODE static void gicv3_irq_enable(uint32 intid) |
| { |
| uint32 cpu = get_cpu_id(); |
| |
| writel(1 << (intid % 32), ARM64_ADDR_GICD_BASE + GICD_ISENABLER + (intid / 32) * 4); |
| if ((intid >= VGICV3_MIN_SPI) && (intid <= VGICV3_MAX_SPI)) |
| gicd_wait_for_rwp(); |
| else |
| gicr_wait_for_rwp(cpu); |
| } |
| |
| GUEST_CODE static noinline void guest_handle_irq_setup(struct api_call_irq_setup* cmd) |
| { |
| int nr_spis = cmd->nr_spis; |
| if ((nr_spis > VGICV3_MAX_SPI - VGICV3_MIN_SPI) || (nr_spis < 0)) |
| nr_spis = 32; |
| int nr_cpus = cmd->nr_cpus; |
| |
| gicv3_dist_init(nr_spis); |
| for (int i = 0; i < nr_cpus; i++) |
| gicv3_cpu_init(i); |
| for (int i = 0; i < nr_spis; i++) |
| gicv3_irq_enable(VGICV3_MIN_SPI + i); |
| // Set up the vector table. |
| asm(R"( |
| adr x1, guest_vector_table |
| msr vbar_el1, x1 |
| msr daifclr, #0b1111 |
| )" |
| : |
| : |
| : "x1"); |
| } |
| |
| GUEST_CODE static noinline void guest_handle_memwrite(struct api_call_memwrite* cmd) |
| { |
| uint64 dest = cmd->base_addr + cmd->offset; |
| switch (cmd->len) { |
| case 1: { |
| volatile uint8* p = (uint8*)dest; |
| *p = (uint8)cmd->value; |
| break; |
| } |
| |
| case 2: { |
| volatile uint16* p = (uint16*)dest; |
| *p = (uint16)cmd->value; |
| break; |
| } |
| case 4: { |
| volatile uint32* p = (uint32*)dest; |
| *p = (uint32)cmd->value; |
| break; |
| } |
| case 8: |
| default: { |
| volatile uint64* p = (uint64*)dest; |
| *p = (uint64)cmd->value; |
| break; |
| } |
| } |
| } |
| |
| GUEST_CODE static void guest_prepare_its(int nr_cpus, int nr_devices, int nr_events); |
| |
| GUEST_CODE static noinline void guest_handle_its_setup(struct api_call_3* cmd) |
| { |
| guest_prepare_its(cmd->args[0], cmd->args[1], cmd->args[2]); |
| } |
| |
| // Registers saved by one_irq_handler() and received by guest_irq_handler(). |
| struct ex_regs { |
| uint64 regs[31]; |
| uint64 sp; |
| uint64 pc; |
| uint64 pstate; |
| }; |
| |
| // Placeholder function to declare one_irq_handler() inside the assembly blob. We cannot put it |
| // into a separate .S file, because syzkaller requires a standalone header for reproducers. |
| __attribute__((used)) |
| GUEST_CODE static void |
| one_irq_handler_fn() |
| { |
| asm volatile( |
| R"(.global one_irq_handler |
| one_irq_handler: |
| # Allocate 34 * uint64 for struct ex_regs. |
| add sp, sp, #-16 * 17 |
| # Store registers x0-x29 on the stack. |
| stp x0, x1, [sp, #16 * 0] |
| stp x2, x3, [sp, #16 * 1] |
| stp x4, x5, [sp, #16 * 2] |
| stp x6, x7, [sp, #16 * 3] |
| stp x8, x9, [sp, #16 * 4] |
| stp x10, x11, [sp, #16 * 5] |
| stp x12, x13, [sp, #16 * 6] |
| stp x14, x15, [sp, #16 * 7] |
| stp x16, x17, [sp, #16 * 8] |
| stp x18, x19, [sp, #16 * 9] |
| stp x20, x21, [sp, #16 * 10] |
| stp x22, x23, [sp, #16 * 11] |
| stp x24, x25, [sp, #16 * 12] |
| stp x26, x27, [sp, #16 * 13] |
| stp x28, x29, [sp, #16 * 14] |
| |
| add x1, sp, #16 * 17 |
| # Store x30 and SP (before allocating ex_regs). |
| stp x30, x1, [sp, #16 * 15] |
| |
| # ELR_EL1 holds the PC to return to. |
| mrs x1, elr_el1 |
| # SPSR_EL1 is the saved PSTATE. |
| mrs x2, spsr_el1 |
| # Also store them to ex_regs. |
| stp x1, x2, [sp, #16 * 16] |
| |
| # Call guest_irq_handler(ex_regs). |
| mov x0, sp |
| bl guest_irq_handler |
| |
| # Restore ELR_EL1 and SPSR_EL1. |
| ldp x1, x2, [sp, #16 * 16] |
| msr elr_el1, x1 |
| msr spsr_el1, x2 |
| |
| # Restore the GP registers x0-x30 (ignoring SP). |
| ldp x30, xzr, [sp, #16 * 15] |
| ldp x28, x29, [sp, #16 * 14] |
| ldp x26, x27, [sp, #16 * 13] |
| ldp x24, x25, [sp, #16 * 12] |
| ldp x22, x23, [sp, #16 * 11] |
| ldp x20, x21, [sp, #16 * 10] |
| ldp x18, x19, [sp, #16 * 9] |
| ldp x16, x17, [sp, #16 * 8] |
| ldp x14, x15, [sp, #16 * 7] |
| ldp x12, x13, [sp, #16 * 6] |
| ldp x10, x11, [sp, #16 * 5] |
| ldp x8, x9, [sp, #16 * 4] |
| ldp x6, x7, [sp, #16 * 3] |
| ldp x4, x5, [sp, #16 * 2] |
| ldp x2, x3, [sp, #16 * 1] |
| ldp x0, x1, [sp, #16 * 0] |
| |
| add sp, sp, #16 * 17 |
| |
| # Use ERET to exit from an exception. |
| eret)" |
| : |
| : |
| : "memory"); |
| } |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| __attribute__((used)) |
| GUEST_CODE static void |
| guest_irq_handler(struct ex_regs* regs) |
| { |
| uint64 iar0, iar1, irq_num = 0; |
| bool is_group0 = false; |
| // Acknowledge the interrupt by reading the IAR. |
| // Depending on the particular interrupt's Group (0 or 1), its number will appear in either ICC_IAR0_EL1, or ICC_IAR1_EL1. |
| // The other register will contain a special interrupt number between 1020 and 1023. |
| // Numbers below 1020 are SGIs, PPIs and SPIs, numbers above 1023 are reserved interrupts and LPIs. |
| asm volatile("mrs %0, " ICC_IAR0_EL1 |
| : "=r"(iar0)); |
| asm volatile("mrs %0, " ICC_IAR1_EL1 |
| : "=r"(iar1)); |
| if ((iar0 < 1020) || (iar0 > 1023)) { |
| irq_num = iar0; |
| is_group0 = true; |
| } else if ((iar1 < 1020) || (iar1 > 1023)) { |
| irq_num = iar1; |
| } else { |
| return; |
| } |
| |
| // Handle the interrupt by doing a uexit. |
| // TODO(glider): do something more interesting here. |
| guest_uexit(UEXIT_IRQ); |
| |
| // Signal End of Interrupt (EOI) by writing back to the EOIR. |
| if (is_group0) { |
| asm volatile("msr " ICC_EOIR0_EL1 ", %0" |
| : |
| : "r"(irq_num)); |
| } else { |
| asm volatile("msr " ICC_EOIR1_EL1 ", %0" |
| : |
| : "r"(irq_num)); |
| } |
| // Deactivate the interrupt. |
| asm volatile("msr " ICC_DIR_EL1 ", %0" |
| : |
| : "r"(irq_num)); |
| } |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| // Default IRQ handler. |
| #define IRQ_ENTRY \ |
| ".balign 0x80\n" \ |
| "b one_irq_handler\n" |
| |
| // Unused IRQ entry. |
| #define IRQ_ENTRY_DUMMY \ |
| ".balign 0x80\n" \ |
| "eret\n" |
| |
| // clang-format off |
| // guest_vector_table_fn() is never used, it is just needed to declare guest_vector_table() |
| // inside the assembly blob. |
| __attribute__((used)) |
| GUEST_CODE static void guest_vector_table_fn() |
| { |
| // Exception vector table as explained at |
| // https://developer.arm.com/documentation/100933/0100/AArch64-exception-vector-table. |
| asm volatile( |
| ".global guest_vector_table\n" |
| ".balign 2048\n" |
| "guest_vector_table:\n" |
| // Exception handlers for current EL with SP0. |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY |
| |
| // Exception handlers for current EL with SPx. |
| IRQ_ENTRY_DUMMY |
| // Only handle IRQ/vIRQ for now. |
| IRQ_ENTRY |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY |
| |
| // Exception handlers for lower EL using AArch64. |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY |
| |
| // Exception handlers for lower EL using AArch32. |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY |
| IRQ_ENTRY_DUMMY); |
| } |
| // clang-format on |
| |
| // ITS setup below. |
| #define GITS_CTLR 0x0000 |
| #define GITS_CBASER 0x0080 |
| #define GITS_CWRITER 0x0088 |
| #define GITS_CREADR 0x0090 |
| #define GITS_BASER 0x0100 |
| |
| #define GITS_CTLR_ENABLE (1U << 0) |
| |
| #define GIC_BASER_InnerShareable 1ULL |
| |
| #define GIC_PAGE_SIZE_64K 2ULL |
| #define GITS_BASER_PAGE_SIZE_SHIFT (8) |
| #define __GITS_BASER_PSZ(sz) (GIC_PAGE_SIZE_##sz << GITS_BASER_PAGE_SIZE_SHIFT) |
| #define GITS_BASER_PAGE_SIZE_64K __GITS_BASER_PSZ(64K) |
| |
| #define GIC_BASER_CACHE_RaWaWb 7ULL |
| #define GITS_BASER_INNER_CACHEABILITY_SHIFT (59) |
| #define GITS_BASER_RaWaWb GIC_BASER_CACHEABILITY(GITS_BASER, INNER, RaWaWb) |
| |
| #define GITS_CBASER_INNER_CACHEABILITY_SHIFT (59) |
| #define GITS_CBASER_RaWaWb GIC_BASER_CACHEABILITY(GITS_CBASER, INNER, RaWaWb) |
| |
| #define GICR_PROPBASER_SHAREABILITY_SHIFT (10) |
| #define GICR_PROPBASER_INNER_CACHEABILITY_SHIFT (7) |
| #define GICR_PROPBASER_RaWaWb GIC_BASER_CACHEABILITY(GICR_PROPBASER, INNER, RaWaWb) |
| #define GICR_PROPBASER_IDBITS_MASK (0x1f) |
| |
| #define GIC_BASER_CACHEABILITY(reg, inner_outer, type) \ |
| (GIC_BASER_CACHE_##type << reg##_##inner_outer##_CACHEABILITY_SHIFT) |
| |
| #define GITS_BASER_SHAREABILITY_SHIFT (10) |
| #define GITS_CBASER_SHAREABILITY_SHIFT (10) |
| #define GIC_BASER_SHAREABILITY(reg, type) \ |
| (GIC_BASER_##type << reg##_SHAREABILITY_SHIFT) |
| #define GITS_BASER_InnerShareable \ |
| GIC_BASER_SHAREABILITY(GITS_BASER, InnerShareable) |
| |
| #define GITS_CBASER_InnerShareable \ |
| GIC_BASER_SHAREABILITY(GITS_CBASER, InnerShareable) |
| |
| #define GICR_PROPBASER_InnerShareable \ |
| GIC_BASER_SHAREABILITY(GICR_PROPBASER, InnerShareable) |
| |
| #define GICR_PENDBASER_InnerShareable \ |
| GIC_BASER_SHAREABILITY(GICR_PENDBASER, InnerShareable) |
| |
| #define GICR_PENDBASER_SHAREABILITY_SHIFT (10) |
| #define GICR_PENDBASER_INNER_CACHEABILITY_SHIFT (7) |
| #define GICR_PENDBASER_RaWaWb GIC_BASER_CACHEABILITY(GICR_PENDBASER, INNER, RaWaWb) |
| |
| #define GITS_BASER_TYPE_NONE 0 |
| #define GITS_BASER_TYPE_DEVICE 1 |
| #define GITS_BASER_TYPE_VCPU 2 |
| #define GITS_BASER_TYPE_RESERVED3 3 |
| #define GITS_BASER_TYPE_COLLECTION 4 |
| #define GITS_BASER_TYPE_RESERVED5 5 |
| #define GITS_BASER_TYPE_RESERVED6 6 |
| #define GITS_BASER_TYPE_RESERVED7 7 |
| |
| #define GITS_BASER_TYPE_SHIFT (56) |
| #define GITS_BASER_TYPE(r) (((r) >> GITS_BASER_TYPE_SHIFT) & 7) |
| |
| #define GITS_BASER_NR_REGS 8 |
| #define GITS_BASER_VALID (1ULL << 63) |
| |
| #define GITS_CBASER_VALID (1ULL << 63) |
| |
| GUEST_CODE static uint64 its_read_u64(unsigned long offset) |
| { |
| return readq(ARM64_ADDR_GITS_BASE + offset); |
| } |
| |
| GUEST_CODE static void its_write_u64(unsigned long offset, uint64 val) |
| { |
| writeq(val, ARM64_ADDR_GITS_BASE + offset); |
| } |
| |
| GUEST_CODE static uint32 its_read_u32(unsigned long offset) |
| { |
| return readl(ARM64_ADDR_GITS_BASE + offset); |
| } |
| |
| GUEST_CODE static void its_write_u32(unsigned long offset, uint32 val) |
| { |
| writel(val, ARM64_ADDR_GITS_BASE + offset); |
| } |
| |
| struct its_cmd_block { |
| // Kernel defines this struct as a union, but we don't need raw_cmd_le for now. |
| uint64 raw_cmd[4]; |
| }; |
| |
| // Guest memcpy implementation is using volatile accesses to prevent the compiler from optimizing it |
| // into a memcpy() call. |
| GUEST_CODE static noinline void guest_memcpy(void* dst, void* src, size_t size) |
| { |
| volatile char* pdst = (char*)dst; |
| volatile char* psrc = (char*)src; |
| for (size_t i = 0; i < size; i++) |
| pdst[i] = psrc[i]; |
| } |
| |
| // Send an ITS command by copying it to the command queue at the offset defined by GITS_CWRITER. |
| // https://developer.arm.com/documentation/100336/0106/operation/interrupt-translation-service--its-/its-commands-and-errors. |
| GUEST_CODE static noinline void its_send_cmd(uint64 cmdq_base, struct its_cmd_block* cmd) |
| { |
| uint64 cwriter = its_read_u64(GITS_CWRITER); |
| struct its_cmd_block* dst = (struct its_cmd_block*)(cmdq_base + cwriter); |
| uint64 cbaser = its_read_u64(GITS_CBASER); |
| size_t cmdq_size = ((cbaser & 0xFF) + 1) * SZ_4K; |
| guest_memcpy(dst, cmd, sizeof(*cmd)); |
| dmb(); |
| uint64 next = (cwriter + sizeof(*cmd)) % cmdq_size; |
| its_write_u64(GITS_CWRITER, next); |
| // KVM synchronously processes the command after writing to GITS_CWRITER. |
| // Hardware ITS implementation would've required polling here. |
| } |
| |
| GUEST_CODE static unsigned long its_find_baser(unsigned int type) |
| { |
| for (int i = 0; i < GITS_BASER_NR_REGS; i++) { |
| uint64 baser; |
| unsigned long offset = GITS_BASER + (i * sizeof(baser)); |
| |
| baser = its_read_u64(offset); |
| if (GITS_BASER_TYPE(baser) == type) |
| return offset; |
| } |
| |
| GUEST_ASSERT(0); |
| return -1; |
| } |
| |
| GUEST_CODE static void its_install_table(unsigned int type, uint64 base, size_t size) |
| { |
| unsigned long offset = its_find_baser(type); |
| uint64 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); |
| } |
| |
| GUEST_CODE static void its_install_cmdq(uint64 base, size_t size) |
| { |
| uint64 cbaser = ((size / SZ_4K) - 1) | |
| GITS_CBASER_InnerShareable | |
| base | |
| GITS_CBASER_RaWaWb | |
| GITS_CBASER_VALID; |
| |
| its_write_u64(GITS_CBASER, cbaser); |
| } |
| |
| GUEST_CODE static void its_init(uint64 coll_tbl, |
| uint64 device_tbl, uint64 cmdq) |
| { |
| its_install_table(GITS_BASER_TYPE_COLLECTION, coll_tbl, SZ_64K); |
| its_install_table(GITS_BASER_TYPE_DEVICE, device_tbl, SZ_64K); |
| its_install_cmdq(cmdq, SZ_64K); |
| |
| uint32 ctlr = its_read_u32(GITS_CTLR); |
| ctlr |= GITS_CTLR_ENABLE; |
| its_write_u32(GITS_CTLR, ctlr); |
| } |
| |
| #define GIC_LPI_OFFSET 8192 |
| |
| #define GITS_CMD_MAPD 0x08 |
| #define GITS_CMD_MAPC 0x09 |
| #define GITS_CMD_MAPTI 0x0a |
| #define GITS_CMD_MAPI 0x0b |
| #define GITS_CMD_MOVI 0x01 |
| #define GITS_CMD_DISCARD 0x0f |
| #define GITS_CMD_INV 0x0c |
| #define GITS_CMD_MOVALL 0x0e |
| #define GITS_CMD_INVALL 0x0d |
| #define GITS_CMD_INT 0x03 |
| #define GITS_CMD_CLEAR 0x04 |
| #define GITS_CMD_SYNC 0x05 |
| |
| // Avoid inlining this function, because it may cause emitting constants into .rodata. |
| GUEST_CODE static noinline void |
| its_mask_encode(uint64* raw_cmd, uint64 val, int h, int l) |
| { |
| uint64 mask = GENMASK_ULL(h, l); |
| *raw_cmd &= ~mask; |
| *raw_cmd |= (val << l) & mask; |
| } |
| |
| GUEST_CODE static void its_encode_cmd(struct its_cmd_block* cmd, uint8 cmd_nr) |
| { |
| its_mask_encode(&cmd->raw_cmd[0], cmd_nr, 7, 0); |
| } |
| |
| GUEST_CODE static void its_encode_devid(struct its_cmd_block* cmd, uint32 devid) |
| { |
| its_mask_encode(&cmd->raw_cmd[0], devid, 63, 32); |
| } |
| |
| GUEST_CODE static void its_encode_event_id(struct its_cmd_block* cmd, uint32 id) |
| { |
| its_mask_encode(&cmd->raw_cmd[1], id, 31, 0); |
| } |
| |
| GUEST_CODE static void its_encode_phys_id(struct its_cmd_block* cmd, uint32 phys_id) |
| { |
| its_mask_encode(&cmd->raw_cmd[1], phys_id, 63, 32); |
| } |
| |
| GUEST_CODE static void its_encode_size(struct its_cmd_block* cmd, uint8 size) |
| { |
| its_mask_encode(&cmd->raw_cmd[1], size, 4, 0); |
| } |
| |
| GUEST_CODE static void its_encode_itt(struct its_cmd_block* cmd, uint64 itt_addr) |
| { |
| its_mask_encode(&cmd->raw_cmd[2], itt_addr >> 8, 51, 8); |
| } |
| |
| GUEST_CODE static void its_encode_valid(struct its_cmd_block* cmd, int valid) |
| { |
| its_mask_encode(&cmd->raw_cmd[2], !!valid, 63, 63); |
| } |
| |
| GUEST_CODE static void its_encode_target(struct its_cmd_block* cmd, uint64 target_addr) |
| { |
| its_mask_encode(&cmd->raw_cmd[2], target_addr >> 16, 51, 16); |
| } |
| |
| // RDbase2 encoded in the fourth double word of the command. |
| GUEST_CODE static void its_encode_target2(struct its_cmd_block* cmd, uint64 target_addr) |
| { |
| its_mask_encode(&cmd->raw_cmd[3], target_addr >> 16, 51, 16); |
| } |
| |
| GUEST_CODE static void its_encode_collection(struct its_cmd_block* cmd, uint16 col) |
| { |
| its_mask_encode(&cmd->raw_cmd[2], col, 15, 0); |
| } |
| |
| GUEST_CODE static noinline void guest_memzero(void* ptr, size_t size) |
| { |
| volatile char* p = (char*)ptr; |
| for (size_t i = 0; i < size; i++) |
| p[i] = 0; |
| } |
| |
| GUEST_CODE static void its_send_mapd_cmd(uint64 cmdq_base, uint32 device_id, uint64 itt_base, |
| size_t num_idbits, bool valid) |
| { |
| struct its_cmd_block cmd; |
| guest_memzero(&cmd, sizeof(cmd)); |
| its_encode_cmd(&cmd, GITS_CMD_MAPD); |
| its_encode_devid(&cmd, device_id); |
| its_encode_size(&cmd, num_idbits - 1); |
| its_encode_itt(&cmd, itt_base); |
| its_encode_valid(&cmd, valid); |
| |
| its_send_cmd(cmdq_base, &cmd); |
| } |
| |
| GUEST_CODE static void its_send_mapc_cmd(uint64 cmdq_base, uint32 vcpu_id, uint32 collection_id, bool valid) |
| { |
| struct its_cmd_block cmd; |
| guest_memzero(&cmd, sizeof(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); |
| } |
| |
| GUEST_CODE static void its_send_mapti_cmd(uint64 cmdq_base, uint32 device_id, |
| uint32 event_id, uint32 collection_id, |
| uint32 intid) |
| { |
| struct its_cmd_block cmd; |
| guest_memzero(&cmd, sizeof(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); |
| } |
| |
| GUEST_CODE static void its_send_devid_eventid_icid_cmd(uint64 cmdq_base, uint8 cmd_nr, uint32 device_id, |
| uint32 event_id, uint32 intid) |
| { |
| struct its_cmd_block cmd; |
| guest_memzero(&cmd, sizeof(cmd)); |
| its_encode_cmd(&cmd, cmd_nr); |
| its_encode_devid(&cmd, device_id); |
| its_encode_event_id(&cmd, event_id); |
| its_encode_phys_id(&cmd, intid); |
| its_send_cmd(cmdq_base, &cmd); |
| } |
| |
| GUEST_CODE static void its_send_devid_eventid_cmd(uint64 cmdq_base, uint8 cmd_nr, uint32 device_id, |
| uint32 event_id) |
| { |
| struct its_cmd_block cmd; |
| guest_memzero(&cmd, sizeof(cmd)); |
| its_encode_cmd(&cmd, cmd_nr); |
| its_encode_devid(&cmd, device_id); |
| its_encode_event_id(&cmd, event_id); |
| its_send_cmd(cmdq_base, &cmd); |
| } |
| |
| GUEST_CODE static void its_send_movall_cmd(uint64 cmdq_base, uint32 vcpu_id, uint32 vcpu_id2) |
| { |
| struct its_cmd_block cmd; |
| guest_memzero(&cmd, sizeof(cmd)); |
| its_encode_cmd(&cmd, GITS_CMD_MOVALL); |
| its_encode_target(&cmd, vcpu_id); |
| its_encode_target2(&cmd, vcpu_id2); |
| |
| its_send_cmd(cmdq_base, &cmd); |
| } |
| |
| GUEST_CODE static void |
| its_send_invall_cmd(uint64 cmdq_base, uint32 collection_id) |
| { |
| struct its_cmd_block cmd; |
| guest_memzero(&cmd, sizeof(cmd)); |
| its_encode_cmd(&cmd, GITS_CMD_INVALL); |
| its_encode_collection(&cmd, collection_id); |
| its_send_cmd(cmdq_base, &cmd); |
| } |
| |
| // We assume that the number of supported IDbits for the proproperties table is 16, so the size of the |
| // table itself is 64K. |
| // TODO(glider): it may be interesting to use a different size here. |
| #define SYZOS_NUM_IDBITS 16 |
| |
| GUEST_CODE static void its_send_sync_cmd(uint64 cmdq_base, uint32 vcpu_id) |
| { |
| struct its_cmd_block cmd; |
| guest_memzero(&cmd, sizeof(cmd)); |
| its_encode_cmd(&cmd, GITS_CMD_SYNC); |
| its_encode_target(&cmd, vcpu_id); |
| its_send_cmd(cmdq_base, &cmd); |
| } |
| |
| // This function is carefully written in a way that prevents jump table emission. |
| // SyzOS cannot reference global constants, and compilers are very eager to generate a jump table |
| // for a switch over GITS commands. |
| // To work around that, we replace the switch statement with a series of if statements. |
| // In addition, cmd->type is stored in a volatile variable, so that it is read on each if statement, |
| // preventing the compiler from folding them together. |
| GUEST_CODE static noinline void guest_handle_its_send_cmd(struct api_call_its_send_cmd* cmd) |
| { |
| volatile uint8 type = cmd->type; |
| if (type == GITS_CMD_MAPD) { |
| uint64 itt_base = ARM64_ADDR_ITS_ITT_TABLES + cmd->devid * SZ_64K; |
| its_send_mapd_cmd(ARM64_ADDR_ITS_CMDQ_BASE, cmd->devid, itt_base, |
| SYZOS_NUM_IDBITS, cmd->valid); |
| return; |
| } |
| if (type == GITS_CMD_MAPC) { |
| its_send_mapc_cmd(ARM64_ADDR_ITS_CMDQ_BASE, cmd->cpuid, cmd->cpuid, |
| cmd->valid); |
| return; |
| } |
| if (type == GITS_CMD_MAPTI) { |
| its_send_mapti_cmd(ARM64_ADDR_ITS_CMDQ_BASE, cmd->devid, cmd->eventid, |
| cmd->cpuid, cmd->intid); |
| return; |
| } |
| if (type == GITS_CMD_MAPI || type == GITS_CMD_MOVI) { |
| its_send_devid_eventid_icid_cmd(ARM64_ADDR_ITS_CMDQ_BASE, type, |
| cmd->devid, cmd->eventid, cmd->intid); |
| return; |
| } |
| if (type == GITS_CMD_MOVALL) { |
| its_send_movall_cmd(ARM64_ADDR_ITS_CMDQ_BASE, cmd->cpuid, cmd->cpuid2); |
| return; |
| } |
| if (type == GITS_CMD_INVALL) { |
| its_send_invall_cmd(ARM64_ADDR_ITS_CMDQ_BASE, cmd->cpuid); |
| return; |
| } |
| if (type == GITS_CMD_INT || type == GITS_CMD_INV || type == GITS_CMD_DISCARD || type == GITS_CMD_CLEAR) { |
| its_send_devid_eventid_cmd(ARM64_ADDR_ITS_CMDQ_BASE, type, cmd->devid, |
| cmd->eventid); |
| return; |
| } |
| if (type == GITS_CMD_SYNC) { |
| its_send_sync_cmd(ARM64_ADDR_ITS_CMDQ_BASE, cmd->cpuid); |
| return; |
| } |
| } |
| |
| GUEST_CODE static noinline void guest_setup_its_mappings(uint64 cmdq_base, |
| uint64 itt_tables, |
| uint32 nr_events, |
| uint32 nr_devices, |
| uint32 nr_cpus) |
| { |
| if ((nr_events < 1) || (nr_devices < 1) || (nr_cpus < 1)) |
| return; |
| |
| // Event IDs start from 0 and map to LPI IDs starting from GIC_LPI_OFFSET. |
| uint32 coll_id, device_id, event_id, intid = GIC_LPI_OFFSET; |
| for (coll_id = 0; coll_id < nr_cpus; coll_id++) { |
| // If GITS_TYPER.PTA == 0, RDbase is just the CPU id. |
| its_send_mapc_cmd(cmdq_base, coll_id, coll_id, true); |
| } |
| // Round-robin the LPIs to all of the vCPUs in the VM. |
| coll_id = 0; |
| for (device_id = 0; device_id < nr_devices; device_id++) { |
| uint64 itt_base = itt_tables + (device_id * SZ_64K); |
| its_send_mapd_cmd(cmdq_base, device_id, itt_base, SYZOS_NUM_IDBITS, true); |
| for (event_id = 0; event_id < nr_events; event_id++) { |
| its_send_mapti_cmd(cmdq_base, device_id, event_id, coll_id, intid++); |
| coll_id = (coll_id + 1) % nr_cpus; |
| } |
| } |
| } |
| |
| GUEST_CODE static void guest_invalidate_all_rdists(uint64 cmdq_base, int nr_cpus) |
| { |
| for (int i = 0; i < nr_cpus; i++) |
| its_send_invall_cmd(cmdq_base, i); |
| } |
| |
| // Set up GIRC_PROPBASER and GICR_PENDBASER. |
| void gic_rdist_enable_lpis(uint64 cfg_table, size_t cfg_table_size, |
| uint64 pend_table) |
| { |
| uint64 rdist_base = gicr_base_cpu(get_cpu_id()); |
| uint64 val = (cfg_table | |
| GICR_PROPBASER_InnerShareable | |
| GICR_PROPBASER_RaWaWb | |
| ((SYZOS_NUM_IDBITS - 1) & GICR_PROPBASER_IDBITS_MASK)); |
| |
| writeq(val, rdist_base + GICR_PROPBASER); |
| |
| val = (pend_table | |
| GICR_PENDBASER_InnerShareable | |
| GICR_PENDBASER_RaWaWb); |
| writeq(val, rdist_base + GICR_PENDBASER); |
| |
| uint64 ctlr = readl(rdist_base + GICR_CTLR); |
| ctlr |= GICR_CTLR_ENABLE_LPIS; |
| writel(ctlr, rdist_base + GICR_CTLR); |
| } |
| |
| #define LPI_PROP_DEFAULT_PRIO 0xa0 |
| #define LPI_PROP_GROUP1 (1 << 1) |
| #define LPI_PROP_ENABLED (1 << 0) |
| |
| // TODO(glider) non-volatile access is compiled into: |
| // 0000000000452154 <configure_lpis.constprop.0>: |
| // 452154: 4f05e460 movi v0.16b, #0xa3 |
| // 452158: 3d800000 str q0, [x0] |
| // 45215c: d65f03c0 ret |
| // , which for some reason hangs. |
| GUEST_CODE static noinline void configure_lpis(uint64 prop_table, int nr_devices, int nr_events) |
| { |
| int nr_lpis = nr_devices * nr_events; |
| volatile uint8* tbl = (uint8*)prop_table; |
| for (int i = 0; i < nr_lpis; i++) { |
| tbl[i] = LPI_PROP_DEFAULT_PRIO | |
| LPI_PROP_GROUP1 | |
| LPI_PROP_ENABLED; |
| } |
| } |
| |
| GUEST_CODE static void guest_prepare_its(int nr_cpus, int nr_devices, int nr_events) |
| { |
| configure_lpis(ARM64_ADDR_ITS_PROP_TABLE, nr_devices, nr_events); |
| gic_rdist_enable_lpis(ARM64_ADDR_ITS_PROP_TABLE, SZ_64K, ARM64_ADDR_ITS_PEND_TABLES); |
| its_init(ARM64_ADDR_ITS_COLL_TABLE, ARM64_ADDR_ITS_DEVICE_TABLE, ARM64_ADDR_ITS_CMDQ_BASE); |
| guest_setup_its_mappings(ARM64_ADDR_ITS_CMDQ_BASE, ARM64_ADDR_ITS_ITT_TABLES, nr_events, nr_devices, nr_cpus); |
| guest_invalidate_all_rdists(ARM64_ADDR_ITS_CMDQ_BASE, nr_cpus); |
| } |