| // Copyright 2020 The Chromium OS 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 <errno.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| |
| #include "linux-headers/virtwl.h" // NOLINT(build/include_directory) |
| #include "wayland_channel.h" // NOLINT(build/include_directory) |
| |
| #define VIRTWL_DEVICE "/dev/wl0" |
| #define MAX_SEND_SIZE (DEFAULT_BUFFER_SIZE - sizeof(struct virtwl_ioctl_txn)) |
| |
| VirtWaylandChannel::~VirtWaylandChannel() { |
| if (virtwl_ >= 0) |
| close(virtwl_); |
| } |
| |
| int32_t VirtWaylandChannel::init() { |
| virtwl_ = open(VIRTWL_DEVICE, O_RDWR); |
| int32_t ret; |
| struct WaylandBufferCreateInfo create_info = {0}; |
| struct WaylandBufferCreateOutput create_output = {0}; |
| create_output.fd = -1; |
| |
| if (virtwl_ == -1) |
| return -errno; |
| |
| create_info.dmabuf = true; |
| supports_dmabuf_ = true; |
| |
| ret = allocate(create_info, create_output); |
| if (ret && errno == ENOTTY) { |
| fprintf(stderr, |
| "warning: virtwl-dmabuf driver not supported by host," |
| " using virtwl instead\n"); |
| supports_dmabuf_ = false; |
| } else if (create_output.fd >= 0) { |
| // Close the returned dmabuf fd in case the invalid dmabuf metadata |
| // given above actually manages to return an fd successfully. |
| close(create_output.fd); |
| create_output.fd = -1; |
| } |
| |
| return 0; |
| } |
| |
| bool VirtWaylandChannel::supports_dmabuf(void) { |
| return supports_dmabuf_; |
| } |
| |
| int32_t VirtWaylandChannel::create_context(int& out_channel_fd) { |
| int ret; |
| struct virtwl_ioctl_new new_ctx = { |
| .type = VIRTWL_IOCTL_NEW_CTX, |
| .fd = -1, |
| .flags = 0, |
| }; |
| |
| ret = ioctl(virtwl_, VIRTWL_IOCTL_NEW, &new_ctx); |
| if (ret) |
| return -errno; |
| |
| out_channel_fd = new_ctx.fd; |
| |
| return 0; |
| } |
| |
| int32_t VirtWaylandChannel::create_pipe(int& out_pipe_fd) { |
| int ret; |
| struct virtwl_ioctl_new new_pipe = { |
| .type = VIRTWL_IOCTL_NEW_PIPE_READ, |
| .fd = -1, |
| .flags = 0, |
| }; |
| |
| new_pipe.size = 0; |
| |
| ret = ioctl(virtwl_, VIRTWL_IOCTL_NEW, &new_pipe); |
| if (ret) |
| return -errno; |
| |
| out_pipe_fd = new_pipe.fd; |
| |
| return 0; |
| } |
| |
| int32_t VirtWaylandChannel::send(const struct WaylandSendReceive& send) { |
| int ret; |
| uint8_t ioctl_buffer[DEFAULT_BUFFER_SIZE]; |
| |
| struct virtwl_ioctl_txn* txn = (struct virtwl_ioctl_txn*)ioctl_buffer; |
| void* send_data = &txn->data; |
| |
| if (send.data_size > max_send_size()) |
| return -EINVAL; |
| |
| memcpy(send_data, send.data, send.data_size); |
| |
| for (uint32_t i = 0; i < WAYLAND_MAX_FDs; i++) { |
| if (i < send.num_fds) { |
| txn->fds[i] = send.fds[i]; |
| } else { |
| txn->fds[i] = -1; |
| } |
| } |
| |
| txn->len = send.data_size; |
| ret = ioctl(send.channel_fd, VIRTWL_IOCTL_SEND, txn); |
| if (ret) |
| return -errno; |
| |
| return 0; |
| } |
| |
| int32_t VirtWaylandChannel::handle_channel_event( |
| enum WaylandChannelEvent& event_type, |
| struct WaylandSendReceive& receive, |
| int& out_read_pipe) { |
| int ret; |
| uint8_t ioctl_buffer[DEFAULT_BUFFER_SIZE]; |
| |
| struct virtwl_ioctl_txn* txn = (struct virtwl_ioctl_txn*)ioctl_buffer; |
| size_t max_recv_size = sizeof(ioctl_buffer) - sizeof(struct virtwl_ioctl_txn); |
| void* recv_data = &txn->data; |
| |
| txn->len = max_recv_size; |
| ret = ioctl(receive.channel_fd, VIRTWL_IOCTL_RECV, txn); |
| if (ret) |
| return -errno; |
| |
| for (uint32_t i = 0; i < WAYLAND_MAX_FDs; i++) { |
| if (txn->fds[i] >= 0) { |
| receive.num_fds++; |
| receive.fds[i] = txn->fds[i]; |
| } else { |
| break; |
| } |
| } |
| |
| if (txn->len > 0) { |
| receive.data = reinterpret_cast<uint8_t*>(calloc(1, txn->len)); |
| if (!receive.data) |
| return -ENOMEM; |
| |
| memcpy(receive.data, recv_data, txn->len); |
| } |
| |
| receive.data_size = txn->len; |
| event_type = WaylandChannelEvent::Receive; |
| return 0; |
| } |
| |
| int32_t VirtWaylandChannel::allocate( |
| const struct WaylandBufferCreateInfo& create_info, |
| struct WaylandBufferCreateOutput& create_output) { |
| int ret; |
| struct virtwl_ioctl_new ioctl_new = {0}; |
| |
| if (create_info.dmabuf) { |
| ioctl_new.type = VIRTWL_IOCTL_NEW_DMABUF; |
| ioctl_new.fd = -1; |
| ioctl_new.flags = 0; |
| ioctl_new.dmabuf.width = create_info.width; |
| ioctl_new.dmabuf.height = create_info.height; |
| ioctl_new.dmabuf.format = create_info.drm_format; |
| } else { |
| ioctl_new.type = VIRTWL_IOCTL_NEW_ALLOC; |
| ioctl_new.fd = -1; |
| ioctl_new.flags = 0; |
| ioctl_new.size = create_info.size; |
| create_output.host_size = create_info.size; |
| } |
| |
| ret = ioctl(virtwl_, VIRTWL_IOCTL_NEW, &ioctl_new); |
| if (ret) |
| return -errno; |
| |
| if (create_info.dmabuf) { |
| create_output.strides[0] = ioctl_new.dmabuf.stride0; |
| create_output.strides[1] = ioctl_new.dmabuf.stride1; |
| create_output.strides[2] = ioctl_new.dmabuf.stride2; |
| |
| create_output.offsets[0] = ioctl_new.dmabuf.offset0; |
| create_output.offsets[1] = ioctl_new.dmabuf.offset1; |
| create_output.offsets[2] = ioctl_new.dmabuf.offset2; |
| |
| // The common layer will consider multi-planar sizes as needed. |
| create_output.host_size = create_output.strides[0] * create_info.height; |
| } |
| |
| create_output.fd = ioctl_new.fd; |
| |
| return 0; |
| } |
| |
| int32_t VirtWaylandChannel::sync(int dmabuf_fd, uint64_t flags) { |
| struct virtwl_ioctl_dmabuf_sync sync = {0}; |
| int ret; |
| |
| sync.flags = flags; |
| ret = ioctl(dmabuf_fd, VIRTWL_IOCTL_DMABUF_SYNC, &sync); |
| if (ret) |
| return -errno; |
| |
| return 0; |
| } |
| |
| int32_t VirtWaylandChannel::handle_pipe(int read_fd, |
| bool readable, |
| bool& hang_up) { |
| return 0; |
| } |
| |
| size_t VirtWaylandChannel::max_send_size(void) { |
| return MAX_SEND_SIZE; |
| } |