/* * Copyright (C) 2017 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. */ #include "common/vsoc/lib/region_view.h" #define LOG_TAG "vsoc: region_host" #include <stdio.h> #include <string.h> #include <linux/futex.h> #include <sys/mman.h> #include <sys/socket.h> #include <sys/syscall.h> #include <sys/types.h> #include <unistd.h> #include <iomanip> #include <sstream> #include <thread> #include <vector> #include <glog/logging.h> #include "common/libs/fs/shared_fd.h" #include "common/libs/fs/shared_select.h" using cvd::SharedFD; namespace { class HostRegionControl : public vsoc::RegionControl { public: HostRegionControl(const char* region_name, const SharedFD& incoming_interrupt_fd, const SharedFD& outgoing_interrupt_fd, const SharedFD& shared_memory_fd) : region_name_{region_name}, incoming_interrupt_fd_{incoming_interrupt_fd}, outgoing_interrupt_fd_{outgoing_interrupt_fd}, shared_memory_fd_{shared_memory_fd} {} int CreateFdScopedPermission(const char* /*managed_region_name*/, uint32_t /*owner_offset*/, uint32_t /*owned_val*/, uint32_t /*begin_offset*/, uint32_t /*end_offset*/) override { return -1; } bool InitializeRegion(); virtual bool InterruptPeer() override { uint64_t one = 1; ssize_t rval = outgoing_interrupt_fd_->Write(&one, sizeof(one)); if (rval != sizeof(one)) { LOG(FATAL) << __FUNCTION__ << ": rval (" << rval << ") != sizeof(one))"; return false; } return true; } // Wake the local signal table scanner. Primarily used during shutdown virtual void InterruptSelf() override { uint64_t one = 1; ssize_t rval = incoming_interrupt_fd_->Write(&one, sizeof(one)); if (rval != sizeof(one)) { LOG(FATAL) << __FUNCTION__ << ": rval (" << rval << ") != sizeof(one))"; } } virtual void WaitForInterrupt() override { // Check then act isn't a problem here: the other side does // the following things in exactly this order: // 1. exchanges 1 with interrupt_signalled // 2. if interrupt_signalled was 0 it increments the eventfd // eventfd increments are persistent, so if interrupt_signalled was set // back to 1 while we are going to sleep the sleep will return // immediately. uint64_t missed{}; cvd::SharedFDSet readset; readset.Set(incoming_interrupt_fd_); cvd::Select(&readset, NULL, NULL, NULL); ssize_t rval = incoming_interrupt_fd_->Read(&missed, sizeof(missed)); if (rval != sizeof(missed)) { LOG(FATAL) << __FUNCTION__ << ": rval (" << rval << ") != sizeof(missed)), are there more than one threads " "waiting for interrupts?"; } if (!missed) { LOG(FATAL) << __FUNCTION__ << ": woke with 0 interrupts"; } } virtual void* Map() override { if (region_base_) { return region_base_; } // Now actually map the region region_base_ = shared_memory_fd_->Mmap(0, region_size(), PROT_READ | PROT_WRITE, MAP_SHARED, region_desc_.region_begin_offset); if (region_base_ == MAP_FAILED) { LOG(FATAL) << "mmap failed for offset " << region_desc_.region_begin_offset << " (" << shared_memory_fd_->StrError() << ")"; region_base_ = nullptr; } return region_base_; } virtual int SignalSelf(uint32_t offset) override { return syscall(SYS_futex, region_offset_to_pointer<int32_t*>(offset), FUTEX_WAKE, -1, nullptr, nullptr, 0); } virtual int WaitForSignal(uint32_t offset, uint32_t expected_value) override { return syscall(SYS_futex, region_offset_to_pointer<int32_t*>(offset), FUTEX_WAIT, expected_value, nullptr, nullptr, 0); } protected: const char* region_name_{}; cvd::SharedFD incoming_interrupt_fd_; cvd::SharedFD outgoing_interrupt_fd_; cvd::SharedFD shared_memory_fd_; }; // Default path to the ivshmem_server socket. This can vary when we're // launching multiple CVDs. constexpr int kMaxSupportedProtocolVersion = 0; bool HostRegionControl::InitializeRegion() { size_t region_name_len = strlen(region_name_); if (region_name_len >= VSOC_DEVICE_NAME_SZ) { LOG(FATAL) << "Region name length (" << region_name_len << ") not < " << VSOC_DEVICE_NAME_SZ; return false; } vsoc_shm_layout_descriptor layout; ssize_t rval = shared_memory_fd_->Pread(&layout, sizeof(layout), 0); if (rval != sizeof(layout)) { LOG(FATAL) << "Unable to read layout, rval=" << rval << " (" << shared_memory_fd_->StrError() << ")"; return false; } if (layout.major_version != CURRENT_VSOC_LAYOUT_MAJOR_VERSION) { LOG(FATAL) << "Incompatible major version: saw " << layout.major_version << " wanted " << CURRENT_VSOC_LAYOUT_MAJOR_VERSION; } std::vector<vsoc_device_region> descriptors; descriptors.resize(layout.region_count); ssize_t wanted = sizeof(vsoc_device_region) * layout.region_count; rval = shared_memory_fd_->Pread(descriptors.data(), wanted, layout.vsoc_region_desc_offset); if (rval != wanted) { LOG(FATAL) << "Unable to read region descriptors, rval=" << rval << " (" << shared_memory_fd_->StrError() << ")"; return false; } for (const auto& desc : descriptors) { if (!strcmp(region_name_, desc.device_name)) { region_desc_ = desc; return true; } } std::ostringstream buf; for (const auto& desc : descriptors) { buf << " " << desc.device_name; } LOG(FATAL) << "Region name of " << region_name_ << " not found among:" << buf.str(); return false; } } // namespace std::shared_ptr<vsoc::RegionControl> vsoc::RegionControl::Open( const char* region_name, const char* domain) { AutoFreeBuffer msg; SharedFD region_server = SharedFD::SocketLocalClient(domain, false, SOCK_STREAM); if (!region_server->IsOpen()) { LOG(FATAL) << "Could not contact ivshmem_server (" << region_server->StrError() << ")"; return nullptr; } // Check server protocol version. uint32_t protocol_version; ssize_t bytes = region_server->Recv(&protocol_version, sizeof(protocol_version), MSG_NOSIGNAL); if (bytes != sizeof(protocol_version)) { LOG(FATAL) << "Failed to recv protocol version; res=" << bytes << " (" << region_server->StrError() << ")"; return nullptr; } if (protocol_version > kMaxSupportedProtocolVersion) { LOG(FATAL) << "Unsupported protocol version " << protocol_version << "; max supported version is " << kMaxSupportedProtocolVersion; return nullptr; } // Send requested region. int16_t size = strlen(region_name); bytes = region_server->Send(&size, sizeof(size), MSG_NOSIGNAL); if (bytes != sizeof(size)) { LOG(FATAL) << "Failed to send region name length; res=" << bytes << " (" << region_server->StrError() << ")"; return nullptr; } bytes = region_server->Send(region_name, size, MSG_NOSIGNAL); if (bytes != size) { LOG(FATAL) << "Failed to send region name; res=" << bytes << " (" << region_server->StrError() << ")"; return nullptr; } // Receive control sockets. uint64_t control_data; struct iovec iov { &control_data, sizeof(control_data) }; cvd::InbandMessageHeader hdr{}; hdr.msg_iov = &iov; hdr.msg_iovlen = 1; SharedFD fds[3]; bytes = region_server->RecvMsgAndFDs(hdr, 0, &fds); if (bytes != sizeof(control_data)) { LOG(FATAL) << "Failed to complete handshake; res=" << bytes << " (" << region_server->StrError() << ")"; return nullptr; } HostRegionControl* rval = new HostRegionControl(region_name, fds[0], fds[1], fds[2]); if (!rval) { return nullptr; } // Search for the region header if (!rval->InitializeRegion()) { // We already logged, so we can just bail out. return nullptr; } return std::shared_ptr<RegionControl>(rval); }