blob: 646695f759947d0f4798cfa4e71866bf34eca5d1 [file] [log] [blame]
// Copyright (C) 2019 The Android Open Source Project
// Copyright (C) 2019 Google Inc.
//
// 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 <linux/types.h>
#include <linux/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdlib>
#include <errno.h>
#include <memory>
#if PLATFORM_SDK_VERSION < 26
#include <cutils/log.h>
#else
#include <log/log.h>
#endif
#include "goldfish_address_space.h"
namespace {
struct goldfish_address_space_allocate_block {
__u64 size;
__u64 offset;
__u64 phys_addr;
};
struct goldfish_address_space_claim_shared {
__u64 offset;
__u64 size;
};
#define GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC 'G'
#define GOLDFISH_ADDRESS_SPACE_IOCTL_OP(OP, T) _IOWR(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, OP, T)
#define GOLDFISH_ADDRESS_SPACE_IOCTL_ALLOCATE_BLOCK GOLDFISH_ADDRESS_SPACE_IOCTL_OP(10, struct goldfish_address_space_allocate_block)
#define GOLDFISH_ADDRESS_SPACE_IOCTL_DEALLOCATE_BLOCK GOLDFISH_ADDRESS_SPACE_IOCTL_OP(11, __u64)
#define GOLDFISH_ADDRESS_SPACE_IOCTL_PING GOLDFISH_ADDRESS_SPACE_IOCTL_OP(12, struct goldfish_address_space_ping)
#define GOLDFISH_ADDRESS_SPACE_IOCTL_CLAIM_SHARED GOLDFISH_ADDRESS_SPACE_IOCTL_OP(13, struct goldfish_address_space_claim_shared)
#define GOLDFISH_ADDRESS_SPACE_IOCTL_UNCLAIM_SHARED GOLDFISH_ADDRESS_SPACE_IOCTL_OP(14, __u64)
const char GOLDFISH_ADDRESS_SPACE_DEVICE_NAME[] = "/dev/goldfish_address_space";
const int HOST_MEMORY_ALLOCATOR_COMMAND_ALLOCATE_ID = 1;
const int HOST_MEMORY_ALLOCATOR_COMMAND_UNALLOCATE_ID = 2;
int create_address_space_fd()
{
return ::open(GOLDFISH_ADDRESS_SPACE_DEVICE_NAME, O_RDWR);
}
long ioctl_allocate(int fd, struct goldfish_address_space_allocate_block *request)
{
return ::ioctl(fd, GOLDFISH_ADDRESS_SPACE_IOCTL_ALLOCATE_BLOCK, request);
}
long ioctl_deallocate(int fd, uint64_t offset)
{
return ::ioctl(fd, GOLDFISH_ADDRESS_SPACE_IOCTL_DEALLOCATE_BLOCK, &offset);
}
long ioctl_ping(int fd, struct goldfish_address_space_ping *request)
{
return ::ioctl(fd, GOLDFISH_ADDRESS_SPACE_IOCTL_PING, request);
}
long set_address_space_subdevice_type(int fd, uint64_t type)
{
struct goldfish_address_space_ping request;
::memset(&request, 0, sizeof(request));
request.version = sizeof(request);
request.metadata = type;
return ioctl_ping(fd, &request);
}
long ioctl_claim_shared(int fd, struct goldfish_address_space_claim_shared *request)
{
return ::ioctl(fd, GOLDFISH_ADDRESS_SPACE_IOCTL_CLAIM_SHARED, request);
}
long ioctl_unclaim_shared(int fd, uint64_t offset)
{
return ::ioctl(fd, GOLDFISH_ADDRESS_SPACE_IOCTL_UNCLAIM_SHARED, &offset);
}
} // namespace
GoldfishAddressSpaceBlockProvider::GoldfishAddressSpaceBlockProvider(GoldfishAddressSpaceSubdeviceType subdevice)
: m_handle(create_address_space_fd())
{
if ((subdevice != GoldfishAddressSpaceSubdeviceType::NoSubdevice) && is_opened()) {
const long ret = set_address_space_subdevice_type(m_handle, subdevice);
if (ret) {
ALOGE("%s: set_address_space_subdevice_type failed for device_type=%lu, ret=%ld",
__func__, static_cast<unsigned long>(subdevice), ret);
close();
}
}
}
GoldfishAddressSpaceBlockProvider::~GoldfishAddressSpaceBlockProvider()
{
if (is_opened()) {
::close(m_handle);
}
}
bool GoldfishAddressSpaceBlockProvider::is_opened() const
{
return m_handle >= 0;
}
void GoldfishAddressSpaceBlockProvider::close()
{
if (is_opened()) {
::close(m_handle);
m_handle = -1;
}
}
address_space_handle_t GoldfishAddressSpaceBlockProvider::release()
{
address_space_handle_t handle = m_handle;
m_handle = -1;
return handle;
}
void GoldfishAddressSpaceBlockProvider::closeHandle(address_space_handle_t handle)
{
::close(handle);
}
GoldfishAddressSpaceBlock::GoldfishAddressSpaceBlock()
: m_handle(-1)
, m_mmaped_ptr(NULL)
, m_phys_addr(0)
, m_host_addr(0)
, m_offset(0)
, m_size(0) {}
GoldfishAddressSpaceBlock::~GoldfishAddressSpaceBlock()
{
destroy();
}
GoldfishAddressSpaceBlock &GoldfishAddressSpaceBlock::operator=(const GoldfishAddressSpaceBlock &rhs)
{
m_mmaped_ptr = rhs.m_mmaped_ptr;
m_phys_addr = rhs.m_phys_addr;
m_host_addr = rhs.m_host_addr;
m_offset = rhs.m_offset;
m_size = rhs.m_size;
m_handle = rhs.m_handle;
return *this;
}
bool GoldfishAddressSpaceBlock::allocate(GoldfishAddressSpaceBlockProvider *provider, size_t size)
{
ALOGD("%s: Ask for block of size 0x%llx\n", __func__,
(unsigned long long)size);
destroy();
if (!provider->is_opened()) {
return false;
}
struct goldfish_address_space_allocate_block request;
::memset(&request, 0, sizeof(request));
request.size = size;
long res = ioctl_allocate(provider->m_handle, &request);
if (res) {
return false;
} else {
m_phys_addr = request.phys_addr;
m_offset = request.offset;
m_size = request.size;
m_handle = provider->m_handle;
m_is_shared_mapping = false;
ALOGD("%s: ioctl allocate returned offset 0x%llx size 0x%llx\n", __func__,
(unsigned long long)m_offset,
(unsigned long long)m_size);
return true;
}
}
bool GoldfishAddressSpaceBlock::claimShared(GoldfishAddressSpaceBlockProvider *provider, uint64_t offset, uint64_t size)
{
ALOGD("%s: Ask to claim region [0x%llx 0x%llx]\n", __func__,
(unsigned long long)offset,
(unsigned long long)offset + size);
destroy();
if (!provider->is_opened()) {
return false;
}
struct goldfish_address_space_claim_shared request;
request.offset = offset;
request.size = size;
long res = ioctl_claim_shared(provider->m_handle, &request);
if (res) {
return false;
}
m_offset = offset;
m_size = size;
m_handle = provider->m_handle;
m_is_shared_mapping = true;
return true;
}
uint64_t GoldfishAddressSpaceBlock::physAddr() const
{
return m_phys_addr;
}
uint64_t GoldfishAddressSpaceBlock::hostAddr() const
{
return m_host_addr;
}
void *GoldfishAddressSpaceBlock::mmap(uint64_t host_addr)
{
if (m_size == 0) {
ALOGE("%s: called with zero size\n", __func__);
return NULL;
}
if (m_mmaped_ptr) {
ALOGE("'mmap' called for an already mmaped address block");
::abort();
}
void *result;
const int res = memoryMap(NULL, m_size, m_handle, m_offset, &result);
if (res) {
ALOGE("%s: host memory map failed with size 0x%llx "
"off 0x%llx errno %d\n",
__func__,
(unsigned long long)m_size,
(unsigned long long)m_offset, res);
return NULL;
} else {
m_mmaped_ptr = result;
m_host_addr = host_addr;
return guestPtr();
}
}
void *GoldfishAddressSpaceBlock::guestPtr() const
{
return reinterpret_cast<char *>(m_mmaped_ptr) + (m_host_addr & (PAGE_SIZE - 1));
}
void GoldfishAddressSpaceBlock::destroy()
{
if (m_mmaped_ptr && m_size) {
memoryUnmap(m_mmaped_ptr, m_size);
m_mmaped_ptr = NULL;
}
if (m_size) {
long res = -EINVAL;
if (m_is_shared_mapping) {
res = ioctl_unclaim_shared(m_handle, m_offset);
if (res) {
ALOGE("ioctl_unclaim_shared failed, res=%ld", res);
::abort();
}
} else {
res = ioctl_deallocate(m_handle, m_offset);
if (res) {
ALOGE("ioctl_deallocate failed, res=%ld", res);
::abort();
}
}
m_is_shared_mapping = false;
m_phys_addr = 0;
m_host_addr = 0;
m_offset = 0;
m_size = 0;
}
}
void GoldfishAddressSpaceBlock::release()
{
m_handle = -1;
m_mmaped_ptr = NULL;
m_phys_addr = 0;
m_host_addr = 0;
m_offset = 0;
m_size = 0;
}
int GoldfishAddressSpaceBlock::memoryMap(void *addr,
size_t len,
address_space_handle_t fd,
uint64_t off,
void** dst) {
void* ptr = ::mmap64(addr, len, PROT_WRITE, MAP_SHARED, fd, off);
if (MAP_FAILED == ptr) {
return errno;
} else {
*dst = ptr;
return 0;
}
}
void GoldfishAddressSpaceBlock::memoryUnmap(void *ptr, size_t size)
{
::munmap(ptr, size);
}
GoldfishAddressSpaceHostMemoryAllocator::GoldfishAddressSpaceHostMemoryAllocator()
: m_provider(GoldfishAddressSpaceSubdeviceType::HostMemoryAllocator) {}
bool GoldfishAddressSpaceHostMemoryAllocator::is_opened() const { return m_provider.is_opened(); }
long GoldfishAddressSpaceHostMemoryAllocator::hostMalloc(GoldfishAddressSpaceBlock *block, size_t size)
{
if (size == 0) {
return -EINVAL;
}
if (block->size() > 0) {
return -EINVAL;
}
if (!m_provider.is_opened()) {
return -ENODEV;
}
if (!block->allocate(&m_provider, size)) {
return -ENOMEM;
}
struct goldfish_address_space_ping request;
::memset(&request, 0, sizeof(request));
request.version = sizeof(request);
request.offset = block->offset();
request.size = block->size();
request.metadata = HOST_MEMORY_ALLOCATOR_COMMAND_ALLOCATE_ID;
long ret = ioctl_ping(m_provider.m_handle, &request);
if (ret) {
return ret;
}
ret = static_cast<long>(request.metadata);
if (ret) {
return ret;
}
block->mmap(0);
return 0;
}
void GoldfishAddressSpaceHostMemoryAllocator::hostFree(GoldfishAddressSpaceBlock *block)
{
if (block->size() == 0) {
return;
}
if (!m_provider.is_opened()) {
ALOGE("%s: device is not available", __func__);
::abort();
}
if (block->guestPtr()) {
struct goldfish_address_space_ping request;
::memset(&request, 0, sizeof(request));
request.version = sizeof(request);
request.offset = block->offset();
request.metadata = HOST_MEMORY_ALLOCATOR_COMMAND_UNALLOCATE_ID;
const long ret = ioctl_ping(m_provider.m_handle, &request);
if (ret) {
ALOGE("%s: ioctl_ping failed, ret=%ld", __func__, ret);
::abort();
}
}
block->replace(NULL);
}
address_space_handle_t goldfish_address_space_open() {
return ::open(GOLDFISH_ADDRESS_SPACE_DEVICE_NAME, O_RDWR);
}
void goldfish_address_space_close(address_space_handle_t handle) {
::close(handle);
}
bool goldfish_address_space_allocate(
address_space_handle_t handle,
size_t size, uint64_t* phys_addr, uint64_t* offset) {
struct goldfish_address_space_allocate_block request;
::memset(&request, 0, sizeof(request));
request.size = size;
long res = ioctl_allocate(handle, &request);
if (res) return false;
*phys_addr = request.phys_addr;
*offset = request.offset;
return true;
}
bool goldfish_address_space_free(
address_space_handle_t handle, uint64_t offset) {
long res = ioctl_deallocate(handle, offset);
if (res) {
ALOGE("ioctl_deallocate failed, res=%ld", res);
::abort();
}
return true;
}
bool goldfish_address_space_claim_shared(
address_space_handle_t handle, uint64_t offset, uint64_t size) {
struct goldfish_address_space_claim_shared request;
request.offset = offset;
request.size = size;
long res = ioctl_claim_shared(handle, &request);
if (res) return false;
return true;
}
bool goldfish_address_space_unclaim_shared(
address_space_handle_t handle, uint64_t offset) {
long res = ioctl_unclaim_shared(handle, offset);
if (res) {
ALOGE("ioctl_unclaim_shared failed, res=%ld", res);
::abort();
}
return true;
}
// pgoff is the offset into the page to return in the result
void* goldfish_address_space_map(
address_space_handle_t handle,
uint64_t offset, uint64_t size,
uint64_t pgoff) {
void* res = ::mmap64(0, size, PROT_WRITE, MAP_SHARED, handle, offset);
if (res == MAP_FAILED) {
ALOGE("%s: failed to map. errno: %d\n", __func__, errno);
return 0;
}
return (void*)(((char*)res) + (uintptr_t)(pgoff & (PAGE_SIZE - 1)));
}
void goldfish_address_space_unmap(void* ptr, uint64_t size) {
void* pagePtr = (void*)(((uintptr_t)ptr) & ~(PAGE_SIZE - 1));
::munmap(pagePtr, size);
}
bool goldfish_address_space_set_subdevice_type(
address_space_handle_t handle, GoldfishAddressSpaceSubdeviceType type,
address_space_handle_t* handle_out) {
struct goldfish_address_space_ping request;
request.metadata = (uint64_t)type;
*handle_out = handle;
return goldfish_address_space_ping(handle, &request);
}
bool goldfish_address_space_ping(
address_space_handle_t handle,
struct goldfish_address_space_ping* ping) {
long res = ioctl_ping(handle, ping);
if (res) {
ALOGE("%s: ping failed: errno: %d\n", __func__, errno);
return false;
}
return true;
}