processor.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * RISC-V code
  4. *
  5. * Copyright (C) 2021 Western Digital Corporation or its affiliates.
  6. */
  7. #include <linux/compiler.h>
  8. #include <assert.h>
  9. #include "kvm_util.h"
  10. #include "processor.h"
  11. #include "ucall_common.h"
  12. #define DEFAULT_RISCV_GUEST_STACK_VADDR_MIN 0xac0000
  13. static vm_vaddr_t exception_handlers;
  14. bool __vcpu_has_ext(struct kvm_vcpu *vcpu, uint64_t ext)
  15. {
  16. unsigned long value = 0;
  17. int ret;
  18. ret = __vcpu_get_reg(vcpu, ext, &value);
  19. return !ret && !!value;
  20. }
  21. static uint64_t page_align(struct kvm_vm *vm, uint64_t v)
  22. {
  23. return (v + vm->page_size) & ~(vm->page_size - 1);
  24. }
  25. static uint64_t pte_addr(struct kvm_vm *vm, uint64_t entry)
  26. {
  27. return ((entry & PGTBL_PTE_ADDR_MASK) >> PGTBL_PTE_ADDR_SHIFT) <<
  28. PGTBL_PAGE_SIZE_SHIFT;
  29. }
  30. static uint64_t ptrs_per_pte(struct kvm_vm *vm)
  31. {
  32. return PGTBL_PAGE_SIZE / sizeof(uint64_t);
  33. }
  34. static uint64_t pte_index_mask[] = {
  35. PGTBL_L0_INDEX_MASK,
  36. PGTBL_L1_INDEX_MASK,
  37. PGTBL_L2_INDEX_MASK,
  38. PGTBL_L3_INDEX_MASK,
  39. };
  40. static uint32_t pte_index_shift[] = {
  41. PGTBL_L0_INDEX_SHIFT,
  42. PGTBL_L1_INDEX_SHIFT,
  43. PGTBL_L2_INDEX_SHIFT,
  44. PGTBL_L3_INDEX_SHIFT,
  45. };
  46. static uint64_t pte_index(struct kvm_vm *vm, vm_vaddr_t gva, int level)
  47. {
  48. TEST_ASSERT(level > -1,
  49. "Negative page table level (%d) not possible", level);
  50. TEST_ASSERT(level < vm->pgtable_levels,
  51. "Invalid page table level (%d)", level);
  52. return (gva & pte_index_mask[level]) >> pte_index_shift[level];
  53. }
  54. void virt_arch_pgd_alloc(struct kvm_vm *vm)
  55. {
  56. size_t nr_pages = page_align(vm, ptrs_per_pte(vm) * 8) / vm->page_size;
  57. if (vm->pgd_created)
  58. return;
  59. vm->pgd = vm_phy_pages_alloc(vm, nr_pages,
  60. KVM_GUEST_PAGE_TABLE_MIN_PADDR,
  61. vm->memslots[MEM_REGION_PT]);
  62. vm->pgd_created = true;
  63. }
  64. void virt_arch_pg_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr)
  65. {
  66. uint64_t *ptep, next_ppn;
  67. int level = vm->pgtable_levels - 1;
  68. TEST_ASSERT((vaddr % vm->page_size) == 0,
  69. "Virtual address not on page boundary,\n"
  70. " vaddr: 0x%lx vm->page_size: 0x%x", vaddr, vm->page_size);
  71. TEST_ASSERT(sparsebit_is_set(vm->vpages_valid,
  72. (vaddr >> vm->page_shift)),
  73. "Invalid virtual address, vaddr: 0x%lx", vaddr);
  74. TEST_ASSERT((paddr % vm->page_size) == 0,
  75. "Physical address not on page boundary,\n"
  76. " paddr: 0x%lx vm->page_size: 0x%x", paddr, vm->page_size);
  77. TEST_ASSERT((paddr >> vm->page_shift) <= vm->max_gfn,
  78. "Physical address beyond maximum supported,\n"
  79. " paddr: 0x%lx vm->max_gfn: 0x%lx vm->page_size: 0x%x",
  80. paddr, vm->max_gfn, vm->page_size);
  81. ptep = addr_gpa2hva(vm, vm->pgd) + pte_index(vm, vaddr, level) * 8;
  82. if (!*ptep) {
  83. next_ppn = vm_alloc_page_table(vm) >> PGTBL_PAGE_SIZE_SHIFT;
  84. *ptep = (next_ppn << PGTBL_PTE_ADDR_SHIFT) |
  85. PGTBL_PTE_VALID_MASK;
  86. }
  87. level--;
  88. while (level > -1) {
  89. ptep = addr_gpa2hva(vm, pte_addr(vm, *ptep)) +
  90. pte_index(vm, vaddr, level) * 8;
  91. if (!*ptep && level > 0) {
  92. next_ppn = vm_alloc_page_table(vm) >>
  93. PGTBL_PAGE_SIZE_SHIFT;
  94. *ptep = (next_ppn << PGTBL_PTE_ADDR_SHIFT) |
  95. PGTBL_PTE_VALID_MASK;
  96. }
  97. level--;
  98. }
  99. paddr = paddr >> PGTBL_PAGE_SIZE_SHIFT;
  100. *ptep = (paddr << PGTBL_PTE_ADDR_SHIFT) |
  101. PGTBL_PTE_PERM_MASK | PGTBL_PTE_VALID_MASK;
  102. }
  103. vm_paddr_t addr_arch_gva2gpa(struct kvm_vm *vm, vm_vaddr_t gva)
  104. {
  105. uint64_t *ptep;
  106. int level = vm->pgtable_levels - 1;
  107. if (!vm->pgd_created)
  108. goto unmapped_gva;
  109. ptep = addr_gpa2hva(vm, vm->pgd) + pte_index(vm, gva, level) * 8;
  110. if (!ptep)
  111. goto unmapped_gva;
  112. level--;
  113. while (level > -1) {
  114. ptep = addr_gpa2hva(vm, pte_addr(vm, *ptep)) +
  115. pte_index(vm, gva, level) * 8;
  116. if (!ptep)
  117. goto unmapped_gva;
  118. level--;
  119. }
  120. return pte_addr(vm, *ptep) + (gva & (vm->page_size - 1));
  121. unmapped_gva:
  122. TEST_FAIL("No mapping for vm virtual address gva: 0x%lx level: %d",
  123. gva, level);
  124. exit(1);
  125. }
  126. static void pte_dump(FILE *stream, struct kvm_vm *vm, uint8_t indent,
  127. uint64_t page, int level)
  128. {
  129. #ifdef DEBUG
  130. static const char *const type[] = { "pte", "pmd", "pud", "p4d"};
  131. uint64_t pte, *ptep;
  132. if (level < 0)
  133. return;
  134. for (pte = page; pte < page + ptrs_per_pte(vm) * 8; pte += 8) {
  135. ptep = addr_gpa2hva(vm, pte);
  136. if (!*ptep)
  137. continue;
  138. fprintf(stream, "%*s%s: %lx: %lx at %p\n", indent, "",
  139. type[level], pte, *ptep, ptep);
  140. pte_dump(stream, vm, indent + 1,
  141. pte_addr(vm, *ptep), level - 1);
  142. }
  143. #endif
  144. }
  145. void virt_arch_dump(FILE *stream, struct kvm_vm *vm, uint8_t indent)
  146. {
  147. int level = vm->pgtable_levels - 1;
  148. uint64_t pgd, *ptep;
  149. if (!vm->pgd_created)
  150. return;
  151. for (pgd = vm->pgd; pgd < vm->pgd + ptrs_per_pte(vm) * 8; pgd += 8) {
  152. ptep = addr_gpa2hva(vm, pgd);
  153. if (!*ptep)
  154. continue;
  155. fprintf(stream, "%*spgd: %lx: %lx at %p\n", indent, "",
  156. pgd, *ptep, ptep);
  157. pte_dump(stream, vm, indent + 1,
  158. pte_addr(vm, *ptep), level - 1);
  159. }
  160. }
  161. void riscv_vcpu_mmu_setup(struct kvm_vcpu *vcpu)
  162. {
  163. struct kvm_vm *vm = vcpu->vm;
  164. unsigned long satp;
  165. /*
  166. * The RISC-V Sv48 MMU mode supports 56-bit physical address
  167. * for 48-bit virtual address with 4KB last level page size.
  168. */
  169. switch (vm->mode) {
  170. case VM_MODE_P52V48_4K:
  171. case VM_MODE_P48V48_4K:
  172. case VM_MODE_P40V48_4K:
  173. break;
  174. default:
  175. TEST_FAIL("Unknown guest mode, mode: 0x%x", vm->mode);
  176. }
  177. satp = (vm->pgd >> PGTBL_PAGE_SIZE_SHIFT) & SATP_PPN;
  178. satp |= SATP_MODE_48;
  179. vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(satp), satp);
  180. }
  181. void vcpu_arch_dump(FILE *stream, struct kvm_vcpu *vcpu, uint8_t indent)
  182. {
  183. struct kvm_riscv_core core;
  184. vcpu_get_reg(vcpu, RISCV_CORE_REG(mode), &core.mode);
  185. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.pc), &core.regs.pc);
  186. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.ra), &core.regs.ra);
  187. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.sp), &core.regs.sp);
  188. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.gp), &core.regs.gp);
  189. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.tp), &core.regs.tp);
  190. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.t0), &core.regs.t0);
  191. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.t1), &core.regs.t1);
  192. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.t2), &core.regs.t2);
  193. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s0), &core.regs.s0);
  194. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s1), &core.regs.s1);
  195. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.a0), &core.regs.a0);
  196. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.a1), &core.regs.a1);
  197. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.a2), &core.regs.a2);
  198. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.a3), &core.regs.a3);
  199. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.a4), &core.regs.a4);
  200. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.a5), &core.regs.a5);
  201. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.a6), &core.regs.a6);
  202. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.a7), &core.regs.a7);
  203. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s2), &core.regs.s2);
  204. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s3), &core.regs.s3);
  205. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s4), &core.regs.s4);
  206. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s5), &core.regs.s5);
  207. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s6), &core.regs.s6);
  208. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s7), &core.regs.s7);
  209. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s8), &core.regs.s8);
  210. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s9), &core.regs.s9);
  211. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s10), &core.regs.s10);
  212. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.s11), &core.regs.s11);
  213. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.t3), &core.regs.t3);
  214. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.t4), &core.regs.t4);
  215. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.t5), &core.regs.t5);
  216. vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.t6), &core.regs.t6);
  217. fprintf(stream,
  218. " MODE: 0x%lx\n", core.mode);
  219. fprintf(stream,
  220. " PC: 0x%016lx RA: 0x%016lx SP: 0x%016lx GP: 0x%016lx\n",
  221. core.regs.pc, core.regs.ra, core.regs.sp, core.regs.gp);
  222. fprintf(stream,
  223. " TP: 0x%016lx T0: 0x%016lx T1: 0x%016lx T2: 0x%016lx\n",
  224. core.regs.tp, core.regs.t0, core.regs.t1, core.regs.t2);
  225. fprintf(stream,
  226. " S0: 0x%016lx S1: 0x%016lx A0: 0x%016lx A1: 0x%016lx\n",
  227. core.regs.s0, core.regs.s1, core.regs.a0, core.regs.a1);
  228. fprintf(stream,
  229. " A2: 0x%016lx A3: 0x%016lx A4: 0x%016lx A5: 0x%016lx\n",
  230. core.regs.a2, core.regs.a3, core.regs.a4, core.regs.a5);
  231. fprintf(stream,
  232. " A6: 0x%016lx A7: 0x%016lx S2: 0x%016lx S3: 0x%016lx\n",
  233. core.regs.a6, core.regs.a7, core.regs.s2, core.regs.s3);
  234. fprintf(stream,
  235. " S4: 0x%016lx S5: 0x%016lx S6: 0x%016lx S7: 0x%016lx\n",
  236. core.regs.s4, core.regs.s5, core.regs.s6, core.regs.s7);
  237. fprintf(stream,
  238. " S8: 0x%016lx S9: 0x%016lx S10: 0x%016lx S11: 0x%016lx\n",
  239. core.regs.s8, core.regs.s9, core.regs.s10, core.regs.s11);
  240. fprintf(stream,
  241. " T3: 0x%016lx T4: 0x%016lx T5: 0x%016lx T6: 0x%016lx\n",
  242. core.regs.t3, core.regs.t4, core.regs.t5, core.regs.t6);
  243. }
  244. static void __aligned(16) guest_unexp_trap(void)
  245. {
  246. sbi_ecall(KVM_RISCV_SELFTESTS_SBI_EXT,
  247. KVM_RISCV_SELFTESTS_SBI_UNEXP,
  248. 0, 0, 0, 0, 0, 0);
  249. }
  250. void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code)
  251. {
  252. vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.pc), (unsigned long)guest_code);
  253. }
  254. struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id)
  255. {
  256. int r;
  257. size_t stack_size;
  258. unsigned long stack_vaddr;
  259. unsigned long current_gp = 0;
  260. struct kvm_mp_state mps;
  261. struct kvm_vcpu *vcpu;
  262. stack_size = vm->page_size == 4096 ? DEFAULT_STACK_PGS * vm->page_size :
  263. vm->page_size;
  264. stack_vaddr = __vm_vaddr_alloc(vm, stack_size,
  265. DEFAULT_RISCV_GUEST_STACK_VADDR_MIN,
  266. MEM_REGION_DATA);
  267. vcpu = __vm_vcpu_add(vm, vcpu_id);
  268. riscv_vcpu_mmu_setup(vcpu);
  269. /*
  270. * With SBI HSM support in KVM RISC-V, all secondary VCPUs are
  271. * powered-off by default so we ensure that all secondary VCPUs
  272. * are powered-on using KVM_SET_MP_STATE ioctl().
  273. */
  274. mps.mp_state = KVM_MP_STATE_RUNNABLE;
  275. r = __vcpu_ioctl(vcpu, KVM_SET_MP_STATE, &mps);
  276. TEST_ASSERT(!r, "IOCTL KVM_SET_MP_STATE failed (error %d)", r);
  277. /* Setup global pointer of guest to be same as the host */
  278. asm volatile (
  279. "add %0, gp, zero" : "=r" (current_gp) : : "memory");
  280. vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.gp), current_gp);
  281. /* Setup stack pointer and program counter of guest */
  282. vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.sp), stack_vaddr + stack_size);
  283. /* Setup sscratch for guest_get_vcpuid() */
  284. vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(sscratch), vcpu_id);
  285. /* Setup default exception vector of guest */
  286. vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(stvec), (unsigned long)guest_unexp_trap);
  287. return vcpu;
  288. }
  289. void vcpu_args_set(struct kvm_vcpu *vcpu, unsigned int num, ...)
  290. {
  291. va_list ap;
  292. uint64_t id = RISCV_CORE_REG(regs.a0);
  293. int i;
  294. TEST_ASSERT(num >= 1 && num <= 8, "Unsupported number of args,\n"
  295. " num: %u", num);
  296. va_start(ap, num);
  297. for (i = 0; i < num; i++) {
  298. switch (i) {
  299. case 0:
  300. id = RISCV_CORE_REG(regs.a0);
  301. break;
  302. case 1:
  303. id = RISCV_CORE_REG(regs.a1);
  304. break;
  305. case 2:
  306. id = RISCV_CORE_REG(regs.a2);
  307. break;
  308. case 3:
  309. id = RISCV_CORE_REG(regs.a3);
  310. break;
  311. case 4:
  312. id = RISCV_CORE_REG(regs.a4);
  313. break;
  314. case 5:
  315. id = RISCV_CORE_REG(regs.a5);
  316. break;
  317. case 6:
  318. id = RISCV_CORE_REG(regs.a6);
  319. break;
  320. case 7:
  321. id = RISCV_CORE_REG(regs.a7);
  322. break;
  323. }
  324. vcpu_set_reg(vcpu, id, va_arg(ap, uint64_t));
  325. }
  326. va_end(ap);
  327. }
  328. void kvm_exit_unexpected_exception(int vector, int ec)
  329. {
  330. ucall(UCALL_UNHANDLED, 2, vector, ec);
  331. }
  332. void assert_on_unhandled_exception(struct kvm_vcpu *vcpu)
  333. {
  334. struct ucall uc;
  335. if (get_ucall(vcpu, &uc) == UCALL_UNHANDLED) {
  336. TEST_FAIL("Unexpected exception (vector:0x%lx, ec:0x%lx)",
  337. uc.args[0], uc.args[1]);
  338. }
  339. }
  340. struct handlers {
  341. exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS];
  342. };
  343. void route_exception(struct ex_regs *regs)
  344. {
  345. struct handlers *handlers = (struct handlers *)exception_handlers;
  346. int vector = 0, ec;
  347. ec = regs->cause & ~CAUSE_IRQ_FLAG;
  348. if (ec >= NR_EXCEPTIONS)
  349. goto unexpected_exception;
  350. /* Use the same handler for all the interrupts */
  351. if (regs->cause & CAUSE_IRQ_FLAG) {
  352. vector = 1;
  353. ec = 0;
  354. }
  355. if (handlers && handlers->exception_handlers[vector][ec])
  356. return handlers->exception_handlers[vector][ec](regs);
  357. unexpected_exception:
  358. return kvm_exit_unexpected_exception(vector, ec);
  359. }
  360. void vcpu_init_vector_tables(struct kvm_vcpu *vcpu)
  361. {
  362. extern char exception_vectors;
  363. vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(stvec), (unsigned long)&exception_vectors);
  364. }
  365. void vm_init_vector_tables(struct kvm_vm *vm)
  366. {
  367. vm->handlers = __vm_vaddr_alloc(vm, sizeof(struct handlers),
  368. vm->page_size, MEM_REGION_DATA);
  369. *(vm_vaddr_t *)addr_gva2hva(vm, (vm_vaddr_t)(&exception_handlers)) = vm->handlers;
  370. }
  371. void vm_install_exception_handler(struct kvm_vm *vm, int vector, exception_handler_fn handler)
  372. {
  373. struct handlers *handlers = addr_gva2hva(vm, vm->handlers);
  374. assert(vector < NR_EXCEPTIONS);
  375. handlers->exception_handlers[0][vector] = handler;
  376. }
  377. void vm_install_interrupt_handler(struct kvm_vm *vm, exception_handler_fn handler)
  378. {
  379. struct handlers *handlers = addr_gva2hva(vm, vm->handlers);
  380. handlers->exception_handlers[1][0] = handler;
  381. }
  382. uint32_t guest_get_vcpuid(void)
  383. {
  384. return csr_read(CSR_SSCRATCH);
  385. }
  386. struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0,
  387. unsigned long arg1, unsigned long arg2,
  388. unsigned long arg3, unsigned long arg4,
  389. unsigned long arg5)
  390. {
  391. register uintptr_t a0 asm ("a0") = (uintptr_t)(arg0);
  392. register uintptr_t a1 asm ("a1") = (uintptr_t)(arg1);
  393. register uintptr_t a2 asm ("a2") = (uintptr_t)(arg2);
  394. register uintptr_t a3 asm ("a3") = (uintptr_t)(arg3);
  395. register uintptr_t a4 asm ("a4") = (uintptr_t)(arg4);
  396. register uintptr_t a5 asm ("a5") = (uintptr_t)(arg5);
  397. register uintptr_t a6 asm ("a6") = (uintptr_t)(fid);
  398. register uintptr_t a7 asm ("a7") = (uintptr_t)(ext);
  399. struct sbiret ret;
  400. asm volatile (
  401. "ecall"
  402. : "+r" (a0), "+r" (a1)
  403. : "r" (a2), "r" (a3), "r" (a4), "r" (a5), "r" (a6), "r" (a7)
  404. : "memory");
  405. ret.error = a0;
  406. ret.value = a1;
  407. return ret;
  408. }
  409. bool guest_sbi_probe_extension(int extid, long *out_val)
  410. {
  411. struct sbiret ret;
  412. ret = sbi_ecall(SBI_EXT_BASE, SBI_EXT_BASE_PROBE_EXT, extid,
  413. 0, 0, 0, 0, 0);
  414. __GUEST_ASSERT(!ret.error || ret.error == SBI_ERR_NOT_SUPPORTED,
  415. "ret.error=%ld, ret.value=%ld\n", ret.error, ret.value);
  416. if (ret.error == SBI_ERR_NOT_SUPPORTED)
  417. return false;
  418. if (out_val)
  419. *out_val = ret.value;
  420. return true;
  421. }
  422. unsigned long get_host_sbi_spec_version(void)
  423. {
  424. struct sbiret ret;
  425. ret = sbi_ecall(SBI_EXT_BASE, SBI_EXT_BASE_GET_SPEC_VERSION, 0,
  426. 0, 0, 0, 0, 0);
  427. GUEST_ASSERT(!ret.error);
  428. return ret.value;
  429. }