/* * FreeBSD kernel wrapper for KQEMU * Copyright (c) 2005 Antony T Curtis * * Based upon the Linux wrapper by Fabrice Bellard */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __KERNEL__ #include "kqemu.h" static MALLOC_DEFINE(M_KQEMU, "KQEMU", "KQEMU Resources"); /* lock the page at virtual address 'user_addr' and return its page index. Return -1 if error */ unsigned long CDECL kqemu_lock_user_page(unsigned long user_addr) { int rc; caddr_t addr = (caddr_t) user_addr; vm_page_t m; vm_offset_t paddr; /*kqemu_log("kqemu_lock_user_page(0x%08x)\n", addr);*/ rc = vm_fault_quick(addr, VM_PROT_READ|VM_PROT_WRITE); if (rc < 0) { /*kqemu_log("vm_fault_quick failed rc=%d\n",rc);*/ return -1; } paddr = vtophys(addr); m = PHYS_TO_VM_PAGE(paddr); vm_page_wire(m); return (long)(paddr >> PAGE_SHIFT); } void CDECL kqemu_unlock_user_page(unsigned long page_index) { vm_page_t m; vm_offset_t paddr; /*kqemu_log("kqemu_unlock_user_page(0x%08x)\n",page_index);*/ paddr = (vm_offset_t)(page_index << PAGE_SHIFT); m = PHYS_TO_VM_PAGE(paddr); vm_page_unwire(m, 1); } unsigned long CDECL kqemu_alloc_zeroed_page(void) { void *addr; /*kqemu_log("kqemu_alloc_zeroed_page()\n");*/ addr = contigmalloc(PAGE_SIZE, M_KQEMU, M_WAITOK, 0, ~0ul, PAGE_SIZE, 0); if (!addr) { /*kqemu_log("contigmalloc failed\n");*/ return -1; } memset(addr, 0, PAGE_SIZE); return ((long) addr) >> PAGE_SHIFT; } void CDECL kqemu_free_page(unsigned long page_index) { /*kqemu_log("kqemu_free_page(0x%08x)\n", page_index);*/ contigfree((void *)(page_index << PAGE_SHIFT), PAGE_SIZE, M_KQEMU); } void * CDECL kqemu_page_kaddr(unsigned long page_index) { caddr_t addr; /*kqemu_log("kqemu_page_kaddr(0x%08x)\n", page_index);*/ addr = (caddr_t)(page_index << PAGE_SHIFT); return (void *)addr; } /* contraint: each page of the vmalloced area must be in the first 4 GB of physical memory */ void * CDECL kqemu_vmalloc(unsigned int size) { /*kqemu_log("kqemu_vmalloc(0x%08x)\n", size);*/ return malloc(size, M_KQEMU, M_WAITOK); } void CDECL kqemu_vfree(void *ptr) { /*kqemu_log("kqemu_vfree(0x%08x)\n", ptr);*/ return free(ptr, M_KQEMU); } unsigned long CDECL kqemu_vmalloc_to_phys(const void *vaddr) { vm_offset_t paddr = (vm_offset_t)vaddr; return (long)(paddr >> PAGE_SHIFT); } static int curpriority_cmp(struct proc *p) { int c_class, p_class; c_class = RTP_PRIO_BASE(curproc->p_rtprio.type); p_class = RTP_PRIO_BASE(p->p_rtprio.type); if (p_class != c_class) return (p_class - c_class); if (p_class == RTP_PRIO_NORMAL) return (((int)p->p_priority - (int)curpriority) / PPQ); return ((int)p->p_rtprio.prio - (int)curproc->p_rtprio.prio); } /* return TRUE if a signal is pending (i.e. the guest must stop execution) */ int CDECL kqemu_schedule(void) { struct proc *p = curproc; if (curpriority_cmp(p) > 0) { int s = splhigh(); p->p_priority = MAXPRI; setrunqueue(p); p->p_stats->p_ru.ru_nvcsw++; mi_switch(); splx(s); } return issignal(curproc) != 0; } static char log_buf[4096]; void CDECL kqemu_log(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(log_buf, sizeof(log_buf), fmt, ap); printf("kqemu: %s", log_buf); va_end(ap); } /*********************************************************/ #define KQEMU_MAX_INSTANCES 4 struct kqemu_instance { struct kqemu_state *state; }; static int kqemu_ref_count = 0; static int max_locked_pages; static dev_t kqemu_dev; static d_open_t kqemu_open; static d_close_t kqemu_close; static d_ioctl_t kqemu_ioctl; static struct cdevsw kqemu_cdevsw = { /* open */ kqemu_open, /* close */ kqemu_close, /* read */ noread, /* write */ nowrite, /* ioctl */ kqemu_ioctl, /* poll */ nopoll, /* mmap */ nommap, /* strategy */ nostrategy, /* name */ "kqemu", /* maj */ KQEMU_MAJOR, /* dump */ nodump, /* psize */ nopsize, /* flags */ 0, /* bmaj */ -1 }; int kqemu_open(dev, flags, fmt, p) dev_t dev; int flags, fmt; struct proc *p; { struct kqemu_instance *ks; if (dev->si_drv1 || kqemu_ref_count >= KQEMU_MAX_INSTANCES) return(EBUSY); if ((flags & (FREAD|FWRITE)) == FREAD) return(EPERM); ks = (struct kqemu_instance *) malloc(sizeof(*ks), M_KQEMU, M_WAITOK); if (ks == NULL) return(ENOMEM); memset(ks, 0, sizeof *ks); dev->si_drv1 = ks; kqemu_ref_count++; kqemu_log("opened by pid=%d\n", p->p_pid); return(0); } int kqemu_close(dev, flags, fmt, p) dev_t dev; int flags, fmt; struct proc *p; { struct kqemu_instance *ks = (struct kqemu_instance *) dev->si_drv1; if (ks->state) { kqemu_delete(ks->state); ks->state = NULL; } free(ks, M_KQEMU); dev->si_drv1 = NULL; kqemu_ref_count--; kqemu_log("closed by pid=%d\n", p->p_pid); return(0); } int kqemu_ioctl(dev, cmd, cmdarg, flags, p) dev_t dev; unsigned long cmd; caddr_t cmdarg; int flags; struct proc *p; { struct kqemu_instance *ks = (struct kqemu_instance *) dev->si_drv1; struct kqemu_state *s = ks->state; long ret; /*int error;*/ switch (cmd) { case KQEMU_INIT: /*kqemu_log("KQEMU_INIT data=0x%08x\n",cmdarg);*/ { if (s) return(EIO); if (!(s = kqemu_init((struct kqemu_init *)cmdarg, max_locked_pages))) return(ENOMEM); ks->state = s; ret = 0; break; } case KQEMU_EXEC: /*kqemu_log("KQEMU_EXEC data=0x%08x\n",cmdarg);*/ { struct kqemu_cpu_state *ctx; if (!s) return(EIO); ctx = kqemu_get_cpu_state(s); memcpy((void *)ctx, (void *)cmdarg, sizeof(struct kqemu_cpu_state)); ret = kqemu_exec(s); memcpy((void *)cmdarg, (void *)ctx, sizeof(struct kqemu_cpu_state)); break; } case KQEMU_GET_VERSION: /*kqemu_log("KQEMU_GET_VERSION data=0x%08x\n",cmdarg);*/ { *(int *)cmdarg = KQEMU_VERSION; ret = 0; break; } default: /*kqemu_log("ioctl unknown 0x%08x\n",cmd);*/ return(ENXIO); } return(ret); } static int init_module(void) { int rc; printf("QEMU Accelerator Module version %d.%d.%d, Copyright (c) 2005 Fabrice Bellard\n" "FreeBSD wrapper port, Copyright (c) 2005 Antony T Curtis\n" "This is a proprietary product. Read the LICENSE file for more information\n" "Redistribution of this module is prohibited without authorization\n", (KQEMU_VERSION >> 16), (KQEMU_VERSION >> 8) & 0xff, (KQEMU_VERSION) & 0xff); max_locked_pages = physmem / (2 * KQEMU_MAX_INSTANCES); if (max_locked_pages > 32768) max_locked_pages = 32768; if ((rc = cdevsw_add(&kqemu_cdevsw))) { kqemu_log("error registering cdevsw, rc=%d\n", rc); return(ENOENT); } kqemu_dev = make_dev(&kqemu_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "kqemu"); kqemu_log("KQEMU installed, max_instances=%d max_locked_mem=%dkB.\n", KQEMU_MAX_INSTANCES, max_locked_pages * 4); kqemu_ref_count = 0; return 0; } static void cleanup_module(void) { int rc; destroy_dev(kqemu_dev); if ((rc = cdevsw_remove(&kqemu_cdevsw))) kqemu_log("error unregistering, rc=%d\n", rc); } static int kqemu_modevent(module_t mod, int type, void *data) { int err = 0; switch (type) { case MOD_LOAD: err = init_module(); break; case MOD_UNLOAD: if (kqemu_ref_count > 0) { err = EBUSY; break; } /* fall through */ case MOD_SHUTDOWN: cleanup_module(); break; default: err = EINVAL; break; } return(err); } static moduledata_t kqemu_mod = { "kqemu_driver", kqemu_modevent, NULL }; DECLARE_MODULE(kqemu, kqemu_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);