/*
* 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"
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <thread>
#include <android-base/logging.h>
#include <uapi/vsoc_shm.h>
using cvd::SharedFD;
namespace {
class GuestRegionControl : public vsoc::RegionControl {
public:
explicit GuestRegionControl(const SharedFD& region_fd,
const vsoc_device_region& desc)
: region_fd_{region_fd} {
region_desc_ = desc;
}
virtual bool InterruptPeer() override;
virtual void InterruptSelf() override;
virtual void WaitForInterrupt() override;
virtual void* Map() override;
virtual int SignalSelf(uint32_t offset) override;
virtual int WaitForSignal(uint32_t offset, uint32_t expected_value) override;
protected:
int CreateFdScopedPermission(const char* managed_region_name,
uint32_t owner_offset, uint32_t owned_val,
uint32_t begin_offset,
uint32_t end_offset) override;
cvd::SharedFD region_fd_;
};
std::string device_path_from_name(const char* region_name) {
return std::string("/dev/") + region_name;
}
bool GuestRegionControl::InterruptPeer() {
int rval = region_fd_->Ioctl(VSOC_SEND_INTERRUPT_TO_HOST, 0);
if ((rval == -1) && (errno != EBUSY)) {
LOG(INFO) << __FUNCTION__ << ": ioctl failed (" << strerror(errno) << ")";
}
return !rval;
}
void GuestRegionControl::InterruptSelf() {
region_fd_->Ioctl(VSOC_SELF_INTERRUPT, 0);
}
void GuestRegionControl::WaitForInterrupt() {
region_fd_->Ioctl(VSOC_WAIT_FOR_INCOMING_INTERRUPT, 0);
}
int GuestRegionControl::SignalSelf(uint32_t offset) {
return region_fd_->Ioctl(VSOC_COND_WAKE, reinterpret_cast<void*>(offset));
}
int GuestRegionControl::WaitForSignal(uint32_t offset,
uint32_t expected_value) {
struct vsoc_cond_wait wait;
wait.offset = offset;
wait.value = expected_value;
wait.wake_time_sec = 0;
wait.wake_time_nsec = 0;
wait.wait_type = VSOC_WAIT_IF_EQUAL;
wait.wakes = 1000;
wait.reserved_1 = 0;
int rval = region_fd_->Ioctl(VSOC_COND_WAIT, &wait);
if (rval == -1) {
return rval;
}
// Clamp the number of wakes if it overflows an integer.
rval = wait.wakes;
if (rval >= 0) {
return rval;
}
return INT_MAX;
}
int GuestRegionControl::CreateFdScopedPermission(
const char* managed_region_name, uint32_t owner_offset,
uint32_t owned_value, uint32_t begin_offset,
uint32_t end_offset) {
if (!region_fd_->IsOpen()) {
LOG(FATAL) << "Can't create permission before opening controller region";
return -EINVAL;
}
int managed_region_fd =
open(device_path_from_name(managed_region_name).c_str(), O_RDWR);
if (managed_region_fd < 0) {
int errno_ = errno;
LOG(FATAL) << "Can't open managed region: " << managed_region_name;
return -errno_;
}
fd_scoped_permission_arg perm;
perm.perm.begin_offset = begin_offset;
perm.perm.end_offset = end_offset;
perm.perm.owned_value = owned_value;
perm.perm.owner_offset = owner_offset;
perm.managed_region_fd = managed_region_fd;
LOG(INFO) << "owner offset: " << perm.perm.owner_offset;
int retval = region_fd_->Ioctl(VSOC_CREATE_FD_SCOPED_PERMISSION, &perm);
if (retval) {
int errno_ = errno;
close(managed_region_fd);
if (errno_ != EBUSY) {
LOG(FATAL) << "Unable to create fd scoped permission ("
<< strerror(errno_) << ")";
}
return -errno_;
}
return managed_region_fd;
}
void* GuestRegionControl::Map() {
region_base_ =
region_fd_->Mmap(0, region_size(), PROT_READ | PROT_WRITE, MAP_SHARED, 0);
if (region_base_ == MAP_FAILED) {
LOG(FATAL) << "mmap failed (" << region_fd_->StrError() << ")";
region_base_ = nullptr;
}
return region_base_;
}
} // namespace
// domain is here to ensure that this method has the same signature as the
// method on host regions.
std::shared_ptr<vsoc::RegionControl> vsoc::RegionControl::Open(
const char* region_name) {
std::string path = device_path_from_name(region_name);
SharedFD fd = SharedFD::Open(path.c_str(), O_RDWR);
if (!fd->IsOpen()) {
LOG(FATAL) << "Unable to open region " << region_name << " ("
<< fd->StrError() << ")";
return nullptr;
}
vsoc_device_region desc;
if (fd->Ioctl(VSOC_DESCRIBE_REGION, &desc)) {
LOG(FATAL) << "Unable to obtain region descriptor (" << fd->StrError()
<< ")";
return nullptr;
}
return std::shared_ptr<vsoc::RegionControl>(new GuestRegionControl(fd, desc));
}