| // Copyright 2019 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| #pragma once |
| |
| #include "android/base/ring_buffer.h" |
| |
| #include <functional> |
| #include <cstddef> |
| |
| // This file defines common types for address space graphics and provides |
| // documentation. |
| |
| // Address space graphics====================================================== |
| // |
| // Basic idea |
| // |
| // Address space graphics (ASG) is a subdevice of the address space device that |
| // provides a way to run graphics commands and data with fewer VM exits by |
| // leveraging shared memory ring buffers. |
| // |
| // Each GL/Vk thread in the guest is associated with a context (asg_context). |
| // asg_context consists of pointers into the shared memory that view it as a |
| // collection of ring buffers and a common write buffer. |
| // |
| // Consumer concept |
| // |
| // ASG does not assume a particular rendering backend (though we will use |
| // RenderThread's). This is for ease of coding/testing and flexibility; the |
| // implementation is not coupled to emugl/libOpenglRender. |
| // |
| // Instead, there is the concept of a "Consumer" of ASG that will do something |
| // with the data arriving from the shared memory region, and possibly reply |
| // back to the guest. We register functions to construct and deconstruct |
| // Consumers as part of emulator init (setConsumer). |
| // |
| // Guest workflow |
| // |
| // 1. Open address space device |
| // |
| // 2. Create the graphics context as the subdevice |
| // |
| // 3. ping(ASG_GET_RING) to get the offset/size of the ring buffer admin. info |
| // |
| // 4. ping(ASG_GET_BUFFER) to get the offset/size of the shared transfer buffer. |
| // |
| // 5. ioctl(CLAIM_SHARED) and mmap on those two offset/size pairs to get a |
| // guest-side mapping. |
| // |
| // 6. call asg_context_create on the ring and buffer pointers to create the asg_context. |
| // |
| // 7. Now the guest and host share asg_context pts and can communicate. |
| // |
| // 8. But usually the guest will sometimes need to ping(ASG_NOTIFY_AVAILABLE) |
| // so that the host side (which is usually a separate thread that we don't want |
| // to spin too much) wakes up and processes data. |
| |
| namespace android { |
| namespace base { |
| |
| class Stream; |
| |
| } // namespace base |
| } // namespace android |
| |
| #define ADDRESS_SPACE_GRAPHICS_DEVICE_ID 0 |
| #define ADDRESS_SPACE_GRAPHICS_PAGE_SIZE 4096 |
| #define ADDRESS_SPACE_GRAPHICS_BLOCK_SIZE (16ULL * 1048576ULL) |
| |
| // AddressSpaceGraphicsContext shares memory with |
| // the guest via the following layout: |
| extern "C" { |
| |
| struct asg_ring_storage { // directly shared with guest |
| char to_host[ADDRESS_SPACE_GRAPHICS_PAGE_SIZE]; |
| char to_host_large_xfer[ADDRESS_SPACE_GRAPHICS_PAGE_SIZE]; |
| char from_host_large_xfer[ADDRESS_SPACE_GRAPHICS_PAGE_SIZE]; |
| }; |
| |
| // Set by the address space graphics device to notify the guest that the host |
| // has slept or is able to consume something, or we are exiting, or there is an |
| // error. |
| enum asg_host_state { |
| // The host renderthread is asleep and needs to be woken up. |
| ASG_HOST_STATE_NEED_NOTIFY = 0, |
| |
| // The host renderthread is active and can consume new data |
| // without notification. |
| ASG_HOST_STATE_CAN_CONSUME = 1, |
| |
| // Normal exit |
| ASG_HOST_STATE_EXIT = 2, |
| |
| // Error: Something weird happened and we need to exit. |
| ASG_HOST_STATE_ERROR = 3, |
| |
| // Host is rendering |
| ASG_HOST_STATE_RENDERING = 4, |
| }; |
| |
| struct asg_ring_config; |
| |
| // Each context has a pair of ring buffers for communication |
| // to and from the host. There is another ring buffer for large xfers |
| // to the host (all xfers from the host are already considered "large"). |
| // |
| // Each context also comes with _one_ auxiliary buffer to hold both its own |
| // commands and to perform private DMA transfers. |
| struct asg_context { // ptrs into RingStorage |
| struct ring_buffer* to_host; |
| char* buffer; |
| asg_host_state* host_state; |
| asg_ring_config* ring_config; |
| struct ring_buffer_with_view to_host_large_xfer; |
| struct ring_buffer_with_view from_host_large_xfer; |
| }; |
| |
| // Helper function that will be common between guest and host: |
| // Given ring storage and a write buffer, returns asg_context that |
| // is the correct view into it. |
| static inline struct asg_context asg_context_create( |
| char* ring_storage, |
| char* buffer, |
| uint32_t buffer_size) { |
| |
| struct asg_context res; |
| |
| res.to_host = |
| reinterpret_cast<struct ring_buffer*>( |
| ring_storage + |
| offsetof(struct asg_ring_storage, to_host)); |
| res.to_host_large_xfer.ring = |
| reinterpret_cast<struct ring_buffer*>( |
| ring_storage + |
| offsetof(struct asg_ring_storage, to_host_large_xfer)); |
| res.from_host_large_xfer.ring = |
| reinterpret_cast<struct ring_buffer*>( |
| ring_storage + |
| offsetof(struct asg_ring_storage, from_host_large_xfer)); |
| |
| ring_buffer_init(res.to_host); |
| |
| res.buffer = buffer; |
| res.host_state = |
| reinterpret_cast<asg_host_state*>( |
| &res.to_host->state); |
| res.ring_config = |
| reinterpret_cast<asg_ring_config*>( |
| res.to_host->config); |
| |
| ring_buffer_view_init( |
| res.to_host_large_xfer.ring, |
| &res.to_host_large_xfer.view, |
| (uint8_t*)res.buffer, buffer_size); |
| |
| ring_buffer_view_init( |
| res.from_host_large_xfer.ring, |
| &res.from_host_large_xfer.view, |
| (uint8_t*)res.buffer, buffer_size); |
| |
| return res; |
| } |
| |
| // During operation, the guest sends commands and data over the auxiliary |
| // buffer while using the |to_host| ring to communicate what parts of the auxiliary |
| // buffer is outstanding traffic needing to be consumed by the host. |
| // After a transfer completes to the host, the host may write back data. |
| // The guest then reads the results on the same auxiliary buffer |
| // while being notified of which parts to read via the |from_host| ring. |
| // |
| // The size of the auxiliary buffer and flush interval is defined by |
| // the following config.ini android_hw setting: |
| // |
| // 1) android_hw->hw_gltransport_asg_writeBufferSize |
| // 2) android_hw->hw_gltransport_asg_writeStepSize |
| // |
| // 1) the size for the auxiliary buffer |
| // 2) the step size over which commands are flushed to the host |
| // |
| // When transferring commands, command data is built up in writeStepSize |
| // chunks and flushed to the host when either writeStepSize is reached or |
| // the guest flushes explicitly. |
| // |
| // Command vs. Data Modes |
| // |
| // For command data larger than writeStepSize or when transferring data, we |
| // fall back to using a different mode where the entire auxiliary buffer is |
| // used to perform the transfer, |asg_writeBufferSize| steps at a time. The |
| // host is also notified of the total transport size. |
| // |
| // When writing back to the guest, it is assumed that the write buffer will |
| // be completely empty as the guest has already flushed and the host has |
| // already consumed all commands/data, and is writing back. In this case, |
| // the full auxiliary buffer is used at the same time for writing back to |
| // the guest. |
| // |
| // Larger / Shared transfers |
| // |
| // Each of |to_host| and |from_host| can contain elements of type 1, 2, or 3: |
| // Type 1: 8 bytes: 4 bytes offset, 4 bytes size. Relative to write buffer. |
| struct __attribute__((__packed__)) asg_type1_xfer { |
| uint32_t offset; |
| uint32_t size; |
| }; |
| // Type 2: 16 bytes: 16 bytes offset into address space PCI space, 8 bytes |
| // size. |
| struct __attribute__((__packed__)) asg_type2_xfer { |
| uint64_t physAddr; |
| uint64_t size; |
| }; |
| // Type 3: There is a large transfer of known size and the entire write buffer |
| // will be used to send it over. |
| // |
| // For type 1 transfers, we get the corresponding host virtual address by |
| // adding the offset to the beginning of the write buffer. For type 2 |
| // transfers, we need to calculate the guest physical address and then call |
| // addressspacecontrolops.gethostptr, which is slower since it goes through |
| // a data structure map of existing mappings. |
| // |
| // The rings never contain a mix of type 1 and 2 elements. For to_host, |
| // the guest initiates changes between type 1 and 2. |
| // |
| // The config fields: |
| // |
| struct asg_ring_config { |
| // config[0]: size of the auxiliary buffer |
| uint32_t buffer_size; |
| |
| // config[1]: flush interval for the auxiliary buffer |
| uint32_t flush_interval; |
| |
| // the position of the interval in the auxiliary buffer |
| // that the host has read so far |
| uint32_t host_consumed_pos; |
| |
| // the start of the places the guest might write to next |
| uint32_t guest_write_pos; |
| |
| // 1 if transfers are of type 1, 2 if transfers of type 2, |
| // 3 if the overall transfer size is known and we are sending something large. |
| uint32_t transfer_mode; |
| |
| // the size of the transfer, used if transfer size is known. |
| // Set before setting config[2] to 3. |
| uint32_t transfer_size; |
| |
| // error state |
| uint32_t in_error; |
| }; |
| |
| // State/config changes may only occur if the ring is empty, or the state |
| // is transitioning to Error. That way, the host and guest have a chance to |
| // synchronize on the same state. |
| // |
| // Thus far we've established how commands and data are transferred |
| // to and from the host. Next, let's discuss how AddressSpaceGraphicsContext |
| // talks to the code that actually does something with the commands |
| // and sends data back. |
| |
| } // extern "C" |
| |
| namespace android { |
| namespace emulation { |
| namespace asg { |
| |
| // Consumer Concept |
| // |
| // AddressSpaceGraphicsContext's are each associated with a consumer that |
| // takes data off the auxiliary buffer and to_host, while sending back data |
| // over the auxiliary buffer / from_host. |
| // |
| // will read the commands and write back data. |
| // |
| // The consumer type is fixed at startup. The interface is as follows: |
| |
| // Called by the consumer, implemented in AddressSpaceGraphicsContext: |
| // |
| // Called when the consumer doesn't find anything to |
| // read in to_host. Will make the consumer sleep |
| // until another Ping(NotifyAvailable). |
| using OnUnavailableReadCallback = |
| std::function<int()>; |
| |
| // Unpacks a type 2 transfer into host pointer and size. |
| using GetPtrCallback = |
| std::function<char*(uint64_t)>; |
| |
| struct ConsumerCallbacks { |
| OnUnavailableReadCallback onUnavailableRead; |
| GetPtrCallback getPtr; |
| }; |
| |
| using ConsumerCreateCallback = |
| std::function<void* (struct asg_context, ConsumerCallbacks)>; |
| using ConsumerDestroyCallback = |
| std::function<void(void*)>; |
| using ConsumerSaveCallback = |
| std::function<void(void*, base::Stream*)>; |
| using ConsumerLoadCallback = |
| std::function<void(void*, base::Stream*)>; |
| |
| struct ConsumerInterface { |
| ConsumerCreateCallback create; |
| ConsumerDestroyCallback destroy; |
| ConsumerSaveCallback save; |
| ConsumerLoadCallback load; |
| }; |
| |
| } // namespace asg |
| } // namespace emulation |
| } // namespace android |
| |
| // The interface for the guest: |
| |
| extern "C" { |
| // Handled outside in address_space_device.cpp: |
| // |
| // Ping(device id): Create the device. On the host, the two rings and |
| // auxiliary buffer are allocated. The two rings are allocated up front. |
| // Both the auxiliary buffers and the rings are allocated from blocks of |
| // rings and auxiliary buffers. New blocks are created if we run out either |
| // way. |
| enum asg_command { |
| // Ping(get_ring): Returns, in the fields: |
| // metadata: offset to give to claimShared and mmap() in the guest |
| // size: size to give to claimShared and mmap() in the guest |
| ASG_GET_RING = 0, |
| |
| // Ping(get_buffer): Returns, in the fields: |
| // metadata: offset to give to claimShared and mmap() in the guest |
| // size: size to give to claimShared and mmap() in the guest |
| ASG_GET_BUFFER = 1, |
| |
| // Ping(set_version): Run after the guest reads and negotiates its |
| // version of the device with the host. The host now knows the guest's |
| // version and can proceed with a protocol that works for both. |
| // size (in): the version of the guest |
| // size (out): the version of the host |
| // metadata (out): hostmem id |
| // After this command runs, the consumer is |
| // implicitly created. |
| ASG_SET_VERSION = 2, |
| |
| // Ping(notiy_available): Wakes up the consumer from sleep so it |
| // can read data via toHost |
| ASG_NOTIFY_AVAILABLE = 3, |
| |
| // Retrieve the host config |
| ASG_GET_CONFIG = 4, |
| }; |
| |
| } // extern "C" |