/** * @file kernel/arch/x86_64/vbox.c * @brief VirtualBox Guest Additions driver * * Implements the following features: * - Absolute mouse cursor positioning * - "Hardware" cursor sprites * - Automatic display modesetting * - "Seamless" mode rectangle device * - Log device * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2016-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VBOX_VENDOR_ID 0x80EE #define VBOX_DEVICE_ID 0xCAFE static void vbox_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) { if (v == VBOX_VENDOR_ID && d == VBOX_DEVICE_ID) { *((uint32_t *)extra) = device; } } #define VMM_GetMouseState 1 #define VMM_SetMouseState 2 #define VMM_SetPointerShape 3 #define VMM_AcknowledgeEvents 41 #define VMM_ReportGuestInfo 50 #define VMM_GetDisplayChangeRequest 51 #define VMM_ReportGuestCapabilities 55 #define VMM_VideoSetVisibleRegion 72 #define VMMCAP_SeamlessMode (1 << 0) #define VMMCAP_HostWindows (1 << 1) #define VMMCAP_Graphics (1 << 2) #define VMMDEV_VERSION 0x00010003 #define VBOX_REQUEST_HEADER_VERSION 0x10001 struct vbox_header { uint32_t size; uint32_t version; uint32_t requestType; int32_t rc; uint32_t reserved1; uint32_t reserved2; }; struct vbox_guest_info { struct vbox_header header; uint32_t version; uint32_t ostype; }; struct vbox_guest_caps { struct vbox_header header; uint32_t caps; }; struct vbox_ack_events { struct vbox_header header; uint32_t events; }; struct vbox_display_change { struct vbox_header header; uint32_t xres; uint32_t yres; uint32_t bpp; uint32_t eventack; }; struct vbox_mouse { struct vbox_header header; uint32_t features; int32_t x; int32_t y; }; struct vbox_rtrect { int32_t xLeft; int32_t yTop; int32_t xRight; int32_t yBottom; }; struct vbox_visibleregion { struct vbox_header header; uint32_t count; struct vbox_rtrect rect[1]; }; struct vbox_pointershape { struct vbox_header header; uint32_t flags; uint32_t xHot; uint32_t yHot; uint32_t width; uint32_t height; unsigned char data[]; }; #define EARLY_LOG_DEVICE 0x504 static size_t _vbox_write(size_t size, uint8_t * buffer) { for (unsigned int i = 0; i < size; ++i) { outportb(EARLY_LOG_DEVICE, buffer[i]); } return size; } void vbox_set_log(void) { printf_output = &_vbox_write; printf("Hello world, using VBox machine log for kernel output\n"); } static uint32_t vbox_device = 0; static uint32_t vbox_port = 0x0; static int vbox_irq = 0; static struct vbox_ack_events * vbox_irq_ack; static uint32_t vbox_phys_ack; static struct vbox_display_change * vbox_disp; static uint32_t vbox_phys_disp; static struct vbox_mouse * vbox_m; static uint32_t vbox_phys_mouse; static struct vbox_mouse * vbox_mg; static uint32_t vbox_phys_mouse_get; static struct vbox_visibleregion * vbox_visibleregion; static uint32_t vbox_phys_visibleregion; static struct vbox_pointershape * vbox_pointershape = NULL; static uint32_t vbox_phys_pointershape; static volatile uint32_t * vbox_vmmdev = 0; static fs_node_t * mouse_pipe; static fs_node_t * rect_pipe; static fs_node_t * pointer_pipe; static int mouse_state; #define PACKETS_IN_PIPE 1024 #define DISCARD_POINT 32 static int vbox_irq_handler(struct regs *r) { if (!vbox_vmmdev[2]) return 0; vbox_irq_ack->events = vbox_vmmdev[2]; outportl(vbox_port, vbox_phys_ack); irq_ack(vbox_irq); outportl(vbox_port, vbox_phys_mouse_get); unsigned int x, y; if (lfb_vid_memory && lfb_resolution_x && lfb_resolution_y && vbox_mg->x && vbox_mg->y) { x = ((unsigned int)vbox_mg->x * lfb_resolution_x) / 0xFFFF; y = ((unsigned int)vbox_mg->y * lfb_resolution_y) / 0xFFFF; } else { x = vbox_mg->x; y = vbox_mg->y; } mouse_device_packet_t packet; packet.magic = MOUSE_MAGIC; packet.x_difference = x; packet.y_difference = y; packet.buttons = 0; mouse_device_packet_t bitbucket; while (pipe_size(mouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) { read_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket); } write_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&packet); outportl(vbox_port, vbox_phys_disp); if (lfb_resolution_x && vbox_disp->xres && (vbox_disp->xres != lfb_resolution_x || vbox_disp->yres != lfb_resolution_y)) { lfb_set_resolution(vbox_disp->xres, vbox_disp->yres); } return 1; } #define VBOX_MOUSE_ON (1 << 0) | (1 << 4) #define VBOX_MOUSE_OFF (0) static void mouse_on_off(unsigned int status) { mouse_state = status; vbox_m->header.size = sizeof(struct vbox_mouse); vbox_m->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_m->header.requestType = VMM_SetMouseState; vbox_m->header.rc = 0; vbox_m->header.reserved1 = 0; vbox_m->header.reserved2 = 0; vbox_m->features = status; vbox_m->x = 0; vbox_m->y = 0; outportl(vbox_port, vbox_phys_mouse); } static int ioctl_mouse(fs_node_t * node, unsigned long request, void * argp) { if (request == 1) { /* Disable */ mouse_on_off(VBOX_MOUSE_OFF); return 0; } if (request == 2) { /* Enable */ mouse_on_off(VBOX_MOUSE_ON); return 0; } if (request == 3) { return mouse_state == (VBOX_MOUSE_ON); } return -1; } static ssize_t write_pointer(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { if (!mouse_state) { return -1; } memcpy(&vbox_pointershape->data[288], buffer, 48*48*4); outportl(vbox_port, vbox_phys_pointershape); return size; } static ssize_t write_rectpipe(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { (void)node; (void)offset; /* TODO should check size */ /* This is kinda special and always assumes everything was written at once. */ uint32_t count = ((uint32_t *)buffer)[0]; vbox_visibleregion->count = count; if (count > 254) count = 254; /* enforce maximum */ buffer += sizeof(uint32_t); for (unsigned int i = 0; i < count; ++i) { memcpy(&vbox_visibleregion->rect[i], buffer, sizeof(struct vbox_rtrect)); buffer += sizeof(struct vbox_rtrect); } vbox_visibleregion->header.size = sizeof(struct vbox_header) + sizeof(uint32_t) + sizeof(int32_t) * count * 4; outportl(vbox_port, vbox_phys_visibleregion); return size; } static void * kvmalloc_p(size_t size, uint32_t * outphys) { uintptr_t index = mmu_allocate_n_frames(size / 0x1000) << 12; *outphys = index; return mmu_map_from_physical(index); } static int vbox_install(int argc, char * argv[]) { if (args_present("novbox")) return -ENODEV; pci_scan(vbox_scan_pci, -1, &vbox_device); if (!vbox_device) return -ENODEV; if (!args_present("novboxdebug")) { vbox_set_log(); } uintptr_t t = pci_read_field(vbox_device, PCI_BAR0, 4); if (t > 0) { vbox_port = (t & 0xFFFFFFF0); } uint16_t c = pci_read_field(vbox_device, PCI_COMMAND, 2); //fprintf(&vb, "Command register: 0x%4x\n", c); if (!!(c & (1 << 10))) { //fprintf(&vb, "Interrupts are disabled\n"); } mouse_pipe = make_pipe(sizeof(mouse_device_packet_t) * PACKETS_IN_PIPE); mouse_pipe->flags = FS_CHARDEVICE; mouse_pipe->ioctl = ioctl_mouse; vfs_mount("/dev/absmouse", mouse_pipe); vbox_irq = pci_get_interrupt(vbox_device); //fprintf(&vb, "irq line is %d\n", vbox_irq); irq_install_handler(vbox_irq, vbox_irq_handler, "vbox"); uint32_t vbox_phys = 0; struct vbox_guest_info * packet = (void*)kvmalloc_p(0x1000, &vbox_phys); packet->header.size = sizeof(struct vbox_guest_info); packet->header.version = VBOX_REQUEST_HEADER_VERSION; packet->header.requestType = VMM_ReportGuestInfo; packet->header.rc = 0; packet->header.reserved1 = 0; packet->header.reserved2 = 0; packet->version = VMMDEV_VERSION; packet->ostype = 0x00100; /* Unknown, x86-64 */ outportl(vbox_port, vbox_phys); struct vbox_guest_caps * caps = (void*)kvmalloc_p(0x1000, &vbox_phys); caps->header.size = sizeof(struct vbox_guest_caps); caps->header.version = VBOX_REQUEST_HEADER_VERSION; caps->header.requestType = VMM_ReportGuestCapabilities; caps->header.rc = 0; caps->header.reserved1 = 0; caps->header.reserved2 = 0; caps->caps = VMMCAP_Graphics | (args_present("novboxseamless") ? 0 : VMMCAP_SeamlessMode); outportl(vbox_port, vbox_phys); vbox_irq_ack = (void*)kvmalloc_p(0x1000, &vbox_phys_ack); vbox_irq_ack->header.size = sizeof(struct vbox_ack_events); vbox_irq_ack->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_irq_ack->header.requestType = VMM_AcknowledgeEvents; vbox_irq_ack->header.rc = 0; vbox_irq_ack->header.reserved1 = 0; vbox_irq_ack->header.reserved2 = 0; vbox_irq_ack->events = 0; vbox_disp = (void*)kvmalloc_p(0x1000, &vbox_phys_disp); vbox_disp->header.size = sizeof(struct vbox_display_change); vbox_disp->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_disp->header.requestType = VMM_GetDisplayChangeRequest; vbox_disp->header.rc = 0; vbox_disp->header.reserved1 = 0; vbox_disp->header.reserved2 = 0; vbox_disp->xres = 0; vbox_disp->yres = 0; vbox_disp->bpp = 0; vbox_disp->eventack = 1; vbox_m = (void*)kvmalloc_p(0x1000, &vbox_phys_mouse); mouse_on_off(VBOX_MOUSE_ON); /* For use with later receives */ vbox_mg = (void*)kvmalloc_p(0x1000, &vbox_phys_mouse_get); vbox_mg->header.size = sizeof(struct vbox_mouse); vbox_mg->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_mg->header.requestType = VMM_GetMouseState; vbox_mg->header.rc = 0; vbox_mg->header.reserved1 = 0; vbox_mg->header.reserved2 = 0; if (!args_present("novboxpointer")) { vbox_pointershape = (void*)kvmalloc_p(0x4000, &vbox_phys_pointershape); if (vbox_pointershape) { //fprintf(&vb, "Got a valid set of pages to load up a cursor.\n"); vbox_pointershape->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_pointershape->header.requestType = VMM_SetPointerShape; vbox_pointershape->header.rc = 0; vbox_pointershape->header.reserved1 = 0; vbox_pointershape->header.reserved2 = 0; vbox_pointershape->flags = (1 << 0) | (1 << 1) | (1 << 2); vbox_pointershape->xHot = 26; vbox_pointershape->yHot = 26; vbox_pointershape->width = 48; vbox_pointershape->height = 48; unsigned int mask_bytes = ((vbox_pointershape->width + 7) / 8) * vbox_pointershape->height; for (uint32_t i = 0; i < mask_bytes; ++i) { vbox_pointershape->data[i] = 0x00; } while (mask_bytes & 3) { mask_bytes++; } int base = mask_bytes; //fprintf(&vb, "mask_bytes = %d\n", mask_bytes); vbox_pointershape->header.size = sizeof(struct vbox_pointershape) + (48*48*4)+mask_bytes; /* update later */ for (int i = 0; i < 48 * 48; ++i) { vbox_pointershape->data[base+i*4] = 0x00; /* blue */ vbox_pointershape->data[base+i*4+1] = 0x00; /* red */ vbox_pointershape->data[base+i*4+2] = 0x00; /* green */ vbox_pointershape->data[base+i*4+3] = 0x00; /* alpha */ } outportl(vbox_port, vbox_phys_pointershape); if (vbox_pointershape->header.rc < 0) { //fprintf(&vb, "Bad response code: -%d\n", -vbox_pointershape->header.rc); } else { /* Success, let's install the device file */ //fprintf(&vb, "Successfully initialized cursor, going to allow compositor to set it.\n"); pointer_pipe = malloc(sizeof(fs_node_t)); memset(pointer_pipe, 0, sizeof(fs_node_t)); pointer_pipe->mask = 0666; pointer_pipe->flags = FS_CHARDEVICE; pointer_pipe->write = write_pointer; vfs_mount("/dev/vboxpointer", pointer_pipe); } } } if (!args_present("novboxseamless")) { vbox_visibleregion = (void*)kvmalloc_p(0x1000, &vbox_phys_visibleregion); vbox_visibleregion->header.size = sizeof(struct vbox_header) + sizeof(uint32_t) + sizeof(int32_t) * 4; /* TODO + more for additional rects? */ vbox_visibleregion->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_visibleregion->header.requestType = VMM_VideoSetVisibleRegion; vbox_visibleregion->header.rc = 0; vbox_visibleregion->header.reserved1 = 0; vbox_visibleregion->header.reserved2 = 0; vbox_visibleregion->count = 1; vbox_visibleregion->rect[0].xLeft = 0; vbox_visibleregion->rect[0].yTop = 0; vbox_visibleregion->rect[0].xRight = 1440; vbox_visibleregion->rect[0].yBottom = 900; outportl(vbox_port, vbox_phys_visibleregion); rect_pipe = malloc(sizeof(fs_node_t)); memset(rect_pipe, 0, sizeof(fs_node_t)); rect_pipe->mask = 0666; rect_pipe->flags = FS_CHARDEVICE; rect_pipe->write = write_rectpipe; vfs_mount("/dev/vboxrects", rect_pipe); } /* device memory region mapping? */ { uintptr_t t = pci_read_field(vbox_device, PCI_BAR1, 4); //fprintf(&vb, "mapping vmm_dev = 0x%x\n", t); if (t > 0) { vbox_vmmdev = mmu_map_from_physical(t & 0xFFFFFFF0); printf("Setting vbox mem device at %p\n", (void*)vbox_vmmdev); } } vbox_vmmdev[3] = 0xFFFFFFFF; /* Enable all for now */ return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "vbox", .init = vbox_install, .fini = fini, };