Goto sanos source index

//
// vmi.c
//
// VMWare Virtual Machine Interface (VMI)
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os/krnl.h>
#include <os/vmi.h>

#ifdef VMACH

#define SET_MACH_FUNC(func, addr) *((unsigned long *) &mach.func) = addr

static struct vrom_header *vmi = NULL;
static unsigned long vmicall[NUM_VMI_CALLS];

#define VMCALL(func) call vmicall[func*4]
#define VMJUMP(func) jmp vmicall[func*4]

//
// VMI machine functions
//

static __declspec(naked) int __fastcall vmi_in(port_t port) {
  __asm {
    mov edx, ecx
    xor eax, eax
    VMJUMP(VMI_CALL_INB)
  }
}

static __declspec(naked) unsigned short __fastcall vmi_inw(port_t port) {
  __asm {
    mov edx, ecx
    VMJUMP(VMI_CALL_INW)
  }
}

static __declspec(naked) unsigned long __fastcall vmi_ind(port_t port) {
  __asm {
    mov edx, ecx
    VMJUMP(VMI_CALL_IN)
  }
}

static void vmi_insw(port_t port, void *buf, int count) {
  __asm {
    mov edx, port
    mov edi, buf
    mov ecx, count
    VMCALL(VMI_CALL_INSW);
  }
}

static void vmi_insd(port_t port, void *buf, int count) {
  __asm {
    mov edx, port
    mov edi, buf
    mov ecx, count
    VMCALL(VMI_CALL_INS)
  }
}

static __declspec(naked) int __fastcall vmi_out(port_t port, int val) {
  __asm {
    mov eax,edx
    mov edx,ecx
    VMJUMP(VMI_CALL_OUTB)
  }
}

static __declspec(naked) unsigned short __fastcall vmi_outw(port_t port, unsigned short val) {
  __asm {
    mov eax,edx
    mov edx,ecx
    VMJUMP(VMI_CALL_OUTW)
  }
}

static __declspec(naked) unsigned long __fastcall vmi_outd(port_t port, unsigned long val) {
  __asm {
    mov eax,edx
    mov edx,ecx
    VMJUMP(VMI_CALL_OUT)
  }
}

static void vmi_outsw(port_t port, void *buf, int count) {
  __asm {
    mov edx, port
    mov esi, buf
    mov ecx, count
    VMCALL(VMI_CALL_OUTSW)
  }
}

static void vmi_outsd(port_t port, void *buf, int count) {
  __asm {
    mov edx, port
    mov esi, buf
    mov ecx, count
    VMCALL(VMI_CALL_OUTS)
  }
}

static void vmi_cpuid(unsigned long reg, unsigned long values[4]) {
  __asm {
    mov eax, reg
    VMCALL(VMI_CALL_CPUID)
    mov esi, values
    mov [esi], eax
    mov [esi+4], ebx
    mov [esi+8], ecx
    mov [esi+12], edx
  }
}

static unsigned long vmi_get_cr0() {
  unsigned long val;

  __asm  {
    VMCALL(VMI_CALL_GetCR0);
    mov val, eax
  }

  return val;
}

static void vmi_set_cr0(unsigned long val) {
  __asm {
    mov eax, val
    VMCALL(VMI_CALL_SetCR0);
  }
}

static unsigned long vmi_get_cr2() {
  unsigned long val;

  __asm {
    VMCALL(VMI_CALL_GetCR2);
    mov val, eax
  }

  return val;
}

static void vmi_wrmsr(unsigned long reg, unsigned long valuelow, unsigned long valuehigh) {
  __asm {
    mov ecx, reg
    mov eax, valuelow
    mov edx, valuehigh
    VMCALL(VMI_CALL_WRMSR);
  }
}

static void vmi_set_gdt_entry(int entry, unsigned long addr, unsigned long size, int access, int granularity) {
  union dte dte;

  seginit(&dte.segment, addr, size, access, granularity);

  __asm {
    mov eax, offset syspage.gdt
    mov edx, entry
    mov ecx, dte.desc.low
    push dte.desc.high
    VMCALL(VMI_CALL_WriteGDTEntry)
    add esp, 4
  }
}

static void vmi_set_idt_gate(int intrno, void *handler) {
  union dte dte;

  dte.gate.offset_low = (unsigned short) (((unsigned long) handler) & 0xFFFF);
  dte.gate.selector = SEL_KTEXT | mach.kring;
  dte.gate.access = D_PRESENT | D_INT | D_DPL0;
  dte.gate.offset_high = (unsigned short) (((unsigned long) handler) >> 16);

  __asm {
    mov eax, offset syspage.idt
    mov edx, intrno
    mov ecx, dte.desc.low
    push dte.desc.high
    VMCALL(VMI_CALL_WriteIDTEntry)
    add esp, 4
  }
}

static void vmi_set_idt_trap(int intrno, void *handler) {
  union dte dte;

  dte.gate.offset_low = (unsigned short) (((unsigned long) handler) & 0xFFFF);
  dte.gate.selector = SEL_KTEXT | mach.kring;
  dte.gate.access = D_PRESENT | D_TRAP | D_DPL3;
  dte.gate.offset_high = (unsigned short) (((unsigned long) handler) >> 16);

  __asm {
    mov eax, offset syspage.idt
    mov edx, intrno
    mov ecx, dte.desc.low
    push dte.desc.high
    VMCALL(VMI_CALL_WriteIDTEntry)
    add esp, 4
  }
}

static void vmi_switch_kernel_stack() {
  __asm {
    mov eax, offset syspage.tss
    mov edx, syspage.tss.esp0
    VMCALL(VMI_CALL_UpdateKernelStack)
  }
}

static void vmi_flushtlb() {
  __asm {
    mov eax, VMI_FLUSH_TLB
    VMCALL(VMI_CALL_FlushTLB)
  }
}

static void vmi_invlpage(void *addr) {
  __asm {
    mov eax, addr
    VMCALL(VMI_CALL_InvalPage)
  }
}

static void vmi_flush_deferred_calls() {
  __asm {
    mov eax, VMI_FLUSH_PT_UPDATES | VMI_FLUSH_CPU_STATE
    VMCALL(VMI_CALL_FlushDeferredCalls)
  }
}

static void vmi_register_page_dir(unsigned long pfn) {
  __asm {
    mov eax, pfn
    mov edx, VMI_PAGE_PD | VMI_PAGE_PT
    xor ecx, ecx
    push ecx
    push ecx
    VMCALL(VMI_CALL_AllocatePage)
    add esp, 8
  }
}

static void vmi_register_page_table(unsigned long pfn) {
  __asm {
    mov eax, pfn
    mov edx, VMI_PAGE_PT
    xor ecx, ecx
    push ecx
    push ecx
    VMCALL(VMI_CALL_AllocatePage)
    add esp, 8
  }
}

static void vmi_set_linear_mapping(int slot, unsigned long vaddr, unsigned long pages, unsigned long pfn) {
  __asm {
    mov eax, slot
    mov edx, vaddr
    mov ecx, pages
    push pfn
    VMCALL(VMI_CALL_SetLinearMapping)
    add esp, 4
  }
}

static void vmi_set_page_dir_entry(pte_t *pde, unsigned long value) {
  kprintf("vmi: vmi_set_linear_mapping %p %d\n", PAGEADDR(pde), virt2pfn(pde));

  vmi_set_linear_mapping(0, PAGEADDR(pde), 1, virt2pfn(pde));

  kprintf("vmi: vmi_set_page_dir_entry\n");

  __asm {
    mov eax, value
    mov edx, pde
    mov ecx, VMI_PAGE_PD
    VMCALL(VMI_CALL_SetPxE)
  }

  kprintf("vmi: vmi_set_page_dir_entry done\n");
  vmi_flush_deferred_calls();
}

static void vmi_set_page_table_entry(pte_t *pte, unsigned long value) {
  vmi_set_linear_mapping(0, PAGEADDR(pte), 1, virt2pfn(pte));

  __asm {
    mov eax, value
    mov edx, pte
    mov ecx, VMI_PAGE_PT
    VMCALL(VMI_CALL_SetPxE)
  }

  vmi_flush_deferred_calls();
}

static void vmi_reboot() {
  __asm {
    mov eax, VMI_REBOOT_HARD
    VMCALL(VMI_CALL_Reboot)
  }
}

//
// Initialize VMI
//

static int vmi_init() {
  int rc;

  __asm {
    mov ecx, vmi
    add ecx, VMI_CALL_Init * VROM_CALL_LEN
    call ecx
    mov rc, eax
  }

  kprintf("vmi: VMI_CALL_Init retuned %d\n", rc);
  return rc;
}

static void vmi_get_reloc_info(int callno, struct vmi_relocation_info *reloc) {
  __asm {
    mov eax, callno
    mov ecx, vmi
    add ecx, VMI_CALL_GetRelocationInfo * VROM_CALL_LEN
    call ecx
    mov ecx, reloc
    mov dword ptr [ecx], eax
    mov dword ptr [ecx + 4], edx
  }
}

int probe_vmi() {
  unsigned long base;

  // VMI ROM is in option ROM area, check signature
  for (base = 0xC0000; base < 0xE0000; base += 2048) {
    struct vrom_header *rom;

    rom = (struct vrom_header *) base;

    if (rom->romSignature != 0xAA55) continue;
    //kprintf("vrom: sig ok at 0x%p, vrom sig 0x%08X\n", rom, rom->vRomSignature);

    if (rom->vRomSignature == VMI_SIGNATURE) {
      kprintf("mach: VMWare VMI ROM version %d.%d detected\n", rom->APIVersionMajor, rom->APIVersionMinor);
      if (rom->APIVersionMajor == VMI_API_REV_MAJOR) {
        vmi = rom;
        break;
      }
    }
  }

  //kprintf("vmi: ROM at 0x%p\n", vmi);
  return vmi != NULL;
}

int init_vmi() {
  int i;
  int cssel;

  kprintf("mach: entering VMI paravirt mode\n");
  if (vmi_init() < 0) return -ENXIO;

  kprintf("mach: now in VMI mode\n");
  for (i = VMI_CALL_Init; i < NUM_VMI_CALLS; i++) {
    struct vmi_relocation_info reloc;

    vmi_get_reloc_info(i, &reloc);
    if (reloc.type != VMI_RELOCATION_CALL_REL) panic("vmi: unsupported reloc type");
    vmicall[i] = reloc.eip;
  }

  // Determine kernel ring
  __asm {
    push cs
    pop cssel
  }
  mach.kring = cssel & 3;
  kprintf("vmi: kernel ring %d\n", mach.kring);

  // Setup direct VMI calls
  SET_MACH_FUNC(sti, vmicall[VMI_CALL_EnableInterrupts]);
  SET_MACH_FUNC(cli, vmicall[VMI_CALL_DisableInterrupts]);
  SET_MACH_FUNC(hlt, vmicall[VMI_CALL_Halt]);
  SET_MACH_FUNC(iretd, vmicall[VMI_CALL_IRET]);
  SET_MACH_FUNC(sysret, vmicall[VMI_CALL_SYSEXIT]);
  SET_MACH_FUNC(rdtsc, vmicall[VMI_CALL_RDTSC]);
  SET_MACH_FUNC(poweroff, vmicall[VMI_CALL_Shutdown]);

  // Setup VMI calls
  mach.in = vmi_in;
  mach.inw = vmi_inw;
  mach.ind = vmi_ind;
  mach.insw = vmi_insw;
  mach.insd = vmi_insd;
  mach.out = vmi_out;
  mach.outw = vmi_outw;
  mach.outd = vmi_outd;
  mach.outsw = vmi_outsw;
  mach.outsd = vmi_outsd;
  mach.cpuid = vmi_cpuid;
  mach.get_cr0 = vmi_get_cr0;
  mach.set_cr0 = vmi_set_cr0;
  mach.get_cr2 = vmi_get_cr2;
  mach.wrmsr = vmi_wrmsr;
  mach.set_gdt_entry = vmi_set_gdt_entry;
  mach.set_idt_gate = vmi_set_idt_gate;
  mach.set_idt_trap = vmi_set_idt_trap;
  mach.switch_kernel_stack = vmi_switch_kernel_stack;
  mach.flushtlb = vmi_flushtlb;
  mach.invlpage = vmi_invlpage;
  mach.register_page_dir = vmi_register_page_dir;
  mach.register_page_table = vmi_register_page_table;
  mach.set_page_dir_entry = vmi_set_page_dir_entry;
  mach.set_page_table_entry = vmi_set_page_table_entry;
  mach.reboot = vmi_reboot;

  return 0;
}

#endif