Merge changes from topic "nnapi-aidl-memory"

* changes:
  Update NN utility code and VTS tests with new Memory type
  Improve the structure of NNAPI AIDL Memory
This commit is contained in:
Michael Butler 2021-04-13 16:29:33 +00:00 committed by Gerrit Code Review
commit 6594b5f1b4
17 changed files with 557 additions and 187 deletions

View file

@ -0,0 +1,39 @@
/*
* Copyright 2021 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.common;
@VintfStability
parcelable Ashmem {
ParcelFileDescriptor fd;
long size;
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2021 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.common;
@VintfStability
parcelable MappableFile {
long length;
int prot;
ParcelFileDescriptor fd;
long offset;
}

View file

@ -1,14 +1,30 @@
/*
* Copyright 2019 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped

View file

@ -0,0 +1,34 @@
/*
* Copyright 2021 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.
*/
package android.hardware.common;
import android.os.ParcelFileDescriptor;
/**
* Type that holds same memory as the "ashmem" hidl_memory type from HIDL.
*/
@VintfStability
parcelable Ashmem {
/**
* A handle to a memory region.
*/
ParcelFileDescriptor fd;
/**
* Size of the memory region in bytes.
*/
long size;
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2021 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.
*/
package android.hardware.common;
import android.os.ParcelFileDescriptor;
/**
* A region of a file that can be mapped into memory.
*
* In Linux, MappableFile may be used with mmap as `MAP_SHARED`.
*
* MappableFile is compatible with ::android::base::MappedFile.
*/
@VintfStability
parcelable MappableFile {
/**
* Length of the mapping region in bytes.
*/
long length;
/**
* The desired memory protection for the mapping.
*
* In Linux, prot is either `PROT_NONE` (indicating that mapped pages may not be accessed) or
* the bitwise OR of one or more of the following flags:
* - `PROT_READ` (indicating that the mapped pages may be read)
* - `PROT_WRITE` (indicating that the mapped pages may be written)
*/
int prot;
/**
* A handle to a mappable file.
*/
ParcelFileDescriptor fd;
/**
* The offset in the file to the beginning of the mapping region in number of bytes.
*
* Note: Some mapping functions require that the offset is aligned to the page size.
*/
long offset;
}

View file

@ -18,6 +18,11 @@ cc_library_static {
"android.hardware.common-V2-ndk_platform",
"libcutils",
],
apex_available: [
"//apex_available:platform",
"com.android.neuralnetworks",
],
min_sdk_version: "29",
}
cc_test {

View file

@ -34,6 +34,7 @@ aidl_interface {
apex_available: [
"//apex_available:platform",
"com.android.media.swcodec",
"com.android.neuralnetworks",
],
min_sdk_version: "29",
},

View file

@ -16,6 +16,7 @@ aidl_interface {
stability: "vintf",
imports: [
"android.hardware.common",
"android.hardware.graphics.common",
],
backend: {
java: {

View file

@ -33,8 +33,8 @@
package android.hardware.neuralnetworks;
@VintfStability
parcelable Memory {
android.hardware.common.NativeHandle handle;
long size;
String name;
union Memory {
android.hardware.common.Ashmem ashmem;
android.hardware.common.MappableFile mappableFile;
android.hardware.graphics.common.HardwareBuffer hardwareBuffer;
}

View file

@ -15,16 +15,26 @@
*/
package android.hardware.neuralnetworks;
import android.hardware.common.NativeHandle;
import android.os.ParcelFileDescriptor;
import android.hardware.common.Ashmem;
import android.hardware.common.MappableFile;
import android.hardware.graphics.common.HardwareBuffer;
/**
* A type that is used to pass pieces of shared memory between processes.
* The type structure mimics hidl_memory type from HIDL.
* The different types of memory that can be shared across processes.
*/
@VintfStability
parcelable Memory {
NativeHandle handle;
long size;
String name;
union Memory {
/**
* Ashmem hidl_memory type from HIDL.
*/
Ashmem ashmem;
/**
* File that can be mapped.
*/
MappableFile mappableFile;
/**
* AIDL representation of AHardwareBuffer.
*/
HardwareBuffer hardwareBuffer;
}

View file

@ -31,6 +31,8 @@ cc_library_static {
export_include_dirs: ["include"],
cflags: ["-Wthread-safety"],
static_libs: [
"android.hardware.graphics.common-V2-ndk_platform",
"libaidlcommonsupport",
"libarect",
"neuralnetworks_types",
"neuralnetworks_utils_hal_common",
@ -51,7 +53,9 @@ cc_test {
],
static_libs: [
"android.hardware.common-V2-ndk_platform",
"android.hardware.graphics.common-V2-ndk_platform",
"android.hardware.neuralnetworks-V1-ndk_platform",
"libaidlcommonsupport",
"libgmock",
"libneuralnetworks_common",
"neuralnetworks_types",

View file

@ -16,8 +16,13 @@
#include "Conversions.h"
#include <aidl/android/hardware/common/Ashmem.h>
#include <aidl/android/hardware/common/MappableFile.h>
#include <aidl/android/hardware/common/NativeHandle.h>
#include <aidl/android/hardware/graphics/common/HardwareBuffer.h>
#include <aidlcommonsupport/NativeHandle.h>
#include <android-base/logging.h>
#include <android-base/mapped_file.h>
#include <android-base/unique_fd.h>
#include <android/binder_auto_utils.h>
#include <android/hardware_buffer.h>
@ -125,28 +130,17 @@ struct NativeHandleDeleter {
using UniqueNativeHandle = std::unique_ptr<native_handle_t, NativeHandleDeleter>;
static GeneralResult<UniqueNativeHandle> nativeHandleFromAidlHandle(const NativeHandle& handle) {
std::vector<base::unique_fd> fds;
fds.reserve(handle.fds.size());
for (const auto& fd : handle.fds) {
auto duplicatedFd = NN_TRY(dupFd(fd.get()));
fds.emplace_back(duplicatedFd.release());
GeneralResult<UniqueNativeHandle> nativeHandleFromAidlHandle(const NativeHandle& handle) {
auto nativeHandle = UniqueNativeHandle(dupFromAidl(handle));
if (nativeHandle.get() == nullptr) {
return NN_ERROR() << "android::dupFromAidl failed to convert the common::NativeHandle to a "
"native_handle_t";
}
constexpr size_t kIntMax = std::numeric_limits<int>::max();
CHECK_LE(handle.fds.size(), kIntMax);
CHECK_LE(handle.ints.size(), kIntMax);
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() << "Failed to create native_handle";
if (!std::all_of(nativeHandle->data + 0, nativeHandle->data + nativeHandle->numFds,
[](int fd) { return fd >= 0; })) {
return NN_ERROR() << "android::dupFromAidl returned an invalid native_handle_t";
}
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]);
return UniqueNativeHandle(nativeHandle);
return nativeHandle;
}
} // anonymous namespace
@ -353,67 +347,66 @@ GeneralResult<MeasureTiming> unvalidatedConvert(bool measureTiming) {
return measureTiming ? MeasureTiming::YES : MeasureTiming::NO;
}
static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) {
return (value + multiple - 1) / multiple * multiple;
}
GeneralResult<SharedMemory> unvalidatedConvert(const aidl_hal::Memory& memory) {
VERIFY_NON_NEGATIVE(memory.size) << "Memory size must not be negative";
if (memory.size > std::numeric_limits<size_t>::max()) {
return NN_ERROR() << "Memory: size must be <= std::numeric_limits<size_t>::max()";
}
using Tag = aidl_hal::Memory::Tag;
switch (memory.getTag()) {
case Tag::ashmem: {
const auto& ashmem = memory.get<Tag::ashmem>();
VERIFY_NON_NEGATIVE(ashmem.size) << "Memory size must not be negative";
if (ashmem.size > std::numeric_limits<size_t>::max()) {
return NN_ERROR() << "Memory: size must be <= std::numeric_limits<size_t>::max()";
}
if (memory.name != "hardware_buffer_blob") {
return std::make_shared<const Memory>(Memory{
.handle = NN_TRY(unvalidatedConvertHelper(memory.handle)),
.size = static_cast<size_t>(memory.size),
.name = memory.name,
});
}
auto handle = Memory::Ashmem{
.fd = NN_TRY(dupFd(ashmem.fd.get())),
.size = static_cast<size_t>(ashmem.size),
};
return std::make_shared<const Memory>(Memory{.handle = std::move(handle)});
}
case Tag::mappableFile: {
const auto& mappableFile = memory.get<Tag::mappableFile>();
VERIFY_NON_NEGATIVE(mappableFile.length) << "Memory size must not be negative";
VERIFY_NON_NEGATIVE(mappableFile.offset) << "Memory offset must not be negative";
if (mappableFile.length > std::numeric_limits<size_t>::max()) {
return NN_ERROR() << "Memory: size must be <= std::numeric_limits<size_t>::max()";
}
if (mappableFile.offset > std::numeric_limits<size_t>::max()) {
return NN_ERROR() << "Memory: offset must be <= std::numeric_limits<size_t>::max()";
}
const auto size = static_cast<uint32_t>(memory.size);
const auto format = AHARDWAREBUFFER_FORMAT_BLOB;
const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
const uint32_t width = size;
const uint32_t height = 1; // height is always 1 for BLOB mode AHardwareBuffer.
const uint32_t layers = 1; // layers is always 1 for BLOB mode AHardwareBuffer.
const size_t size = static_cast<size_t>(mappableFile.length);
const int prot = mappableFile.prot;
const int fd = mappableFile.fd.get();
const size_t offset = static_cast<size_t>(mappableFile.offset);
const UniqueNativeHandle handle = NN_TRY(nativeHandleFromAidlHandle(memory.handle));
const native_handle_t* nativeHandle = handle.get();
return createSharedMemoryFromFd(size, prot, fd, offset);
}
case Tag::hardwareBuffer: {
const auto& hardwareBuffer = memory.get<Tag::hardwareBuffer>();
// AHardwareBuffer_createFromHandle() might fail because an allocator
// expects a specific stride value. In that case, we try to guess it by
// aligning the width to small powers of 2.
// TODO(b/174120849): Avoid stride assumptions.
AHardwareBuffer* hardwareBuffer = nullptr;
status_t status = UNKNOWN_ERROR;
for (uint32_t alignment : {1, 4, 32, 64, 128, 2, 8, 16}) {
const uint32_t stride = roundUpToMultiple(width, alignment);
AHardwareBuffer_Desc desc{
.width = width,
.height = height,
.layers = layers,
.format = format,
.usage = usage,
.stride = stride,
};
status = AHardwareBuffer_createFromHandle(&desc, nativeHandle,
AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE,
&hardwareBuffer);
if (status == NO_ERROR) {
break;
const UniqueNativeHandle handle =
NN_TRY(nativeHandleFromAidlHandle(hardwareBuffer.handle));
const native_handle_t* nativeHandle = handle.get();
const AHardwareBuffer_Desc desc{
.width = static_cast<uint32_t>(hardwareBuffer.description.width),
.height = static_cast<uint32_t>(hardwareBuffer.description.height),
.layers = static_cast<uint32_t>(hardwareBuffer.description.layers),
.format = static_cast<uint32_t>(hardwareBuffer.description.format),
.usage = static_cast<uint64_t>(hardwareBuffer.description.usage),
.stride = static_cast<uint32_t>(hardwareBuffer.description.stride),
};
AHardwareBuffer* ahwb = nullptr;
const status_t status = AHardwareBuffer_createFromHandle(
&desc, nativeHandle, AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE, &ahwb);
if (status != NO_ERROR) {
return NN_ERROR() << "createFromHandle failed";
}
return createSharedMemoryFromAHWB(ahwb, /*takeOwnership=*/true);
}
}
if (status != NO_ERROR) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
<< "Can't create AHardwareBuffer from handle. Error: " << status;
}
return std::make_shared<const Memory>(Memory{
.handle = HardwareBufferHandle(hardwareBuffer, /*takeOwnership=*/true),
.size = static_cast<size_t>(memory.size),
.name = memory.name,
});
return NN_ERROR() << "Unrecognized Memory::Tag: " << memory.getTag();
}
GeneralResult<Timing> unvalidatedConvert(const aidl_hal::Timing& timing) {
@ -645,20 +638,95 @@ struct overloaded : Ts... {
template <class... Ts>
overloaded(Ts...)->overloaded<Ts...>;
static nn::GeneralResult<common::NativeHandle> aidlHandleFromNativeHandle(
const native_handle_t& handle) {
common::NativeHandle aidlNativeHandle;
nn::GeneralResult<common::NativeHandle> aidlHandleFromNativeHandle(
const native_handle_t& nativeHandle) {
auto handle = ::android::dupToAidl(&nativeHandle);
if (!std::all_of(handle.fds.begin(), handle.fds.end(),
[](const ndk::ScopedFileDescriptor& fd) { return fd.get() >= 0; })) {
return NN_ERROR() << "android::dupToAidl returned an invalid common::NativeHandle";
}
return handle;
}
aidlNativeHandle.fds.reserve(handle.numFds);
for (int i = 0; i < handle.numFds; ++i) {
auto duplicatedFd = NN_TRY(nn::dupFd(handle.data[i]));
aidlNativeHandle.fds.emplace_back(duplicatedFd.release());
nn::GeneralResult<Memory> unvalidatedConvert(const nn::Memory::Ashmem& memory) {
if constexpr (std::numeric_limits<size_t>::max() > std::numeric_limits<int64_t>::max()) {
if (memory.size > std::numeric_limits<int64_t>::max()) {
return (
NN_ERROR()
<< "Memory::Ashmem: size must be <= std::numeric_limits<int64_t>::max()")
.
operator nn::GeneralResult<Memory>();
}
}
aidlNativeHandle.ints = std::vector<int>(&handle.data[handle.numFds],
&handle.data[handle.numFds + handle.numInts]);
auto fd = NN_TRY(nn::dupFd(memory.fd));
auto handle = common::Ashmem{
.fd = ndk::ScopedFileDescriptor(fd.release()),
.size = static_cast<int64_t>(memory.size),
};
return Memory::make<Memory::Tag::ashmem>(std::move(handle));
}
return aidlNativeHandle;
nn::GeneralResult<Memory> unvalidatedConvert(const nn::Memory::Fd& memory) {
if constexpr (std::numeric_limits<size_t>::max() > std::numeric_limits<int64_t>::max()) {
if (memory.size > std::numeric_limits<int64_t>::max()) {
return (NN_ERROR() << "Memory::Fd: size must be <= std::numeric_limits<int64_t>::max()")
.
operator nn::GeneralResult<Memory>();
}
if (memory.offset > std::numeric_limits<int64_t>::max()) {
return (
NN_ERROR()
<< "Memory::Fd: offset must be <= std::numeric_limits<int64_t>::max()")
.
operator nn::GeneralResult<Memory>();
}
}
auto fd = NN_TRY(nn::dupFd(memory.fd));
auto handle = common::MappableFile{
.length = static_cast<int64_t>(memory.size),
.prot = memory.prot,
.fd = ndk::ScopedFileDescriptor(fd.release()),
.offset = static_cast<int64_t>(memory.offset),
};
return Memory::make<Memory::Tag::mappableFile>(std::move(handle));
}
nn::GeneralResult<Memory> unvalidatedConvert(const nn::Memory::HardwareBuffer& memory) {
const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(memory.handle.get());
if (nativeHandle == nullptr) {
return (NN_ERROR() << "unvalidatedConvert failed because AHardwareBuffer_getNativeHandle "
"returned nullptr")
.
operator nn::GeneralResult<Memory>();
}
auto handle = NN_TRY(aidlHandleFromNativeHandle(*nativeHandle));
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(memory.handle.get(), &desc);
const auto description = graphics::common::HardwareBufferDescription{
.width = static_cast<int32_t>(desc.width),
.height = static_cast<int32_t>(desc.height),
.layers = static_cast<int32_t>(desc.layers),
.format = static_cast<graphics::common::PixelFormat>(desc.format),
.usage = static_cast<graphics::common::BufferUsage>(desc.usage),
.stride = static_cast<int32_t>(desc.stride),
};
auto hardwareBuffer = graphics::common::HardwareBuffer{
.description = std::move(description),
.handle = std::move(handle),
};
return Memory::make<Memory::Tag::hardwareBuffer>(std::move(hardwareBuffer));
}
nn::GeneralResult<Memory> unvalidatedConvert(const nn::Memory::Unknown& /*memory*/) {
return (NN_ERROR() << "Unable to convert Unknown memory type")
.
operator nn::GeneralResult<Memory>();
}
} // namespace
@ -693,41 +761,12 @@ nn::GeneralResult<common::NativeHandle> unvalidatedConvert(const nn::SharedHandl
}
nn::GeneralResult<Memory> unvalidatedConvert(const nn::SharedMemory& memory) {
CHECK(memory != nullptr);
if (memory->size > std::numeric_limits<int64_t>::max()) {
return NN_ERROR() << "Memory size doesn't fit into int64_t.";
if (memory == nullptr) {
return (NN_ERROR() << "Unable to convert nullptr memory")
.
operator nn::GeneralResult<Memory>();
}
if (const auto* handle = std::get_if<nn::Handle>(&memory->handle)) {
return Memory{
.handle = NN_TRY(unvalidatedConvert(*handle)),
.size = static_cast<int64_t>(memory->size),
.name = memory->name,
};
}
const auto* ahwb = std::get<nn::HardwareBufferHandle>(memory->handle).get();
AHardwareBuffer_Desc bufferDesc;
AHardwareBuffer_describe(ahwb, &bufferDesc);
if (bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB) {
CHECK_EQ(memory->size, bufferDesc.width);
CHECK_EQ(memory->name, "hardware_buffer_blob");
} else {
CHECK_EQ(memory->size, 0u);
CHECK_EQ(memory->name, "hardware_buffer");
}
const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
if (nativeHandle == nullptr) {
return NN_ERROR() << "unvalidatedConvert failed because AHardwareBuffer_getNativeHandle "
"returned nullptr";
}
return Memory{
.handle = NN_TRY(aidlHandleFromNativeHandle(*nativeHandle)),
.size = static_cast<int64_t>(memory->size),
.name = memory->name,
};
return std::visit([](const auto& x) { return unvalidatedConvert(x); }, memory->handle);
}
nn::GeneralResult<ErrorStatus> unvalidatedConvert(const nn::ErrorStatus& errorStatus) {

View file

@ -16,12 +16,20 @@
#include "Utils.h"
#include <aidl/android/hardware/common/Ashmem.h>
#include <aidl/android/hardware/common/MappableFile.h>
#include <aidl/android/hardware/graphics/common/HardwareBuffer.h>
#include <android/binder_auto_utils.h>
#include <android/binder_status.h>
#include <nnapi/Result.h>
#include <nnapi/SharedMemory.h>
namespace aidl::android::hardware::neuralnetworks::utils {
namespace {
nn::GeneralResult<ndk::ScopedFileDescriptor> clone(const ndk::ScopedFileDescriptor& fd);
using utils::clone;
template <typename Type>
nn::GeneralResult<std::vector<Type>> cloneVec(const std::vector<Type>& arguments) {
std::vector<Type> clonedObjects;
@ -37,24 +45,52 @@ nn::GeneralResult<std::vector<Type>> clone(const std::vector<Type>& arguments) {
return cloneVec(arguments);
}
nn::GeneralResult<ndk::ScopedFileDescriptor> clone(const ndk::ScopedFileDescriptor& fd) {
auto duplicatedFd = NN_TRY(nn::dupFd(fd.get()));
return ndk::ScopedFileDescriptor(duplicatedFd.release());
}
nn::GeneralResult<common::NativeHandle> clone(const common::NativeHandle& handle) {
return common::NativeHandle{
.fds = NN_TRY(cloneVec(handle.fds)),
.ints = handle.ints,
};
}
} // namespace
nn::GeneralResult<Memory> clone(const Memory& memory) {
common::NativeHandle nativeHandle;
nativeHandle.ints = memory.handle.ints;
nativeHandle.fds.reserve(memory.handle.fds.size());
for (const auto& fd : memory.handle.fds) {
const int newFd = dup(fd.get());
if (newFd < 0) {
return NN_ERROR() << "Couldn't dup a file descriptor";
switch (memory.getTag()) {
case Memory::Tag::ashmem: {
const auto& ashmem = memory.get<Memory::Tag::ashmem>();
auto handle = common::Ashmem{
.fd = NN_TRY(clone(ashmem.fd)),
.size = ashmem.size,
};
return Memory::make<Memory::Tag::ashmem>(std::move(handle));
}
case Memory::Tag::mappableFile: {
const auto& memFd = memory.get<Memory::Tag::mappableFile>();
auto handle = common::MappableFile{
.length = memFd.length,
.prot = memFd.prot,
.fd = NN_TRY(clone(memFd.fd)),
.offset = memFd.offset,
};
return Memory::make<Memory::Tag::mappableFile>(std::move(handle));
}
case Memory::Tag::hardwareBuffer: {
const auto& hardwareBuffer = memory.get<Memory::Tag::hardwareBuffer>();
auto handle = graphics::common::HardwareBuffer{
.description = hardwareBuffer.description,
.handle = NN_TRY(clone(hardwareBuffer.handle)),
};
return Memory::make<Memory::Tag::hardwareBuffer>(std::move(handle));
}
nativeHandle.fds.emplace_back(newFd);
}
return Memory{
.handle = std::move(nativeHandle),
.size = memory.size,
.name = memory.name,
};
return (NN_ERROR() << "Unrecognized Memory::Tag: " << memory.getTag())
.
operator nn::GeneralResult<Memory>();
}
nn::GeneralResult<RequestMemoryPool> clone(const RequestMemoryPool& requestPool) {

View file

@ -50,9 +50,11 @@ cc_test {
],
static_libs: [
"android.hardware.common-V2-ndk_platform",
"android.hardware.graphics.common-V2-ndk_platform",
"android.hardware.neuralnetworks-V1-ndk_platform",
"android.hidl.allocator@1.0",
"android.hidl.memory@1.0",
"libaidlcommonsupport",
"libgmock",
"libhidlmemory",
"libneuralnetworks_generated_test_harness",

View file

@ -16,6 +16,7 @@
#define LOG_TAG "neuralnetworks_aidl_hal_test"
#include <aidl/android/hardware/graphics/common/PixelFormat.h>
#include <android-base/logging.h>
#include <android/binder_auto_utils.h>
#include <android/binder_interface_utils.h>
@ -659,10 +660,26 @@ class MemoryDomainCopyTestBase : public MemoryDomainTestBase {
return allocateBuffer(preparedModel, inputIndexes, outputIndexes, {});
}
size_t getSize(const Memory& memory) {
switch (memory.getTag()) {
case Memory::Tag::ashmem:
return memory.get<Memory::Tag::ashmem>().size;
case Memory::Tag::mappableFile:
return memory.get<Memory::Tag::mappableFile>().length;
case Memory::Tag::hardwareBuffer: {
const auto& hardwareBuffer = memory.get<Memory::Tag::hardwareBuffer>();
const bool isBlob =
hardwareBuffer.description.format == graphics::common::PixelFormat::BLOB;
return isBlob ? hardwareBuffer.description.width : 0;
}
}
return 0;
}
Memory allocateSharedMemory(uint32_t size) {
const auto sharedMemory = nn::createSharedMemory(size).value();
auto memory = utils::convert(sharedMemory).value();
EXPECT_EQ(memory.size, size);
EXPECT_EQ(getSize(memory), size);
return memory;
}
@ -690,7 +707,7 @@ class MemoryDomainCopyTestBase : public MemoryDomainTestBase {
void initializeDeviceMemory(const std::shared_ptr<IBuffer>& buffer) {
Memory memory = allocateSharedMemory(kTestOperandDataSize);
ASSERT_EQ(memory.size, kTestOperandDataSize);
ASSERT_EQ(getSize(memory), kTestOperandDataSize);
testCopyFrom(buffer, memory, utils::toSigned(kTestOperand.dimensions).value(),
ErrorStatus::NONE);
}

View file

@ -259,12 +259,16 @@ template <>
size_t sizeForBinder(const Memory& memory) {
// This is just a guess.
size_t size = 0;
const NativeHandle& handle = memory.handle;
size += sizeof(decltype(handle.fds)::value_type) * handle.fds.size();
size += sizeof(decltype(handle.ints)::value_type) * handle.ints.size();
size += sizeForBinder(memory.name);
size += sizeof(memory);
size_t size = sizeof(Memory);
// Only hardwareBuffer type memory has dynamic memory that needs to be accounted for (in the
// form of a NativeHandle type). The other other types of memory (MappableFile, Ashmem) use a
// single file descriptor (with metadata) instead.
if (memory.getTag() == Memory::Tag::hardwareBuffer) {
const NativeHandle& handle = memory.get<Memory::Tag::hardwareBuffer>().handle;
size += sizeof(decltype(handle.fds)::value_type) * handle.fds.size();
size += sizeof(decltype(handle.ints)::value_type) * handle.ints.size();
}
return size;
}

View file

@ -89,6 +89,59 @@ void copyPointersToSharedMemory(nn::Model::Subgraph* subgraph,
});
}
nn::GeneralResult<hidl_handle> createNativeHandleFrom(base::unique_fd fd,
const std::vector<int32_t>& ints) {
constexpr size_t kIntMax = std::numeric_limits<int>::max();
CHECK_LE(ints.size(), kIntMax);
native_handle_t* nativeHandle = native_handle_create(1, static_cast<int>(ints.size()));
if (nativeHandle == nullptr) {
return NN_ERROR() << "Failed to create native_handle";
}
nativeHandle->data[0] = fd.release();
std::copy(ints.begin(), ints.end(), nativeHandle->data + 1);
hidl_handle handle;
handle.setTo(nativeHandle, /*shouldOwn=*/true);
return handle;
}
nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Ashmem& memory) {
auto fd = NN_TRY(nn::dupFd(memory.fd));
auto handle = NN_TRY(createNativeHandleFrom(std::move(fd), {}));
return hidl_memory("ashmem", std::move(handle), memory.size);
}
nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Fd& memory) {
auto fd = NN_TRY(nn::dupFd(memory.fd));
const auto [lowOffsetBits, highOffsetBits] = nn::getIntsFromOffset(memory.offset);
const std::vector<int> ints = {memory.prot, lowOffsetBits, highOffsetBits};
auto handle = NN_TRY(createNativeHandleFrom(std::move(fd), ints));
return hidl_memory("mmap_fd", std::move(handle), memory.size);
}
nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::HardwareBuffer& memory) {
const auto* ahwb = memory.handle.get();
AHardwareBuffer_Desc bufferDesc;
AHardwareBuffer_describe(ahwb, &bufferDesc);
const bool isBlob = bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB;
const size_t size = isBlob ? bufferDesc.width : 0;
const char* const name = isBlob ? "hardware_buffer_blob" : "hardware_buffer";
const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
const hidl_handle hidlHandle(nativeHandle);
hidl_handle copiedHandle(hidlHandle);
return hidl_memory(name, std::move(copiedHandle), size);
}
nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Unknown& memory) {
return hidl_memory(memory.name, NN_TRY(hidlHandleFromSharedHandle(memory.handle)), memory.size);
}
} // anonymous namespace
nn::Capabilities::OperandPerformanceTable makeQuantized8PerformanceConsistentWithP(
@ -255,27 +308,7 @@ nn::GeneralResult<hidl_memory> createHidlMemoryFromSharedMemory(const nn::Shared
if (memory == nullptr) {
return NN_ERROR() << "Memory must be non-empty";
}
if (const auto* handle = std::get_if<nn::Handle>(&memory->handle)) {
return hidl_memory(memory->name, NN_TRY(hidlHandleFromSharedHandle(*handle)), memory->size);
}
const auto* ahwb = std::get<nn::HardwareBufferHandle>(memory->handle).get();
AHardwareBuffer_Desc bufferDesc;
AHardwareBuffer_describe(ahwb, &bufferDesc);
if (bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB) {
CHECK_EQ(memory->size, bufferDesc.width);
CHECK_EQ(memory->name, "hardware_buffer_blob");
} else {
CHECK_EQ(memory->size, 0u);
CHECK_EQ(memory->name, "hardware_buffer");
}
const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
const hidl_handle hidlHandle(nativeHandle);
hidl_handle handle(hidlHandle);
return hidl_memory(memory->name, std::move(handle), memory->size);
return std::visit([](const auto& x) { return createHidlMemoryFrom(x); }, memory->handle);
}
static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) {
@ -283,14 +316,53 @@ static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) {
}
nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory) {
CHECK_LE(memory.size(), std::numeric_limits<uint32_t>::max());
CHECK_LE(memory.size(), std::numeric_limits<size_t>::max());
if (!memory.valid()) {
return NN_ERROR() << "Unable to convert invalid hidl_memory";
}
if (memory.name() == "ashmem") {
if (memory.handle()->numFds != 1) {
return NN_ERROR() << "Unable to convert invalid ashmem memory object with "
<< memory.handle()->numFds << " numFds, but expected 1";
}
if (memory.handle()->numInts != 0) {
return NN_ERROR() << "Unable to convert invalid ashmem memory object with "
<< memory.handle()->numInts << " numInts, but expected 0";
}
auto handle = nn::Memory::Ashmem{
.fd = NN_TRY(nn::dupFd(memory.handle()->data[0])),
.size = static_cast<size_t>(memory.size()),
};
return std::make_shared<const nn::Memory>(nn::Memory{.handle = std::move(handle)});
}
if (memory.name() == "mmap_fd") {
if (memory.handle()->numFds != 1) {
return NN_ERROR() << "Unable to convert invalid mmap_fd memory object with "
<< memory.handle()->numFds << " numFds, but expected 1";
}
if (memory.handle()->numInts != 3) {
return NN_ERROR() << "Unable to convert invalid mmap_fd memory object with "
<< memory.handle()->numInts << " numInts, but expected 3";
}
const int fd = memory.handle()->data[0];
const int prot = memory.handle()->data[1];
const int lower = memory.handle()->data[2];
const int higher = memory.handle()->data[3];
const size_t offset = nn::getOffsetFromInts(lower, higher);
return nn::createSharedMemoryFromFd(static_cast<size_t>(memory.size()), prot, fd, offset);
}
if (memory.name() != "hardware_buffer_blob") {
return std::make_shared<const nn::Memory>(nn::Memory{
auto handle = nn::Memory::Unknown{
.handle = NN_TRY(sharedHandleFromNativeHandle(memory.handle())),
.size = static_cast<uint32_t>(memory.size()),
.size = static_cast<size_t>(memory.size()),
.name = memory.name(),
});
};
return std::make_shared<const nn::Memory>(nn::Memory{.handle = std::move(handle)});
}
const auto size = memory.size();
@ -328,11 +400,7 @@ nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_
<< "Can't create AHardwareBuffer from handle. Error: " << status;
}
return std::make_shared<const nn::Memory>(nn::Memory{
.handle = nn::HardwareBufferHandle(hardwareBuffer, /*takeOwnership=*/true),
.size = static_cast<uint32_t>(memory.size()),
.name = memory.name(),
});
return nn::createSharedMemoryFromAHWB(hardwareBuffer, /*takeOwnership=*/true);
}
nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::Handle& handle) {