diff options
Diffstat (limited to 'src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c')
| -rw-r--r-- | src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c | 1089 |
1 files changed, 1089 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c b/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c new file mode 100644 index 000000000..7fa74a1b6 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c @@ -0,0 +1,1089 @@ +/* $Rev: 15999 $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Linux specifics. + */ + +/* + * Copyright (C) 2006-2007 Sun Microsystems, Inc. + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa + * Clara, CA 95054 USA or visit http://www.sun.com if you need + * additional information or have any questions. + * Some lines of code to disable the local APIC on x86_64 machines taken + * from a Mandriva patch by Gwenole Beauchesne <gbeauchesne@mandriva.com>. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#include "../SUPDrvInternal.h" +#include "the-linux-kernel.h" +#include "version-generated.h" + +#include <iprt/assert.h> +#include <iprt/spinlock.h> +#include <iprt/semaphore.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <VBox/log.h> +#include <iprt/mp.h> + +/** @todo figure out the exact version number */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16) +# include <iprt/power.h> +# define VBOX_WITH_SUSPEND_NOTIFICATION +#endif + +#include <linux/sched.h> +#ifdef CONFIG_DEVFS_FS +# include <linux/devfs_fs_kernel.h> +#endif +#ifdef CONFIG_VBOXDRV_AS_MISC +# include <linux/miscdevice.h> +#endif +#ifdef CONFIG_X86_LOCAL_APIC +# include <asm/apic.h> +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +# include <asm/nmi.h> +# endif +#endif +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION +# include <linux/platform_device.h> +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) +# include <asm/pgtable.h> +# define global_flush_tlb __flush_tlb_global +#endif + +#include <iprt/mem.h> + + +/* devfs defines */ +#if defined(CONFIG_DEVFS_FS) && !defined(CONFIG_VBOXDRV_AS_MISC) +# ifdef VBOX_WITH_HARDENING +# define VBOX_DEV_FMASK (S_IWUSR | S_IRUSR) +# else +# define VBOX_DEV_FMASK (S_IRUGO | S_IWUGO) +# endif + +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + +# define VBOX_REGISTER_DEVFS() \ +({ \ + void *rc = NULL; \ + if (devfs_mk_cdev(MKDEV(DEVICE_MAJOR, 0), \ + S_IFCHR | VBOX_DEV_FMASK, \ + DEVICE_NAME) == 0) \ + rc = (void *)' '; /* return not NULL */ \ + rc; \ + }) + +# define VBOX_UNREGISTER_DEVFS(handle) \ + devfs_remove(DEVICE_NAME); + +# else /* < 2.6.0 */ + +# define VBOX_REGISTER_DEVFS() \ + devfs_register(NULL, DEVICE_NAME, DEVFS_FL_DEFAULT, \ + DEVICE_MAJOR, 0, \ + S_IFCHR | VBOX_DEV_FMASK, \ + &gFileOpsVBoxDrv, NULL) + +# define VBOX_UNREGISTER_DEVFS(handle) \ + if (handle != NULL) \ + devfs_unregister(handle) + +# endif /* < 2.6.0 */ +#endif /* CONFIG_DEV_FS && !CONFIG_VBOXDEV_AS_MISC */ + +#ifndef CONFIG_VBOXDRV_AS_MISC +# if defined(CONFIG_DEVFS_FS) && LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 0) +# define VBOX_REGISTER_DEVICE(a,b,c) devfs_register_chrdev(a,b,c) +# define VBOX_UNREGISTER_DEVICE(a,b) devfs_unregister_chrdev(a,b) +# else +# define VBOX_REGISTER_DEVICE(a,b,c) register_chrdev(a,b,c) +# define VBOX_UNREGISTER_DEVICE(a,b) unregister_chrdev(a,b) +# endif +#endif /* !CONFIG_VBOXDRV_AS_MISC */ + + +#ifdef CONFIG_X86_HIGH_ENTRY +# error "CONFIG_X86_HIGH_ENTRY is not supported by VBoxDrv at this time." +#endif + +#ifdef CONFIG_X86_LOCAL_APIC + +/* If an NMI occurs while we are inside the world switcher the machine will + * crash. The Linux NMI watchdog generates periodic NMIs increasing a counter + * which is compared with another counter increased in the timer interrupt + * handler. We disable the NMI watchdog. + * + * - Linux >= 2.6.21: The watchdog is disabled by default on i386 and x86_64. + * - Linux < 2.6.21: The watchdog is normally enabled by default on x86_64 + * and disabled on i386. + */ +# if defined(RT_ARCH_AMD64) +# if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 21) && !defined(VBOX_REDHAT_KABI) +# define DO_DISABLE_NMI 1 +# endif +# endif + +# if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +extern int nmi_active; +# define nmi_atomic_read(P) *(P) +# define nmi_atomic_set(P, V) *(P) = (V) +# define nmi_atomic_dec(P) nmi_atomic_set(P, 0) +# else +# define nmi_atomic_read(P) atomic_read(P) +# define nmi_atomic_set(P, V) atomic_set(P, V) +# define nmi_atomic_dec(P) atomic_dec(P) +# endif + +# ifndef X86_FEATURE_ARCH_PERFMON +# define X86_FEATURE_ARCH_PERFMON (3*32+9) /* Intel Architectural PerfMon */ +# endif +# ifndef MSR_ARCH_PERFMON_EVENTSEL0 +# define MSR_ARCH_PERFMON_EVENTSEL0 0x186 +# endif +# ifndef ARCH_PERFMON_UNHALTED_CORE_CYCLES_PRESENT +# define ARCH_PERFMON_UNHALTED_CORE_CYCLES_PRESENT (1 << 0) +# endif + +#endif /* CONFIG_X86_LOCAL_APIC */ + +#define xstr(s) str(s) +#define str(s) #s + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +/** + * Device extention & session data association structure. + */ +static SUPDRVDEVEXT g_DevExt; + +/** Registered devfs device handle. */ +#if defined(CONFIG_DEVFS_FS) && !defined(CONFIG_VBOXDRV_AS_MISC) +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +static void *g_hDevFsVBoxDrv = NULL; +# else +static devfs_handle_t g_hDevFsVBoxDrv = NULL; +# endif +#endif + +#ifndef CONFIG_VBOXDRV_AS_MISC +/** Module major number */ +#define DEVICE_MAJOR 234 +/** Saved major device number */ +static int g_iModuleMajor; +#endif /* !CONFIG_VBOXDRV_AS_MISC */ + +/** Module parameter. + * Not prefixed because the name is used by macros and the end of this file. */ +static int force_async_tsc = 0; + +/** The module name. */ +#define DEVICE_NAME "vboxdrv" + +#ifdef RT_ARCH_AMD64 +/** + * Memory for the executable memory heap (in IPRT). + */ +extern uint8_t g_abExecMemory[1572864]; /* 1.5 MB */ +__asm__(".section execmemory, \"awx\", @progbits\n\t" + ".align 32\n\t" + ".globl g_abExecMemory\n" + "g_abExecMemory:\n\t" + ".zero 1572864\n\t" + ".type g_abExecMemory, @object\n\t" + ".size g_abExecMemory, 1572864\n\t" + ".text\n\t"); +#endif + + +/******************************************************************************* +* Internal Functions * +*******************************************************************************/ +static int VBoxDrvLinuxInit(void); +static void VBoxDrvLinuxUnload(void); +static int VBoxDrvLinuxCreate(struct inode *pInode, struct file *pFilp); +static int VBoxDrvLinuxClose(struct inode *pInode, struct file *pFilp); +#ifdef HAVE_UNLOCKED_IOCTL +static long VBoxDrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg); +#else +static int VBoxDrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg); +#endif +static int VBoxDrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg); +static int VBoxDrvLinuxErr2LinuxErr(int); +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION +static int VBoxDrvProbe(struct platform_device *pDev); +static int VBoxDrvSuspend(struct platform_device *pDev, pm_message_t State); +static int VBoxDrvResume(struct platform_device *pDev); +static void VBoxDevRelease(struct device *pDev); +#endif + +/** The file_operations structure. */ +static struct file_operations gFileOpsVBoxDrv = +{ + owner: THIS_MODULE, + open: VBoxDrvLinuxCreate, + release: VBoxDrvLinuxClose, +#ifdef HAVE_UNLOCKED_IOCTL + unlocked_ioctl: VBoxDrvLinuxIOCtl, +#else + ioctl: VBoxDrvLinuxIOCtl, +#endif +}; + +#ifdef CONFIG_VBOXDRV_AS_MISC +/** The miscdevice structure. */ +static struct miscdevice gMiscDevice = +{ + minor: MISC_DYNAMIC_MINOR, + name: DEVICE_NAME, + fops: &gFileOpsVBoxDrv, +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) && \ + LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 17) + devfs_name: DEVICE_NAME, +# endif +}; +#endif + + +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION +static struct platform_driver gPlatformDriver = +{ + .probe = VBoxDrvProbe, + .suspend = VBoxDrvSuspend, + .resume = VBoxDrvResume, + /** @todo .shutdown? */ + .driver = + { + .name = "vboxdrv" + } +}; + +static struct platform_device gPlatformDevice = +{ + .name = "vboxdrv", + .dev = + { + .release = VBoxDevRelease + } +}; +#endif /* VBOX_WITH_SUSPEND_NOTIFICATION */ + + +#ifdef CONFIG_X86_LOCAL_APIC +# ifdef DO_DISABLE_NMI +/** Stop AMD NMI watchdog (x86_64 only). */ +static int vboxdrvStopK7Watchdog(void) +{ + wrmsr(MSR_K7_EVNTSEL0, 0, 0); + return 1; +} + +/** Stop Intel P4 NMI watchdog (x86_64 only). */ +static int vboxdrvStopP4Watchdog(void) +{ + wrmsr(MSR_P4_IQ_CCCR0, 0, 0); + wrmsr(MSR_P4_IQ_CCCR1, 0, 0); + wrmsr(MSR_P4_CRU_ESCR0, 0, 0); + return 1; +} + +/** The new method of detecting the event counter */ +static int vboxdrvStopIntelArchWatchdog(void) +{ + unsigned ebx; + + ebx = cpuid_ebx(10); + if (!(ebx & ARCH_PERFMON_UNHALTED_CORE_CYCLES_PRESENT)) + wrmsr(MSR_ARCH_PERFMON_EVENTSEL0, 0, 0); + return 1; +} + +/** Stop NMI watchdog. */ +static void vboxdrvStopApicNmiWatchdog(void *unused) +{ + int stopped = 0; + + /* only support LOCAL and IO APICs for now */ + if ((nmi_watchdog != NMI_LOCAL_APIC) && + (nmi_watchdog != NMI_IO_APIC)) + return; + + if (nmi_watchdog == NMI_LOCAL_APIC) + { + switch (boot_cpu_data.x86_vendor) + { + case X86_VENDOR_AMD: + if (strstr(boot_cpu_data.x86_model_id, "Screwdriver")) + return; + stopped = vboxdrvStopK7Watchdog(); + break; + case X86_VENDOR_INTEL: + if (cpu_has(&boot_cpu_data, X86_FEATURE_ARCH_PERFMON)) + { + stopped = vboxdrvStopIntelArchWatchdog(); + break; + } + stopped = vboxdrvStopP4Watchdog(); + break; + default: + return; + } + } + + if (stopped) + nmi_atomic_dec(&nmi_active); +} + +/** Disable LAPIC NMI watchdog. */ +static void DisableLapicNmiWatchdog(void) +{ + BUG_ON(nmi_watchdog != NMI_LOCAL_APIC); + + if (nmi_atomic_read(&nmi_active) <= 0) + return; + + on_each_cpu(vboxdrvStopApicNmiWatchdog, NULL, 1, 1); + + BUG_ON(nmi_atomic_read(&nmi_active) != 0); + + /* tell do_nmi() and others that we're not active any more */ + nmi_watchdog = NMI_NONE; +} + +/** Shutdown NMI. */ +static void vboxdrvNmiCpuShutdown(void * dummy) +{ + unsigned int vERR, vPC; + + vPC = apic_read(APIC_LVTPC); + + if ((GET_APIC_DELIVERY_MODE(vPC) == APIC_MODE_NMI) && !(vPC & APIC_LVT_MASKED)) + { + vERR = apic_read(APIC_LVTERR); + apic_write(APIC_LVTERR, vERR | APIC_LVT_MASKED); + apic_write(APIC_LVTPC, vPC | APIC_LVT_MASKED); + apic_write(APIC_LVTERR, vERR); + } +} + +static void vboxdrvNmiShutdown(void) +{ + on_each_cpu(vboxdrvNmiCpuShutdown, NULL, 0, 1); +} +# endif /* DO_DISABLE_NMI */ +#endif /* CONFIG_X86_LOCAL_APIC */ + + +DECLINLINE(RTUID) vboxdrvLinuxUid(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + return current->cred->uid; +#else + return current->uid; +#endif +} + +DECLINLINE(RTGID) vboxdrvLinuxGid(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + return current->cred->gid; +#else + return current->gid; +#endif +} + +DECLINLINE(RTUID) vboxdrvLinuxEuid(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + return current->cred->euid; +#else + return current->euid; +#endif +} + +/** + * Initialize module. + * + * @returns appropriate status code. + */ +static int __init VBoxDrvLinuxInit(void) +{ + int rc; + + dprintf(("VBoxDrv::ModuleInit\n")); + +#ifdef CONFIG_X86_LOCAL_APIC + /* + * If an NMI occurs while we are inside the world switcher the macine will crash. + * The Linux NMI watchdog generates periodic NMIs increasing a counter which is + * compared with another counter increased in the timer interrupt handler. Therefore + * we don't allow to setup an NMI watchdog. + */ +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) && !defined(VBOX_REDHAT_KABI) + /* + * First test: NMI actiated? Works only works with Linux 2.6 -- 2.4 does not export + * the nmi_watchdog variable. + */ +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) || \ + (defined CONFIG_X86_64 && LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +# ifdef DO_DISABLE_NMI + if (nmi_atomic_read(&nmi_active) > 0) + { + printk(KERN_DEBUG DEVICE_NAME ": Trying to deactivate the NMI watchdog...\n"); + + switch (nmi_watchdog) + { + case NMI_LOCAL_APIC: + DisableLapicNmiWatchdog(); + break; + case NMI_NONE: + nmi_atomic_dec(&nmi_active); + break; + } + + if (nmi_atomic_read(&nmi_active) == 0) + { + vboxdrvNmiShutdown(); + printk(KERN_DEBUG DEVICE_NAME ": Successfully done.\n"); + } + else + printk(KERN_DEBUG DEVICE_NAME ": Failed!\n"); + } +# endif /* DO_DISABLE_NMI */ + + /* + * Permanent IO_APIC mode active? No way to handle this! + */ + if (nmi_watchdog == NMI_IO_APIC) + { + printk(KERN_ERR DEVICE_NAME + ": NMI watchdog in IO_APIC mode active -- refused to load the kernel module!\n" + DEVICE_NAME + ": Please disable the NMI watchdog by specifying 'nmi_watchdog=0' at kernel\n" + DEVICE_NAME + ": command line.\n"); + return -EINVAL; + } + + /* + * See arch/i386/kernel/nmi.c on >= 2.6.19: -1 means it can never enabled again + */ + nmi_atomic_set(&nmi_active, -1); + printk(KERN_DEBUG DEVICE_NAME ": Trying to deactivate the NMI watchdog permanently...\n"); + + /* + * Now fall through and see if it actually was enabled before. If so, fail + * as we cannot deactivate it cleanly from here. + */ +# else /* < 2.6.19 */ + /* + * Older 2.6 kernels: nmi_watchdog is not initalized by default + */ + if (nmi_watchdog != NMI_NONE) + goto nmi_activated; +# endif +# endif /* >= 2.6.0 && !defined(VBOX_REDHAT_KABI) */ + + /* + * Second test: Interrupt generated by performance counter not masked and can + * generate an NMI. Works also with Linux 2.4. + */ + { + unsigned int v, ver, maxlvt; + + v = apic_read(APIC_LVR); + ver = GET_APIC_VERSION(v); + /* 82489DXs do not report # of LVT entries. */ + maxlvt = APIC_INTEGRATED(ver) ? GET_APIC_MAXLVT(v) : 2; + if (maxlvt >= 4) + { + /* Read status of performance counter IRQ vector */ + v = apic_read(APIC_LVTPC); + + /* performance counter generates NMI and is not masked? */ + if ((GET_APIC_DELIVERY_MODE(v) == APIC_MODE_NMI) && !(v & APIC_LVT_MASKED)) + { +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) || \ + (defined CONFIG_X86_64 && LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) + printk(KERN_ERR DEVICE_NAME + ": NMI watchdog either active or at least initialized. Please disable the NMI\n" + DEVICE_NAME + ": watchdog by specifying 'nmi_watchdog=0' at kernel command line.\n"); + return -EINVAL; +# else /* < 2.6.19 */ +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) && !defined(VBOX_REDHAT_KABI) +nmi_activated: +# endif + printk(KERN_ERR DEVICE_NAME + ": NMI watchdog active -- refused to load the kernel module! Please disable\n" + DEVICE_NAME + ": the NMI watchdog by specifying 'nmi_watchdog=0' at kernel command line.\n"); + return -EINVAL; +# endif /* >= 2.6.19 */ + } + } + } +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) + printk(KERN_DEBUG DEVICE_NAME ": Successfully done.\n"); +# endif /* >= 2.6.19 */ +#endif /* CONFIG_X86_LOCAL_APIC */ + + /* + * Check for synchronous/asynchronous TSC mode. + */ + printk(KERN_DEBUG DEVICE_NAME ": Found %u processor cores.\n", (unsigned)RTMpGetOnlineCount()); +#ifdef CONFIG_VBOXDRV_AS_MISC + rc = misc_register(&gMiscDevice); + if (rc) + { + printk(KERN_ERR DEVICE_NAME ": Can't register misc device! rc=%d\n", rc); + return rc; + } +#else /* !CONFIG_VBOXDRV_AS_MISC */ + /* + * Register character device. + */ + g_iModuleMajor = DEVICE_MAJOR; + rc = VBOX_REGISTER_DEVICE((dev_t)g_iModuleMajor, DEVICE_NAME, &gFileOpsVBoxDrv); + if (rc < 0) + { + dprintf(("VBOX_REGISTER_DEVICE failed with rc=%#x!\n", rc)); + return rc; + } + + /* + * Save returned module major number + */ + if (DEVICE_MAJOR != 0) + g_iModuleMajor = DEVICE_MAJOR; + else + g_iModuleMajor = rc; + rc = 0; + +#ifdef CONFIG_DEVFS_FS + /* + * Register a device entry + */ + g_hDevFsVBoxDrv = VBOX_REGISTER_DEVFS(); + if (g_hDevFsVBoxDrv == NULL) + { + dprintf(("devfs_register failed!\n")); + rc = -EINVAL; + } +#endif +#endif /* !CONFIG_VBOXDRV_AS_MISC */ + if (!rc) + { + /* + * Initialize the runtime. + * On AMD64 we'll have to donate the high rwx memory block to the exec allocator. + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { +#ifdef RT_ARCH_AMD64 + rc = RTR0MemExecDonate(&g_abExecMemory[0], sizeof(g_abExecMemory)); + printk("VBoxDrv: dbg - g_abExecMemory=%p\n", (void *)&g_abExecMemory[0]); +#endif + /* + * Initialize the device extension. + */ + if (RT_SUCCESS(rc)) + rc = supdrvInitDevExt(&g_DevExt); + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION + rc = platform_driver_register(&gPlatformDriver); + if (rc == 0) + { + rc = platform_device_register(&gPlatformDevice); + if (rc == 0) +#endif + { + printk(KERN_INFO DEVICE_NAME ": TSC mode is %s, kernel timer mode is " +#ifdef VBOX_HRTIMER + "'high-res'" +#else + "'normal'" +#endif + ".\n", + g_DevExt.pGip->u32Mode == SUPGIPMODE_SYNC_TSC ? "'synchronous'" : "'asynchronous'"); + LogFlow(("VBoxDrv::ModuleInit returning %#x\n", rc)); + printk(KERN_DEBUG DEVICE_NAME ": Successfully loaded version " + VBOX_VERSION_STRING " (interface " xstr(SUPDRV_IOC_VERSION) ").\n"); + return rc; + } +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION + else + platform_driver_unregister(&gPlatformDriver); + } +#endif + } + + rc = -EINVAL; + RTR0Term(); + } + else + rc = -EINVAL; + + /* + * Failed, cleanup and return the error code. + */ +#if defined(CONFIG_DEVFS_FS) && !defined(CONFIG_VBOXDRV_AS_MISC) + VBOX_UNREGISTER_DEVFS(g_hDevFsVBoxDrv); +#endif + } +#ifdef CONFIG_VBOXDRV_AS_MISC + misc_deregister(&gMiscDevice); + dprintf(("VBoxDrv::ModuleInit returning %#x (minor:%d)\n", rc, gMiscDevice.minor)); +#else + VBOX_UNREGISTER_DEVICE(g_iModuleMajor, DEVICE_NAME); + dprintf(("VBoxDrv::ModuleInit returning %#x (major:%d)\n", rc, g_iModuleMajor)); +#endif + return rc; +} + + +/** + * Unload the module. + */ +static void __exit VBoxDrvLinuxUnload(void) +{ + int rc; + dprintf(("VBoxDrvLinuxUnload\n")); + NOREF(rc); + +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION + platform_device_unregister(&gPlatformDevice); + platform_driver_unregister(&gPlatformDriver); +#endif + + /* + * I Don't think it's possible to unload a driver which processes have + * opened, at least we'll blindly assume that here. + */ +#ifdef CONFIG_VBOXDRV_AS_MISC + rc = misc_deregister(&gMiscDevice); + if (rc < 0) + { + dprintf(("misc_deregister failed with rc=%#x\n", rc)); + } +#else /* !CONFIG_VBOXDRV_AS_MISC */ +# ifdef CONFIG_DEVFS_FS + /* + * Unregister a device entry + */ + VBOX_UNREGISTER_DEVFS(g_hDevFsVBoxDrv); +# endif /* devfs */ + VBOX_UNREGISTER_DEVICE(g_iModuleMajor, DEVICE_NAME); +#endif /* !CONFIG_VBOXDRV_AS_MISC */ + + /* + * Destroy GIP, delete the device extension and terminate IPRT. + */ + supdrvDeleteDevExt(&g_DevExt); + RTR0Term(); +} + + +/** + * Device open. Called on open /dev/vboxdrv + * + * @param pInode Pointer to inode info structure. + * @param pFilp Associated file pointer. + */ +static int VBoxDrvLinuxCreate(struct inode *pInode, struct file *pFilp) +{ + int rc; + PSUPDRVSESSION pSession; + Log(("VBoxDrvLinuxCreate: pFilp=%p pid=%d/%d %s\n", pFilp, RTProcSelf(), current->pid, current->comm)); + +#ifdef VBOX_WITH_HARDENING + /* + * Only root is allowed to access the device, enforce it! + */ + if (vboxdrvLinuxEuid() != 0 /* root */ ) + { + Log(("VBoxDrvLinuxCreate: euid=%d, expected 0 (root)\n", vboxdrvLinuxEuid())); + return -EPERM; + } +#endif /* VBOX_WITH_HARDENING */ + + /* + * Call common code for the rest. + */ + rc = supdrvCreateSession(&g_DevExt, true /* fUser */, (PSUPDRVSESSION *)&pSession); + if (!rc) + { + pSession->Uid = vboxdrvLinuxUid(); + pSession->Gid = vboxdrvLinuxGid(); + } + + pFilp->private_data = pSession; + + Log(("VBoxDrvLinuxCreate: g_DevExt=%p pSession=%p rc=%d/%d (pid=%d/%d %s)\n", + &g_DevExt, pSession, rc, VBoxDrvLinuxErr2LinuxErr(rc), + RTProcSelf(), current->pid, current->comm)); + return VBoxDrvLinuxErr2LinuxErr(rc); +} + + +/** + * Close device. + * + * @param pInode Pointer to inode info structure. + * @param pFilp Associated file pointer. + */ +static int VBoxDrvLinuxClose(struct inode *pInode, struct file *pFilp) +{ + Log(("VBoxDrvLinuxClose: pFilp=%p pSession=%p pid=%d/%d %s\n", + pFilp, pFilp->private_data, RTProcSelf(), current->pid, current->comm)); + supdrvCloseSession(&g_DevExt, (PSUPDRVSESSION)pFilp->private_data); + pFilp->private_data = NULL; + return 0; +} + + +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION +/** + * Dummy device release function. We have to provide this function, + * otherwise the kernel will complain. + * + * @param pDev Pointer to the platform device. + */ +static void VBoxDevRelease(struct device *pDev) +{ +} + +/** + * Dummy probe function. + * + * @param pDev Pointer to the platform device. + */ +static int VBoxDrvProbe(struct platform_device *pDev) +{ + return 0; +} + +/** + * Suspend callback. + * @param pDev Pointer to the platform device. + * @param State message type, see Documentation/power/devices.txt. + */ +static int VBoxDrvSuspend(struct platform_device *pDev, pm_message_t State) +{ + RTPowerSignalEvent(RTPOWEREVENT_SUSPEND); + return 0; +} + +/** + * Resume callback. + * + * @param pDev Pointer to the platform device. + */ +static int VBoxDrvResume(struct platform_device *pDev) +{ + RTPowerSignalEvent(RTPOWEREVENT_RESUME); + return 0; +} +#endif /* VBOX_WITH_SUSPEND_NOTIFICATION */ + + +/** + * Device I/O Control entry point. + * + * @param pFilp Associated file pointer. + * @param uCmd The function specified to ioctl(). + * @param ulArg The argument specified to ioctl(). + */ +#ifdef HAVE_UNLOCKED_IOCTL +static long VBoxDrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg) +#else +static int VBoxDrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg) +#endif +{ + /* + * Deal with the two high-speed IOCtl that takes it's arguments from + * the session and iCmd, and only returns a VBox status code. + */ +#ifdef HAVE_UNLOCKED_IOCTL + if (RT_LIKELY( uCmd == SUP_IOCTL_FAST_DO_RAW_RUN + || uCmd == SUP_IOCTL_FAST_DO_HWACC_RUN + || uCmd == SUP_IOCTL_FAST_DO_NOP)) + return supdrvIOCtlFast(uCmd, ulArg, &g_DevExt, (PSUPDRVSESSION)pFilp->private_data); + return VBoxDrvLinuxIOCtlSlow(pFilp, uCmd, ulArg); + +#else /* !HAVE_UNLOCKED_IOCTL */ + + int rc; + unlock_kernel(); + if (RT_LIKELY( uCmd == SUP_IOCTL_FAST_DO_RAW_RUN + || uCmd == SUP_IOCTL_FAST_DO_HWACC_RUN + || uCmd == SUP_IOCTL_FAST_DO_NOP)) + rc = supdrvIOCtlFast(uCmd, ulArg, &g_DevExt, (PSUPDRVSESSION)pFilp->private_data); + else + rc = VBoxDrvLinuxIOCtlSlow(pFilp, uCmd, ulArg); + lock_kernel(); + return rc; +#endif /* !HAVE_UNLOCKED_IOCTL */ +} + + +/** + * Device I/O Control entry point. + * + * @param pFilp Associated file pointer. + * @param uCmd The function specified to ioctl(). + * @param ulArg The argument specified to ioctl(). + */ +static int VBoxDrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg) +{ + int rc; + SUPREQHDR Hdr; + PSUPREQHDR pHdr; + uint32_t cbBuf; + + Log6(("VBoxDrvLinuxIOCtl: pFilp=%p uCmd=%#x ulArg=%p pid=%d/%d\n", pFilp, uCmd, (void *)ulArg, RTProcSelf(), current->pid)); + + /* + * Read the header. + */ + if (RT_UNLIKELY(copy_from_user(&Hdr, (void *)ulArg, sizeof(Hdr)))) + { + Log(("VBoxDrvLinuxIOCtl: copy_from_user(,%#lx,) failed; uCmd=%#x.\n", ulArg, uCmd)); + return -EFAULT; + } + if (RT_UNLIKELY((Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC)) + { + Log(("VBoxDrvLinuxIOCtl: bad header magic %#x; uCmd=%#x\n", Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK, uCmd)); + return -EINVAL; + } + + /* + * Buffer the request. + */ + cbBuf = RT_MAX(Hdr.cbIn, Hdr.cbOut); + if (RT_UNLIKELY(cbBuf > _1M*16)) + { + Log(("VBoxDrvLinuxIOCtl: too big cbBuf=%#x; uCmd=%#x\n", cbBuf, uCmd)); + return -E2BIG; + } + if (RT_UNLIKELY(cbBuf != _IOC_SIZE(uCmd) && _IOC_SIZE(uCmd))) + { + Log(("VBoxDrvLinuxIOCtl: bad ioctl cbBuf=%#x _IOC_SIZE=%#x; uCmd=%#x.\n", cbBuf, _IOC_SIZE(uCmd), uCmd)); + return -EINVAL; + } + pHdr = RTMemAlloc(cbBuf); + if (RT_UNLIKELY(!pHdr)) + { + OSDBGPRINT(("VBoxDrvLinuxIOCtl: failed to allocate buffer of %d bytes for uCmd=%#x.\n", cbBuf, uCmd)); + return -ENOMEM; + } + if (RT_UNLIKELY(copy_from_user(pHdr, (void *)ulArg, Hdr.cbIn))) + { + Log(("VBoxDrvLinuxIOCtl: copy_from_user(,%#lx, %#x) failed; uCmd=%#x.\n", ulArg, Hdr.cbIn, uCmd)); + RTMemFree(pHdr); + return -EFAULT; + } + + /* + * Process the IOCtl. + */ + rc = supdrvIOCtl(uCmd, &g_DevExt, (PSUPDRVSESSION)pFilp->private_data, pHdr); + + /* + * Copy ioctl data and output buffer back to user space. + */ + if (RT_LIKELY(!rc)) + { + uint32_t cbOut = pHdr->cbOut; + if (RT_UNLIKELY(cbOut > cbBuf)) + { + OSDBGPRINT(("VBoxDrvLinuxIOCtl: too much output! %#x > %#x; uCmd=%#x!\n", cbOut, cbBuf, uCmd)); + cbOut = cbBuf; + } + if (RT_UNLIKELY(copy_to_user((void *)ulArg, pHdr, cbOut))) + { + /* this is really bad! */ + OSDBGPRINT(("VBoxDrvLinuxIOCtl: copy_to_user(%#lx,,%#x); uCmd=%#x!\n", ulArg, cbOut, uCmd)); + rc = -EFAULT; + } + } + else + { + Log(("VBoxDrvLinuxIOCtl: pFilp=%p uCmd=%#x ulArg=%p failed, rc=%d\n", pFilp, uCmd, (void *)ulArg, rc)); + rc = -EINVAL; + } + RTMemFree(pHdr); + + Log6(("VBoxDrvLinuxIOCtl: returns %d (pid=%d/%d)\n", rc, RTProcSelf(), current->pid)); + return rc; +} + + +/** + * The SUPDRV IDC entry point. + * + * @returns VBox status code, see supdrvIDC. + * @param iReq The request code. + * @param pReq The request. + */ +int VBOXCALL SUPDrvLinuxIDC(uint32_t uReq, PSUPDRVIDCREQHDR pReq) +{ + PSUPDRVSESSION pSession; + + /* + * Some quick validations. + */ + if (RT_UNLIKELY(!VALID_PTR(pReq))) + return VERR_INVALID_POINTER; + + pSession = pReq->pSession; + if (pSession) + { + if (RT_UNLIKELY(!VALID_PTR(pSession))) + return VERR_INVALID_PARAMETER; + if (RT_UNLIKELY(pSession->pDevExt != &g_DevExt)) + return VERR_INVALID_PARAMETER; + } + else if (RT_UNLIKELY(uReq != SUPDRV_IDC_REQ_CONNECT)) + return VERR_INVALID_PARAMETER; + + /* + * Do the job. + */ + return supdrvIDC(uReq, &g_DevExt, pSession, pReq); +} + +EXPORT_SYMBOL(SUPDrvLinuxIDC); + + +/** + * Initializes any OS specific object creator fields. + */ +void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) +{ + NOREF(pObj); + NOREF(pSession); +} + + +/** + * Checks if the session can access the object. + * + * @returns true if a decision has been made. + * @returns false if the default access policy should be applied. + * + * @param pObj The object in question. + * @param pSession The session wanting to access the object. + * @param pszObjName The object name, can be NULL. + * @param prc Where to store the result when returning true. + */ +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) +{ + NOREF(pObj); + NOREF(pSession); + NOREF(pszObjName); + NOREF(prc); + return false; +} + + +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) +{ + return force_async_tsc != 0; +} + + +/** + * Converts a supdrv error code to an linux error code. + * + * @returns corresponding linux error code. + * @param rc supdrv error code (SUPDRV_ERR_* defines). + */ +static int VBoxDrvLinuxErr2LinuxErr(int rc) +{ + switch (rc) + { + case 0: return 0; + case SUPDRV_ERR_GENERAL_FAILURE: return -EACCES; + case SUPDRV_ERR_INVALID_PARAM: return -EINVAL; + case SUPDRV_ERR_INVALID_MAGIC: return -EILSEQ; + case SUPDRV_ERR_INVALID_HANDLE: return -ENXIO; + case SUPDRV_ERR_INVALID_POINTER: return -EFAULT; + case SUPDRV_ERR_LOCK_FAILED: return -ENOLCK; + case SUPDRV_ERR_ALREADY_LOADED: return -EEXIST; + case SUPDRV_ERR_PERMISSION_DENIED: return -EPERM; + case SUPDRV_ERR_VERSION_MISMATCH: return -ENOSYS; + case SUPDRV_ERR_IDT_FAILED: return -1000; + } + + return -EPERM; +} + + +RTDECL(int) SUPR0Printf(const char *pszFormat, ...) +{ +#if 1 + va_list args; + char szMsg[512]; + + va_start(args, pszFormat); + vsnprintf(szMsg, sizeof(szMsg) - 1, pszFormat, args); + szMsg[sizeof(szMsg) - 1] = '\0'; + printk("%s", szMsg); + va_end(args); +#else + /* forward to printf - needs some more GCC hacking to fix ebp... */ + __asm__ __volatile__ ("mov %0, %esp\n\t" + "jmp %1\n\t", + :: "r" ((uintptr_t)&pszFormat - 4), + "m" (printk)); +#endif + return 0; +} + +module_init(VBoxDrvLinuxInit); +module_exit(VBoxDrvLinuxUnload); + +MODULE_AUTHOR("Sun Microsystems, Inc."); +MODULE_DESCRIPTION("VirtualBox Support Driver"); +MODULE_LICENSE("GPL"); +#ifdef MODULE_VERSION +MODULE_VERSION(VBOX_VERSION_STRING " (" xstr(SUPDRV_IOC_VERSION) ")"); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +module_param(force_async_tsc, int, 0444); +#else +MODULE_PARM(force_async_tsc, "i"); +#endif +MODULE_PARM_DESC(force_async_tsc, "force the asynchronous TSC mode"); + |
