// Copyright 2020 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef VM_TOOLS_SOMMELIER_VIRTUALIZATION_WAYLAND_CHANNEL_H_ #define VM_TOOLS_SOMMELIER_VIRTUALIZATION_WAYLAND_CHANNEL_H_ #include #include #include /* * Copied from `VIRTWL_SEND_MAX_ALLOCS`. It was originally set this way * because it seemed like a reasonable limit. */ #define WAYLAND_MAX_FDs 28 // Default buffer size based on the size of a typical page. #define DEFAULT_BUFFER_SIZE 4096 struct WaylandSendReceive { int channel_fd; int fds[WAYLAND_MAX_FDs]; uint32_t num_fds; uint8_t* data; size_t data_size; }; enum WaylandChannelEvent { None, Receive, ReceiveAndProxy, Read, }; struct WaylandBufferCreateInfo { /* * If true, create a dmabuf on the host. If not, create a shared memory * region. A dmabuf can be scanned out by the display engine directly, * enabling zero copy. A shared memory region necessitates a copy to a * dma-buf by the host compositor. */ bool dmabuf; /* * dma-buf parameters. The allocation is done by host minigbm and used when * crosvm is built with the "wl-dmabuf" feature and virtgpu 3d is not * enabled. The modifier is not present, because we only want to allocate * linear zero-copy buffers in this case. The modifier makes sense when * virtgpu 3d is enabled, but in that case guest Mesa gbm (backed by Virgl) * allocates the resource, not sommelier. */ uint32_t width; uint32_t height; uint32_t drm_format; /* * Shared memory region parameters. The allocation is done by memfd(..) on * the host. */ uint32_t size; }; /* * Linux mode-setting APIs [drmModeAddFB2(..)] and Wayland normally specify * four planes, even though three are used in practice. Follow that convention * here. */ struct WaylandBufferCreateOutput { int fd; uint32_t offsets[4]; uint32_t strides[4]; uint64_t host_size; }; class WaylandChannel { public: WaylandChannel() {} virtual ~WaylandChannel() {} // Initializes the Wayland Channel. Returns 0 on success, -errno on failure. virtual int32_t init() = 0; // Returns true if the Wayland channel supports dmabuf, false otherwise. If // dmabuf is supported, Sommelier will use the `zwp_linux_dmabuf_v1` // protocol. virtual bool supports_dmabuf(void) = 0; // Creates a new context for handling the wayland command stream. Returns 0 // on success, and a pollable `out_channel_fd`. This fd represents the // connection to the host compositor, and used for subsequent `send` and // `receive` operations. // // Returns -errno on failure. virtual int32_t create_context(int& out_channel_fd) = 0; // Creates a new clipboard pipe for Wayland input. Note this interface can't // wrap a call to "pipe", and is named based on VIRTWL_IOCTL_NEW_PIPE. A new // interface may be designed in the future. // // Returns 0 on success, and a readable `out_pipe_fd`. // Returns -errno on failure. virtual int32_t create_pipe(int& out_pipe_fd) = 0; // Sends fds and associated commands to the host [like sendmsg(..)]. The fds // are converted to host handles using an implementation specific method. // For virtwl, either: // (a) virtwl allocated resources are sent. // (b) The virtgpu resource handle is fished out by virtwl. // // Returns 0 on success. Returns -errno on failure. If `send.data_size` is // than greater zero, then the caller must provide a pointer to valid memory // in `send.data`. virtual int32_t send(const struct WaylandSendReceive& send) = 0; // Handles a poll event on the channel file descriptor. // // Returns 0 on success. Returns -errno on failure. On success, the type of // event is given by `event_type`. // // If `event_type` is WaylandChannelEvent::Receive, the caller must forward // received fds and associated commands to the client. // // If `event_type` is WaylandChannelEvent::ReceiveAndProxy, `out_read_pipe` // is also returned in addition to the `receive` data. The caller does not // take ownership of `out_read_pipe`. The caller must poll `out_read_pipe` // in addition to forwarding the data given by `receive`. The `handle_pipe` // function must be called the case of `out_read_pipe` event. // // In both above cases, if the returned `receive.data_size` is than greater // zero, then the caller takes ownership of `receive.data` and must free(..) // the memory when appropriate. // // If `event_type` is WaylandChannelEvent::Read, then both `out_read_pipe` and // `receive` are meaningless. The implementation handles the event internally. virtual int32_t handle_channel_event(enum WaylandChannelEvent& event_type, struct WaylandSendReceive& receive, int& out_read_pipe) = 0; // Allocates a shared memory resource or dma-buf on the host. Maps it into // the guest. The intended use case for this function is sharing resources // with the host compositor when virtgpu 3d is not enabled. // // Returns 0 on success. Returns -errno on success. virtual int32_t allocate(const struct WaylandBufferCreateInfo& create_info, struct WaylandBufferCreateOutput& create_output) = 0; // Synchronizes accesses to previously created host dma-buf. // Returns 0 on success. Returns -errno on failure. virtual int32_t sync(int dmabuf_fd, uint64_t flags) = 0; // Reads from the specified `read_fd` and forwards to the host if `readable` // is true. Closes the `read_fd` and the proxied write fd on the host if // `hang_up` is true and all the data has been read. // // `read_fd` *must* be a read pipe given by `handle_channel_event` when the // `event_type` is WaylandChannelEvent::ReceiveAndProxy. virtual int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) = 0; // Returns the maximum size of opaque data that the channel is able to handle // in the `send` function. Must be less than or equal to DEFAULT_BUFFER_SIZE. virtual size_t max_send_size(void) = 0; }; class VirtWaylandChannel : public WaylandChannel { public: VirtWaylandChannel() : virtwl_{-1}, supports_dmabuf_(false) {} ~VirtWaylandChannel(); int32_t init() override; bool supports_dmabuf() override; int32_t create_context(int& out_channel_fd) override; int32_t create_pipe(int& out_pipe_fd) override; int32_t send(const struct WaylandSendReceive& send) override; int32_t handle_channel_event(enum WaylandChannelEvent& event_type, struct WaylandSendReceive& receive, int& out_read_pipe) override; int32_t allocate(const struct WaylandBufferCreateInfo& create_info, struct WaylandBufferCreateOutput& create_output) override; int32_t sync(int dmabuf_fd, uint64_t flags) override; int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) override; size_t max_send_size(void) override; private: // virtwl device file descriptor int32_t virtwl_; bool supports_dmabuf_; }; class VirtGpuChannel : public WaylandChannel { public: VirtGpuChannel() : virtgpu_{-1}, ring_addr_{MAP_FAILED}, ring_handle_{0}, supports_dmabuf_(false), descriptor_id_{1} {} ~VirtGpuChannel(); int32_t init() override; bool supports_dmabuf() override; int32_t create_context(int& out_channel_fd) override; int32_t create_pipe(int& out_pipe_fd) override; int32_t send(const struct WaylandSendReceive& send) override; int32_t handle_channel_event(enum WaylandChannelEvent& event_type, struct WaylandSendReceive& receive, int& out_read_pipe) override; int32_t allocate(const struct WaylandBufferCreateInfo& create_info, struct WaylandBufferCreateOutput& create_output) override; int32_t sync(int dmabuf_fd, uint64_t flags) override; int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) override; size_t max_send_size(void) override; private: /* * This provides the full description of the buffer -- width, height, strides, * offsets and host_size. Meant for internal virtgpu channel use only. */ struct BufferDescription { struct WaylandBufferCreateInfo input; struct WaylandBufferCreateOutput output; uint32_t blob_id; }; /* * Provides the read end and write end of a pipe, along with the inode (a * guest unique identifier) and host descriptor id; */ struct PipeDescription { int read_fd; int write_fd; uint32_t identifier_type; uint32_t inode; uint32_t identifier; }; int32_t image_query(const struct WaylandBufferCreateInfo& input, struct WaylandBufferCreateOutput& output, uint64_t& blob_id); int32_t submit_cmd(uint32_t* cmd, uint32_t size, uint32_t ring_idx, bool wait); int32_t channel_poll(void); int32_t close_gem_handle(uint32_t gem_handle); int32_t create_host_blob(uint64_t blob_id, uint64_t size, int& out_fd); int32_t fd_analysis(int fd, uint32_t& identifier, uint32_t& identifier_type); int32_t create_fd(uint32_t identifier, uint32_t identifier_type, uint32_t identifier_size, int& out_fd); int32_t create_pipe_internal(int& out_pipe_fd, uint32_t identifier, uint32_t identifier_type); int32_t handle_receive(enum WaylandChannelEvent& event_type, struct WaylandSendReceive& receive, int& out_read_pipe); int32_t handle_read(void); int32_t pipe_lookup(uint32_t identifier_type, uint32_t& identifier, int& fd, size_t& index); int32_t virtgpu_; void* ring_addr_; uint32_t ring_handle_; bool supports_dmabuf_; // Matches the crosvm-side descriptor_id, must be an odd number. uint32_t descriptor_id_; std::vector description_cache_; std::vector pipe_cache_; }; int open_virtgpu(char** drm_device); #endif // VM_TOOLS_SOMMELIER_VIRTUALIZATION_WAYLAND_CHANNEL_H_