/*
 * 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));
}