From 0b733c9c80b3bb20f4c9dd697061b5212bd8d43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Tue, 10 Oct 2023 18:11:18 +0200 Subject: [PATCH] virtio_gpu: initial driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sample for qemu: -device virtio-vga,edid=on,xres=1024,yres=768 -display sdl display mode can be set in the Screen preferences. Change-Id: If1d6aeecb208ce7c62c42eea1a95c71237c4375a Reviewed-on: https://review.haiku-os.org/c/haiku/+/7038 Tested-by: Commit checker robot Reviewed-by: Jérôme Duval --- headers/private/graphics/virtio/virtio_info.h | 43 + src/add-ons/accelerants/Jamfile | 1 + src/add-ons/accelerants/virtio/Jamfile | 13 + src/add-ons/accelerants/virtio/accelerant.cpp | 202 ++++ src/add-ons/accelerants/virtio/accelerant.h | 30 + .../accelerants/virtio/accelerant_protos.h | 42 + src/add-ons/accelerants/virtio/hooks.cpp | 55 ++ src/add-ons/accelerants/virtio/mode.cpp | 184 ++++ src/add-ons/kernel/drivers/graphics/Jamfile | 2 + .../kernel/drivers/graphics/virtio/Jamfile | 10 + .../kernel/drivers/graphics/virtio/viogpu.h | 450 +++++++++ .../drivers/graphics/virtio/virtio_gpu.cpp | 896 ++++++++++++++++++ 12 files changed, 1928 insertions(+) create mode 100644 headers/private/graphics/virtio/virtio_info.h create mode 100644 src/add-ons/accelerants/virtio/Jamfile create mode 100644 src/add-ons/accelerants/virtio/accelerant.cpp create mode 100644 src/add-ons/accelerants/virtio/accelerant.h create mode 100644 src/add-ons/accelerants/virtio/accelerant_protos.h create mode 100644 src/add-ons/accelerants/virtio/hooks.cpp create mode 100644 src/add-ons/accelerants/virtio/mode.cpp create mode 100644 src/add-ons/kernel/drivers/graphics/virtio/Jamfile create mode 100644 src/add-ons/kernel/drivers/graphics/virtio/viogpu.h create mode 100644 src/add-ons/kernel/drivers/graphics/virtio/virtio_gpu.cpp diff --git a/headers/private/graphics/virtio/virtio_info.h b/headers/private/graphics/virtio/virtio_info.h new file mode 100644 index 0000000000..43a53b235b --- /dev/null +++ b/headers/private/graphics/virtio/virtio_info.h @@ -0,0 +1,43 @@ +/* + * Copyright 2005-2009, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ +#ifndef VIRTIO_INFO_H +#define VIRTIO_INFO_H + + +#include +#include + +#include + + +struct virtio_gpu_shared_info { + area_id mode_list_area; // area containing display mode list + uint32 mode_count; + display_mode current_mode; + uint32 bytes_per_row; + + area_id frame_buffer_area; // area of frame buffer + uint8* frame_buffer; + // pointer to frame buffer (visible by all apps!) + + edid1_raw edid_raw; + bool has_edid; + uint32 dpms_capabilities; + + char name[32]; + uint32 vram_size; +}; + +//----------------- ioctl() interface ---------------- + +// list ioctls +enum { + VIRTIO_GPU_GET_PRIVATE_DATA = B_DEVICE_OP_CODES_END + 1, + VIRTIO_GPU_GET_DEVICE_NAME, + VIRTIO_GPU_SET_DISPLAY_MODE, +}; + + +#endif /* VIRTIO_INFO_H */ diff --git a/src/add-ons/accelerants/Jamfile b/src/add-ons/accelerants/Jamfile index db7e0a1358..9b6a99e650 100644 --- a/src/add-ons/accelerants/Jamfile +++ b/src/add-ons/accelerants/Jamfile @@ -15,3 +15,4 @@ SubInclude HAIKU_TOP src add-ons accelerants radeon_hd ; SubInclude HAIKU_TOP src add-ons accelerants s3 ; SubInclude HAIKU_TOP src add-ons accelerants vesa ; SubInclude HAIKU_TOP src add-ons accelerants via ; +SubInclude HAIKU_TOP src add-ons accelerants virtio ; diff --git a/src/add-ons/accelerants/virtio/Jamfile b/src/add-ons/accelerants/virtio/Jamfile new file mode 100644 index 0000000000..71fe5611cb --- /dev/null +++ b/src/add-ons/accelerants/virtio/Jamfile @@ -0,0 +1,13 @@ +SubDir HAIKU_TOP src add-ons accelerants virtio ; + +UsePrivateHeaders graphics ; +UsePrivateHeaders [ FDirName graphics virtio ] ; +UsePrivateHeaders [ FDirName graphics common ] ; +UsePrivateHeaders shared ; + +Addon virtio_gpu.accelerant : + accelerant.cpp + hooks.cpp + mode.cpp + : libaccelerantscommon.a +; diff --git a/src/add-ons/accelerants/virtio/accelerant.cpp b/src/add-ons/accelerants/virtio/accelerant.cpp new file mode 100644 index 0000000000..24b99ad701 --- /dev/null +++ b/src/add-ons/accelerants/virtio/accelerant.cpp @@ -0,0 +1,202 @@ +/* + * Copyright 2005-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Copyright 2016, Jessica Hamilton, jessica.l.hamilton@gmail.com. + * Distributed under the terms of the MIT License. + */ + + +#include "accelerant_protos.h" +#include "accelerant.h" + +#include +#include +#include +#include +#include + +#include + + +#define TRACE_ACCELERANT +#ifdef TRACE_ACCELERANT +extern "C" void _sPrintf(const char *format, ...); +# define TRACE(x) _sPrintf x +#else +# define TRACE(x) ; +#endif + + +struct accelerant_info *gInfo; + + +// #pragma mark - + + +/*! This is the common accelerant_info initializer. It is called by + both, the first accelerant and all clones. +*/ +static status_t +init_common(int device, bool isClone) +{ + // initialize global accelerant info structure + + gInfo = (accelerant_info *)malloc(sizeof(accelerant_info)); + MemoryDeleter infoDeleter(gInfo); + if (gInfo == NULL) + return B_NO_MEMORY; + + memset(gInfo, 0, sizeof(accelerant_info)); + + gInfo->is_clone = isClone; + gInfo->device = device; + gInfo->current_mode = UINT16_MAX; + + // get basic info from driver + + area_id sharedArea; + if (ioctl(device, VIRTIO_GPU_GET_PRIVATE_DATA, &sharedArea, sizeof(area_id)) != 0) + return B_ERROR; + + AreaDeleter sharedDeleter(clone_area("virtio_gpu shared info", + (void **)&gInfo->shared_info, B_ANY_ADDRESS, + B_READ_AREA | B_WRITE_AREA, sharedArea)); + status_t status = gInfo->shared_info_area = sharedDeleter.Get(); + if (status < B_OK) + return status; + + infoDeleter.Detach(); + sharedDeleter.Detach(); + return B_OK; +} + + +/*! Cleans up everything done by a successful init_common(). */ +static void +uninit_common(void) +{ + delete_area(gInfo->shared_info_area); + gInfo->shared_info_area = -1; + gInfo->shared_info = NULL; + + // close the file handle ONLY if we're the clone + // (this is what Be tells us ;) + if (gInfo->is_clone) + close(gInfo->device); + + free(gInfo); +} + + +// #pragma mark - public accelerant functions + + +/*! Init primary accelerant */ +status_t +virtio_gpu_init_accelerant(int device) +{ + TRACE(("virtio_gpu_init_accelerant()\n")); + + status_t status = init_common(device, false); + if (status != B_OK) + return status; + + status = create_mode_list(); + if (status != B_OK) { + uninit_common(); + return status; + } + + return B_OK; +} + + +ssize_t +virtio_gpu_accelerant_clone_info_size(void) +{ + // clone info is device name, so return its maximum size + return B_PATH_NAME_LENGTH; +} + + +void +virtio_gpu_get_accelerant_clone_info(void *info) +{ + ioctl(gInfo->device, VIRTIO_GPU_GET_DEVICE_NAME, info, B_PATH_NAME_LENGTH); +} + + +status_t +virtio_gpu_clone_accelerant(void *info) +{ + TRACE(("virtio_gpu_clone_accelerant()\n")); + + // create full device name + char path[MAXPATHLEN]; + strcpy(path, "/dev/"); + strcat(path, (const char *)info); + + int fd = open(path, B_READ_WRITE); + if (fd < 0) + return errno; + + status_t status = init_common(fd, true); + if (status != B_OK) + goto err1; + + // get read-only clone of supported display modes + status = gInfo->mode_list_area = clone_area( + "virtio_gpu cloned modes", (void **)&gInfo->mode_list, + B_ANY_ADDRESS, B_READ_AREA, gInfo->shared_info->mode_list_area); + if (status < B_OK) + goto err2; + + return B_OK; + +err2: + uninit_common(); +err1: + close(fd); + return status; +} + + +/*! This function is called for both, the primary accelerant and all of + its clones. +*/ +void +virtio_gpu_uninit_accelerant(void) +{ + TRACE(("virtio_gpu_uninit_accelerant()\n")); + + // delete accelerant instance data + delete_area(gInfo->mode_list_area); + gInfo->mode_list = NULL; + + uninit_common(); +} + + +status_t +virtio_gpu_get_accelerant_device_info(accelerant_device_info *info) +{ + info->version = B_ACCELERANT_VERSION; + strcpy(info->name, "VirtioGpu Driver"); + strcpy(info->chipset, "Virtio"); + // ToDo: provide some more insight here... + strcpy(info->serial_no, "None"); + +#if 0 + info->memory = ??? + info->dac_speed = ??? +#endif + + return B_OK; +} + + +sem_id +virtio_gpu_accelerant_retrace_semaphore() +{ + return -1; +} + diff --git a/src/add-ons/accelerants/virtio/accelerant.h b/src/add-ons/accelerants/virtio/accelerant.h new file mode 100644 index 0000000000..c21b7e81e9 --- /dev/null +++ b/src/add-ons/accelerants/virtio/accelerant.h @@ -0,0 +1,30 @@ +/* + * Copyright 2005-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Copyright 2016, Jessica Hamilton, jessica.l.hamilton@gmail.com. + * Distributed under the terms of the MIT License. + */ +#ifndef VIRTIO_GPU_ACCELERANT_H +#define VIRTIO_GPU_ACCELERANT_H + + +#include "virtio_info.h" + + +typedef struct accelerant_info { + int device; + bool is_clone; + + area_id shared_info_area; + virtio_gpu_shared_info* shared_info; + + area_id mode_list_area; + // cloned list of standard display modes + display_mode *mode_list; + uint16 current_mode; +} accelerant_info; + +extern accelerant_info *gInfo; + +extern status_t create_mode_list(void); + +#endif /* VIRTIO_GPU_ACCELERANT_H */ diff --git a/src/add-ons/accelerants/virtio/accelerant_protos.h b/src/add-ons/accelerants/virtio/accelerant_protos.h new file mode 100644 index 0000000000..a2081eff04 --- /dev/null +++ b/src/add-ons/accelerants/virtio/accelerant_protos.h @@ -0,0 +1,42 @@ +/* + * Copyright 2005-2008, Axel Dörfler, axeld@pinc-software.de. + * Copyright 2016, Jessica Hamilton, jessica.l.hamilton@gmail.com. + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef _ACCELERANT_PROTOS_H +#define _ACCELERANT_PROTOS_H + + +#include +#include "video_overlay.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +// general +status_t virtio_gpu_init_accelerant(int fd); +ssize_t virtio_gpu_accelerant_clone_info_size(void); +void virtio_gpu_get_accelerant_clone_info(void *data); +status_t virtio_gpu_clone_accelerant(void *data); +void virtio_gpu_uninit_accelerant(void); +status_t virtio_gpu_get_accelerant_device_info(accelerant_device_info *adi); +sem_id virtio_gpu_accelerant_retrace_semaphore(void); + +// modes & constraints +uint32 virtio_gpu_accelerant_mode_count(void); +status_t virtio_gpu_get_mode_list(display_mode *dm); +status_t virtio_gpu_get_preferred_mode(display_mode *mode); +status_t virtio_gpu_set_display_mode(display_mode *modeToSet); +status_t virtio_gpu_get_display_mode(display_mode *currentMode); +status_t virtio_gpu_get_edid_info(void *info, size_t size, uint32 *_version); +status_t virtio_gpu_get_frame_buffer_config(frame_buffer_config *config); +status_t virtio_gpu_get_pixel_clock_limits(display_mode *dm, uint32 *low, + uint32 *high); + +#ifdef __cplusplus +} +#endif + +#endif /* _ACCELERANT_PROTOS_H */ diff --git a/src/add-ons/accelerants/virtio/hooks.cpp b/src/add-ons/accelerants/virtio/hooks.cpp new file mode 100644 index 0000000000..946af3b748 --- /dev/null +++ b/src/add-ons/accelerants/virtio/hooks.cpp @@ -0,0 +1,55 @@ +/* + * Copyright 2005-2012, Axel Dörfler, axeld@pinc-software.de. + * Copyright 2016, Jessica Hamilton, jessica.l.hamilton@gmail.com + * All rights reserved. Distributed under the terms of the MIT License. + */ + + +#include "accelerant_protos.h" +#include "accelerant.h" + +#include + + +extern "C" void* +get_accelerant_hook(uint32 feature, void* data) +{ + switch (feature) { + /* general */ + case B_INIT_ACCELERANT: + return (void*)virtio_gpu_init_accelerant; + case B_UNINIT_ACCELERANT: + return (void*)virtio_gpu_uninit_accelerant; + case B_CLONE_ACCELERANT: + return (void*)virtio_gpu_clone_accelerant; + case B_ACCELERANT_CLONE_INFO_SIZE: + return (void*)virtio_gpu_accelerant_clone_info_size; + case B_GET_ACCELERANT_CLONE_INFO: + return (void*)virtio_gpu_get_accelerant_clone_info; + case B_GET_ACCELERANT_DEVICE_INFO: + return (void*)virtio_gpu_get_accelerant_device_info; + case B_ACCELERANT_RETRACE_SEMAPHORE: + return (void*)virtio_gpu_accelerant_retrace_semaphore; + + /* mode configuration */ + case B_ACCELERANT_MODE_COUNT: + return (void*)virtio_gpu_accelerant_mode_count; + case B_GET_MODE_LIST: + return (void*)virtio_gpu_get_mode_list; + case B_GET_PREFERRED_DISPLAY_MODE: + return (void*)virtio_gpu_get_preferred_mode; + case B_SET_DISPLAY_MODE: + return (void*)virtio_gpu_set_display_mode; + case B_GET_DISPLAY_MODE: + return (void*)virtio_gpu_get_display_mode; + case B_GET_EDID_INFO: + return (void*)virtio_gpu_get_edid_info; + case B_GET_FRAME_BUFFER_CONFIG: + return (void*)virtio_gpu_get_frame_buffer_config; + case B_GET_PIXEL_CLOCK_LIMITS: + return (void*)virtio_gpu_get_pixel_clock_limits; + } + + return NULL; +} + diff --git a/src/add-ons/accelerants/virtio/mode.cpp b/src/add-ons/accelerants/virtio/mode.cpp new file mode 100644 index 0000000000..77dae18787 --- /dev/null +++ b/src/add-ons/accelerants/virtio/mode.cpp @@ -0,0 +1,184 @@ +/* + * Copyright 2005-2015, Axel Dörfler, axeld@pinc-software.de. + * Copyright 2016, Jessica Hamilton, jessica.l.hamilton@gmail.com. + * Distributed under the terms of the MIT License. + */ + + +#include +#include + +#include +#include + +#include "accelerant_protos.h" +#include "accelerant.h" + + +#define TRACE_MODE +#ifdef TRACE_MODE +extern "C" void _sPrintf(const char* format, ...); +# define TRACE(x) _sPrintf x +#else +# define TRACE(x) ; +#endif + + +bool +operator==(const display_mode &lhs, const display_mode &rhs) +{ + return lhs.space == rhs.space + && lhs.virtual_width == rhs.virtual_width + && lhs.virtual_height == rhs.virtual_height + && lhs.h_display_start == rhs.h_display_start + && lhs.v_display_start == rhs.v_display_start; +} + + +/*! Checks if the specified \a mode can be set using VESA. */ +static bool +is_mode_supported(display_mode* mode) +{ + return (mode != NULL) && (*mode == gInfo->shared_info->current_mode); +} + + +/*! Creates the initial mode list of the primary accelerant. + It's called from vesa_init_accelerant(). +*/ +status_t +create_mode_list(void) +{ + const color_space colorspace[] = { + (color_space)gInfo->shared_info->current_mode.space + }; + + if (!gInfo->shared_info->has_edid) { + display_mode mode = gInfo->shared_info->current_mode; + + compute_display_timing(mode.virtual_width, mode.virtual_height, 60, false, + &mode.timing); + fill_display_mode(mode.virtual_width, mode.virtual_height, &mode); + + gInfo->mode_list_area = create_display_modes("virtio_gpu modes", + NULL, &mode, 1, colorspace, 1, is_mode_supported, &gInfo->mode_list, + &gInfo->shared_info->mode_count); + } else { + edid1_info edidInfo; + edid_decode(&edidInfo, &gInfo->shared_info->edid_raw); + gInfo->mode_list_area = create_display_modes("virtio_gpu modes", + &edidInfo, NULL, 0, colorspace, 1, NULL, &gInfo->mode_list, + &gInfo->shared_info->mode_count); + } + if (gInfo->mode_list_area < 0) + return gInfo->mode_list_area; + + gInfo->shared_info->mode_list_area = gInfo->mode_list_area; + return B_OK; +} + + +// #pragma mark - + + +uint32 +virtio_gpu_accelerant_mode_count(void) +{ + TRACE(("virtio_gpu_accelerant_mode_count() = %d\n", + gInfo->shared_info->mode_count)); + return gInfo->shared_info->mode_count; +} + + +status_t +virtio_gpu_get_mode_list(display_mode* modeList) +{ + TRACE(("virtio_gpu_get_mode_info()\n")); + memcpy(modeList, gInfo->mode_list, + gInfo->shared_info->mode_count * sizeof(display_mode)); + return B_OK; +} + + +status_t +virtio_gpu_get_preferred_mode(display_mode* _mode) +{ + TRACE(("virtio_gpu_get_preferred_mode()\n")); + *_mode = gInfo->shared_info->current_mode; + + return B_OK; +} + + +status_t +virtio_gpu_set_display_mode(display_mode* _mode) +{ + TRACE(("virtio_gpu_set_display_mode()\n")); + if (_mode != NULL && *_mode == gInfo->shared_info->current_mode) + return B_OK; + + return ioctl(gInfo->device, VIRTIO_GPU_SET_DISPLAY_MODE, + _mode, sizeof(display_mode)); +} + + +status_t +virtio_gpu_get_display_mode(display_mode* _currentMode) +{ + TRACE(("virtio_gpu_get_display_mode()\n")); + *_currentMode = gInfo->shared_info->current_mode; + return B_OK; +} + + +status_t +virtio_gpu_get_edid_info(void* info, size_t size, uint32* _version) +{ + TRACE(("virtio_gpu_get_edid_info()\n")); + + if (!gInfo->shared_info->has_edid) + return B_ERROR; + if (size < sizeof(struct edid1_info)) + return B_BUFFER_OVERFLOW; + + edid_decode((edid1_info*)info, &gInfo->shared_info->edid_raw); + *_version = EDID_VERSION_1; + edid_dump((edid1_info*)info); + return B_OK; +} + + +status_t +virtio_gpu_get_frame_buffer_config(frame_buffer_config* config) +{ + TRACE(("virtio_gpu_get_frame_buffer_config()\n")); + + config->frame_buffer = gInfo->shared_info->frame_buffer; + TRACE(("virtio_gpu_get_frame_buffer_config() = %" B_PRIxADDR "\n", + config->frame_buffer)); + //config->frame_buffer_dma = gInfo->shared_info->physical_frame_buffer; + config->bytes_per_row = gInfo->shared_info->bytes_per_row; + TRACE(("virtio_gpu_get_frame_buffer_config() %p\n", config->frame_buffer)); + return B_OK; +} + + +status_t +virtio_gpu_get_pixel_clock_limits(display_mode* mode, uint32* _low, uint32* _high) +{ + TRACE(("virtio_gpu_get_pixel_clock_limits()\n")); + + // TODO: do some real stuff here (taken from radeon driver) + uint32 totalPixel = (uint32)mode->timing.h_total + * (uint32)mode->timing.v_total; + uint32 clockLimit = 2000000; + + // lower limit of about 48Hz vertical refresh + *_low = totalPixel * 48L / 1000L; + if (*_low > clockLimit) + return B_ERROR; + + *_high = clockLimit; + return B_OK; +} + diff --git a/src/add-ons/kernel/drivers/graphics/Jamfile b/src/add-ons/kernel/drivers/graphics/Jamfile index 07d24ace4d..cfaa4e4878 100644 --- a/src/add-ons/kernel/drivers/graphics/Jamfile +++ b/src/add-ons/kernel/drivers/graphics/Jamfile @@ -16,3 +16,5 @@ SubInclude HAIKU_TOP src add-ons kernel drivers graphics s3 ; SubInclude HAIKU_TOP src add-ons kernel drivers graphics skeleton ; SubInclude HAIKU_TOP src add-ons kernel drivers graphics vesa ; SubInclude HAIKU_TOP src add-ons kernel drivers graphics via ; +SubInclude HAIKU_TOP src add-ons kernel drivers graphics virtio ; + diff --git a/src/add-ons/kernel/drivers/graphics/virtio/Jamfile b/src/add-ons/kernel/drivers/graphics/virtio/Jamfile new file mode 100644 index 0000000000..46fc42c470 --- /dev/null +++ b/src/add-ons/kernel/drivers/graphics/virtio/Jamfile @@ -0,0 +1,10 @@ +SubDir HAIKU_TOP src add-ons kernel drivers graphics virtio ; + +UsePrivateKernelHeaders ; +UsePrivateHeaders [ FDirName graphics common ] ; +UsePrivateHeaders [ FDirName graphics virtio ] ; +UsePrivateHeaders drivers graphics virtio ; + +KernelAddon virtio_gpu : + virtio_gpu.cpp +; diff --git a/src/add-ons/kernel/drivers/graphics/virtio/viogpu.h b/src/add-ons/kernel/drivers/graphics/virtio/viogpu.h new file mode 100644 index 0000000000..a00d991d73 --- /dev/null +++ b/src/add-ons/kernel/drivers/graphics/virtio/viogpu.h @@ -0,0 +1,450 @@ +/* + * Virtio GPU Device + * + * Copyright Red Hat, Inc. 2013-2014 + * + * Authors: + * Dave Airlie + * Gerd Hoffmann + * + * This header is BSD licensed so anyone can use the definitions + * to implement compatible drivers/servers: + * + * 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 IBM 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 IBM 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. + */ + +#ifndef VIRTIO_GPU_HW_H +#define VIRTIO_GPU_HW_H + +#include + +#define __u8 uint8_t +#define __u32 uint32_t +#define __le16 uint16_t +#define __le32 uint32_t +#define __le64 uint64_t + +/* + * VIRTIO_GPU_CMD_CTX_* + * VIRTIO_GPU_CMD_*_3D + */ +#define VIRTIO_GPU_F_VIRGL (1ULL << 0) + +/* + * VIRTIO_GPU_CMD_GET_EDID + */ +#define VIRTIO_GPU_F_EDID (1ULL << 1) +/* + * VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID + */ +#define VIRTIO_GPU_F_RESOURCE_UUID (1ULL << 2) + +/* + * VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB + */ +#define VIRTIO_GPU_F_RESOURCE_BLOB (1ULL << 3) + +enum virtio_gpu_ctrl_type { + VIRTIO_GPU_UNDEFINED = 0, + + /* 2d commands */ + VIRTIO_GPU_CMD_GET_DISPLAY_INFO = 0x0100, + VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, + VIRTIO_GPU_CMD_RESOURCE_UNREF, + VIRTIO_GPU_CMD_SET_SCANOUT, + VIRTIO_GPU_CMD_RESOURCE_FLUSH, + VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D, + VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING, + VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING, + VIRTIO_GPU_CMD_GET_CAPSET_INFO, + VIRTIO_GPU_CMD_GET_CAPSET, + VIRTIO_GPU_CMD_GET_EDID, + VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID, + VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB, + VIRTIO_GPU_CMD_SET_SCANOUT_BLOB, + + /* 3d commands */ + VIRTIO_GPU_CMD_CTX_CREATE = 0x0200, + VIRTIO_GPU_CMD_CTX_DESTROY, + VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE, + VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE, + VIRTIO_GPU_CMD_RESOURCE_CREATE_3D, + VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, + VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D, + VIRTIO_GPU_CMD_SUBMIT_3D, + VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB, + VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB, + + /* cursor commands */ + VIRTIO_GPU_CMD_UPDATE_CURSOR = 0x0300, + VIRTIO_GPU_CMD_MOVE_CURSOR, + + /* success responses */ + VIRTIO_GPU_RESP_OK_NODATA = 0x1100, + VIRTIO_GPU_RESP_OK_DISPLAY_INFO, + VIRTIO_GPU_RESP_OK_CAPSET_INFO, + VIRTIO_GPU_RESP_OK_CAPSET, + VIRTIO_GPU_RESP_OK_EDID, + VIRTIO_GPU_RESP_OK_RESOURCE_UUID, + VIRTIO_GPU_RESP_OK_MAP_INFO, + + /* error responses */ + VIRTIO_GPU_RESP_ERR_UNSPEC = 0x1200, + VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY, + VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID, + VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID, + VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID, + VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER, +}; + +enum virtio_gpu_shm_id { + VIRTIO_GPU_SHM_ID_UNDEFINED = 0, + /* + * VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB + * VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB + */ + VIRTIO_GPU_SHM_ID_HOST_VISIBLE = 1 +}; + +#define VIRTIO_GPU_FLAG_FENCE (1 << 0) + +struct virtio_gpu_ctrl_hdr { + __le32 type; + __le32 flags; + __le64 fence_id; + __le32 ctx_id; + __le32 padding; +} _PACKED; + +/* data passed in the cursor vq */ + +struct virtio_gpu_cursor_pos { + __le32 scanout_id; + __le32 x; + __le32 y; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_CMD_UPDATE_CURSOR, VIRTIO_GPU_CMD_MOVE_CURSOR */ +struct virtio_gpu_update_cursor { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_cursor_pos pos; /* update & move */ + __le32 resource_id; /* update only */ + __le32 hot_x; /* update only */ + __le32 hot_y; /* update only */ + __le32 padding; +} _PACKED; + +/* data passed in the control vq, 2d related */ + +struct virtio_gpu_rect { + __le32 x; + __le32 y; + __le32 width; + __le32 height; +} _PACKED; + +/* VIRTIO_GPU_CMD_RESOURCE_UNREF */ +struct virtio_gpu_resource_unref { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: create a 2d resource with a format */ +struct virtio_gpu_resource_create_2d { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; + __le32 format; + __le32 width; + __le32 height; +} _PACKED; + +/* VIRTIO_GPU_CMD_SET_SCANOUT */ +struct virtio_gpu_set_scanout { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_rect r; + __le32 scanout_id; + __le32 resource_id; +} _PACKED; + +/* VIRTIO_GPU_CMD_RESOURCE_FLUSH */ +struct virtio_gpu_resource_flush { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_rect r; + __le32 resource_id; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: simple transfer to_host */ +struct virtio_gpu_transfer_to_host_2d { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_rect r; + __le64 offset; + __le32 resource_id; + __le32 padding; +} _PACKED; + +struct virtio_gpu_mem_entry { + __le64 addr; + __le32 length; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING */ +struct virtio_gpu_resource_attach_backing { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; + __le32 nr_entries; +} _PACKED; + +/* VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING */ +struct virtio_gpu_resource_detach_backing { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_RESP_OK_DISPLAY_INFO */ +#define VIRTIO_GPU_MAX_SCANOUTS 16 +struct virtio_gpu_resp_display_info { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_display_one { + struct virtio_gpu_rect r; + __le32 enabled; + __le32 flags; + } pmodes[VIRTIO_GPU_MAX_SCANOUTS]; +} _PACKED; + +/* data passed in the control vq, 3d related */ + +struct virtio_gpu_box { + __le32 x, y, z; + __le32 w, h, d; +} _PACKED; + +/* VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D */ +struct virtio_gpu_transfer_host_3d { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_box box; + __le64 offset; + __le32 resource_id; + __le32 level; + __le32 stride; + __le32 layer_stride; +} _PACKED; + +/* VIRTIO_GPU_CMD_RESOURCE_CREATE_3D */ +#define VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP (1 << 0) +struct virtio_gpu_resource_create_3d { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; + __le32 target; + __le32 format; + __le32 bind; + __le32 width; + __le32 height; + __le32 depth; + __le32 array_size; + __le32 last_level; + __le32 nr_samples; + __le32 flags; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_CMD_CTX_CREATE */ +struct virtio_gpu_ctx_create { + struct virtio_gpu_ctrl_hdr hdr; + __le32 nlen; + __le32 padding; + char debug_name[64]; +} _PACKED; + +/* VIRTIO_GPU_CMD_CTX_DESTROY */ +struct virtio_gpu_ctx_destroy { + struct virtio_gpu_ctrl_hdr hdr; +} _PACKED; + +/* VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE, VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE */ +struct virtio_gpu_ctx_resource { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_CMD_SUBMIT_3D */ +struct virtio_gpu_cmd_submit { + struct virtio_gpu_ctrl_hdr hdr; + __le32 size; + __le32 padding; +} _PACKED; + +#define VIRTIO_GPU_CAPSET_VIRGL 1 +#define VIRTIO_GPU_CAPSET_VIRGL2 2 + +/* VIRTIO_GPU_CMD_GET_CAPSET_INFO */ +struct virtio_gpu_get_capset_info { + struct virtio_gpu_ctrl_hdr hdr; + __le32 capset_index; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_RESP_OK_CAPSET_INFO */ +struct virtio_gpu_resp_capset_info { + struct virtio_gpu_ctrl_hdr hdr; + __le32 capset_id; + __le32 capset_max_version; + __le32 capset_max_size; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_CMD_GET_CAPSET */ +struct virtio_gpu_get_capset { + struct virtio_gpu_ctrl_hdr hdr; + __le32 capset_id; + __le32 capset_version; +} _PACKED; + +/* VIRTIO_GPU_RESP_OK_CAPSET */ +struct virtio_gpu_resp_capset { + struct virtio_gpu_ctrl_hdr hdr; + __u8 capset_data[]; +} _PACKED; + +/* VIRTIO_GPU_CMD_GET_EDID */ +struct virtio_gpu_cmd_get_edid { + struct virtio_gpu_ctrl_hdr hdr; + __le32 scanout; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_RESP_OK_EDID */ +struct virtio_gpu_resp_edid { + struct virtio_gpu_ctrl_hdr hdr; + __le32 size; + __le32 padding; + __u8 edid[1024]; +} _PACKED; + +#define VIRTIO_GPU_EVENT_DISPLAY (1 << 0) + +struct virtio_gpu_config { + __le32 events_read; + __le32 events_clear; + __le32 num_scanouts; + __le32 num_capsets; +} _PACKED; + +/* simple formats for fbcon/X use */ +enum virtio_gpu_formats { + VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM = 1, + VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM = 2, + VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM = 3, + VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM = 4, + + VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM = 67, + VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM = 68, + + VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM = 121, + VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM = 134, +}; + +/* VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID */ +struct virtio_gpu_resource_assign_uuid { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; + __le32 padding; +} _PACKED; + +/* VIRTIO_GPU_RESP_OK_RESOURCE_UUID */ +struct virtio_gpu_resp_resource_uuid { + struct virtio_gpu_ctrl_hdr hdr; + __u8 uuid[16]; +} _PACKED; + +/* VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB */ +struct virtio_gpu_resource_create_blob { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; +#define VIRTIO_GPU_BLOB_MEM_GUEST 0x0001 +#define VIRTIO_GPU_BLOB_MEM_HOST3D 0x0002 +#define VIRTIO_GPU_BLOB_MEM_HOST3D_GUEST 0x0003 + +#define VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE 0x0001 +#define VIRTIO_GPU_BLOB_FLAG_USE_SHAREABLE 0x0002 +#define VIRTIO_GPU_BLOB_FLAG_USE_CROSS_DEVICE 0x0004 + /* zero is invalid blob mem */ + __le32 blob_mem; + __le32 blob_flags; + __le32 nr_entries; + __le64 blob_id; + __le64 size; + /* + * sizeof(nr_entries * virtio_gpu_mem_entry) bytes follow + */ +} _PACKED; + +/* VIRTIO_GPU_CMD_SET_SCANOUT_BLOB */ +struct virtio_gpu_set_scanout_blob { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_rect r; + __le32 scanout_id; + __le32 resource_id; + __le32 width; + __le32 height; + __le32 format; + __le32 padding; + __le32 strides[4]; + __le32 offsets[4]; +} _PACKED; + +/* VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB */ +struct virtio_gpu_resource_map_blob { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; + __le32 padding; + __le64 offset; +} _PACKED; + +/* VIRTIO_GPU_RESP_OK_MAP_INFO */ +#define VIRTIO_GPU_MAP_CACHE_MASK 0x0f +#define VIRTIO_GPU_MAP_CACHE_NONE 0x00 +#define VIRTIO_GPU_MAP_CACHE_CACHED 0x01 +#define VIRTIO_GPU_MAP_CACHE_UNCACHED 0x02 +#define VIRTIO_GPU_MAP_CACHE_WC 0x03 +struct virtio_gpu_resp_map_info { + struct virtio_gpu_ctrl_hdr hdr; + __u32 map_info; + __u32 padding; +} _PACKED; + +/* VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB */ +struct virtio_gpu_resource_unmap_blob { + struct virtio_gpu_ctrl_hdr hdr; + __le32 resource_id; + __le32 padding; +} _PACKED; + +#endif diff --git a/src/add-ons/kernel/drivers/graphics/virtio/virtio_gpu.cpp b/src/add-ons/kernel/drivers/graphics/virtio/virtio_gpu.cpp new file mode 100644 index 0000000000..8d47249baa --- /dev/null +++ b/src/add-ons/kernel/drivers/graphics/virtio/virtio_gpu.cpp @@ -0,0 +1,896 @@ +/* + * Copyright 2023, Jérôme Duval, jerome.duval@gmail.com. + * Distributed under the terms of the MIT License. + */ + + +#include + +#include + +#include +#include +#include + +#include + +#include "viogpu.h" + + +#define VIRTIO_GPU_DRIVER_MODULE_NAME "drivers/graphics/virtio_gpu/driver_v1" +#define VIRTIO_GPU_DEVICE_MODULE_NAME "drivers/graphics/virtio_gpu/device_v1" +#define VIRTIO_GPU_DEVICE_ID_GENERATOR "virtio_gpu/device_id" + + +typedef struct { + device_node* node; + ::virtio_device virtio_device; + virtio_device_interface* virtio; + + uint64 features; + + ::virtio_queue controlQueue; + mutex commandLock; + area_id commandArea; + addr_t commandBuffer; + phys_addr_t commandPhysAddr; + sem_id commandDone; + uint64 fenceId; + + ::virtio_queue cursorQueue; + + int displayResourceId; + uint32 framebufferWidth; + uint32 framebufferHeight; + area_id framebufferArea; + addr_t framebuffer; + size_t framebufferSize; + uint32 displayWidth; + uint32 displayHeight; + + thread_id updateThread; + bool updateThreadRunning; + + area_id sharedArea; + virtio_gpu_shared_info* sharedInfo; +} virtio_gpu_driver_info; + + +typedef struct { + virtio_gpu_driver_info* info; +} virtio_gpu_handle; + + +#include +#include +#include + +#include + +#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1)) + + +#define DEVICE_NAME "virtio_gpu" +#define ACCELERANT_NAME "virtio_gpu.accelerant" +#define TRACE_VIRTIO_GPU +#ifdef TRACE_VIRTIO_GPU +# define TRACE(x...) dprintf(DEVICE_NAME ": " x) +#else +# define TRACE(x...) ; +#endif +#define ERROR(x...) dprintf("\33[33m" DEVICE_NAME ":\33[0m " x) +#define CALLED() TRACE("CALLED %s\n", __PRETTY_FUNCTION__) + + +static device_manager_info* sDeviceManager; + + +static void virtio_gpu_vqwait(void* driverCookie, void* cookie); + + +const char* +get_feature_name(uint64 feature) +{ + switch (feature) { + case VIRTIO_GPU_F_VIRGL: + return "virgl"; + case VIRTIO_GPU_F_EDID: + return "edid"; + case VIRTIO_GPU_F_RESOURCE_UUID: + return "res_uuid"; + case VIRTIO_GPU_F_RESOURCE_BLOB: + return "res_blob"; + } + return NULL; +} + + +static status_t +virtio_gpu_drain_queues(virtio_gpu_driver_info* info) +{ + while (info->virtio->queue_dequeue(info->controlQueue, NULL, NULL)) + ; + + while (info->virtio->queue_dequeue(info->cursorQueue, NULL, NULL)) + ; + + return B_OK; +} + + +status_t +virtio_gpu_send_cmd(virtio_gpu_driver_info* info, void *cmd, size_t cmdSize, void *response, + size_t responseSize) +{ + struct virtio_gpu_ctrl_hdr *hdr = (struct virtio_gpu_ctrl_hdr *)info->commandBuffer; + struct virtio_gpu_ctrl_hdr *responseHdr = (struct virtio_gpu_ctrl_hdr *)response; + + memcpy((void*)info->commandBuffer, cmd, cmdSize); + memset((void*)(info->commandBuffer + cmdSize), 0, responseSize); + hdr->flags |= VIRTIO_GPU_FLAG_FENCE; + hdr->fence_id = ++info->fenceId; + + physical_entry entries[] { + { info->commandPhysAddr, cmdSize }, + { info->commandPhysAddr + cmdSize, responseSize }, + }; + if (!info->virtio->queue_is_empty(info->controlQueue)) + return B_ERROR; + + status_t status = info->virtio->queue_request_v(info->controlQueue, entries, 1, 1, NULL); + if (status != B_OK) + return status; + + acquire_sem(info->commandDone); + + while (!info->virtio->queue_dequeue(info->controlQueue, NULL, NULL)) + spin(10); + + memcpy(response, (void*)(info->commandBuffer + cmdSize), responseSize); + + if (responseHdr->fence_id != info->fenceId) { + ERROR("response fence id not right\n"); + } + return B_OK; +} + + +status_t +virtio_gpu_get_display_info(virtio_gpu_driver_info* info) +{ + CALLED(); + struct virtio_gpu_ctrl_hdr hdr = {}; + struct virtio_gpu_resp_display_info displayInfo = {}; + + hdr.type = VIRTIO_GPU_CMD_GET_DISPLAY_INFO; + + virtio_gpu_send_cmd(info, &hdr, sizeof(hdr), &displayInfo, sizeof(displayInfo)); + + if (displayInfo.hdr.type != VIRTIO_GPU_RESP_OK_DISPLAY_INFO) { + ERROR("failed getting display info\n"); + return B_ERROR; + } + + if (!displayInfo.pmodes[0].enabled) { + ERROR("pmodes[0] is not enabled\n"); + return B_BAD_VALUE; + } + + info->displayWidth = displayInfo.pmodes[0].r.width; + info->displayHeight = displayInfo.pmodes[0].r.height; + TRACE("virtio_gpu_get_display_info width %" B_PRIu32 " height %" B_PRIu32 "\n", + info->displayWidth, info->displayHeight); + + return B_OK; +} + + +status_t +virtio_gpu_get_edids(virtio_gpu_driver_info* info, int scanout) +{ + CALLED(); + struct virtio_gpu_cmd_get_edid getEdid = {}; + struct virtio_gpu_resp_edid response = {}; + getEdid.hdr.type = VIRTIO_GPU_CMD_GET_EDID; + getEdid.scanout = scanout; + + virtio_gpu_send_cmd(info, &getEdid, sizeof(getEdid), &response, sizeof(response)); + + if (response.hdr.type != VIRTIO_GPU_RESP_OK_EDID) { + ERROR("failed getting edids %d\n", response.hdr.type); + return B_ERROR; + } + + info->sharedInfo->has_edid = true; + memcpy(&info->sharedInfo->edid_raw, response.edid, sizeof(edid1_raw)); + TRACE("virtio_gpu_get_edids success\n"); + + return B_OK; +} + + +status_t +virtio_gpu_create_2d(virtio_gpu_driver_info* info, int resourceId, int width, int height) +{ + CALLED(); + struct virtio_gpu_resource_create_2d resource = {}; + struct virtio_gpu_ctrl_hdr response = {}; + + resource.hdr.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D; + resource.resource_id = resourceId; + resource.format = VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM; + resource.width = width; + resource.height = height; + + virtio_gpu_send_cmd(info, &resource, sizeof(resource), &response, sizeof(response)); + + if (response.type != VIRTIO_GPU_RESP_OK_NODATA) { + ERROR("viogpu_create_2d: failed %d\n", response.type); + return B_ERROR; + } + + return B_OK; +} + + +status_t +virtio_gpu_unref(virtio_gpu_driver_info* info, int resourceId) +{ + CALLED(); + struct virtio_gpu_resource_unref resource = {}; + struct virtio_gpu_ctrl_hdr response = {}; + + resource.hdr.type = VIRTIO_GPU_CMD_RESOURCE_UNREF; + resource.resource_id = resourceId; + + virtio_gpu_send_cmd(info, &resource, sizeof(resource), &response, sizeof(response)); + + if (response.type != VIRTIO_GPU_RESP_OK_NODATA) { + ERROR("virtio_gpu_unref: failed %d\n", response.type); + return B_ERROR; + } + + return B_OK; +} + + +status_t +virtio_gpu_attach_backing(virtio_gpu_driver_info* info, int resourceId) +{ + CALLED(); + struct virtio_gpu_resource_attach_backing_entries { + struct virtio_gpu_resource_attach_backing backing; + struct virtio_gpu_mem_entry entries[16]; + } _PACKED backing = {}; + struct virtio_gpu_ctrl_hdr response = {}; + + physical_entry entries[16] = {}; + status_t status = get_memory_map((void*)info->framebuffer, info->framebufferSize, entries, 16); + if (status != B_OK) { + ERROR("virtio_gpu_attach_backing get_memory_map failed: %s\n", strerror(status)); + return status; + } + + backing.backing.hdr.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING; + backing.backing.resource_id = resourceId; + for (int i = 0; i < 16; i++) { + if (entries[i].size == 0) + break; + TRACE("virtio_gpu_attach_backing %d %lx %lx\n", i, entries[i].address, entries[i].size); + backing.entries[i].addr = entries[i].address; + backing.entries[i].length = entries[i].size; + backing.backing.nr_entries++; + } + + virtio_gpu_send_cmd(info, &backing, sizeof(backing), &response, sizeof(response)); + + if (response.type != VIRTIO_GPU_RESP_OK_NODATA) { + ERROR("virtio_gpu_attach_backing failed: %d\n", response.type); + return B_ERROR; + } + + return B_OK; +} + + +status_t +virtio_gpu_detach_backing(virtio_gpu_driver_info* info, int resourceId) +{ + CALLED(); + struct virtio_gpu_resource_detach_backing backing; + struct virtio_gpu_ctrl_hdr response = {}; + + backing.hdr.type = VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING; + backing.resource_id = resourceId; + + virtio_gpu_send_cmd(info, &backing, sizeof(backing), &response, sizeof(response)); + + if (response.type != VIRTIO_GPU_RESP_OK_NODATA) { + ERROR("virtio_gpu_detach_backing failed: %d\n", response.type); + return B_ERROR; + } + + return B_OK; +} + + +status_t +virtio_gpu_set_scanout(virtio_gpu_driver_info* info, int scanoutId, int resourceId, + uint32 width, uint32 height) +{ + CALLED(); + struct virtio_gpu_set_scanout set_scanout = {}; + struct virtio_gpu_ctrl_hdr response = {}; + + set_scanout.hdr.type = VIRTIO_GPU_CMD_SET_SCANOUT; + set_scanout.scanout_id = scanoutId; + set_scanout.resource_id = resourceId; + set_scanout.r.width = width; + set_scanout.r.height = height; + + virtio_gpu_send_cmd(info, &set_scanout, sizeof(set_scanout), &response, sizeof(response)); + + if (response.type != VIRTIO_GPU_RESP_OK_NODATA) { + ERROR("virtio_gpu_set_scanout failed %d\n", response.type); + return B_ERROR; + } + + return B_OK; +} + + +status_t +virtio_gpu_transfer_to_host_2d(virtio_gpu_driver_info* info, int resourceId, + uint32 width, uint32 height) +{ + struct virtio_gpu_transfer_to_host_2d transferToHost = {}; + struct virtio_gpu_ctrl_hdr response = {}; + + transferToHost.hdr.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D; + transferToHost.resource_id = resourceId; + transferToHost.r.width = width; + transferToHost.r.height = height; + + virtio_gpu_send_cmd(info, &transferToHost, sizeof(transferToHost), &response, + sizeof(response)); + + if (response.type != VIRTIO_GPU_RESP_OK_NODATA) { + ERROR("virtio_gpu_transfer_to_host_2d failed %d\n", response.type); + return B_ERROR; + } + + return B_OK; +} + + +status_t +virtio_gpu_flush_resource(virtio_gpu_driver_info* info, int resourceId, uint32 width, + uint32 height) +{ + struct virtio_gpu_resource_flush resourceFlush = {}; + struct virtio_gpu_ctrl_hdr response = {}; + + resourceFlush.hdr.type = VIRTIO_GPU_CMD_RESOURCE_FLUSH; + resourceFlush.resource_id = resourceId; + resourceFlush.r.width = width; + resourceFlush.r.height = height; + + virtio_gpu_send_cmd(info, &resourceFlush, sizeof(resourceFlush), &response, sizeof(response)); + + if (response.type != VIRTIO_GPU_RESP_OK_NODATA) { + ERROR("virtio_gpu_flush_resource failed %d\n", response.type); + return B_ERROR; + } + + return B_OK; +} + + +status_t +virtio_update_thread(void *arg) +{ + virtio_gpu_driver_info* info = (virtio_gpu_driver_info*)arg; + + while (info->updateThreadRunning) { + bigtime_t start = system_time(); + MutexLocker commandLocker(&info->commandLock); + virtio_gpu_transfer_to_host_2d(info, info->displayResourceId, info->displayWidth, + info->displayHeight); + virtio_gpu_flush_resource(info, info->displayResourceId, info->displayWidth, info->displayHeight); + bigtime_t delay = system_time() - start; + if (delay < 20000) + snooze(20000 - delay); + } + return B_OK; +} + + +status_t +virtio_gpu_set_display_mode(virtio_gpu_driver_info* info, display_mode *mode) +{ + CALLED(); + + int newResourceId = info->displayResourceId + 1; + + // create framebuffer area + TRACE("virtio_gpu_set_display_mode %" B_PRIu32 " %" B_PRIu32 "\n", mode->virtual_width, + mode->virtual_height); + + status_t status = virtio_gpu_create_2d(info, newResourceId, mode->virtual_width, mode->virtual_height); + if (status != B_OK) + return status; + + status = virtio_gpu_attach_backing(info, newResourceId); + if (status != B_OK) + return status; + + status = virtio_gpu_unref(info, info->displayResourceId); + if (status != B_OK) + return status; + + info->displayResourceId = newResourceId; + info->displayWidth = mode->virtual_width; + info->displayHeight = mode->virtual_height; + + status = virtio_gpu_set_scanout(info, 0, 0, 0, 0); + if (status != B_OK) + return status; + + status = virtio_gpu_set_scanout(info, 0, info->displayResourceId, info->displayWidth, info->displayHeight); + if (status != B_OK) + return status; + + status = virtio_gpu_transfer_to_host_2d(info, info->displayResourceId, info->displayWidth, info->displayHeight); + if (status != B_OK) + return status; + + status = virtio_gpu_flush_resource(info, info->displayResourceId, info->displayWidth, info->displayHeight); + if (status != B_OK) + return status; + + { + virtio_gpu_shared_info& sharedInfo = *info->sharedInfo; + sharedInfo.frame_buffer_area = info->framebufferArea; + sharedInfo.frame_buffer = (uint8*)info->framebuffer; + sharedInfo.bytes_per_row = info->displayWidth * 4; + sharedInfo.current_mode.virtual_width = info->displayWidth; + sharedInfo.current_mode.virtual_height = info->displayHeight; + sharedInfo.current_mode.space = B_RGB32; + } + + return B_OK; +} + + +// #pragma mark - device module API + + +static status_t +virtio_gpu_init_device(void* _info, void** _cookie) +{ + CALLED(); + virtio_gpu_driver_info* info = (virtio_gpu_driver_info*)_info; + + device_node* parent = sDeviceManager->get_parent_node(info->node); + sDeviceManager->get_driver(parent, (driver_module_info**)&info->virtio, + (void**)&info->virtio_device); + sDeviceManager->put_node(parent); + + info->virtio->negotiate_features(info->virtio_device, VIRTIO_GPU_F_EDID, + &info->features, &get_feature_name); + + // TODO read config + + // Setup queues + ::virtio_queue virtioQueues[2]; + status_t status = info->virtio->alloc_queues(info->virtio_device, 2, + virtioQueues); + if (status != B_OK) { + ERROR("queue allocation failed (%s)\n", strerror(status)); + return status; + } + + info->controlQueue = virtioQueues[0]; + info->cursorQueue = virtioQueues[1]; + + // create command buffer area + info->commandArea = create_area("virtiogpu command buffer", (void**)&info->commandBuffer, + B_ANY_KERNEL_BLOCK_ADDRESS, B_PAGE_SIZE, + B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA); + if (info->commandArea < B_OK) { + status = info->commandArea; + goto err1; + } + + physical_entry entry; + status = get_memory_map((void*)info->commandBuffer, B_PAGE_SIZE, &entry, 1); + if (status != B_OK) + goto err2; + + info->commandPhysAddr = entry.address; + mutex_init(&info->commandLock, "virtiogpu command lock"); + + // Setup interrupt + status = info->virtio->setup_interrupt(info->virtio_device, NULL, info); + if (status != B_OK) { + ERROR("interrupt setup failed (%s)\n", strerror(status)); + goto err3; + } + + status = info->virtio->queue_setup_interrupt(info->controlQueue, + virtio_gpu_vqwait, info); + if (status != B_OK) { + ERROR("queue interrupt setup failed (%s)\n", strerror(status)); + goto err3; + } + + *_cookie = info; + return B_OK; + +err3: +err2: + delete_area(info->commandArea); +err1: + return status; +} + + +static void +virtio_gpu_uninit_device(void* _cookie) +{ + CALLED(); + virtio_gpu_driver_info* info = (virtio_gpu_driver_info*)_cookie; + + info->virtio->free_interrupts(info->virtio_device); + + mutex_destroy(&info->commandLock); + + delete_area(info->commandArea); + info->commandArea = -1; + info->virtio->free_queues(info->virtio_device); +} + + +static status_t +virtio_gpu_open(void* _info, const char* path, int openMode, void** _cookie) +{ + CALLED(); + virtio_gpu_driver_info* info = (virtio_gpu_driver_info*)_info; + status_t status; + size_t sharedSize = (sizeof(virtio_gpu_shared_info) + 7) & ~7; + MutexLocker commandLocker; + + virtio_gpu_handle* handle = (virtio_gpu_handle*)malloc( + sizeof(virtio_gpu_handle)); + if (handle == NULL) + return B_NO_MEMORY; + + info->commandDone = create_sem(1, "virtio_gpu_command"); + if (info->commandDone < B_OK) + goto error; + + info->sharedArea = create_area("virtio_gpu shared info", + (void**)&info->sharedInfo, B_ANY_KERNEL_ADDRESS, + ROUND_TO_PAGE_SIZE(sharedSize), B_FULL_LOCK, + B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA); + if (info->sharedArea < 0) + goto error; + memset(info->sharedInfo, 0, sizeof(virtio_gpu_shared_info)); + + commandLocker.SetTo(&info->commandLock, false, true); + + status = virtio_gpu_get_display_info(info); + if (status != B_OK) + goto error; + + if ((info->features & VIRTIO_GPU_F_EDID) != 0) + virtio_gpu_get_edids(info, 0); + + // so we can fit every mode + info->framebufferWidth = 3840; + info->framebufferHeight = 2160; + + // create framebuffer area + info->framebufferSize = 4 * info->framebufferWidth * info->framebufferHeight; + info->framebufferArea = create_area("virtio_gpu framebuffer", (void**)&info->framebuffer, + B_ANY_KERNEL_ADDRESS, info->framebufferSize, + B_FULL_LOCK | B_CONTIGUOUS, B_READ_AREA | B_WRITE_AREA); + if (info->framebufferArea < B_OK) { + status = info->framebufferArea; + goto error; + } + + info->displayResourceId = 1; + status = virtio_gpu_create_2d(info, info->displayResourceId, info->displayWidth, + info->displayHeight); + if (status != B_OK) + goto error; + + status = virtio_gpu_attach_backing(info, info->displayResourceId); + if (status != B_OK) + goto error; + + status = virtio_gpu_set_scanout(info, 0, info->displayResourceId, info->displayWidth, + info->displayHeight); + if (status != B_OK) + goto error; + + { + virtio_gpu_shared_info& sharedInfo = *info->sharedInfo; + sharedInfo.frame_buffer_area = info->framebufferArea; + sharedInfo.frame_buffer = (uint8*)info->framebuffer; + sharedInfo.bytes_per_row = info->displayWidth * 4; + sharedInfo.current_mode.virtual_width = info->displayWidth; + sharedInfo.current_mode.virtual_height = info->displayHeight; + sharedInfo.current_mode.space = B_RGB32; + } + info->updateThreadRunning = true; + info->updateThread = spawn_kernel_thread(virtio_update_thread, "virtio_gpu update", + B_DISPLAY_PRIORITY, info); + if (info->updateThread < B_OK) + goto error; + resume_thread(info->updateThread); + + handle->info = info; + + *_cookie = handle; + return B_OK; + +error: + delete_area(info->framebufferArea); + info->framebufferArea = -1; + delete_sem(info->commandDone); + info->commandDone = -1; + free(handle); + return B_ERROR; +} + + +static status_t +virtio_gpu_close(void* cookie) +{ + virtio_gpu_handle* handle = (virtio_gpu_handle*)cookie; + CALLED(); + + virtio_gpu_driver_info* info = handle->info; + info->updateThreadRunning = false; + delete_sem(info->commandDone); + info->commandDone = -1; + + return B_OK; +} + + +static status_t +virtio_gpu_free(void* cookie) +{ + CALLED(); + virtio_gpu_handle* handle = (virtio_gpu_handle*)cookie; + + virtio_gpu_driver_info* info = handle->info; + int32 result; + wait_for_thread(info->updateThread, &result); + info->updateThread = -1; + virtio_gpu_drain_queues(info); + free(handle); + return B_OK; +} + + +static void +virtio_gpu_vqwait(void* driverCookie, void* cookie) +{ + CALLED(); + virtio_gpu_driver_info* info = (virtio_gpu_driver_info*)cookie; + + release_sem_etc(info->commandDone, 1, B_DO_NOT_RESCHEDULE); +} + + +static status_t +virtio_gpu_read(void* cookie, off_t pos, void* buffer, size_t* _length) +{ + *_length = 0; + return B_NOT_ALLOWED; +} + + +static status_t +virtio_gpu_write(void* cookie, off_t pos, const void* buffer, + size_t* _length) +{ + *_length = 0; + return B_NOT_ALLOWED; +} + + +static status_t +virtio_gpu_ioctl(void* cookie, uint32 op, void* buffer, size_t length) +{ + CALLED(); + virtio_gpu_handle* handle = (virtio_gpu_handle*)cookie; + virtio_gpu_driver_info* info = handle->info; + + // TRACE("ioctl(op = %lx)\n", op); + + switch (op) { + case B_GET_ACCELERANT_SIGNATURE: + dprintf(DEVICE_NAME ": acc: %s\n", ACCELERANT_NAME); + if (user_strlcpy((char*)buffer, ACCELERANT_NAME, + B_FILE_NAME_LENGTH) < B_OK) + return B_BAD_ADDRESS; + + return B_OK; + + // needed to share data between kernel and accelerant + case VIRTIO_GPU_GET_PRIVATE_DATA: + return user_memcpy(buffer, &info->sharedArea, sizeof(area_id)); + + case VIRTIO_GPU_SET_DISPLAY_MODE: + { + if (length != sizeof(display_mode)) + return B_BAD_VALUE; + + display_mode mode; + if (user_memcpy(&mode, buffer, sizeof(display_mode)) != B_OK) + return B_BAD_ADDRESS; + + MutexLocker commandLocker(&info->commandLock); + + return virtio_gpu_set_display_mode(info, &mode); + } + default: + ERROR("ioctl: unknown message %" B_PRIx32 "\n", op); + break; + } + + + return B_DEV_INVALID_IOCTL; +} + + +// #pragma mark - driver module API + + +static float +virtio_gpu_supports_device(device_node* parent) +{ + CALLED(); + const char* bus; + uint16 deviceType; + + // make sure parent is really the Virtio bus manager + if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)) + return -1; + + if (strcmp(bus, "virtio")) + return 0.0; + + // check whether it's really a Virtio GPU device + if (sDeviceManager->get_attr_uint16(parent, VIRTIO_DEVICE_TYPE_ITEM, + &deviceType, true) != B_OK || deviceType != VIRTIO_DEVICE_ID_GPU) + return 0.0; + + TRACE("Virtio gpu device found!\n"); + + return 0.6; +} + + +static status_t +virtio_gpu_register_device(device_node* node) +{ + CALLED(); + + device_attr attrs[] = { + { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "Virtio GPU"} }, + { NULL } + }; + + return sDeviceManager->register_node(node, VIRTIO_GPU_DRIVER_MODULE_NAME, + attrs, NULL, NULL); +} + + +static status_t +virtio_gpu_init_driver(device_node* node, void** cookie) +{ + CALLED(); + + virtio_gpu_driver_info* info = (virtio_gpu_driver_info*)malloc( + sizeof(virtio_gpu_driver_info)); + if (info == NULL) + return B_NO_MEMORY; + + memset(info, 0, sizeof(*info)); + + info->node = node; + + *cookie = info; + return B_OK; +} + + +static void +virtio_gpu_uninit_driver(void* _cookie) +{ + CALLED(); + virtio_gpu_driver_info* info = (virtio_gpu_driver_info*)_cookie; + free(info); +} + + +static status_t +virtio_gpu_register_child_devices(void* _cookie) +{ + CALLED(); + virtio_gpu_driver_info* info = (virtio_gpu_driver_info*)_cookie; + status_t status; + + int32 id = sDeviceManager->create_id(VIRTIO_GPU_DEVICE_ID_GENERATOR); + if (id < 0) + return id; + + char name[64]; + snprintf(name, sizeof(name), "graphics/virtio/%" B_PRId32, + id); + + status = sDeviceManager->publish_device(info->node, name, + VIRTIO_GPU_DEVICE_MODULE_NAME); + + return status; +} + + +// #pragma mark - + + +module_dependency module_dependencies[] = { + {B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager}, + {} +}; + +struct device_module_info sVirtioGpuDevice = { + { + VIRTIO_GPU_DEVICE_MODULE_NAME, + 0, + NULL + }, + + virtio_gpu_init_device, + virtio_gpu_uninit_device, + NULL, // remove, + + virtio_gpu_open, + virtio_gpu_close, + virtio_gpu_free, + virtio_gpu_read, + virtio_gpu_write, + NULL, // io + virtio_gpu_ioctl, + + NULL, // select + NULL, // deselect +}; + +struct driver_module_info sVirtioGpuDriver = { + { + VIRTIO_GPU_DRIVER_MODULE_NAME, + 0, + NULL + }, + + virtio_gpu_supports_device, + virtio_gpu_register_device, + virtio_gpu_init_driver, + virtio_gpu_uninit_driver, + virtio_gpu_register_child_devices, + NULL, // rescan + NULL, // removed +}; + +module_info* modules[] = { + (module_info*)&sVirtioGpuDriver, + (module_info*)&sVirtioGpuDevice, + NULL +};