blob: 4ef99859198342f8012d5938693d9cf1a5b7d292 [file] [log] [blame]
/*
* Copyright (C) 2020 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.
*/
#define LOG_TAG "SharedMemoryAndroid"
#include <android-base/logging.h>
#include <android-base/mapped_file.h>
#include <android-base/scopeguard.h>
#include <algorithm>
#include <any>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "Result.h"
#include "SharedMemory.h"
#include "TypeUtils.h"
#include "Types.h"
#ifndef NN_COMPATIBILITY_LIBRARY_BUILD
#include <android/hidl/allocator/1.0/IAllocator.h>
#include <hidl/HidlSupport.h>
#include <hidlmemory/mapping.h>
#include <sys/mman.h>
#endif // NN_COMPATIBILITY_LIBRARY_BUILD
#ifndef NN_NO_AHWB
#include <android/hardware_buffer.h>
#endif // NN_NO_AHWB
namespace android::nn {
namespace {
#ifndef NN_COMPATIBILITY_LIBRARY_BUILD
using ::android::hardware::hidl_memory;
using ::android::hidl::allocator::V1_0::IAllocator;
const char* const kAllocatorService = "ashmem";
GeneralResult<hidl_memory> allocateSharedMemory(size_t size) {
static const auto allocator = IAllocator::getService(kAllocatorService);
CHECK_GT(size, 0u);
hidl_memory maybeMemory;
auto fn = [&maybeMemory](bool success, const hidl_memory& memory) {
if (success) {
maybeMemory = memory;
}
};
allocator->allocate(size, fn);
if (!maybeMemory.valid()) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
<< "IAllocator::allocate returned an invalid (empty) memory object";
}
return maybeMemory;
}
GeneralResult<hardware::hidl_handle> hidlHandleFromSharedHandle(const Handle& handle) {
std::vector<base::unique_fd> fds;
fds.reserve(handle.fds.size());
for (const auto& fd : handle.fds) {
const int dupFd = dup(fd);
if (dupFd == -1) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
}
fds.emplace_back(dupFd);
}
constexpr size_t kMaxInt = std::numeric_limits<int>::max();
CHECK_LE(handle.fds.size(), kMaxInt);
CHECK_LE(handle.ints.size(), kMaxInt);
native_handle_t* nativeHandle = native_handle_create(static_cast<int>(handle.fds.size()),
static_cast<int>(handle.ints.size()));
if (nativeHandle == nullptr) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle";
}
for (size_t i = 0; i < fds.size(); ++i) {
nativeHandle->data[i] = fds[i].release();
}
std::copy(handle.ints.begin(), handle.ints.end(), &nativeHandle->data[nativeHandle->numFds]);
hardware::hidl_handle hidlHandle;
hidlHandle.setTo(nativeHandle, /*shouldOwn=*/true);
return hidlHandle;
}
GeneralResult<Handle> sharedHandleFromNativeHandle(const native_handle_t* handle) {
if (handle == nullptr) {
return NN_ERROR() << "sharedHandleFromNativeHandle was passed nullptr for handle";
}
std::vector<base::unique_fd> fds;
fds.reserve(handle->numFds);
for (int i = 0; i < handle->numFds; ++i) {
const int dupFd = dup(handle->data[i]);
if (dupFd == -1) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
}
fds.emplace_back(dupFd);
}
std::vector<int> ints(&handle->data[handle->numFds],
&handle->data[handle->numFds + handle->numInts]);
return Handle{.fds = std::move(fds), .ints = std::move(ints)};
}
GeneralResult<SharedMemory> createMemory(const hidl_memory& memory) {
CHECK_LE(memory.size(), std::numeric_limits<uint32_t>::max());
return std::make_shared<const Memory>(Memory{
.handle = NN_TRY(sharedHandleFromNativeHandle(memory.handle())),
.size = static_cast<uint32_t>(memory.size()),
.name = memory.name(),
});
}
GeneralResult<hidl_memory> createHidlMemory(const Memory& memory) {
return hidl_memory(memory.name,
NN_TRY(hidlHandleFromSharedHandle(std::get<Handle>(memory.handle))),
memory.size);
}
GeneralResult<Mapping> mapAshmem(const Memory& memory) {
const auto hidlMemory = NN_TRY(createHidlMemory(memory));
const auto mapping = mapMemory(hidlMemory);
if (mapping == nullptr) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to map memory";
}
auto* const pointer = mapping->getPointer().withDefault(nullptr);
if (pointer == nullptr) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to get the mapped pointer";
}
const auto fullSize = mapping->getSize().withDefault(0);
if (fullSize == 0 || fullSize > std::numeric_limits<size_t>::max()) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to get the mapped size";
}
const size_t size = static_cast<size_t>(fullSize);
return Mapping{
.pointer = pointer,
.size = size,
.context = mapping,
};
}
#endif // NN_COMPATIBILITY_LIBRARY_BUILD
struct MmapFdMappingContext {
int prot;
std::any context;
};
GeneralResult<Mapping> mapMemFd(const Memory& memory) {
const size_t size = memory.size;
const Handle& handle = std::get<Handle>(memory.handle);
const int fd = handle.fds[0];
const int prot = handle.ints[0];
const size_t offset = getOffsetFromInts(handle.ints[1], handle.ints[2]);
std::shared_ptr<base::MappedFile> mapping = base::MappedFile::FromFd(fd, offset, size, prot);
if (mapping == nullptr) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Can't mmap the file descriptor.";
}
void* data = mapping->data();
auto context = MmapFdMappingContext{.prot = prot, .context = std::move(mapping)};
return Mapping{.pointer = data, .size = size, .context = std::move(context)};
}
#ifndef NN_NO_AHWB
GeneralResult<Mapping> mapAhwbBlobMemory(const HardwareBufferHandle& memory) {
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(memory.get(), &desc);
CHECK_EQ(desc.format, AHARDWAREBUFFER_FORMAT_BLOB);
const uint32_t size = desc.width;
void* data = nullptr;
const auto status = AHardwareBuffer_lock(memory.get(), desc.usage, -1, nullptr, &data);
if (status != NO_ERROR) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
<< "Can't lock the AHardwareBuffer. Error: " << status;
}
// Create shared scoped object to munmap.
auto scoped = base::make_scope_guard(
[ahwb = memory.get()] { AHardwareBuffer_unlock(ahwb, nullptr); });
auto sharedScoped = std::make_shared<decltype(scoped)>(std::move(scoped));
return Mapping{.pointer = data, .size = size, .context = std::move(sharedScoped)};
}
GeneralResult<Mapping> mapAhwbMemory(const Memory& /*memory*/) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
<< "Unable to map non-BLOB AHardwareBuffer memory";
}
void freeHardwareBuffer(AHardwareBuffer* buffer) {
if (buffer) {
AHardwareBuffer_release(buffer);
}
}
#endif // NN_NO_AHWB
void freeNoop(AHardwareBuffer* /*buffer*/) {}
} // namespace
#ifndef NN_NO_AHWB
HardwareBufferHandle::HardwareBufferHandle(AHardwareBuffer* handle, bool takeOwnership)
: mHandle(handle, (takeOwnership ? freeHardwareBuffer : freeNoop)) {
CHECK(mHandle != nullptr);
}
#else
HardwareBufferHandle::HardwareBufferHandle(AHardwareBuffer* handle, bool /*takeOwnership*/)
: mHandle(handle, freeNoop) {
CHECK(mHandle != nullptr);
}
#endif // NN_NO_AHWB
AHardwareBuffer* HardwareBufferHandle::get() const {
return mHandle.get();
}
GeneralResult<SharedMemory> createSharedMemory(size_t size) {
#ifndef NN_COMPATIBILITY_LIBRARY_BUILD
const auto memory = NN_TRY(allocateSharedMemory(size));
return createMemory(memory);
#else
(void)size;
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Unimplemented";
#endif // NN_COMPATIBILITY_LIBRARY_BUILD
}
GeneralResult<SharedMemory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset) {
if (size == 0 || fd < 0) {
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Invalid size or fd";
}
// Duplicate the file descriptor so the resultant Memory owns its own version.
int dupFd = dup(fd);
if (dupFd == -1) {
// TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here?
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Failed to dup the fd";
}
std::vector<base::unique_fd> fds;
fds.emplace_back(dupFd);
const auto [lowOffsetBits, highOffsetBits] = getIntsFromOffset(offset);
std::vector<int> ints = {prot, lowOffsetBits, highOffsetBits};
auto handle = Handle{.fds = std::move(fds), .ints = std::move(ints)};
return std::make_shared<const Memory>(
Memory{.handle = std::move(handle), .size = size, .name = "mmap_fd"});
}
GeneralResult<SharedMemory> createSharedMemoryFromAHWB(AHardwareBuffer* ahwb, bool takeOwnership) {
#ifndef NN_NO_AHWB
CHECK(ahwb != nullptr);
auto handle = HardwareBufferHandle(ahwb, takeOwnership);
AHardwareBuffer_Desc bufferDesc;
AHardwareBuffer_describe(ahwb, &bufferDesc);
if (bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB) {
return std::make_shared<const Memory>(Memory{
.handle = std::move(handle),
.size = bufferDesc.width,
.name = "hardware_buffer_blob",
});
}
// memory size is not used for non-BLOB AHWB memory.
return std::make_shared<const Memory>(Memory{
.handle = std::move(handle),
.size = 0,
.name = "hardware_buffer",
});
#else
(void)ahwb;
(void)takeOwnership;
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT)
<< "AHardwareBuffer memory not implemented for support library build yet";
#endif // NN_NO_AHWB
}
GeneralResult<Mapping> map(const SharedMemory& memory) {
if (memory->name == "mmap_fd") {
return mapMemFd(*memory);
}
#ifndef NN_COMPATIBILITY_LIBRARY_BUILD
if (memory->name == "ashmem") {
return mapAshmem(*memory);
}
if (memory->name == "hardware_buffer_blob") {
return mapAhwbBlobMemory(std::get<HardwareBufferHandle>(memory->handle));
}
if (memory->name == "hardware_buffer") {
return mapAhwbMemory(*memory);
}
#endif // NN_COMPATIBILITY_LIBRARY_BUILD
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Cannot map unknown memory " << memory->name;
}
bool flush(const Mapping& mapping) {
if (const auto* mmapFdMapping = std::any_cast<MmapFdMappingContext>(&mapping.context)) {
if (!std::holds_alternative<void*>(mapping.pointer)) {
return true;
}
void* data = std::get<void*>(mapping.pointer);
const int prot = mmapFdMapping->prot;
if (prot & PROT_WRITE) {
const size_t size = mapping.size;
return msync(data, size, MS_SYNC) == 0;
}
}
// No-op for other types of memory.
return true;
}
} // namespace android::nn