// https://syzkaller.appspot.com/bug?id=d219a27084c471908203a8873860a1d16a36f0b8
// autogenerated by syzkaller (https://github.com/google/syzkaller)

#define _GNU_SOURCE

#include <dirent.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include <linux/futex.h>
#include <linux/kvm.h>

#ifndef __NR_ioctl
#define __NR_ioctl 29
#endif
#ifndef __NR_mmap
#define __NR_mmap 222
#endif
#ifndef __NR_openat
#define __NR_openat 56
#endif

static unsigned long long procid;

static void sleep_ms(uint64_t ms)
{
  usleep(ms * 1000);
}

static uint64_t current_time_ms(void)
{
  struct timespec ts;
  if (clock_gettime(CLOCK_MONOTONIC, &ts))
    exit(1);
  return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}

static void thread_start(void* (*fn)(void*), void* arg)
{
  pthread_t th;
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setstacksize(&attr, 128 << 10);
  int i = 0;
  for (; i < 100; i++) {
    if (pthread_create(&th, &attr, fn, arg) == 0) {
      pthread_attr_destroy(&attr);
      return;
    }
    if (errno == EAGAIN) {
      usleep(50);
      continue;
    }
    break;
  }
  exit(1);
}

typedef struct {
  int state;
} event_t;

static void event_init(event_t* ev)
{
  ev->state = 0;
}

static void event_reset(event_t* ev)
{
  ev->state = 0;
}

static void event_set(event_t* ev)
{
  if (ev->state)
    exit(1);
  __atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
  syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}

static void event_wait(event_t* ev)
{
  while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
    syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}

static int event_isset(event_t* ev)
{
  return __atomic_load_n(&ev->state, __ATOMIC_ACQUIRE);
}

static int event_timedwait(event_t* ev, uint64_t timeout)
{
  uint64_t start = current_time_ms();
  uint64_t now = start;
  for (;;) {
    uint64_t remain = timeout - (now - start);
    struct timespec ts;
    ts.tv_sec = remain / 1000;
    ts.tv_nsec = (remain % 1000) * 1000 * 1000;
    syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, &ts);
    if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
      return 1;
    now = current_time_ms();
    if (now - start > timeout)
      return 0;
  }
}

static bool write_file(const char* file, const char* what, ...)
{
  char buf[1024];
  va_list args;
  va_start(args, what);
  vsnprintf(buf, sizeof(buf), what, args);
  va_end(args);
  buf[sizeof(buf) - 1] = 0;
  int len = strlen(buf);
  int fd = open(file, O_WRONLY | O_CLOEXEC);
  if (fd == -1)
    return false;
  if (write(fd, buf, len) != len) {
    int err = errno;
    close(fd);
    errno = err;
    return false;
  }
  close(fd);
  return true;
}

#define X86_ADDR_TEXT 0x0000
#define X86_ADDR_PD_IOAPIC 0x0000
#define X86_ADDR_GDT 0x1000
#define X86_ADDR_LDT 0x1800
#define X86_ADDR_PML4 0x2000
#define X86_ADDR_PDP 0x3000
#define X86_ADDR_PD 0x4000
#define X86_ADDR_STACK0 0x0f80
#define X86_ADDR_VAR_HLT 0x2800
#define X86_ADDR_VAR_SYSRET 0x2808
#define X86_ADDR_VAR_SYSEXIT 0x2810
#define X86_ADDR_VAR_IDT 0x3800
#define X86_ADDR_VAR_TSS64 0x3a00
#define X86_ADDR_VAR_TSS64_CPL3 0x3c00
#define X86_ADDR_VAR_TSS16 0x3d00
#define X86_ADDR_VAR_TSS16_2 0x3e00
#define X86_ADDR_VAR_TSS16_CPL3 0x3f00
#define X86_ADDR_VAR_TSS32 0x4800
#define X86_ADDR_VAR_TSS32_2 0x4a00
#define X86_ADDR_VAR_TSS32_CPL3 0x4c00
#define X86_ADDR_VAR_TSS32_VM86 0x4e00
#define X86_ADDR_VAR_VMXON_PTR 0x5f00
#define X86_ADDR_VAR_VMCS_PTR 0x5f08
#define X86_ADDR_VAR_VMEXIT_PTR 0x5f10
#define X86_ADDR_VAR_VMWRITE_FLD 0x5f18
#define X86_ADDR_VAR_VMWRITE_VAL 0x5f20
#define X86_ADDR_VAR_VMXON 0x6000
#define X86_ADDR_VAR_VMCS 0x7000
#define X86_ADDR_VAR_VMEXIT_CODE 0x9000
#define X86_ADDR_VAR_USER_CODE 0x9100
#define X86_ADDR_VAR_USER_CODE2 0x9120
#define X86_ADDR_SMRAM 0x30000
#define X86_ADDR_EXIT 0x40000
#define X86_ADDR_UEXIT (X86_ADDR_EXIT + 256)
#define X86_ADDR_DIRTY_PAGES 0x41000
#define X86_ADDR_USER_CODE 0x50000
#define X86_ADDR_EXECUTOR_CODE 0x54000
#define X86_ADDR_SCRATCH_CODE 0x58000
#define X86_ADDR_UNUSED 0x200000
#define X86_ADDR_IOAPIC 0xfec00000

#define X86_CR0_PE 1ULL
#define X86_CR0_MP (1ULL << 1)
#define X86_CR0_EM (1ULL << 2)
#define X86_CR0_TS (1ULL << 3)
#define X86_CR0_ET (1ULL << 4)
#define X86_CR0_NE (1ULL << 5)
#define X86_CR0_WP (1ULL << 16)
#define X86_CR0_AM (1ULL << 18)
#define X86_CR0_NW (1ULL << 29)
#define X86_CR0_CD (1ULL << 30)
#define X86_CR0_PG (1ULL << 31)

#define X86_CR4_VME 1ULL
#define X86_CR4_PVI (1ULL << 1)
#define X86_CR4_TSD (1ULL << 2)
#define X86_CR4_DE (1ULL << 3)
#define X86_CR4_PSE (1ULL << 4)
#define X86_CR4_PAE (1ULL << 5)
#define X86_CR4_MCE (1ULL << 6)
#define X86_CR4_PGE (1ULL << 7)
#define X86_CR4_PCE (1ULL << 8)
#define X86_CR4_OSFXSR (1ULL << 8)
#define X86_CR4_OSXMMEXCPT (1ULL << 10)
#define X86_CR4_UMIP (1ULL << 11)
#define X86_CR4_VMXE (1ULL << 13)
#define X86_CR4_SMXE (1ULL << 14)
#define X86_CR4_FSGSBASE (1ULL << 16)
#define X86_CR4_PCIDE (1ULL << 17)
#define X86_CR4_OSXSAVE (1ULL << 18)
#define X86_CR4_SMEP (1ULL << 20)
#define X86_CR4_SMAP (1ULL << 21)
#define X86_CR4_PKE (1ULL << 22)

#define X86_EFER_SCE 1ULL
#define X86_EFER_LME (1ULL << 8)
#define X86_EFER_LMA (1ULL << 10)
#define X86_EFER_NXE (1ULL << 11)
#define X86_EFER_SVME (1ULL << 12)
#define X86_EFER_LMSLE (1ULL << 13)
#define X86_EFER_FFXSR (1ULL << 14)
#define X86_EFER_TCE (1ULL << 15)
#define X86_PDE32_PRESENT 1UL
#define X86_PDE32_RW (1UL << 1)
#define X86_PDE32_USER (1UL << 2)
#define X86_PDE32_PS (1UL << 7)
#define X86_PDE64_PRESENT 1
#define X86_PDE64_RW (1ULL << 1)
#define X86_PDE64_USER (1ULL << 2)
#define X86_PDE64_ACCESSED (1ULL << 5)
#define X86_PDE64_DIRTY (1ULL << 6)
#define X86_PDE64_PS (1ULL << 7)
#define X86_PDE64_G (1ULL << 8)

#define X86_SEL_LDT (1 << 3)
#define X86_SEL_CS16 (2 << 3)
#define X86_SEL_DS16 (3 << 3)
#define X86_SEL_CS16_CPL3 ((4 << 3) + 3)
#define X86_SEL_DS16_CPL3 ((5 << 3) + 3)
#define X86_SEL_CS32 (6 << 3)
#define X86_SEL_DS32 (7 << 3)
#define X86_SEL_CS32_CPL3 ((8 << 3) + 3)
#define X86_SEL_DS32_CPL3 ((9 << 3) + 3)
#define X86_SEL_CS64 (10 << 3)
#define X86_SEL_DS64 (11 << 3)
#define X86_SEL_CS64_CPL3 ((12 << 3) + 3)
#define X86_SEL_DS64_CPL3 ((13 << 3) + 3)
#define X86_SEL_CGATE16 (14 << 3)
#define X86_SEL_TGATE16 (15 << 3)
#define X86_SEL_CGATE32 (16 << 3)
#define X86_SEL_TGATE32 (17 << 3)
#define X86_SEL_CGATE64 (18 << 3)
#define X86_SEL_CGATE64_HI (19 << 3)
#define X86_SEL_TSS16 (20 << 3)
#define X86_SEL_TSS16_2 (21 << 3)
#define X86_SEL_TSS16_CPL3 ((22 << 3) + 3)
#define X86_SEL_TSS32 (23 << 3)
#define X86_SEL_TSS32_2 (24 << 3)
#define X86_SEL_TSS32_CPL3 ((25 << 3) + 3)
#define X86_SEL_TSS32_VM86 (26 << 3)
#define X86_SEL_TSS64 (27 << 3)
#define X86_SEL_TSS64_HI (28 << 3)
#define X86_SEL_TSS64_CPL3 ((29 << 3) + 3)
#define X86_SEL_TSS64_CPL3_HI (30 << 3)

#define X86_MSR_IA32_FEATURE_CONTROL 0x3a
#define X86_MSR_IA32_VMX_BASIC 0x480
#define X86_MSR_IA32_SMBASE 0x9e
#define X86_MSR_IA32_SYSENTER_CS 0x174
#define X86_MSR_IA32_SYSENTER_ESP 0x175
#define X86_MSR_IA32_SYSENTER_EIP 0x176
#define X86_MSR_IA32_STAR 0xC0000081
#define X86_MSR_IA32_LSTAR 0xC0000082
#define X86_MSR_IA32_VMX_PROCBASED_CTLS2 0x48B

#define X86_NEXT_INSN $0xbadc0de
#define X86_PREFIX_SIZE 0xba1d

#define KVM_MAX_VCPU 4
#define KVM_PAGE_SIZE (1 << 12)
#define KVM_GUEST_MEM_SIZE (1024 * KVM_PAGE_SIZE)
#define SZ_4K 0x00001000
#define SZ_64K 0x00010000
#define GENMASK_ULL(h, l)                                                      \
  (((~0ULL) - (1ULL << (l)) + 1ULL) & (~0ULL >> (63 - (h))))
#define ARM64_ADDR_GICD_BASE 0x08000000
#define ARM64_ADDR_GITS_BASE 0x08080000
#define ARM64_ADDR_GICR_BASE 0x080a0000
#define ARM64_ADDR_ITS_TABLES 0xc0000000
#define ARM64_ADDR_EXIT 0xdddd0000
#define ARM64_ADDR_UEXIT (ARM64_ADDR_EXIT + 256)
#define ARM64_ADDR_DIRTY_PAGES 0xdddd1000
#define ARM64_ADDR_USER_CODE 0xeeee0000
#define ARM64_ADDR_EXECUTOR_CODE 0xeeee8000
#define ARM64_ADDR_SCRATCH_CODE 0xeeef0000
#define ARM64_ADDR_EL1_STACK_BOTTOM 0xffff1000
#define ITS_MAX_DEVICES 16
#define ARM64_ADDR_ITS_DEVICE_TABLE (ARM64_ADDR_ITS_TABLES)
#define ARM64_ADDR_ITS_COLL_TABLE (ARM64_ADDR_ITS_DEVICE_TABLE + SZ_64K)
#define ARM64_ADDR_ITS_CMDQ_BASE (ARM64_ADDR_ITS_COLL_TABLE + SZ_64K)
#define ARM64_ADDR_ITS_ITT_TABLES (ARM64_ADDR_ITS_CMDQ_BASE + SZ_64K)
#define ARM64_ADDR_ITS_PROP_TABLE                                              \
  (ARM64_ADDR_ITS_ITT_TABLES + SZ_64K * ITS_MAX_DEVICES)
#define ARM64_ADDR_ITS_PEND_TABLES (ARM64_ADDR_ITS_PROP_TABLE + SZ_64K)

#define X86_ADDR_TEXT 0x0000
#define X86_ADDR_PD_IOAPIC 0x0000
#define X86_ADDR_GDT 0x1000
#define X86_ADDR_LDT 0x1800
#define X86_ADDR_PML4 0x2000
#define X86_ADDR_PDP 0x3000
#define X86_ADDR_PD 0x4000
#define X86_ADDR_STACK0 0x0f80
#define X86_ADDR_VAR_HLT 0x2800
#define X86_ADDR_VAR_SYSRET 0x2808
#define X86_ADDR_VAR_SYSEXIT 0x2810
#define X86_ADDR_VAR_IDT 0x3800
#define X86_ADDR_VAR_TSS64 0x3a00
#define X86_ADDR_VAR_TSS64_CPL3 0x3c00
#define X86_ADDR_VAR_TSS16 0x3d00
#define X86_ADDR_VAR_TSS16_2 0x3e00
#define X86_ADDR_VAR_TSS16_CPL3 0x3f00
#define X86_ADDR_VAR_TSS32 0x4800
#define X86_ADDR_VAR_TSS32_2 0x4a00
#define X86_ADDR_VAR_TSS32_CPL3 0x4c00
#define X86_ADDR_VAR_TSS32_VM86 0x4e00
#define X86_ADDR_VAR_VMXON_PTR 0x5f00
#define X86_ADDR_VAR_VMCS_PTR 0x5f08
#define X86_ADDR_VAR_VMEXIT_PTR 0x5f10
#define X86_ADDR_VAR_VMWRITE_FLD 0x5f18
#define X86_ADDR_VAR_VMWRITE_VAL 0x5f20
#define X86_ADDR_VAR_VMXON 0x6000
#define X86_ADDR_VAR_VMCS 0x7000
#define X86_ADDR_VAR_VMEXIT_CODE 0x9000
#define X86_ADDR_VAR_USER_CODE 0x9100
#define X86_ADDR_VAR_USER_CODE2 0x9120
#define X86_ADDR_SMRAM 0x30000
#define X86_ADDR_EXIT 0x40000
#define X86_ADDR_UEXIT (X86_ADDR_EXIT + 256)
#define X86_ADDR_DIRTY_PAGES 0x41000
#define X86_ADDR_USER_CODE 0x50000
#define X86_ADDR_EXECUTOR_CODE 0x54000
#define X86_ADDR_SCRATCH_CODE 0x58000
#define X86_ADDR_UNUSED 0x200000
#define X86_ADDR_IOAPIC 0xfec00000

#define X86_CR0_PE 1ULL
#define X86_CR0_MP (1ULL << 1)
#define X86_CR0_EM (1ULL << 2)
#define X86_CR0_TS (1ULL << 3)
#define X86_CR0_ET (1ULL << 4)
#define X86_CR0_NE (1ULL << 5)
#define X86_CR0_WP (1ULL << 16)
#define X86_CR0_AM (1ULL << 18)
#define X86_CR0_NW (1ULL << 29)
#define X86_CR0_CD (1ULL << 30)
#define X86_CR0_PG (1ULL << 31)

#define X86_CR4_VME 1ULL
#define X86_CR4_PVI (1ULL << 1)
#define X86_CR4_TSD (1ULL << 2)
#define X86_CR4_DE (1ULL << 3)
#define X86_CR4_PSE (1ULL << 4)
#define X86_CR4_PAE (1ULL << 5)
#define X86_CR4_MCE (1ULL << 6)
#define X86_CR4_PGE (1ULL << 7)
#define X86_CR4_PCE (1ULL << 8)
#define X86_CR4_OSFXSR (1ULL << 8)
#define X86_CR4_OSXMMEXCPT (1ULL << 10)
#define X86_CR4_UMIP (1ULL << 11)
#define X86_CR4_VMXE (1ULL << 13)
#define X86_CR4_SMXE (1ULL << 14)
#define X86_CR4_FSGSBASE (1ULL << 16)
#define X86_CR4_PCIDE (1ULL << 17)
#define X86_CR4_OSXSAVE (1ULL << 18)
#define X86_CR4_SMEP (1ULL << 20)
#define X86_CR4_SMAP (1ULL << 21)
#define X86_CR4_PKE (1ULL << 22)

#define X86_EFER_SCE 1ULL
#define X86_EFER_LME (1ULL << 8)
#define X86_EFER_LMA (1ULL << 10)
#define X86_EFER_NXE (1ULL << 11)
#define X86_EFER_SVME (1ULL << 12)
#define X86_EFER_LMSLE (1ULL << 13)
#define X86_EFER_FFXSR (1ULL << 14)
#define X86_EFER_TCE (1ULL << 15)
#define X86_PDE32_PRESENT 1UL
#define X86_PDE32_RW (1UL << 1)
#define X86_PDE32_USER (1UL << 2)
#define X86_PDE32_PS (1UL << 7)
#define X86_PDE64_PRESENT 1
#define X86_PDE64_RW (1ULL << 1)
#define X86_PDE64_USER (1ULL << 2)
#define X86_PDE64_ACCESSED (1ULL << 5)
#define X86_PDE64_DIRTY (1ULL << 6)
#define X86_PDE64_PS (1ULL << 7)
#define X86_PDE64_G (1ULL << 8)

#define X86_SEL_LDT (1 << 3)
#define X86_SEL_CS16 (2 << 3)
#define X86_SEL_DS16 (3 << 3)
#define X86_SEL_CS16_CPL3 ((4 << 3) + 3)
#define X86_SEL_DS16_CPL3 ((5 << 3) + 3)
#define X86_SEL_CS32 (6 << 3)
#define X86_SEL_DS32 (7 << 3)
#define X86_SEL_CS32_CPL3 ((8 << 3) + 3)
#define X86_SEL_DS32_CPL3 ((9 << 3) + 3)
#define X86_SEL_CS64 (10 << 3)
#define X86_SEL_DS64 (11 << 3)
#define X86_SEL_CS64_CPL3 ((12 << 3) + 3)
#define X86_SEL_DS64_CPL3 ((13 << 3) + 3)
#define X86_SEL_CGATE16 (14 << 3)
#define X86_SEL_TGATE16 (15 << 3)
#define X86_SEL_CGATE32 (16 << 3)
#define X86_SEL_TGATE32 (17 << 3)
#define X86_SEL_CGATE64 (18 << 3)
#define X86_SEL_CGATE64_HI (19 << 3)
#define X86_SEL_TSS16 (20 << 3)
#define X86_SEL_TSS16_2 (21 << 3)
#define X86_SEL_TSS16_CPL3 ((22 << 3) + 3)
#define X86_SEL_TSS32 (23 << 3)
#define X86_SEL_TSS32_2 (24 << 3)
#define X86_SEL_TSS32_CPL3 ((25 << 3) + 3)
#define X86_SEL_TSS32_VM86 (26 << 3)
#define X86_SEL_TSS64 (27 << 3)
#define X86_SEL_TSS64_HI (28 << 3)
#define X86_SEL_TSS64_CPL3 ((29 << 3) + 3)
#define X86_SEL_TSS64_CPL3_HI (30 << 3)

#define X86_MSR_IA32_FEATURE_CONTROL 0x3a
#define X86_MSR_IA32_VMX_BASIC 0x480
#define X86_MSR_IA32_SMBASE 0x9e
#define X86_MSR_IA32_SYSENTER_CS 0x174
#define X86_MSR_IA32_SYSENTER_ESP 0x175
#define X86_MSR_IA32_SYSENTER_EIP 0x176
#define X86_MSR_IA32_STAR 0xC0000081
#define X86_MSR_IA32_LSTAR 0xC0000082
#define X86_MSR_IA32_VMX_PROCBASED_CTLS2 0x48B

#define X86_NEXT_INSN $0xbadc0de
#define X86_PREFIX_SIZE 0xba1d

#define KVM_MAX_VCPU 4
#define KVM_PAGE_SIZE (1 << 12)
#define KVM_GUEST_MEM_SIZE (1024 * KVM_PAGE_SIZE)
#define SZ_4K 0x00001000
#define SZ_64K 0x00010000
#define GENMASK_ULL(h, l)                                                      \
  (((~0ULL) - (1ULL << (l)) + 1ULL) & (~0ULL >> (63 - (h))))
#define ARM64_ADDR_GICD_BASE 0x08000000
#define ARM64_ADDR_GITS_BASE 0x08080000
#define ARM64_ADDR_GICR_BASE 0x080a0000
#define ARM64_ADDR_ITS_TABLES 0xc0000000
#define ARM64_ADDR_EXIT 0xdddd0000
#define ARM64_ADDR_UEXIT (ARM64_ADDR_EXIT + 256)
#define ARM64_ADDR_DIRTY_PAGES 0xdddd1000
#define ARM64_ADDR_USER_CODE 0xeeee0000
#define ARM64_ADDR_EXECUTOR_CODE 0xeeee8000
#define ARM64_ADDR_SCRATCH_CODE 0xeeef0000
#define ARM64_ADDR_EL1_STACK_BOTTOM 0xffff1000
#define ITS_MAX_DEVICES 16
#define ARM64_ADDR_ITS_DEVICE_TABLE (ARM64_ADDR_ITS_TABLES)
#define ARM64_ADDR_ITS_COLL_TABLE (ARM64_ADDR_ITS_DEVICE_TABLE + SZ_64K)
#define ARM64_ADDR_ITS_CMDQ_BASE (ARM64_ADDR_ITS_COLL_TABLE + SZ_64K)
#define ARM64_ADDR_ITS_ITT_TABLES (ARM64_ADDR_ITS_CMDQ_BASE + SZ_64K)
#define ARM64_ADDR_ITS_PROP_TABLE                                              \
  (ARM64_ADDR_ITS_ITT_TABLES + SZ_64K * ITS_MAX_DEVICES)
#define ARM64_ADDR_ITS_PEND_TABLES (ARM64_ADDR_ITS_PROP_TABLE + SZ_64K)

#define GUEST_CODE __attribute__((section("guest")))
#define noinline __attribute__((noinline))
extern char *__start_guest, *__stop_guest;

typedef enum {
  SYZOS_API_UEXIT,
  SYZOS_API_CODE,
  SYZOS_API_MSR,
  SYZOS_API_SMC,
  SYZOS_API_HVC,
  SYZOS_API_IRQ_SETUP,
  SYZOS_API_MEMWRITE,
  SYZOS_API_ITS_SETUP,
  SYZOS_API_ITS_SEND_CMD,
  SYZOS_API_MRS,
  SYZOS_API_STOP,
} syzos_api_id;

struct api_call_header {
  uint64_t call;
  uint64_t size;
};

struct api_call_uexit {
  struct api_call_header header;
  uint64_t exit_code;
};

struct api_call_1 {
  struct api_call_header header;
  uint64_t arg;
};

struct api_call_2 {
  struct api_call_header header;
  uint64_t args[2];
};

struct api_call_3 {
  struct api_call_header header;
  uint64_t args[3];
};

struct api_call_code {
  struct api_call_header header;
  uint32_t insns[];
};

struct api_call_smccc {
  struct api_call_header header;
  uint32_t func_id;
  uint64_t params[5];
};

struct api_call_irq_setup {
  struct api_call_header header;
  uint32_t nr_cpus;
  uint32_t nr_spis;
};

struct api_call_memwrite {
  struct api_call_header header;
  uint64_t base_addr;
  uint64_t offset;
  uint64_t value;
  uint64_t len;
};

struct api_call_its_send_cmd {
  struct api_call_header header;
  uint8_t type;
  uint8_t valid;
  uint32_t cpuid;
  uint32_t devid;
  uint32_t eventid;
  uint32_t intid;
  uint32_t cpuid2;
};

static void guest_uexit(uint64_t exit_code);
static void guest_execute_code(uint32_t* insns, uint64_t size);
static void guest_handle_mrs(uint64_t reg);
static void guest_handle_msr(uint64_t reg, uint64_t 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_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_t)-1,
  UEXIT_IRQ = (uint64_t)-2,
  UEXIT_ASSERT = (uint64_t)-3,
} uexit_code;
__attribute__((used)) GUEST_CODE static void guest_main(uint64_t size,
                                                        uint64_t cpu)
{
  uint64_t 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_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_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_t)-1);
}

GUEST_CODE static noinline void guest_execute_code(uint32_t* insns,
                                                   uint64_t size)
{
  volatile void (*fn)() = (volatile void (*)())insns;
  fn();
}
GUEST_CODE static noinline void guest_uexit(uint64_t exit_code)
{
  volatile uint64_t* ptr = (volatile uint64_t*)ARM64_ADDR_UEXIT;
  *ptr = exit_code;
}

#define MSR_REG_OPCODE 0xd5100000
#define MRS_REG_OPCODE 0xd5300000
GUEST_CODE static uint32_t reg_to_msr(uint64_t reg)
{
  return MSR_REG_OPCODE | ((reg & 0xffff) << 5);
}
GUEST_CODE static uint32_t reg_to_mrs(uint64_t reg)
{
  return MRS_REG_OPCODE | ((reg & 0xffff) << 5);
}
GUEST_CODE static uint32_t get_cpu_id()
{
  uint64_t val = 0;
  asm volatile("mrs %0, tpidr_el1" : "=r"(val));
  return (uint32_t)val;
}
#define MAX_CACHE_LINE_SIZE 256
GUEST_CODE static noinline void guest_handle_mrs(uint64_t reg)
{
  uint32_t mrs = reg_to_mrs(reg);
  uint32_t cpu_id = get_cpu_id();
  uint32_t* insn = (uint32_t*)((uint64_t)ARM64_ADDR_SCRATCH_CODE +
                               cpu_id * MAX_CACHE_LINE_SIZE);
  insn[0] = mrs;
  insn[1] = 0xd65f03c0;
  asm("blr %[pc]\n" : : [pc] "r"(insn) : "x0", "x30");
}
GUEST_CODE static noinline void guest_handle_msr(uint64_t reg, uint64_t val)
{
  uint32_t msr = reg_to_msr(reg);
  uint32_t cpu_id = get_cpu_id();
  uint32_t* insn = (uint32_t*)((uint64_t)ARM64_ADDR_SCRATCH_CODE +
                               cpu_id * MAX_CACHE_LINE_SIZE);
  insn[0] = msr;
  insn[1] = 0xd65f03c0;
  asm("mov x0, %[val]\nblr %[pc]\n"
      :
      : [val] "r"(val), [pc] "r"(insn)
      : "x0", "x30", "memory");
}
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"
      "smc #0\n"
      :
      : [func_id] "r"((uint64_t)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", "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"
      "hvc #0\n"
      :
      : [func_id] "r"((uint64_t)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", "x6", "x7", "x8", "x9", "x10",
        "x11", "x12", "x13", "x14", "x15", "x16", "x17", "memory");
}
#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)
#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)
#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_t val, uint64_t addr)
{
  asm volatile("str %w0, [%1]" : : "rZ"(val), "r"(addr));
}

GUEST_CODE static __always_inline void __raw_writeq(uint64_t val, uint64_t addr)
{
  asm volatile("str %x0, [%1]" : : "rZ"(val), "r"(addr));
}

GUEST_CODE static __always_inline uint32_t __raw_readl(uint64_t addr)
{
  uint32_t val;
  asm volatile("ldr %w0, [%1]" : "=r"(val) : "r"(addr));
  return val;
}

GUEST_CODE static __always_inline uint64_t __raw_readq(uint64_t addr)
{
  uint64_t 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_t __v = __raw_readl(c);                                             \
    dmb();                                                                     \
    __v;                                                                       \
  })
#define writeq(v, c)                                                           \
  ({                                                                           \
    dmb();                                                                     \
    __raw_writeq(v, c);                                                        \
  })
#define readq(c)                                                               \
  ({                                                                           \
    uint64_t __v = __raw_readq(c);                                             \
    dmb();                                                                     \
    __v;                                                                       \
  })
#define GUEST_ASSERT(val)                                                      \
  do {                                                                         \
    if (!(val))                                                                \
      guest_uexit(UEXIT_ASSERT);                                               \
  } while (0)
GUEST_CODE static uint64_t read_cntvct(void)
{
  uint64_t val;
  asm volatile("mrs %0, cntvct_el0" : "=r"(val));
  return val;
}
GUEST_CODE static void guest_udelay(uint32_t us)
{
  uint64_t ticks_per_second = 0;
  asm volatile("mrs %0, cntfrq_el0" : "=r"(ticks_per_second));
  uint64_t start = read_cntvct();
  uint64_t target = start + (us * ticks_per_second) / 1000000;
  while (read_cntvct() < target) {
  }
}
GUEST_CODE static void spin_while_readl(uint64_t reg, uint32_t mask)
{
  volatile unsigned int count = 100000;
  while (readl(reg) & mask) {
    GUEST_ASSERT(count--);
    guest_udelay(10);
  }
}
GUEST_CODE static void gicd_wait_for_rwp()
{
  spin_while_readl(ARM64_ADDR_GICD_BASE + GICD_CTLR, GICD_CTLR_RWP);
}

GUEST_CODE static uint64_t gicr_base_cpu(uint32_t cpu)
{
  return ARM64_ADDR_GICR_BASE + cpu * SZ_64K * 2;
}

GUEST_CODE static uint64_t sgi_base_cpu(uint32_t cpu)
{
  return gicr_base_cpu(cpu) + SZ_64K;
}
GUEST_CODE static void gicr_wait_for_rwp(uint32_t cpu)
{
  spin_while_readl(gicr_base_cpu(cpu) + GICR_CTLR, GICR_CTLR_RWP);
}
GUEST_CODE static void gicv3_dist_init(int nr_spis)
{
  writel(0, ARM64_ADDR_GICD_BASE + GICD_CTLR);
  gicd_wait_for_rwp();
  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);
  }
  for (int i = 32; i < nr_spis + 32; i += 4) {
    writel(GICD_INT_DEF_PRI_X4, ARM64_ADDR_GICD_BASE + GICD_IPRIORITYR + i);
  }
  gicd_wait_for_rwp();
  writel(GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1,
         ARM64_ADDR_GICD_BASE + GICD_CTLR);
  gicd_wait_for_rwp();
}
GUEST_CODE static void gicv3_enable_redist(uint32_t cpu)
{
  uint64_t redist_base_cpu = gicr_base_cpu(cpu);
  uint32_t val = readl(redist_base_cpu + GICR_WAKER);
  val &= ~GICR_WAKER_ProcessorSleep;
  writel(val, ARM64_ADDR_GICR_BASE + GICR_WAKER);
  spin_while_readl(ARM64_ADDR_GICR_BASE + GICR_WAKER,
                   GICR_WAKER_ChildrenAsleep);
}

GUEST_CODE static void gicv3_cpu_init(uint32_t cpu)
{
  uint64_t sgi_base = sgi_base_cpu(cpu);
  gicv3_enable_redist(cpu);
  writel(~0, sgi_base + GICR_IGROUPR0);
  writel(~0, sgi_base + GICR_ICACTIVER0);
  writel(~0, sgi_base + GICR_ICENABLER0);
  for (int i = 0; i < 32; i += 4) {
    writel(GICD_INT_DEF_PRI_X4, sgi_base + GICR_IPRIORITYR0 + i);
  }
  gicr_wait_for_rwp(cpu);
  uint64_t 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));
  uint64_t value = ICC_PMR_DEF_PRIO;
  asm volatile("msr " ICC_PMR_EL1 ", %0" : : "r"(value));
  value = ICC_IGRPEN1_EL1_ENABLE;
  asm volatile("msr " ICC_IGRPEN1_EL1 ", %0" : : "r"(value));
}
#define VGICV3_MIN_SPI 32
#define VGICV3_MAX_SPI 1019
GUEST_CODE static void gicv3_irq_enable(uint32_t intid)
{
  uint32_t 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);
  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_t dest = cmd->base_addr + cmd->offset;
  switch (cmd->len) {
  case 1: {
    volatile uint8_t* p = (uint8_t*)dest;
    *p = (uint8_t)cmd->value;
    break;
  }
  case 2: {
    volatile uint16_t* p = (uint16_t*)dest;
    *p = (uint16_t)cmd->value;
    break;
  }
  case 4: {
    volatile uint32_t* p = (uint32_t*)dest;
    *p = (uint32_t)cmd->value;
    break;
  }
  case 8:
  default: {
    volatile uint64_t* p = (uint64_t*)dest;
    *p = (uint64_t)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]);
}
struct ex_regs {
  uint64_t regs[31];
  uint64_t sp;
  uint64_t pc;
  uint64_t pstate;
};
__attribute__((used)) GUEST_CODE static void one_irq_handler_fn()
{
  asm volatile(
      R"(.global one_irq_handler
	       one_irq_handler:
	       # Allocate 34 * uint64_t 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)"
      :
      :
      : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10",
        "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "x19", "x20",
        "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30",
        "memory");
}

__attribute__((used)) GUEST_CODE static void
guest_irq_handler(struct ex_regs* regs)
{
  uint64_t iar0, iar1, irq_num = 0;
  bool is_group0 = false;
  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;
  }
  guest_uexit(UEXIT_IRQ);
  if (is_group0) {
    asm volatile("msr " ICC_EOIR0_EL1 ", %0" : : "r"(irq_num));
  } else {
    asm volatile("msr " ICC_EOIR1_EL1 ", %0" : : "r"(irq_num));
  }
  asm volatile("msr " ICC_DIR_EL1 ", %0" : : "r"(irq_num));
}
#define IRQ_ENTRY                                                              \
  ".balign 0x80\n"                                                             \
  "b one_irq_handler\n"
#define IRQ_ENTRY_DUMMY                                                        \
  ".balign 0x80\n"                                                             \
  "eret\n"
__attribute__((used)) GUEST_CODE static void guest_vector_table_fn()
{
  asm volatile(
      ".global guest_vector_table\n"
      ".balign 2048\n"
      "guest_vector_table:\n" IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY
          IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY IRQ_ENTRY_DUMMY
              IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY
                  IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY
                      IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY);
}
#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_t its_read_u64(unsigned long offset)
{
  return readq(ARM64_ADDR_GITS_BASE + offset);
}

GUEST_CODE static void its_write_u64(unsigned long offset, uint64_t val)
{
  writeq(val, ARM64_ADDR_GITS_BASE + offset);
}

GUEST_CODE static uint32_t its_read_u32(unsigned long offset)
{
  return readl(ARM64_ADDR_GITS_BASE + offset);
}

GUEST_CODE static void its_write_u32(unsigned long offset, uint32_t val)
{
  writel(val, ARM64_ADDR_GITS_BASE + offset);
}

struct its_cmd_block {
  uint64_t raw_cmd[4];
};
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];
}
GUEST_CODE static noinline void its_send_cmd(uint64_t cmdq_base,
                                             struct its_cmd_block* cmd)
{
  uint64_t cwriter = its_read_u64(GITS_CWRITER);
  struct its_cmd_block* dst = (struct its_cmd_block*)(cmdq_base + cwriter);
  uint64_t cbaser = its_read_u64(GITS_CBASER);
  size_t cmdq_size = ((cbaser & 0xFF) + 1) * SZ_4K;
  guest_memcpy(dst, cmd, sizeof(*cmd));
  dmb();
  uint64_t next = (cwriter + sizeof(*cmd)) % cmdq_size;
  its_write_u64(GITS_CWRITER, next);
}

GUEST_CODE static unsigned long its_find_baser(unsigned int type)
{
  for (int i = 0; i < GITS_BASER_NR_REGS; i++) {
    uint64_t 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_t base,
                                         size_t size)
{
  unsigned long offset = its_find_baser(type);
  uint64_t 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_t base, size_t size)
{
  uint64_t 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_t coll_tbl, uint64_t device_tbl,
                                uint64_t 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_t 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
GUEST_CODE static noinline void its_mask_encode(uint64_t* raw_cmd, uint64_t val,
                                                int h, int l)
{
  uint64_t 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_t 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_t 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_t 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_t 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_t size)
{
  its_mask_encode(&cmd->raw_cmd[1], size, 4, 0);
}

GUEST_CODE static void its_encode_itt(struct its_cmd_block* cmd,
                                      uint64_t 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_t target_addr)
{
  its_mask_encode(&cmd->raw_cmd[2], target_addr >> 16, 51, 16);
}
GUEST_CODE static void its_encode_target2(struct its_cmd_block* cmd,
                                          uint64_t 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_t 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_t cmdq_base, uint32_t device_id,
                                         uint64_t 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_t cmdq_base, uint32_t vcpu_id,
                                         uint32_t 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_t cmdq_base,
                                          uint32_t device_id, uint32_t event_id,
                                          uint32_t collection_id,
                                          uint32_t 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_t cmdq_base,
                                                       uint8_t cmd_nr,
                                                       uint32_t device_id,
                                                       uint32_t event_id,
                                                       uint32_t 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_t cmdq_base,
                                                  uint8_t cmd_nr,
                                                  uint32_t device_id,
                                                  uint32_t 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_t cmdq_base, uint32_t vcpu_id,
                                           uint32_t 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_t cmdq_base,
                                           uint32_t 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);
}
#define SYZOS_NUM_IDBITS 16

GUEST_CODE static void its_send_sync_cmd(uint64_t cmdq_base, uint32_t 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);
}
GUEST_CODE static noinline void
guest_handle_its_send_cmd(struct api_call_its_send_cmd* cmd)
{
  volatile uint8_t type = cmd->type;
  if (type == GITS_CMD_MAPD) {
    uint64_t 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_t cmdq_base,
                                                         uint64_t itt_tables,
                                                         uint32_t nr_events,
                                                         uint32_t nr_devices,
                                                         uint32_t nr_cpus)
{
  if ((nr_events < 1) || (nr_devices < 1) || (nr_cpus < 1))
    return;
  uint32_t coll_id, device_id, event_id, intid = GIC_LPI_OFFSET;
  for (coll_id = 0; coll_id < nr_cpus; coll_id++) {
    its_send_mapc_cmd(cmdq_base, coll_id, coll_id, true);
  }
  coll_id = 0;
  for (device_id = 0; device_id < nr_devices; device_id++) {
    uint64_t 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_t cmdq_base,
                                                   int nr_cpus)
{
  for (int i = 0; i < nr_cpus; i++)
    its_send_invall_cmd(cmdq_base, i);
}
void gic_rdist_enable_lpis(uint64_t cfg_table, size_t cfg_table_size,
                           uint64_t pend_table)
{
  uint64_t rdist_base = gicr_base_cpu(get_cpu_id());
  uint64_t 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_t 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)
GUEST_CODE static noinline void configure_lpis(uint64_t prop_table,
                                               int nr_devices, int nr_events)
{
  int nr_lpis = nr_devices * nr_events;
  volatile uint8_t* tbl = (uint8_t*)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);
}

#define KVM_ARM64_REGS_X0 0x6030000000100000UL
#define KVM_ARM64_REGS_X1 0x6030000000100002UL
#define KVM_ARM64_REGS_PC 0x6030000000100040UL
#define KVM_ARM64_REGS_SP_EL1 0x6030000000100044UL
#define KVM_ARM64_REGS_TPIDR_EL1 0x603000000013c684

struct kvm_text {
  uintptr_t typ;
  const void* text;
  uintptr_t size;
};

struct kvm_opt {
  uint64_t typ;
  uint64_t val;
};

struct addr_size {
  void* addr;
  size_t size;
};

static struct addr_size alloc_guest_mem(struct addr_size* free, size_t size)
{
  struct addr_size ret = {.addr = NULL, .size = 0};
  if (free->size < size)
    return ret;
  ret.addr = free->addr;
  ret.size = size;
  free->addr = (void*)((char*)free->addr + size);
  free->size -= size;
  return ret;
}
static void vm_set_user_memory_region(int vmfd, uint32_t slot, uint32_t flags,
                                      uint64_t guest_phys_addr,
                                      uint64_t memory_size,
                                      uint64_t userspace_addr)
{
  struct kvm_userspace_memory_region memreg;
  memreg.slot = slot;
  memreg.flags = flags;
  memreg.guest_phys_addr = guest_phys_addr;
  memreg.memory_size = memory_size;
  memreg.userspace_addr = userspace_addr;
  ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg);
}

#define ADRP_OPCODE 0x90000000
#define ADRP_OPCODE_MASK 0x9f000000
static void validate_guest_code(void* mem, size_t size)
{
  uint32_t* insns = (uint32_t*)mem;
  for (size_t i = 0; i < size / 4; i++) {
    if ((insns[i] & ADRP_OPCODE_MASK) == ADRP_OPCODE)
      exit(1);
  }
}

static void install_syzos_code(void* host_mem, size_t mem_size)
{
  size_t size = (char*)&__stop_guest - (char*)&__start_guest;
  if (size > mem_size)
    exit(1);
  memcpy(host_mem, &__start_guest, size);
  validate_guest_code(host_mem, size);
}

static void setup_vm(int vmfd, void* host_mem, void** text_slot)
{
  struct addr_size allocator = {.addr = host_mem, .size = KVM_GUEST_MEM_SIZE};
  int slot = 0;
  struct addr_size host_text = alloc_guest_mem(&allocator, 4 * KVM_PAGE_SIZE);
  install_syzos_code(host_text.addr, host_text.size);
  vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY,
                            ARM64_ADDR_EXECUTOR_CODE, host_text.size,
                            (uintptr_t)host_text.addr);
  struct addr_size next = alloc_guest_mem(&allocator, 2 * KVM_PAGE_SIZE);
  vm_set_user_memory_region(vmfd, slot++, KVM_MEM_LOG_DIRTY_PAGES,
                            ARM64_ADDR_DIRTY_PAGES, next.size,
                            (uintptr_t)next.addr);
  next = alloc_guest_mem(&allocator, KVM_MAX_VCPU * KVM_PAGE_SIZE);
  vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY,
                            ARM64_ADDR_USER_CODE, next.size,
                            (uintptr_t)next.addr);
  if (text_slot)
    *text_slot = next.addr;
  next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE);
  vm_set_user_memory_region(vmfd, slot++, 0, ARM64_ADDR_EL1_STACK_BOTTOM,
                            next.size, (uintptr_t)next.addr);
  next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE);
  vm_set_user_memory_region(vmfd, slot++, 0, ARM64_ADDR_SCRATCH_CODE, next.size,
                            (uintptr_t)next.addr);
  int its_size = SZ_64K * (4 + 4 + 16);
  next = alloc_guest_mem(&allocator, its_size);
  vm_set_user_memory_region(vmfd, slot++, 0, ARM64_ADDR_ITS_TABLES, next.size,
                            (uintptr_t)next.addr);
  next = alloc_guest_mem(&allocator, allocator.size);
  vm_set_user_memory_region(vmfd, slot++, 0, 0, next.size,
                            (uintptr_t)next.addr);
}

static void vcpu_set_reg(int vcpu_fd, uint64_t id, uint64_t val)
{
  struct kvm_one_reg reg = {.id = id, .addr = (uint64_t)&val};
  ioctl(vcpu_fd, KVM_SET_ONE_REG, &reg);
}
static void reset_cpu_regs(int cpufd, int cpu_id, size_t text_size)
{
  vcpu_set_reg(cpufd, KVM_ARM64_REGS_PC,
               ARM64_ADDR_EXECUTOR_CODE +
                   ((uint64_t)guest_main - (uint64_t)&__start_guest));
  vcpu_set_reg(cpufd, KVM_ARM64_REGS_SP_EL1,
               ARM64_ADDR_EL1_STACK_BOTTOM + KVM_PAGE_SIZE - 128);
  vcpu_set_reg(cpufd, KVM_ARM64_REGS_TPIDR_EL1, cpu_id);
  vcpu_set_reg(cpufd, KVM_ARM64_REGS_X0, text_size);
  vcpu_set_reg(cpufd, KVM_ARM64_REGS_X1, cpu_id);
}

static void install_user_code(int cpufd, void* user_text_slot, int cpu_id,
                              const void* text, size_t text_size)
{
  if ((cpu_id < 0) || (cpu_id >= KVM_MAX_VCPU))
    return;
  if (!user_text_slot)
    return;
  if (text_size > KVM_PAGE_SIZE)
    text_size = KVM_PAGE_SIZE;
  void* target = (void*)((uint64_t)user_text_slot + (KVM_PAGE_SIZE * cpu_id));
  memcpy(target, text, text_size);
  reset_cpu_regs(cpufd, cpu_id, text_size);
}

static void setup_cpu_with_opts(int vmfd, int cpufd, const struct kvm_opt* opt,
                                int opt_count)
{
  uint32_t features = 0;
  if (opt_count > 1)
    opt_count = 1;
  for (int i = 0; i < opt_count; i++) {
    uint64_t typ = opt[i].typ;
    uint64_t val = opt[i].val;
    switch (typ) {
    case 1:
      features = val;
      break;
    }
  }
  struct kvm_vcpu_init init;
  ioctl(vmfd, KVM_ARM_PREFERRED_TARGET, &init);
  init.features[0] = features;
  ioctl(cpufd, KVM_ARM_VCPU_INIT, &init);
}

struct kvm_syz_vm {
  int vmfd;
  int next_cpu_id;
  void* user_text;
};

static long syz_kvm_setup_syzos_vm(volatile long a0, volatile long a1)
{
  const int vmfd = a0;
  void* host_mem = (void*)a1;
  void* user_text_slot = NULL;
  struct kvm_syz_vm* ret = (struct kvm_syz_vm*)host_mem;
  host_mem = (void*)((uint64_t)host_mem + KVM_PAGE_SIZE);
  setup_vm(vmfd, host_mem, &user_text_slot);
  ret->vmfd = vmfd;
  ret->next_cpu_id = 0;
  ret->user_text = user_text_slot;
  return (long)ret;
}

static long syz_kvm_add_vcpu(volatile long a0, volatile long a1,
                             volatile long a2, volatile long a3)
{
  struct kvm_syz_vm* vm = (struct kvm_syz_vm*)a0;
  struct kvm_text* utext = (struct kvm_text*)a1;
  const void* text = utext->text;
  size_t text_size = utext->size;
  const struct kvm_opt* const opt_array_ptr = (struct kvm_opt*)a2;
  uintptr_t opt_count = a3;
  if (!vm) {
    errno = EINVAL;
    return -1;
  }
  if (vm->next_cpu_id == KVM_MAX_VCPU) {
    errno = ENOMEM;
    return -1;
  }
  int cpu_id = vm->next_cpu_id;
  int cpufd = ioctl(vm->vmfd, KVM_CREATE_VCPU, cpu_id);
  if (cpufd == -1)
    return -1;
  vm->next_cpu_id++;
  setup_cpu_with_opts(vm->vmfd, cpufd, opt_array_ptr, opt_count);
  install_user_code(cpufd, vm->user_text, cpu_id, text, text_size);
  return cpufd;
}

static int kvm_set_device_attr(int dev_fd, uint32_t group, uint64_t attr,
                               void* val)
{
  struct kvm_device_attr kvmattr = {
      .flags = 0,
      .group = group,
      .attr = attr,
      .addr = (uintptr_t)val,
  };
  return ioctl(dev_fd, KVM_SET_DEVICE_ATTR, &kvmattr);
}

static int kvm_create_device(int vm_fd, int type)
{
  struct kvm_create_device create_dev = {
      .type = (uint32_t)type,
      .fd = (uint32_t)-1,
      .flags = 0,
  };
  if (ioctl(vm_fd, KVM_CREATE_DEVICE, &create_dev) != -1)
    return create_dev.fd;
  else
    return -1;
}

#define REDIST_REGION_ATTR_ADDR(count, base, flags, index)                     \
  (((uint64_t)(count) << 52) | ((uint64_t)((base) >> 16) << 16) |              \
   ((uint64_t)(flags) << 12) | index)
static long syz_kvm_vgic_v3_setup(volatile long a0, volatile long a1,
                                  volatile long a2)
{
  const int vm_fd = a0;
  const int nr_vcpus = a1;
  const int want_nr_irq = a2;
  int vgic_fd = kvm_create_device(vm_fd, KVM_DEV_TYPE_ARM_VGIC_V3);
  if (vgic_fd == -1)
    return -1;
  uint32_t nr_irq = want_nr_irq;
  int ret =
      kvm_set_device_attr(vgic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, 0, &nr_irq);
  if (ret == -1) {
    close(vgic_fd);
    return -1;
  }
  uint64_t gicd_base_gpa = ARM64_ADDR_GICD_BASE;
  ret = kvm_set_device_attr(vgic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
                            KVM_VGIC_V3_ADDR_TYPE_DIST, &gicd_base_gpa);
  if (ret == -1) {
    close(vgic_fd);
    return -1;
  }
  uint64_t redist_attr =
      REDIST_REGION_ATTR_ADDR(nr_vcpus, ARM64_ADDR_GICR_BASE, 0, 0);
  ret = kvm_set_device_attr(vgic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
                            KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &redist_attr);
  if (ret == -1) {
    close(vgic_fd);
    return -1;
  }
  ret = kvm_set_device_attr(vgic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
                            KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
  if (ret == -1) {
    close(vgic_fd);
    return -1;
  }
  return vgic_fd;
}

static void kill_and_wait(int pid, int* status)
{
  kill(-pid, SIGKILL);
  kill(pid, SIGKILL);
  for (int i = 0; i < 100; i++) {
    if (waitpid(-1, status, WNOHANG | __WALL) == pid)
      return;
    usleep(1000);
  }
  DIR* dir = opendir("/sys/fs/fuse/connections");
  if (dir) {
    for (;;) {
      struct dirent* ent = readdir(dir);
      if (!ent)
        break;
      if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
        continue;
      char abort[300];
      snprintf(abort, sizeof(abort), "/sys/fs/fuse/connections/%s/abort",
               ent->d_name);
      int fd = open(abort, O_WRONLY);
      if (fd == -1) {
        continue;
      }
      if (write(fd, abort, 1) < 0) {
      }
      close(fd);
    }
    closedir(dir);
  } else {
  }
  while (waitpid(-1, status, __WALL) != pid) {
  }
}

static void setup_test()
{
  prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
  setpgrp();
  write_file("/proc/self/oom_score_adj", "1000");
}

struct thread_t {
  int created, call;
  event_t ready, done;
};

static struct thread_t threads[16];
static void execute_call(int call);
static int running;

static void* thr(void* arg)
{
  struct thread_t* th = (struct thread_t*)arg;
  for (;;) {
    event_wait(&th->ready);
    event_reset(&th->ready);
    execute_call(th->call);
    __atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
    event_set(&th->done);
  }
  return 0;
}

static void execute_one(void)
{
  if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {
  }
  int i, call, thread;
  for (call = 0; call < 10; call++) {
    for (thread = 0; thread < (int)(sizeof(threads) / sizeof(threads[0]));
         thread++) {
      struct thread_t* th = &threads[thread];
      if (!th->created) {
        th->created = 1;
        event_init(&th->ready);
        event_init(&th->done);
        event_set(&th->done);
        thread_start(thr, th);
      }
      if (!event_isset(&th->done))
        continue;
      event_reset(&th->done);
      th->call = call;
      __atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
      event_set(&th->ready);
      event_timedwait(&th->done, 500);
      break;
    }
  }
  for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
    sleep_ms(1);
}

static void execute_one(void);

#define WAIT_FLAGS __WALL

static void loop(void)
{
  int iter = 0;
  for (;; iter++) {
    int pid = fork();
    if (pid < 0)
      exit(1);
    if (pid == 0) {
      setup_test();
      execute_one();
      exit(0);
    }
    int status = 0;
    uint64_t start = current_time_ms();
    for (;;) {
      sleep_ms(10);
      if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
        break;
      if (current_time_ms() - start < 15000)
        continue;
      kill_and_wait(pid, &status);
      break;
    }
  }
}

uint64_t r[5] = {0xffffffffffffffff, 0xffffffffffffffff, 0x0,
                 0xffffffffffffffff, 0xffffffffffffffff};

void execute_call(int call)
{
  intptr_t res = 0;
  switch (call) {
  case 0:
    memcpy((void*)0x20000040, "/dev/kvm\000", 9);
    res = syscall(__NR_openat, /*fd=*/0ul, /*file=*/0x20000040ul, /*flags=*/0,
                  /*mode=*/0);
    if (res != -1)
      r[0] = res;
    break;
  case 1:
    res = syscall(__NR_ioctl, /*fd=*/r[0], /*cmd=*/0xae01, /*type=*/0ul);
    if (res != -1)
      r[1] = res;
    break;
  case 2:
    res = -1;
    res = syz_kvm_setup_syzos_vm(/*fd=*/r[1], /*usermem=*/0x20c00000);
    if (res != -1)
      r[2] = res;
    break;
  case 3:
    *(uint64_t*)0x20000080 = 0;
    *(uint64_t*)0x20000088 = 0x200000c0;
    *(uint64_t*)0x200000c0 = 7;
    *(uint64_t*)0x200000c8 = 0x28;
    *(uint64_t*)0x200000d0 = 2;
    *(uint64_t*)0x200000d8 = 2;
    *(uint64_t*)0x200000e0 = 1;
    *(uint64_t*)0x20000090 = 0x28;
    res = -1;
    res = syz_kvm_add_vcpu(/*vm=*/r[2], /*text=*/0x20000080, /*opts=*/0,
                           /*nopt=*/0);
    if (res != -1)
      r[3] = res;
    break;
  case 4:
    syz_kvm_vgic_v3_setup(/*fd=*/r[1], /*ncpus=*/3, /*nirqs=*/0xa0);
    break;
  case 5:
    *(uint32_t*)0x20000100 = 8;
    *(uint32_t*)0x20000108 = 0;
    res = syscall(__NR_ioctl, /*fd=*/r[1], /*cmd=*/0xc00caee0,
                  /*arg=*/0x20000100ul);
    if (res != -1)
      r[4] = *(uint32_t*)0x20000104;
    break;
  case 6:
    *(uint32_t*)0x20000000 = 0;
    *(uint32_t*)0x20000004 = 0;
    *(uint64_t*)0x20000008 = 4;
    *(uint64_t*)0x20000010 = 0x20000180;
    *(uint64_t*)0x20000180 = 0x8080000;
    syscall(__NR_ioctl, /*fd=*/r[4], /*cmd=*/0x4018aee1, /*arg=*/0x20000000ul);
    break;
  case 7:
    syscall(__NR_ioctl, /*fd=*/r[3], /*cmd=*/0xae80, /*arg=*/0ul);
    break;
  case 8:
    *(uint32_t*)0x20000300 = 0;
    *(uint32_t*)0x20000304 = 4;
    *(uint64_t*)0x20000308 = 1;
    *(uint64_t*)0x20000310 = 0;
    syscall(__NR_ioctl, /*fd=*/r[4], /*cmd=*/0x4018aee1, /*arg=*/0x20000300ul);
    for (int i = 0; i < 64; i++) {
      syscall(__NR_ioctl, /*fd=*/r[4], /*cmd=*/0x4018aee1,
              /*arg=*/0x20000300ul);
    }
    break;
  case 9:
    *(uint32_t*)0x200000c0 = 0;
    *(uint32_t*)0x200000c4 = 4;
    *(uint64_t*)0x200000c8 = 2;
    *(uint64_t*)0x200000d0 = 0;
    syscall(__NR_ioctl, /*fd=*/r[4], /*cmd=*/0x4018aee1, /*arg=*/0x200000c0ul);
    break;
  }
}
int main(void)
{
  syscall(__NR_mmap, /*addr=*/0x1ffff000ul, /*len=*/0x1000ul, /*prot=*/0ul,
          /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
          /*fd=*/(intptr_t)-1, /*offset=*/0ul);
  syscall(__NR_mmap, /*addr=*/0x20000000ul, /*len=*/0x1000000ul,
          /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/ 7ul,
          /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
          /*fd=*/(intptr_t)-1, /*offset=*/0ul);
  syscall(__NR_mmap, /*addr=*/0x21000000ul, /*len=*/0x1000ul, /*prot=*/0ul,
          /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
          /*fd=*/(intptr_t)-1, /*offset=*/0ul);
  const char* reason;
  (void)reason;
  for (procid = 0; procid < 2; procid++) {
    if (fork() == 0) {
      loop();
    }
  }
  sleep(1000000);
  return 0;
}