| // Copyright 2016 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gpu.h" | |
| #include <fuchsia/sysmem/llcpp/fidl.h> | |
| #include <inttypes.h> | |
| #include <lib/image-format/image_format.h> | |
| #include <lib/zircon-internal/align.h> | |
| #include <string.h> | |
| #include <sys/param.h> | |
| #include <zircon/compiler.h> | |
| #include <zircon/time.h> | |
| #include <memory> | |
| #include <utility> | |
| #include <ddk/debug.h> | |
| #include <fbl/alloc_checker.h> | |
| #include <fbl/auto_call.h> | |
| #include <fbl/auto_lock.h> | |
| #include "src/devices/bus/lib/virtio/trace.h" | |
| #include "virtio_gpu.h" | |
| #define LOCAL_TRACE 0 | |
| namespace sysmem = llcpp::fuchsia::sysmem; | |
| namespace virtio { | |
| namespace { | |
| constexpr uint32_t kRefreshRateHz = 30; | |
| constexpr uint64_t kDisplayId = 1; | |
| zx_status_t to_zx_status(uint32_t type) { | |
| LTRACEF("response type %#x\n", type); | |
| if (type != VIRTIO_GPU_RESP_OK_NODATA) { | |
| return ZX_ERR_NO_MEMORY; | |
| } | |
| return ZX_OK; | |
| } | |
| } // namespace | |
| // DDK level ops | |
| typedef struct imported_image { | |
| uint32_t resource_id; | |
| zx::pmt pmt; | |
| } imported_image_t; | |
| zx_status_t GpuDevice::DdkGetProtocol(uint32_t proto_id, void* out) { | |
| auto* proto = static_cast<ddk::AnyProtocol*>(out); | |
| proto->ctx = this; | |
| if (proto_id == ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL) { | |
| proto->ops = &display_controller_impl_protocol_ops_; | |
| return ZX_OK; | |
| } | |
| return ZX_ERR_NOT_SUPPORTED; | |
| } | |
| void GpuDevice::DisplayControllerImplSetDisplayControllerInterface( | |
| const display_controller_interface_protocol_t* intf) { | |
| { | |
| fbl::AutoLock al(&flush_lock_); | |
| dc_intf_ = *intf; | |
| } | |
| added_display_args_t args = {}; | |
| args.display_id = kDisplayId, args.edid_present = false, | |
| args.panel.params = | |
| { | |
| .width = pmode_.r.width, | |
| .height = pmode_.r.height, | |
| .refresh_rate_e2 = kRefreshRateHz * 100, | |
| }, | |
| args.pixel_format_list = &supported_formats_, args.pixel_format_count = 1, | |
| display_controller_interface_on_displays_changed(intf, &args, 1, nullptr, 0, nullptr, 0, nullptr); | |
| } | |
| zx_status_t GpuDevice::DisplayControllerImplImportVmoImage(image_t* image, zx::vmo vmo, | |
| size_t offset) { | |
| return ZX_ERR_NOT_SUPPORTED; | |
| } | |
| zx_status_t GpuDevice::GetVmoAndStride(image_t* image, zx_unowned_handle_t handle, uint32_t index, | |
| zx::vmo* vmo_out, size_t* offset_out, | |
| uint32_t* pixel_size_out, uint32_t* row_bytes_out) { | |
| auto wait_result = | |
| sysmem::BufferCollection::Call::WaitForBuffersAllocated(zx::unowned_channel(handle)); | |
| if (!wait_result.ok()) { | |
| zxlogf(ERROR, "%s: failed to WaitForBuffersAllocated %d", tag(), wait_result.status()); | |
| return wait_result.status(); | |
| } | |
| if (wait_result->status != ZX_OK) { | |
| zxlogf(ERROR, "%s: failed to WaitForBuffersAllocated call %d", tag(), wait_result->status); | |
| return wait_result->status; | |
| } | |
| sysmem::BufferCollectionInfo_2& collection_info = wait_result->buffer_collection_info; | |
| if (!collection_info.settings.has_image_format_constraints) { | |
| zxlogf(ERROR, "%s: bad image format constraints", tag()); | |
| return ZX_ERR_INVALID_ARGS; | |
| } | |
| if (index >= collection_info.buffer_count) { | |
| return ZX_ERR_OUT_OF_RANGE; | |
| } | |
| ZX_DEBUG_ASSERT(collection_info.settings.image_format_constraints.pixel_format.type == | |
| sysmem::PixelFormatType::BGRA32); | |
| ZX_DEBUG_ASSERT( | |
| collection_info.settings.image_format_constraints.pixel_format.has_format_modifier); | |
| ZX_DEBUG_ASSERT( | |
| collection_info.settings.image_format_constraints.pixel_format.format_modifier.value == | |
| sysmem::FORMAT_MODIFIER_LINEAR); | |
| fuchsia_sysmem_ImageFormatConstraints format_constraints; | |
| memcpy(&format_constraints, &collection_info.settings.image_format_constraints, | |
| sizeof(format_constraints)); | |
| uint32_t minimum_row_bytes; | |
| if (!ImageFormatMinimumRowBytes(&format_constraints, image->width, &minimum_row_bytes)) { | |
| zxlogf(ERROR, "%s: Invalid image width %d for collection", tag(), image->width); | |
| return ZX_ERR_INVALID_ARGS; | |
| } | |
| *offset_out = collection_info.buffers[index].vmo_usable_start; | |
| *pixel_size_out = ImageFormatStrideBytesPerWidthPixel(&format_constraints.pixel_format); | |
| *row_bytes_out = minimum_row_bytes; | |
| *vmo_out = std::move(collection_info.buffers[index].vmo); | |
| return ZX_OK; | |
| } | |
| zx_status_t GpuDevice::DisplayControllerImplImportImage(image_t* image, zx_unowned_handle_t handle, | |
| uint32_t index) { | |
| zx::vmo vmo; | |
| size_t offset; | |
| uint32_t pixel_size; | |
| uint32_t row_bytes; | |
| zx_status_t status = | |
| GetVmoAndStride(image, handle, index, &vmo, &offset, &pixel_size, &row_bytes); | |
| if (status != ZX_OK) | |
| return status; | |
| return Import(std::move(vmo), image, offset, pixel_size, row_bytes); | |
| } | |
| zx_status_t GpuDevice::Import(zx::vmo vmo, image_t* image, size_t offset, uint32_t pixel_size, | |
| uint32_t row_bytes) { | |
| if (image->type != IMAGE_TYPE_SIMPLE) { | |
| return ZX_ERR_INVALID_ARGS; | |
| } | |
| fbl::AllocChecker ac; | |
| auto import_data = fbl::make_unique_checked<imported_image_t>(&ac); | |
| if (!ac.check()) { | |
| return ZX_ERR_NO_MEMORY; | |
| } | |
| unsigned size = ZX_ROUNDUP(row_bytes * image->height, PAGE_SIZE); | |
| zx_paddr_t paddr; | |
| zx_status_t status = bti_.pin(ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, vmo, offset, size, &paddr, 1, | |
| &import_data->pmt); | |
| if (status != ZX_OK) { | |
| zxlogf(ERROR, "%s: failed to pin vmo", tag()); | |
| return status; | |
| } | |
| status = allocate_2d_resource(&import_data->resource_id, row_bytes / pixel_size, image->height); | |
| if (status != ZX_OK) { | |
| zxlogf(ERROR, "%s: failed to allocate 2d resource", tag()); | |
| return status; | |
| } | |
| status = attach_backing(import_data->resource_id, paddr, size); | |
| if (status != ZX_OK) { | |
| zxlogf(ERROR, "%s: failed to attach backing store", tag()); | |
| return status; | |
| } | |
| image->handle = reinterpret_cast<uint64_t>(import_data.release()); | |
| return ZX_OK; | |
| } | |
| void GpuDevice::DisplayControllerImplReleaseImage(image_t* image) { | |
| delete reinterpret_cast<imported_image_t*>(image->handle); | |
| } | |
| uint32_t GpuDevice::DisplayControllerImplCheckConfiguration( | |
| const display_config_t** display_configs, size_t display_count, uint32_t** layer_cfg_results, | |
| size_t* layer_cfg_result_count) { | |
| if (display_count != 1) { | |
| ZX_DEBUG_ASSERT(display_count == 0); | |
| return CONFIG_DISPLAY_OK; | |
| } | |
| ZX_DEBUG_ASSERT(display_configs[0]->display_id == kDisplayId); | |
| bool success; | |
| if (display_configs[0]->layer_count != 1) { | |
| success = display_configs[0]->layer_count == 0; | |
| } else { | |
| primary_layer_t* layer = &display_configs[0]->layer_list[0]->cfg.primary; | |
| frame_t frame = { | |
| .x_pos = 0, | |
| .y_pos = 0, | |
| .width = pmode_.r.width, | |
| .height = pmode_.r.height, | |
| }; | |
| success = display_configs[0]->layer_list[0]->type == LAYER_TYPE_PRIMARY && | |
| layer->transform_mode == FRAME_TRANSFORM_IDENTITY && | |
| layer->image.width == pmode_.r.width && layer->image.height == pmode_.r.height && | |
| memcmp(&layer->dest_frame, &frame, sizeof(frame_t)) == 0 && | |
| memcmp(&layer->src_frame, &frame, sizeof(frame_t)) == 0 && | |
| display_configs[0]->cc_flags == 0 && layer->alpha_mode == ALPHA_DISABLE; | |
| } | |
| if (!success) { | |
| layer_cfg_results[0][0] = CLIENT_MERGE_BASE; | |
| for (unsigned i = 1; i < display_configs[0]->layer_count; i++) { | |
| layer_cfg_results[0][i] = CLIENT_MERGE_SRC; | |
| } | |
| layer_cfg_result_count[0] = display_configs[0]->layer_count; | |
| } | |
| return CONFIG_DISPLAY_OK; | |
| } | |
| void GpuDevice::DisplayControllerImplApplyConfiguration(const display_config_t** display_configs, | |
| size_t display_count) { | |
| uint64_t handle = display_count == 0 || display_configs[0]->layer_count == 0 | |
| ? 0 | |
| : display_configs[0]->layer_list[0]->cfg.primary.image.handle; | |
| { | |
| fbl::AutoLock al(&flush_lock_); | |
| current_fb_ = reinterpret_cast<imported_image_t*>(handle); | |
| } | |
| Flush(); | |
| } | |
| zx_status_t GpuDevice::DisplayControllerImplGetSysmemConnection(zx::channel sysmem_handle) { | |
| zx_status_t status = sysmem_connect(&sysmem_, sysmem_handle.release()); | |
| if (status != ZX_OK) { | |
| return status; | |
| } | |
| return ZX_OK; | |
| } | |
| zx_status_t GpuDevice::DisplayControllerImplSetBufferCollectionConstraints( | |
| const image_t* config, zx_unowned_handle_t collection) { | |
| sysmem::BufferCollectionConstraints constraints; | |
| constraints.usage.display = sysmem::displayUsageLayer; | |
| constraints.has_buffer_memory_constraints = true; | |
| sysmem::BufferMemoryConstraints& buffer_constraints = constraints.buffer_memory_constraints; | |
| buffer_constraints.min_size_bytes = 0; | |
| buffer_constraints.max_size_bytes = 0xffffffff; | |
| buffer_constraints.physically_contiguous_required = true; | |
| buffer_constraints.secure_required = false; | |
| buffer_constraints.ram_domain_supported = true; | |
| buffer_constraints.cpu_domain_supported = true; | |
| constraints.image_format_constraints_count = 1; | |
| sysmem::ImageFormatConstraints& image_constraints = constraints.image_format_constraints[0]; | |
| image_constraints.pixel_format.type = sysmem::PixelFormatType::BGRA32; | |
| image_constraints.pixel_format.has_format_modifier = true; | |
| image_constraints.pixel_format.format_modifier.value = sysmem::FORMAT_MODIFIER_LINEAR; | |
| image_constraints.color_spaces_count = 1; | |
| image_constraints.color_space[0].type = sysmem::ColorSpaceType::SRGB; | |
| image_constraints.min_coded_width = 0; | |
| image_constraints.max_coded_width = 0xffffffff; | |
| image_constraints.min_coded_height = 0; | |
| image_constraints.max_coded_height = 0xffffffff; | |
| image_constraints.min_bytes_per_row = 0; | |
| image_constraints.max_bytes_per_row = 0xffffffff; | |
| image_constraints.max_coded_width_times_coded_height = 0xffffffff; | |
| image_constraints.layers = 1; | |
| image_constraints.coded_width_divisor = 1; | |
| image_constraints.coded_height_divisor = 1; | |
| // Bytes per row needs to be a multiple of the pixel size. | |
| image_constraints.bytes_per_row_divisor = 4; | |
| image_constraints.start_offset_divisor = 1; | |
| image_constraints.display_width_divisor = 1; | |
| image_constraints.display_height_divisor = 1; | |
| zx_status_t status = sysmem::BufferCollection::Call::SetConstraints( | |
| zx::unowned_channel(collection), true, constraints) | |
| .status(); | |
| if (status != ZX_OK) { | |
| zxlogf(ERROR, "virtio::GpuDevice: Failed to set constraints"); | |
| return status; | |
| } | |
| return ZX_OK; | |
| } | |
| zx_status_t GpuDevice::DisplayControllerImplGetSingleBufferFramebuffer(zx::vmo* out_vmo, | |
| uint32_t* out_stride) { | |
| return ZX_ERR_NOT_SUPPORTED; | |
| } | |
| GpuDevice::GpuDevice(zx_device_t* bus_device, zx::bti bti, std::unique_ptr<Backend> backend) | |
| : virtio::Device(bus_device, std::move(bti), std::move(backend)), DeviceType(bus_device) { | |
| sem_init(&request_sem_, 0, 1); | |
| sem_init(&response_sem_, 0, 0); | |
| cnd_init(&flush_cond_); | |
| memset(&gpu_req_, 0, sizeof(gpu_req_)); | |
| } | |
| GpuDevice::~GpuDevice() { | |
| io_buffer_release(&gpu_req_); | |
| // TODO: clean up allocated physical memory | |
| sem_destroy(&request_sem_); | |
| sem_destroy(&response_sem_); | |
| cnd_destroy(&flush_cond_); | |
| } | |
| template <typename RequestType, typename ResponseType> | |
| void GpuDevice::send_command_response(const RequestType* cmd, ResponseType** res) { | |
| size_t cmd_len = sizeof(RequestType); | |
| size_t res_len = sizeof(ResponseType); | |
| LTRACEF("dev %p, cmd %p, cmd_len %zu, res %p, res_len %zu\n", this, cmd, cmd_len, res, res_len); | |
| // Keep this single message at a time | |
| sem_wait(&request_sem_); | |
| fbl::MakeAutoCall([this]() { sem_post(&request_sem_); }); | |
| uint16_t i; | |
| struct vring_desc* desc = vring_.AllocDescChain(2, &i); | |
| ZX_ASSERT(desc); | |
| void* gpu_req_base = io_buffer_virt(&gpu_req_); | |
| zx_paddr_t gpu_req_pa = io_buffer_phys(&gpu_req_); | |
| memcpy(gpu_req_base, cmd, cmd_len); | |
| desc->addr = gpu_req_pa; | |
| desc->len = static_cast<uint32_t>(cmd_len); | |
| desc->flags = VRING_DESC_F_NEXT; | |
| // Set the second descriptor to the response with the write bit set | |
| desc = vring_.DescFromIndex(desc->next); | |
| ZX_ASSERT(desc); | |
| *res = reinterpret_cast<ResponseType*>(static_cast<uint8_t*>(gpu_req_base) + cmd_len); | |
| zx_paddr_t res_phys = gpu_req_pa + cmd_len; | |
| memset(*res, 0, res_len); | |
| desc->addr = res_phys; | |
| desc->len = static_cast<uint32_t>(res_len); | |
| desc->flags = VRING_DESC_F_WRITE; | |
| // Submit the transfer & wait for the response | |
| vring_.SubmitChain(i); | |
| vring_.Kick(); | |
| sem_wait(&response_sem_); | |
| } | |
| zx_status_t GpuDevice::get_display_info() { | |
| LTRACEF("dev %p\n", this); | |
| // Construct the get display info message | |
| virtio_gpu_ctrl_hdr req; | |
| memset(&req, 0, sizeof(req)); | |
| req.type = VIRTIO_GPU_CMD_GET_DISPLAY_INFO; | |
| // Send the message and get a response | |
| virtio_gpu_resp_display_info* info; | |
| send_command_response(&req, &info); | |
| if (info->hdr.type != VIRTIO_GPU_RESP_OK_DISPLAY_INFO) { | |
| return ZX_ERR_NOT_FOUND; | |
| } | |
| // We got a response | |
| LTRACEF("response:\n"); | |
| for (int i = 0; i < VIRTIO_GPU_MAX_SCANOUTS; i++) { | |
| if (info->pmodes[i].enabled) { | |
| LTRACEF("%u: x %u y %u w %u h %u flags 0x%x\n", i, info->pmodes[i].r.x, info->pmodes[i].r.y, | |
| info->pmodes[i].r.width, info->pmodes[i].r.height, info->pmodes[i].flags); | |
| if (pmode_id_ < 0) { | |
| // Save the first valid pmode we see | |
| memcpy(&pmode_, &info->pmodes[i], sizeof(pmode_)); | |
| pmode_id_ = i; | |
| } | |
| } | |
| } | |
| return ZX_OK; | |
| } | |
| zx_status_t GpuDevice::allocate_2d_resource(uint32_t* resource_id, uint32_t width, | |
| uint32_t height) { | |
| LTRACEF("dev %p\n", this); | |
| ZX_ASSERT(resource_id); | |
| // Construct the request | |
| virtio_gpu_resource_create_2d req; | |
| memset(&req, 0, sizeof(req)); | |
| req.hdr.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D; | |
| req.resource_id = next_resource_id_++; | |
| *resource_id = req.resource_id; | |
| req.format = VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM; | |
| req.width = width; | |
| req.height = height; | |
| // Send the command and get a response | |
| struct virtio_gpu_ctrl_hdr* res; | |
| send_command_response(&req, &res); | |
| return to_zx_status(res->type); | |
| } | |
| zx_status_t GpuDevice::attach_backing(uint32_t resource_id, zx_paddr_t ptr, size_t buf_len) { | |
| LTRACEF("dev %p, resource_id %u, ptr %#" PRIxPTR ", buf_len %zu\n", this, resource_id, ptr, | |
| buf_len); | |
| ZX_ASSERT(ptr); | |
| // Construct the request | |
| struct { | |
| struct virtio_gpu_resource_attach_backing req; | |
| struct virtio_gpu_mem_entry mem; | |
| } req; | |
| memset(&req, 0, sizeof(req)); | |
| req.req.hdr.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING; | |
| req.req.resource_id = resource_id; | |
| req.req.nr_entries = 1; | |
| req.mem.addr = ptr; | |
| req.mem.length = (uint32_t)buf_len; | |
| // Send the command and get a response | |
| struct virtio_gpu_ctrl_hdr* res; | |
| send_command_response(&req, &res); | |
| return to_zx_status(res->type); | |
| } | |
| zx_status_t GpuDevice::set_scanout(uint32_t scanout_id, uint32_t resource_id, uint32_t width, | |
| uint32_t height) { | |
| LTRACEF("dev %p, scanout_id %u, resource_id %u, width %u, height %u\n", this, scanout_id, | |
| resource_id, width, height); | |
| // Construct the request | |
| virtio_gpu_set_scanout req; | |
| memset(&req, 0, sizeof(req)); | |
| req.hdr.type = VIRTIO_GPU_CMD_SET_SCANOUT; | |
| req.r.x = req.r.y = 0; | |
| req.r.width = width; | |
| req.r.height = height; | |
| req.scanout_id = scanout_id; | |
| req.resource_id = resource_id; | |
| // Send the command and get a response | |
| virtio_gpu_ctrl_hdr* res; | |
| send_command_response(&req, &res); | |
| return to_zx_status(res->type); | |
| } | |
| zx_status_t GpuDevice::flush_resource(uint32_t resource_id, uint32_t width, uint32_t height) { | |
| LTRACEF("dev %p, resource_id %u, width %u, height %u\n", this, resource_id, width, height); | |
| // Construct the request | |
| virtio_gpu_resource_flush req; | |
| memset(&req, 0, sizeof(req)); | |
| req.hdr.type = VIRTIO_GPU_CMD_RESOURCE_FLUSH; | |
| req.r.x = req.r.y = 0; | |
| req.r.width = width; | |
| req.r.height = height; | |
| req.resource_id = resource_id; | |
| // Send the command and get a response | |
| virtio_gpu_ctrl_hdr* res; | |
| send_command_response(&req, &res); | |
| return to_zx_status(res->type); | |
| } | |
| zx_status_t GpuDevice::transfer_to_host_2d(uint32_t resource_id, uint32_t width, uint32_t height) { | |
| LTRACEF("dev %p, resource_id %u, width %u, height %u\n", this, resource_id, width, height); | |
| // Construct the request | |
| virtio_gpu_transfer_to_host_2d req; | |
| memset(&req, 0, sizeof(req)); | |
| req.hdr.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D; | |
| req.r.x = req.r.y = 0; | |
| req.r.width = width; | |
| req.r.height = height; | |
| req.offset = 0; | |
| req.resource_id = resource_id; | |
| // Send the command and get a response | |
| virtio_gpu_ctrl_hdr* res; | |
| send_command_response(&req, &res); | |
| return to_zx_status(res->type); | |
| } | |
| void GpuDevice::Flush() { | |
| fbl::AutoLock al(&flush_lock_); | |
| flush_pending_ = true; | |
| cnd_signal(&flush_cond_); | |
| } | |
| void GpuDevice::virtio_gpu_flusher() { | |
| LTRACE_ENTRY; | |
| zx_time_t next_deadline = zx_clock_get_monotonic(); | |
| zx_time_t period = ZX_SEC(1) / kRefreshRateHz; | |
| for (;;) { | |
| zx_nanosleep(next_deadline); | |
| bool fb_change; | |
| { | |
| fbl::AutoLock al(&flush_lock_); | |
| fb_change = displayed_fb_ != current_fb_; | |
| displayed_fb_ = current_fb_; | |
| } | |
| LTRACEF("flushing\n"); | |
| if (displayed_fb_) { | |
| zx_status_t status = | |
| transfer_to_host_2d(displayed_fb_->resource_id, pmode_.r.width, pmode_.r.height); | |
| if (status != ZX_OK) { | |
| LTRACEF("failed to flush resource\n"); | |
| continue; | |
| } | |
| status = flush_resource(displayed_fb_->resource_id, pmode_.r.width, pmode_.r.height); | |
| if (status != ZX_OK) { | |
| LTRACEF("failed to flush resource\n"); | |
| continue; | |
| } | |
| } | |
| if (fb_change) { | |
| uint32_t res_id = displayed_fb_ ? displayed_fb_->resource_id : 0; | |
| zx_status_t status = set_scanout(pmode_id_, res_id, pmode_.r.width, pmode_.r.height); | |
| if (status != ZX_OK) { | |
| zxlogf(ERROR, "%s: failed to set scanout: %d", tag(), status); | |
| continue; | |
| } | |
| } | |
| { | |
| fbl::AutoLock al(&flush_lock_); | |
| if (dc_intf_.ops) { | |
| uint64_t handles[] = {reinterpret_cast<uint64_t>(displayed_fb_)}; | |
| display_controller_interface_on_display_vsync(&dc_intf_, kDisplayId, next_deadline, handles, | |
| displayed_fb_ != nullptr); | |
| } | |
| } | |
| next_deadline = zx_time_add_duration(next_deadline, period); | |
| } | |
| } | |
| zx_status_t GpuDevice::virtio_gpu_start() { | |
| LTRACEF("dev %p\n", this); | |
| // Get the display info and see if we find a valid pmode | |
| zx_status_t status = get_display_info(); | |
| if (status != ZX_OK) { | |
| zxlogf(ERROR, "%s: failed to get display info", tag()); | |
| return status; | |
| } | |
| if (pmode_id_ < 0) { | |
| zxlogf(ERROR, "%s: failed to find a pmode, exiting", tag()); | |
| return ZX_ERR_NOT_FOUND; | |
| } | |
| printf("virtio-gpu: found display x %u y %u w %u h %u flags 0x%x\n", pmode_.r.x, pmode_.r.y, | |
| pmode_.r.width, pmode_.r.height, pmode_.flags); | |
| // Run a worker thread to shove in flush events | |
| auto virtio_gpu_flusher_entry = [](void* arg) { | |
| static_cast<GpuDevice*>(arg)->virtio_gpu_flusher(); | |
| return 0; | |
| }; | |
| thrd_create_with_name(&flush_thread_, virtio_gpu_flusher_entry, this, "virtio-gpu-flusher"); | |
| thrd_detach(flush_thread_); | |
| LTRACEF("publishing device\n"); | |
| status = DdkAdd("virtio-gpu-display"); | |
| device_ = zxdev(); | |
| if (status != ZX_OK) { | |
| device_ = nullptr; | |
| return status; | |
| } | |
| LTRACE_EXIT; | |
| return ZX_OK; | |
| } | |
| zx_status_t GpuDevice::Init() { | |
| LTRACE_ENTRY; | |
| zx_status_t status = device_get_protocol(bus_device(), ZX_PROTOCOL_SYSMEM, &sysmem_); | |
| if (status != ZX_OK) { | |
| zxlogf(ERROR, "%s: Could not get Display SYSMEM protocol", tag()); | |
| return status; | |
| } | |
| DeviceReset(); | |
| struct virtio_gpu_config config; | |
| CopyDeviceConfig(&config, sizeof(config)); | |
| LTRACEF("events_read 0x%x\n", config.events_read); | |
| LTRACEF("events_clear 0x%x\n", config.events_clear); | |
| LTRACEF("num_scanouts 0x%x\n", config.num_scanouts); | |
| LTRACEF("reserved 0x%x\n", config.reserved); | |
| // Ack and set the driver status bit | |
| DriverStatusAck(); | |
| // XXX check features bits and ack/nak them | |
| // Allocate the main vring | |
| status = vring_.Init(0, 16); | |
| if (status != ZX_OK) { | |
| zxlogf(ERROR, "%s: failed to allocate vring", tag()); | |
| return status; | |
| } | |
| // Allocate a GPU request | |
| status = io_buffer_init(&gpu_req_, bti_.get(), PAGE_SIZE, IO_BUFFER_RW | IO_BUFFER_CONTIG); | |
| if (status != ZX_OK) { | |
| zxlogf(ERROR, "%s: cannot alloc gpu_req buffers %d", tag(), status); | |
| return status; | |
| } | |
| LTRACEF("allocated gpu request at %p, physical address %#" PRIxPTR "\n", | |
| io_buffer_virt(&gpu_req_), io_buffer_phys(&gpu_req_)); | |
| StartIrqThread(); | |
| DriverStatusOk(); | |
| // Start a worker thread that runs through a sequence to finish initializing the GPU | |
| auto virtio_gpu_start_entry = [](void* arg) { | |
| return static_cast<GpuDevice*>(arg)->virtio_gpu_start(); | |
| }; | |
| thrd_create_with_name(&start_thread_, virtio_gpu_start_entry, this, "virtio-gpu-starter"); | |
| thrd_detach(start_thread_); | |
| return ZX_OK; | |
| } | |
| void GpuDevice::IrqRingUpdate() { | |
| LTRACE_ENTRY; | |
| // Parse our descriptor chain, add back to the free queue | |
| auto free_chain = [this](vring_used_elem* used_elem) { | |
| uint16_t i = static_cast<uint16_t>(used_elem->id); | |
| struct vring_desc* desc = vring_.DescFromIndex(i); | |
| for (;;) { | |
| int next; | |
| if (desc->flags & VRING_DESC_F_NEXT) { | |
| next = desc->next; | |
| } else { | |
| // End of chain | |
| next = -1; | |
| } | |
| vring_.FreeDesc(i); | |
| if (next < 0) { | |
| break; | |
| } | |
| i = static_cast<uint16_t>(next); | |
| desc = vring_.DescFromIndex(i); | |
| } | |
| // Notify the request thread | |
| sem_post(&response_sem_); | |
| }; | |
| // Tell the ring to find free chains and hand it back to our lambda | |
| vring_.IrqRingUpdate(free_chain); | |
| } | |
| void GpuDevice::IrqConfigChange() { LTRACE_ENTRY; } | |
| } // namespace virtio |