/* $Id: VBoxGuest-linux.c $ */ /** @file * VBoxGuest - Linux specifics. * * Note. Unfortunately, the difference between this and SUPDrv-linux.c is * a little bit too big to be helpful. */ /* * Copyright (C) 2006-2022 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox 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. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_SUP_DRV #include "the-linux-kernel.h" #if RTLNX_VER_MIN(2,6,15) # define VBOXGUEST_WITH_INPUT_DRIVER #endif #if RTLNX_VER_MIN(4,15,0) # define CONST_4_15 const #else # define CONST_4_15 #endif #include "VBoxGuestInternal.h" #ifdef VBOXGUEST_WITH_INPUT_DRIVER # include #endif #include #include #if RTLNX_VER_MIN(2,6,28) # include #endif #include #include "revision-generated.h" #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** The device name. */ #define DEVICE_NAME "vboxguest" /** The device name for the device node open to everyone. */ #define DEVICE_NAME_USER "vboxuser" /** The name of the PCI driver */ #define DRIVER_NAME DEVICE_NAME /* 2.4.x compatibility macros that may or may not be defined. */ #ifndef IRQ_RETVAL # define irqreturn_t void # define IRQ_RETVAL(n) #endif /* uidgid.h was introduced in 3.5.0. */ #if RTLNX_VER_MAX(3,5,0) # define kgid_t gid_t # define kuid_t uid_t #endif #ifdef VBOXGUEST_WITH_INPUT_DRIVER /** The name of input pointing device for mouse integration. */ # define VBOX_INPUT_DEVICE_NAME "VirtualBox mouse integration" #endif /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static void vgdrvLinuxTermPci(struct pci_dev *pPciDev); static int vgdrvLinuxProbePci(struct pci_dev *pPciDev, const struct pci_device_id *id); static int __init vgdrvLinuxModInit(void); static void __exit vgdrvLinuxModExit(void); static int vgdrvLinuxOpen(struct inode *pInode, struct file *pFilp); static int vgdrvLinuxRelease(struct inode *pInode, struct file *pFilp); #ifdef HAVE_UNLOCKED_IOCTL static long vgdrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg); #else static int vgdrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg); #endif static int vgdrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg, PVBOXGUESTSESSION pSession); static int vgdrvLinuxFAsync(int fd, struct file *pFile, int fOn); static unsigned int vgdrvLinuxPoll(struct file *pFile, poll_table *pPt); static ssize_t vgdrvLinuxRead(struct file *pFile, char *pbBuf, size_t cbRead, loff_t *poff); /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** * Device extention & session data association structure. */ static VBOXGUESTDEVEXT g_DevExt; /** The PCI device. */ static struct pci_dev *g_pPciDev = NULL; /** The base of the I/O port range. */ static RTIOPORT g_IOPortBase; /** The base of the MMIO range. */ static RTHCPHYS g_MMIOPhysAddr = NIL_RTHCPHYS; /** The size of the MMIO range as seen by PCI. */ static uint32_t g_cbMMIO; /** The pointer to the mapping of the MMIO range. */ static void *g_pvMMIOBase; /** Wait queue used by polling. */ static wait_queue_head_t g_PollEventQueue; /** Asynchronous notification stuff. */ static struct fasync_struct *g_pFAsyncQueue; #ifdef VBOXGUEST_WITH_INPUT_DRIVER /** Pre-allocated mouse status VMMDev requests for use in the IRQ handler. */ static VMMDevReqMouseStatusEx *g_pMouseStatusReqEx; #endif #if RTLNX_VER_MIN(2,6,0) /** Whether we've create the logger or not. */ static volatile bool g_fLoggerCreated; /** Release logger group settings. */ static char g_szLogGrp[128]; /** Release logger flags settings. */ static char g_szLogFlags[128]; /** Release logger destination settings. */ static char g_szLogDst[128]; # if 0 /** Debug logger group settings. */ static char g_szDbgLogGrp[128]; /** Debug logger flags settings. */ static char g_szDbgLogFlags[128]; /** Debug logger destination settings. */ static char g_szDbgLogDst[128]; # endif #endif /** The input device handle */ #ifdef VBOXGUEST_WITH_INPUT_DRIVER static struct input_dev *g_pInputDevice = NULL; #endif /** The file_operations structure. */ static struct file_operations g_FileOps = { owner: THIS_MODULE, open: vgdrvLinuxOpen, release: vgdrvLinuxRelease, #ifdef HAVE_UNLOCKED_IOCTL unlocked_ioctl: vgdrvLinuxIOCtl, #else ioctl: vgdrvLinuxIOCtl, #endif fasync: vgdrvLinuxFAsync, read: vgdrvLinuxRead, poll: vgdrvLinuxPoll, llseek: no_llseek, }; /** The miscdevice structure. */ static struct miscdevice g_MiscDevice = { minor: MISC_DYNAMIC_MINOR, name: DEVICE_NAME, fops: &g_FileOps, }; /** The file_operations structure for the user device. * @remarks For the time being we'll be using the same implementation as * /dev/vboxguest here. */ static struct file_operations g_FileOpsUser = { owner: THIS_MODULE, open: vgdrvLinuxOpen, release: vgdrvLinuxRelease, #ifdef HAVE_UNLOCKED_IOCTL unlocked_ioctl: vgdrvLinuxIOCtl, #else ioctl: vgdrvLinuxIOCtl, #endif }; /** The miscdevice structure for the user device. */ static struct miscdevice g_MiscDeviceUser = { minor: MISC_DYNAMIC_MINOR, name: DEVICE_NAME_USER, fops: &g_FileOpsUser, }; /** PCI hotplug structure. */ static const struct pci_device_id g_VBoxGuestPciId[] = { { vendor: VMMDEV_VENDORID, device: VMMDEV_DEVICEID }, { /* empty entry */ } }; MODULE_DEVICE_TABLE(pci, g_VBoxGuestPciId); /** Structure for registering the PCI driver. */ static struct pci_driver g_PciDriver = { name: DRIVER_NAME, id_table: g_VBoxGuestPciId, probe: vgdrvLinuxProbePci, remove: vgdrvLinuxTermPci }; #ifdef VBOXGUEST_WITH_INPUT_DRIVER /** Kernel IDC session to ourselves for use with the mouse events. */ static PVBOXGUESTSESSION g_pKernelSession = NULL; #endif /** * Converts a VBox status code to a linux error code. * * @returns corresponding negative linux error code. * @param rc supdrv error code (SUPDRV_ERR_* defines). */ static int vgdrvLinuxConvertToNegErrno(int rc) { if ( rc > -1000 && rc < 1000) return -RTErrConvertToErrno(rc); switch (rc) { case VERR_HGCM_SERVICE_NOT_FOUND: return -ESRCH; case VINF_HGCM_CLIENT_REJECTED: return 0; case VERR_HGCM_INVALID_CMD_ADDRESS: return -EFAULT; case VINF_HGCM_ASYNC_EXECUTE: return 0; case VERR_HGCM_INTERNAL: return -EPROTO; case VERR_HGCM_INVALID_CLIENT_ID: return -EINVAL; case VINF_HGCM_SAVE_STATE: return 0; /* No reason to return this to a guest */ // case VERR_HGCM_SERVICE_EXISTS: return -EEXIST; default: AssertMsgFailed(("Unhandled error code %Rrc\n", rc)); return -EPROTO; } } /** * Does the PCI detection and init of the device. * * @returns 0 on success, negated errno on failure. */ static int vgdrvLinuxProbePci(struct pci_dev *pPciDev, const struct pci_device_id *id) { int rc; NOREF(id); AssertReturn(!g_pPciDev, -EINVAL); rc = pci_enable_device(pPciDev); if (rc >= 0) { /* I/O Ports are mandatory, the MMIO bit is not. */ g_IOPortBase = pci_resource_start(pPciDev, 0); if (g_IOPortBase != 0) { /* * Map the register address space. */ g_MMIOPhysAddr = pci_resource_start(pPciDev, 1); g_cbMMIO = pci_resource_len(pPciDev, 1); if (request_mem_region(g_MMIOPhysAddr, g_cbMMIO, DEVICE_NAME) != NULL) { g_pvMMIOBase = ioremap(g_MMIOPhysAddr, g_cbMMIO); if (g_pvMMIOBase) { /** @todo why aren't we requesting ownership of the I/O ports as well? */ g_pPciDev = pPciDev; return 0; } /* failure cleanup path */ LogRel((DEVICE_NAME ": ioremap failed; MMIO Addr=%RHp cb=%#x\n", g_MMIOPhysAddr, g_cbMMIO)); rc = -ENOMEM; release_mem_region(g_MMIOPhysAddr, g_cbMMIO); } else { LogRel((DEVICE_NAME ": failed to obtain adapter memory\n")); rc = -EBUSY; } g_MMIOPhysAddr = NIL_RTHCPHYS; g_cbMMIO = 0; g_IOPortBase = 0; } else { LogRel((DEVICE_NAME ": did not find expected hardware resources\n")); rc = -ENXIO; } pci_disable_device(pPciDev); } else LogRel((DEVICE_NAME ": could not enable device: %d\n", rc)); return rc; } /** * Clean up the usage of the PCI device. */ static void vgdrvLinuxTermPci(struct pci_dev *pPciDev) { g_pPciDev = NULL; if (pPciDev) { iounmap(g_pvMMIOBase); g_pvMMIOBase = NULL; release_mem_region(g_MMIOPhysAddr, g_cbMMIO); g_MMIOPhysAddr = NIL_RTHCPHYS; g_cbMMIO = 0; pci_disable_device(pPciDev); } } /** * Interrupt service routine. * * @returns In 2.4 it returns void. * In 2.6 we indicate whether we've handled the IRQ or not. * * @param iIrq The IRQ number. * @param pvDevId The device ID, a pointer to g_DevExt. * @param pRegs Register set. Removed in 2.6.19. */ #if RTLNX_VER_MIN(2,6,19) && !defined(DOXYGEN_RUNNING) static irqreturn_t vgdrvLinuxISR(int iIrq, void *pvDevId) #else static irqreturn_t vgdrvLinuxISR(int iIrq, void *pvDevId, struct pt_regs *pRegs) #endif { bool fTaken = VGDrvCommonISR(&g_DevExt); return IRQ_RETVAL(fTaken); } /** * Registers the ISR and initializes the poll wait queue. */ static int __init vgdrvLinuxInitISR(void) { int rc; init_waitqueue_head(&g_PollEventQueue); rc = request_irq(g_pPciDev->irq, vgdrvLinuxISR, #if RTLNX_VER_MIN(2,6,20) IRQF_SHARED, #else SA_SHIRQ, #endif DEVICE_NAME, &g_DevExt); if (rc) { LogRel((DEVICE_NAME ": could not request IRQ %d: err=%d\n", g_pPciDev->irq, rc)); return rc; } return 0; } /** * Deregisters the ISR. */ static void vgdrvLinuxTermISR(void) { free_irq(g_pPciDev->irq, &g_DevExt); } #ifdef VBOXGUEST_WITH_INPUT_DRIVER /** * Check if extended mouse pointer state request protocol is currently used by driver. * * @returns True if extended protocol is used, False otherwise. */ static bool vgdrvLinuxUsesMouseStatusEx(void) { return g_pMouseStatusReqEx->Core.header.requestType == VMMDevReq_GetMouseStatusEx; } /** * Reports the mouse integration status to the host. * * Calls the kernel IOCtl to report mouse status to the host on behalf of * our kernel session. * * @param fStatus The mouse status to report. */ static int vgdrvLinuxSetMouseStatus(uint32_t fStatus) { int rc; VBGLIOCSETMOUSESTATUS Req; VBGLREQHDR_INIT(&Req.Hdr, SET_MOUSE_STATUS); Req.u.In.fStatus = fStatus; rc = VGDrvCommonIoCtl(VBGL_IOCTL_SET_MOUSE_STATUS, &g_DevExt, g_pKernelSession, &Req.Hdr, sizeof(Req)); if (RT_SUCCESS(rc)) rc = Req.Hdr.rc; return rc; } /** * Called when the input device is first opened. * * Sets up absolute mouse reporting. */ static int vboxguestOpenInputDevice(struct input_dev *pDev) { int rc = vgdrvLinuxSetMouseStatus( VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE | VMMDEV_MOUSE_NEW_PROTOCOL | (vgdrvLinuxUsesMouseStatusEx() ? VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL : 0)); if (RT_FAILURE(rc)) return ENODEV; NOREF(pDev); return 0; } /** * Called if all open handles to the input device are closed. * * Disables absolute reporting. */ static void vboxguestCloseInputDevice(struct input_dev *pDev) { NOREF(pDev); vgdrvLinuxSetMouseStatus(0); } /** * Free corresponding mouse status request structure. */ static void vgdrvLinuxFreeMouseStatusReq(void) { VbglR0GRFree(&g_pMouseStatusReqEx->Core.header); g_pMouseStatusReqEx = NULL; } /** * Creates the kernel input device. */ static int __init vgdrvLinuxCreateInputDevice(void) { /* Try to allocate legacy request data first, and check if host supports extended protocol. */ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&g_pMouseStatusReqEx, sizeof(VMMDevReqMouseStatus), VMMDevReq_GetMouseStatus); if (RT_SUCCESS(rc)) { /* Check if host supports extended mouse state reporting. */ g_pMouseStatusReqEx->Core.mouseFeatures = 0; rc = VbglR0GRPerform(&g_pMouseStatusReqEx->Core.header); if (RT_SUCCESS(rc)) { if (g_pMouseStatusReqEx->Core.mouseFeatures & VMMDEV_MOUSE_HOST_USES_FULL_STATE_PROTOCOL) { VMMDevReqMouseStatusEx *pReqEx = NULL; rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReqEx, sizeof(*pReqEx), VMMDevReq_GetMouseStatusEx); if (RT_SUCCESS(rc)) { /* De-allocate legacy request data, */ VbglR0GRFree(&g_pMouseStatusReqEx->Core.header); /* ..and switch to extended requests. */ g_pMouseStatusReqEx = pReqEx; LogRel(("Host supports full mouse state reporting, switching to extended mouse integration protocol\n")); } else LogRel(("Host supports full mouse state reporting, but feature cannot be initialized, switching to legacy mouse integration protocol\n")); } else LogRel(("Host does not support full mouse state reporting, switching to legacy mouse integration protocol\n")); } else LogRel(("Unable to get host mouse capabilities, switching to legacy mouse integration protocol\n")); } else rc = -ENOMEM; if (RT_SUCCESS(rc)) { g_pInputDevice = input_allocate_device(); if (g_pInputDevice) { g_pInputDevice->name = VBOX_INPUT_DEVICE_NAME; g_pInputDevice->id.bustype = BUS_PCI; g_pInputDevice->id.vendor = VMMDEV_VENDORID; g_pInputDevice->id.product = VMMDEV_DEVICEID; g_pInputDevice->id.version = VBOX_SHORT_VERSION; g_pInputDevice->open = vboxguestOpenInputDevice; g_pInputDevice->close = vboxguestCloseInputDevice; # if RTLNX_VER_MAX(2,6,22) g_pInputDevice->cdev.dev = &g_pPciDev->dev; # else g_pInputDevice->dev.parent = &g_pPciDev->dev; # endif /* Set up input device capabilities. */ ASMBitSet(g_pInputDevice->evbit, EV_ABS); ASMBitSet(g_pInputDevice->evbit, EV_KEY); # ifdef EV_SYN ASMBitSet(g_pInputDevice->evbit, EV_SYN); # endif ASMBitSet(g_pInputDevice->absbit, ABS_X); ASMBitSet(g_pInputDevice->absbit, ABS_Y); input_set_abs_params(g_pInputDevice, ABS_X, VMMDEV_MOUSE_RANGE_MIN, VMMDEV_MOUSE_RANGE_MAX, 0, 0); input_set_abs_params(g_pInputDevice, ABS_Y, VMMDEV_MOUSE_RANGE_MIN, VMMDEV_MOUSE_RANGE_MAX, 0, 0); ASMBitSet(g_pInputDevice->keybit, BTN_MOUSE); /* Report extra capabilities to input layer if extended mouse state protocol * will be used in communication with host. */ if (vgdrvLinuxUsesMouseStatusEx()) { ASMBitSet(g_pInputDevice->evbit, EV_REL); ASMBitSet(g_pInputDevice->evbit, EV_KEY); ASMBitSet(g_pInputDevice->relbit, REL_WHEEL); ASMBitSet(g_pInputDevice->relbit, REL_HWHEEL); ASMBitSet(g_pInputDevice->keybit, BTN_LEFT); ASMBitSet(g_pInputDevice->keybit, BTN_RIGHT); ASMBitSet(g_pInputDevice->keybit, BTN_MIDDLE); ASMBitSet(g_pInputDevice->keybit, BTN_SIDE); ASMBitSet(g_pInputDevice->keybit, BTN_EXTRA); } rc = input_register_device(g_pInputDevice); if (rc == 0) return 0; input_free_device(g_pInputDevice); } else rc = -ENOMEM; vgdrvLinuxFreeMouseStatusReq(); } return rc; } /** * Terminates the kernel input device. */ static void vgdrvLinuxTermInputDevice(void) { /* Notify host that mouse integration is no longer available. */ vgdrvLinuxSetMouseStatus(0); vgdrvLinuxFreeMouseStatusReq(); /* See documentation of input_register_device(): input_free_device() * should not be called after a device has been registered. */ input_unregister_device(g_pInputDevice); } #endif /* VBOXGUEST_WITH_INPUT_DRIVER */ /** * Creates the device nodes. * * @returns 0 on success, negated errno on failure. */ static int __init vgdrvLinuxInitDeviceNodes(void) { /* * The full feature device node. */ int rc = misc_register(&g_MiscDevice); if (!rc) { /* * The device node intended to be accessible by all users. */ rc = misc_register(&g_MiscDeviceUser); if (!rc) return 0; LogRel((DEVICE_NAME ": misc_register failed for %s (rc=%d)\n", DEVICE_NAME_USER, rc)); misc_deregister(&g_MiscDevice); } else LogRel((DEVICE_NAME ": misc_register failed for %s (rc=%d)\n", DEVICE_NAME, rc)); return rc; } /** * Deregisters the device nodes. */ static void vgdrvLinuxTermDeviceNodes(void) { misc_deregister(&g_MiscDevice); misc_deregister(&g_MiscDeviceUser); } /** * Initialize module. * * @returns appropriate status code. */ static int __init vgdrvLinuxModInit(void) { static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; PRTLOGGER pRelLogger; int rc; /* * Initialize IPRT first. */ rc = RTR0Init(0); if (RT_FAILURE(rc)) { printk(KERN_ERR DEVICE_NAME ": RTR0Init failed, rc=%d.\n", rc); return -EINVAL; } /* * Create the release log. * (We do that here instead of common code because we want to log * early failures using the LogRel macro.) */ rc = RTLogCreate(&pRelLogger, 0 /* fFlags */, "all", "VBOX_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups, RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER | RTLOGDEST_USER, NULL); if (RT_SUCCESS(rc)) { #if RTLNX_VER_MIN(2,6,0) RTLogGroupSettings(pRelLogger, g_szLogGrp); RTLogFlags(pRelLogger, g_szLogFlags); RTLogDestinations(pRelLogger, g_szLogDst); #endif RTLogRelSetDefaultInstance(pRelLogger); } #if RTLNX_VER_MIN(2,6,0) g_fLoggerCreated = true; #endif /* * Locate and initialize the PCI device. */ rc = pci_register_driver(&g_PciDriver); if (rc >= 0 && g_pPciDev) { /* * Call the common device extension initializer. */ #if RTLNX_VER_MIN(2,6,0) && defined(RT_ARCH_X86) VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux26; #elif RTLNX_VER_MIN(2,6,0) && defined(RT_ARCH_AMD64) VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux26_x64; #elif RTLNX_VER_MIN(2,4,0) && defined(RT_ARCH_X86) VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux24; #elif RTLNX_VER_MIN(2,4,0) && defined(RT_ARCH_AMD64) VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux24_x64; #else # warning "huh? which arch + version is this?" VBOXOSTYPE enmOsType = VBOXOSTYPE_Linux; #endif rc = VGDrvCommonInitDevExt(&g_DevExt, g_IOPortBase, g_pvMMIOBase, g_cbMMIO, enmOSType, VMMDEV_EVENT_MOUSE_POSITION_CHANGED); if (RT_SUCCESS(rc)) { /* * Register the interrupt service routine for it now that g_DevExt can handle IRQs. */ rc = vgdrvLinuxInitISR(); if (rc >= 0) /** @todo r=bird: status check differs from that inside vgdrvLinuxInitISR. */ { #ifdef VBOXGUEST_WITH_INPUT_DRIVER /* * Create the kernel session for this driver. */ rc = VGDrvCommonCreateKernelSession(&g_DevExt, &g_pKernelSession); if (RT_SUCCESS(rc)) { /* * Create the kernel input device. */ rc = vgdrvLinuxCreateInputDevice(); if (rc >= 0) { #endif /* * Read host configuration. */ VGDrvCommonProcessOptionsFromHost(&g_DevExt); /* * Finally, create the device nodes. */ rc = vgdrvLinuxInitDeviceNodes(); if (rc >= 0) { /* some useful information for the user but don't show this on the console */ LogRel((DEVICE_NAME ": Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n")); LogRel((DEVICE_NAME ": misc device minor %d, IRQ %d, I/O port %RTiop, MMIO at %RHp (size 0x%x)\n", g_MiscDevice.minor, g_pPciDev->irq, g_IOPortBase, g_MMIOPhysAddr, g_cbMMIO)); printk(KERN_DEBUG DEVICE_NAME ": Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " (interface " RT_XSTR(VMMDEV_VERSION) ")\n"); return rc; } /* bail out */ #ifdef VBOXGUEST_WITH_INPUT_DRIVER vgdrvLinuxTermInputDevice(); } else { LogRel((DEVICE_NAME ": vboxguestCreateInputDevice failed with rc=%Rrc\n", rc)); rc = RTErrConvertFromErrno(rc); } VGDrvCommonCloseSession(&g_DevExt, g_pKernelSession); } #endif vgdrvLinuxTermISR(); } VGDrvCommonDeleteDevExt(&g_DevExt); } else { LogRel((DEVICE_NAME ": VGDrvCommonInitDevExt failed with rc=%Rrc\n", rc)); rc = RTErrConvertFromErrno(rc); } } else { LogRel((DEVICE_NAME ": PCI device not found, probably running on physical hardware.\n")); rc = -ENODEV; } pci_unregister_driver(&g_PciDriver); RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); RTLogDestroy(RTLogSetDefaultInstance(NULL)); RTR0Term(); return rc; } /** * Unload the module. */ static void __exit vgdrvLinuxModExit(void) { /* * Inverse order of init. */ vgdrvLinuxTermDeviceNodes(); #ifdef VBOXGUEST_WITH_INPUT_DRIVER vgdrvLinuxTermInputDevice(); VGDrvCommonCloseSession(&g_DevExt, g_pKernelSession); #endif vgdrvLinuxTermISR(); VGDrvCommonDeleteDevExt(&g_DevExt); pci_unregister_driver(&g_PciDriver); RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); RTLogDestroy(RTLogSetDefaultInstance(NULL)); RTR0Term(); } /** * Get the process user ID. * * @returns UID. */ DECLINLINE(RTUID) vgdrvLinuxGetUid(void) { #if RTLNX_VER_MIN(2,6,29) # if RTLNX_VER_MIN(3,5,0) return from_kuid(current_user_ns(), current->cred->uid); # else return current->cred->uid; # endif #else return current->uid; #endif } /** * Checks if the given group number is zero or not. * * @returns true / false. * @param gid The group to check for. */ DECLINLINE(bool) vgdrvLinuxIsGroupZero(kgid_t gid) { #if RTLNX_VER_MIN(3,5,0) return from_kgid(current_user_ns(), gid); #else return gid == 0; #endif } /** * Searches the effective group and supplementary groups for @a gid. * * @returns true if member, false if not. * @param gid The group to check for. */ DECLINLINE(RTGID) vgdrvLinuxIsInGroupEff(kgid_t gid) { return in_egroup_p(gid) != 0; } /** * Check if we can positively or negatively determine that the process is * running under a login on the physical machine console. * * Havne't found a good way to figure this out for graphical sessions, so this * is mostly pointless. But let us try do what we can do. * * @returns VMMDEV_REQUESTOR_CON_XXX. */ static uint32_t vgdrvLinuxRequestorOnConsole(void) { uint32_t fRet = VMMDEV_REQUESTOR_CON_DONT_KNOW; #if RTLNX_VER_MIN(2,6,28) /* First with tty_kref_put(). */ /* * Check for tty0..63, ASSUMING that these are only used for the physical console. */ struct tty_struct *pTty = get_current_tty(); if (pTty) { # if RTLNX_VER_MIN(4,2,0) const char *pszName = tty_name(pTty); # else char szBuf[64]; const char *pszName = tty_name(pTty, szBuf); # endif if ( pszName && pszName[0] == 't' && pszName[1] == 't' && pszName[2] == 'y' && RT_C_IS_DIGIT(pszName[3]) && ( pszName[4] == '\0' || ( RT_C_IS_DIGIT(pszName[4]) && pszName[5] == '\0' && (pszName[3] - '0') * 10 + (pszName[4] - '0') <= 63)) ) fRet = VMMDEV_REQUESTOR_CON_YES; tty_kref_put(pTty); } #endif return fRet; } /** * Device open. Called on open /dev/vboxdrv * * @param pInode Pointer to inode info structure. * @param pFilp Associated file pointer. */ static int vgdrvLinuxOpen(struct inode *pInode, struct file *pFilp) { int rc; PVBOXGUESTSESSION pSession; uint32_t fRequestor; Log((DEVICE_NAME ": pFilp=%p pid=%d/%d %s\n", pFilp, RTProcSelf(), current->pid, current->comm)); /* * Figure out the requestor flags. * ASSUMES that the gid of /dev/vboxuser is what we should consider the special vbox group. */ fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN; if (vgdrvLinuxGetUid() == 0) fRequestor |= VMMDEV_REQUESTOR_USR_ROOT; else fRequestor |= VMMDEV_REQUESTOR_USR_USER; if (MINOR(pInode->i_rdev) == g_MiscDeviceUser.minor) { fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE; if (!vgdrvLinuxIsGroupZero(pInode->i_gid) && vgdrvLinuxIsInGroupEff(pInode->i_gid)) fRequestor |= VMMDEV_REQUESTOR_GRP_VBOX; } fRequestor |= vgdrvLinuxRequestorOnConsole(); /* * Call common code to create the user session. Associate it with * the file so we can access it in the other methods. */ rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &pSession); if (RT_SUCCESS(rc)) pFilp->private_data = pSession; Log(("vgdrvLinuxOpen: g_DevExt=%p pSession=%p rc=%d/%d (pid=%d/%d %s)\n", &g_DevExt, pSession, rc, vgdrvLinuxConvertToNegErrno(rc), RTProcSelf(), current->pid, current->comm)); return vgdrvLinuxConvertToNegErrno(rc); } /** * Close device. * * @param pInode Pointer to inode info structure. * @param pFilp Associated file pointer. */ static int vgdrvLinuxRelease(struct inode *pInode, struct file *pFilp) { Log(("vgdrvLinuxRelease: pFilp=%p pSession=%p pid=%d/%d %s\n", pFilp, pFilp->private_data, RTProcSelf(), current->pid, current->comm)); #if RTLNX_VER_MAX(2,6,28) /* This housekeeping was needed in older kernel versions to ensure that * the file pointer didn't get left on the polling queue. */ vgdrvLinuxFAsync(-1, pFilp, 0); #endif VGDrvCommonCloseSession(&g_DevExt, (PVBOXGUESTSESSION)pFilp->private_data); pFilp->private_data = NULL; return 0; } /** * 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(). */ #if defined(HAVE_UNLOCKED_IOCTL) || defined(DOXYGEN_RUNNING) static long vgdrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg) #else static int vgdrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg) #endif { PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFilp->private_data; int rc; #ifndef HAVE_UNLOCKED_IOCTL unlock_kernel(); #endif #if 0 /* no fast I/O controls defined atm. */ if (RT_LIKELY( ( uCmd == SUP_IOCTL_FAST_DO_RAW_RUN || uCmd == SUP_IOCTL_FAST_DO_HM_RUN || uCmd == SUP_IOCTL_FAST_DO_NOP) && pSession->fUnrestricted == true)) rc = VGDrvCommonIoCtlFast(uCmd, ulArg, &g_DevExt, pSession); else #endif rc = vgdrvLinuxIOCtlSlow(pFilp, uCmd, ulArg, pSession); #ifndef HAVE_UNLOCKED_IOCTL lock_kernel(); #endif return rc; } /** * Device I/O Control entry point, slow variant. * * @param pFilp Associated file pointer. * @param uCmd The function specified to ioctl(). * @param ulArg The argument specified to ioctl(). * @param pSession The session instance. */ static int vgdrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg, PVBOXGUESTSESSION pSession) { int rc; VBGLREQHDR Hdr; PVBGLREQHDR pHdr; uint32_t cbBuf; Log6(("vgdrvLinuxIOCtlSlow: pFilp=%p uCmd=%#x ulArg=%p pid=%d/%d\n", pFilp, uCmd, (void *)ulArg, RTProcSelf(), current->pid)); /* * Read the header. */ if (RT_FAILURE(RTR0MemUserCopyFrom(&Hdr, ulArg, sizeof(Hdr)))) { Log(("vgdrvLinuxIOCtlSlow: copy_from_user(,%#lx,) failed; uCmd=%#x\n", ulArg, uCmd)); return -EFAULT; } if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION)) { Log(("vgdrvLinuxIOCtlSlow: bad header version %#x; uCmd=%#x\n", Hdr.uVersion, uCmd)); return -EINVAL; } /* * Buffer the request. * Note! The header is revalidated by the common code. */ cbBuf = RT_MAX(Hdr.cbIn, Hdr.cbOut); if (RT_UNLIKELY(cbBuf > _1M*16)) { Log(("vgdrvLinuxIOCtlSlow: too big cbBuf=%#x; uCmd=%#x\n", cbBuf, uCmd)); return -E2BIG; } if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr) || (cbBuf != _IOC_SIZE(uCmd) && _IOC_SIZE(uCmd) != 0))) { Log(("vgdrvLinuxIOCtlSlow: bad ioctl cbBuf=%#x _IOC_SIZE=%#x; uCmd=%#x\n", cbBuf, _IOC_SIZE(uCmd), uCmd)); return -EINVAL; } pHdr = RTMemAlloc(cbBuf); if (RT_UNLIKELY(!pHdr)) { LogRel(("vgdrvLinuxIOCtlSlow: failed to allocate buffer of %d bytes for uCmd=%#x\n", cbBuf, uCmd)); return -ENOMEM; } if (RT_FAILURE(RTR0MemUserCopyFrom(pHdr, ulArg, Hdr.cbIn))) { Log(("vgdrvLinuxIOCtlSlow: copy_from_user(,%#lx, %#x) failed; uCmd=%#x\n", ulArg, Hdr.cbIn, uCmd)); RTMemFree(pHdr); return -EFAULT; } if (Hdr.cbIn < cbBuf) RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbBuf - Hdr.cbIn); /* * Process the IOCtl. */ rc = VGDrvCommonIoCtl(uCmd, &g_DevExt, pSession, pHdr, cbBuf); /* * Copy ioctl data and output buffer back to user space. */ if (RT_SUCCESS(rc)) { uint32_t cbOut = pHdr->cbOut; if (RT_UNLIKELY(cbOut > cbBuf)) { LogRel(("vgdrvLinuxIOCtlSlow: too much output! %#x > %#x; uCmd=%#x!\n", cbOut, cbBuf, uCmd)); cbOut = cbBuf; } if (RT_FAILURE(RTR0MemUserCopyTo(ulArg, pHdr, cbOut))) { /* this is really bad! */ LogRel(("vgdrvLinuxIOCtlSlow: copy_to_user(%#lx,,%#x); uCmd=%#x!\n", ulArg, cbOut, uCmd)); rc = -EFAULT; } } else { Log(("vgdrvLinuxIOCtlSlow: pFilp=%p uCmd=%#x ulArg=%p failed, rc=%d\n", pFilp, uCmd, (void *)ulArg, rc)); rc = -EINVAL; } RTMemFree(pHdr); Log6(("vgdrvLinuxIOCtlSlow: returns %d (pid=%d/%d)\n", rc, RTProcSelf(), current->pid)); return rc; } /** * @note This code is duplicated on other platforms with variations, so please * keep them all up to date when making changes! */ int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq) { /* * Simple request validation (common code does the rest). */ int rc; if ( RT_VALID_PTR(pReqHdr) && cbReq >= sizeof(*pReqHdr)) { /* * All requests except the connect one requires a valid session. */ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession; if (pSession) { if ( RT_VALID_PTR(pSession) && pSession->pDevExt == &g_DevExt) rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); else rc = VERR_INVALID_HANDLE; } else if (uReq == VBGL_IOCTL_IDC_CONNECT) { rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession); if (RT_SUCCESS(rc)) { rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); if (RT_FAILURE(rc)) VGDrvCommonCloseSession(&g_DevExt, pSession); } } else rc = VERR_INVALID_HANDLE; } else rc = VERR_INVALID_POINTER; return rc; } EXPORT_SYMBOL(VBoxGuestIDC); /** * Asynchronous notification activation method. * * @returns 0 on success, negative errno on failure. * * @param fd The file descriptor. * @param pFile The file structure. * @param fOn On/off indicator. */ static int vgdrvLinuxFAsync(int fd, struct file *pFile, int fOn) { return fasync_helper(fd, pFile, fOn, &g_pFAsyncQueue); } /** * Poll function. * * This returns ready to read if the mouse pointer mode or the pointer position * has changed since last call to read. * * @returns 0 if no changes, POLLIN | POLLRDNORM if there are unseen changes. * * @param pFile The file structure. * @param pPt The poll table. * * @remarks This is probably not really used, X11 is said to use the fasync * interface instead. */ static unsigned int vgdrvLinuxPoll(struct file *pFile, poll_table *pPt) { PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFile->private_data; uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); unsigned int fMask = pSession->u32MousePosChangedSeq != u32CurSeq ? POLLIN | POLLRDNORM : 0; poll_wait(pFile, &g_PollEventQueue, pPt); return fMask; } /** * Read to go with our poll/fasync response. * * @returns 1 or -EINVAL. * * @param pFile The file structure. * @param pbBuf The buffer to read into. * @param cbRead The max number of bytes to read. * @param poff The current file position. * * @remarks This is probably not really used as X11 lets the driver do its own * event reading. The poll condition is therefore also cleared when we * see VMMDevReq_GetMouseStatus in vgdrvIoCtl_VMMRequest. */ static ssize_t vgdrvLinuxRead(struct file *pFile, char *pbBuf, size_t cbRead, loff_t *poff) { PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFile->private_data; uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); if (*poff != 0) return -EINVAL; /* * Fake a single byte read if we're not up to date with the current mouse position. */ if ( pSession->u32MousePosChangedSeq != u32CurSeq && cbRead > 0) { pSession->u32MousePosChangedSeq = u32CurSeq; pbBuf[0] = 0; return 1; } return 0; } #ifdef VBOXGUEST_WITH_INPUT_DRIVER /** * Get host mouse state. * * @returns IPRT status code. * @param pfMouseFeatures Where to store host mouse capabilities. * @param pX Where to store X axis coordinate. * @param pY Where to store Y axis coordinate. * @param pDz Where to store vertical wheel movement offset (only set if in case of VMMDevReq_GetMouseStatusEx request). * @param pDw Where to store horizontal wheel movement offset (only set if in case of VMMDevReq_GetMouseStatusEx request). * @param pfButtons Where to store mouse buttons state (only set if in case of VMMDevReq_GetMouseStatusEx request). */ static int vgdrvLinuxGetHostMouseState(uint32_t *pfMouseFeatures, int32_t *pX, int32_t *pY, int32_t *pDz, int32_t *pDw, uint32_t *pfButtons) { int rc = VERR_INVALID_PARAMETER; Assert(pfMouseFeatures); Assert(pX); Assert(pY); Assert(pDz); Assert(pDw); Assert(pfButtons); /* Initialize legacy request data. */ g_pMouseStatusReqEx->Core.mouseFeatures = 0; g_pMouseStatusReqEx->Core.pointerXPos = 0; g_pMouseStatusReqEx->Core.pointerYPos = 0; /* Initialize extended request data if VMMDevReq_GetMouseStatusEx is used. */ if (vgdrvLinuxUsesMouseStatusEx()) { g_pMouseStatusReqEx->dz = 0; g_pMouseStatusReqEx->dw = 0; g_pMouseStatusReqEx->fButtons = 0; } /* Get host mouse state - either lagacy or extended version. */ rc = VbglR0GRPerform(&g_pMouseStatusReqEx->Core.header); if (RT_SUCCESS(rc)) { *pfMouseFeatures = g_pMouseStatusReqEx->Core.mouseFeatures; *pX = g_pMouseStatusReqEx->Core.pointerXPos; *pY = g_pMouseStatusReqEx->Core.pointerYPos; /* Get extended mouse state data in case of VMMDevReq_GetMouseStatusEx. */ if (vgdrvLinuxUsesMouseStatusEx()) { *pDz = g_pMouseStatusReqEx->dz; *pDw = g_pMouseStatusReqEx->dw; *pfButtons = g_pMouseStatusReqEx->fButtons; } } return rc; } #endif /* VBOXGUEST_WITH_INPUT_DRIVER */ void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) { #ifdef VBOXGUEST_WITH_INPUT_DRIVER int rc; uint32_t fMouseFeatures = 0; int32_t x = 0; int32_t y = 0; int32_t dz = 0; int32_t dw = 0; uint32_t fButtons = 0; #endif NOREF(pDevExt); /* * Wake up everyone that's in a poll() and post anyone that has * subscribed to async notifications. */ Log3(("VGDrvNativeISRMousePollEvent: wake_up_all\n")); wake_up_all(&g_PollEventQueue); Log3(("VGDrvNativeISRMousePollEvent: kill_fasync\n")); kill_fasync(&g_pFAsyncQueue, SIGIO, POLL_IN); #ifdef VBOXGUEST_WITH_INPUT_DRIVER rc = vgdrvLinuxGetHostMouseState(&fMouseFeatures, &x, &y, &dz, &dw, &fButtons); if (RT_SUCCESS(rc)) { input_report_abs(g_pInputDevice, ABS_X, x); input_report_abs(g_pInputDevice, ABS_Y, y); if ( vgdrvLinuxUsesMouseStatusEx() && fMouseFeatures & VMMDEV_MOUSE_HOST_USES_FULL_STATE_PROTOCOL && fMouseFeatures & VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL) { /* Vertical and horizontal scroll values come as-is from GUI. * Invert values here as it is done in PS/2 mouse driver, so * scrolling direction will be exectly the same. */ input_report_rel(g_pInputDevice, REL_WHEEL, -dz); input_report_rel(g_pInputDevice, REL_HWHEEL, -dw); input_report_key(g_pInputDevice, BTN_LEFT, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_LEFT)); input_report_key(g_pInputDevice, BTN_RIGHT, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_RIGHT)); input_report_key(g_pInputDevice, BTN_MIDDLE, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_MIDDLE)); input_report_key(g_pInputDevice, BTN_SIDE, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_X1)); input_report_key(g_pInputDevice, BTN_EXTRA, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_X2)); } # ifdef EV_SYN input_sync(g_pInputDevice); # endif } #endif Log3(("VGDrvNativeISRMousePollEvent: done\n")); } bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) { RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue); return false; } #if RTLNX_VER_MIN(2,6,0) /** log and dbg_log parameter setter. */ static int vgdrvLinuxParamLogGrpSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam) { if (g_fLoggerCreated) { PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); if (pLogger) RTLogGroupSettings(pLogger, pszValue); } else if (pParam->name[0] != 'd') strlcpy(&g_szLogGrp[0], pszValue, sizeof(g_szLogGrp)); return 0; } /** log and dbg_log parameter getter. */ static int vgdrvLinuxParamLogGrpGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam) { PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); *pszBuf = '\0'; if (pLogger) RTLogQueryGroupSettings(pLogger, pszBuf, _4K); return strlen(pszBuf); } /** log and dbg_log_flags parameter setter. */ static int vgdrvLinuxParamLogFlagsSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam) { if (g_fLoggerCreated) { PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); if (pLogger) RTLogFlags(pLogger, pszValue); } else if (pParam->name[0] != 'd') strlcpy(&g_szLogFlags[0], pszValue, sizeof(g_szLogFlags)); return 0; } /** log and dbg_log_flags parameter getter. */ static int vgdrvLinuxParamLogFlagsGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam) { PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); *pszBuf = '\0'; if (pLogger) RTLogQueryFlags(pLogger, pszBuf, _4K); return strlen(pszBuf); } /** log and dbg_log_dest parameter setter. */ static int vgdrvLinuxParamLogDstSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam) { if (g_fLoggerCreated) { PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); if (pLogger) RTLogDestinations(pLogger, pszValue); } else if (pParam->name[0] != 'd') strlcpy(&g_szLogDst[0], pszValue, sizeof(g_szLogDst)); return 0; } /** log and dbg_log_dest parameter getter. */ static int vgdrvLinuxParamLogDstGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam) { PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); *pszBuf = '\0'; if (pLogger) RTLogQueryDestinations(pLogger, pszBuf, _4K); return strlen(pszBuf); } /** r3_log_to_host parameter setter. */ static int vgdrvLinuxParamR3LogToHostSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam) { g_DevExt.fLoggingEnabled = VBDrvCommonIsOptionValueTrue(pszValue); return 0; } /** r3_log_to_host parameter getter. */ static int vgdrvLinuxParamR3LogToHostGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam) { strcpy(pszBuf, g_DevExt.fLoggingEnabled ? "enabled" : "disabled"); return strlen(pszBuf); } /* * Define module parameters. */ module_param_call(log, vgdrvLinuxParamLogGrpSet, vgdrvLinuxParamLogGrpGet, NULL, 0664); module_param_call(log_flags, vgdrvLinuxParamLogFlagsSet, vgdrvLinuxParamLogFlagsGet, NULL, 0664); module_param_call(log_dest, vgdrvLinuxParamLogDstSet, vgdrvLinuxParamLogDstGet, NULL, 0664); # ifdef LOG_ENABLED module_param_call(dbg_log, vgdrvLinuxParamLogGrpSet, vgdrvLinuxParamLogGrpGet, NULL, 0664); module_param_call(dbg_log_flags, vgdrvLinuxParamLogFlagsSet, vgdrvLinuxParamLogFlagsGet, NULL, 0664); module_param_call(dbg_log_dest, vgdrvLinuxParamLogDstSet, vgdrvLinuxParamLogDstGet, NULL, 0664); # endif module_param_call(r3_log_to_host, vgdrvLinuxParamR3LogToHostSet, vgdrvLinuxParamR3LogToHostGet, NULL, 0664); #endif /* 2.6.0 and later */ module_init(vgdrvLinuxModInit); module_exit(vgdrvLinuxModExit); MODULE_AUTHOR(VBOX_VENDOR); MODULE_DESCRIPTION(VBOX_PRODUCT " Guest Additions for Linux Module"); MODULE_LICENSE("GPL"); #ifdef MODULE_VERSION MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV)); #endif