diff options
author | Scott Lobdell <slobdell@google.com> | 2021-02-23 11:55:14 -0800 |
---|---|---|
committer | Scott Lobdell <slobdell@google.com> | 2021-02-23 11:55:14 -0800 |
commit | 86bfa300dfbcf500ad04bede19a2b5f0e6d418b9 (patch) | |
tree | 0b635f8b37f8adf728064d7615f4bba25b51e418 | |
parent | 7b82a0f697d0cf832803a80f7ed2128002b54dec (diff) | |
parent | f6fd33b5fdc12948537d800af8695ff6767039c2 (diff) |
Merge SP1A.210222.001
Change-Id: I49bafb9c4e7adcb330e0e4c01111788b6ed84a00
352 files changed, 26361 insertions, 2818 deletions
diff --git a/audio/5.0/config/api/current.txt b/audio/5.0/config/api/current.txt index 8458a569c5..dbb5d3bbd6 100644 --- a/audio/5.0/config/api/current.txt +++ b/audio/5.0/config/api/current.txt @@ -199,7 +199,7 @@ package audio.policy.configuration.V5_0 { public static class DevicePorts.DevicePort { ctor public DevicePorts.DevicePort(); method public String getAddress(); - method public java.util.List<audio.policy.configuration.V5_0.AudioFormat> getEncodedFormats(); + method public java.util.List<java.lang.String> getEncodedFormats(); method public audio.policy.configuration.V5_0.Gains getGains(); method public java.util.List<audio.policy.configuration.V5_0.Profile> getProfile(); method public audio.policy.configuration.V5_0.Role getRole(); @@ -207,7 +207,7 @@ package audio.policy.configuration.V5_0 { method public String getType(); method public boolean get_default(); method public void setAddress(String); - method public void setEncodedFormats(java.util.List<audio.policy.configuration.V5_0.AudioFormat>); + method public void setEncodedFormats(java.util.List<java.lang.String>); method public void setGains(audio.policy.configuration.V5_0.Gains); method public void setRole(audio.policy.configuration.V5_0.Role); method public void setTagName(String); @@ -380,10 +380,10 @@ package audio.policy.configuration.V5_0 { public static class SurroundFormats.Format { ctor public SurroundFormats.Format(); - method public audio.policy.configuration.V5_0.AudioFormat getName(); - method public java.util.List<audio.policy.configuration.V5_0.AudioFormat> getSubformats(); - method public void setName(audio.policy.configuration.V5_0.AudioFormat); - method public void setSubformats(java.util.List<audio.policy.configuration.V5_0.AudioFormat>); + method public String getName(); + method public java.util.List<java.lang.String> getSubformats(); + method public void setName(String); + method public void setSubformats(java.util.List<java.lang.String>); } public class SurroundSound { diff --git a/audio/5.0/config/audio_policy_configuration.xsd b/audio/5.0/config/audio_policy_configuration.xsd index b0d1e204bb..f92136cf0a 100644 --- a/audio/5.0/config/audio_policy_configuration.xsd +++ b/audio/5.0/config/audio_policy_configuration.xsd @@ -611,13 +611,13 @@ </xs:sequence> </xs:complexType> <xs:simpleType name="audioFormatsList"> - <xs:list itemType="audioFormat" /> + <xs:list itemType="extendableAudioFormat" /> </xs:simpleType> <xs:complexType name="surroundFormats"> <xs:sequence> <xs:element name="format" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> - <xs:attribute name="name" type="audioFormat" use="required"/> + <xs:attribute name="name" type="extendableAudioFormat" use="required"/> <xs:attribute name="subformats" type="audioFormatsList" /> </xs:complexType> </xs:element> diff --git a/audio/6.0/config/api/current.txt b/audio/6.0/config/api/current.txt index f5d4798603..01db90e818 100644 --- a/audio/6.0/config/api/current.txt +++ b/audio/6.0/config/api/current.txt @@ -199,7 +199,7 @@ package audio.policy.configuration.V6_0 { public static class DevicePorts.DevicePort { ctor public DevicePorts.DevicePort(); method public String getAddress(); - method public java.util.List<audio.policy.configuration.V6_0.AudioFormat> getEncodedFormats(); + method public java.util.List<java.lang.String> getEncodedFormats(); method public audio.policy.configuration.V6_0.Gains getGains(); method public java.util.List<audio.policy.configuration.V6_0.Profile> getProfile(); method public audio.policy.configuration.V6_0.Role getRole(); @@ -207,7 +207,7 @@ package audio.policy.configuration.V6_0 { method public String getType(); method public boolean get_default(); method public void setAddress(String); - method public void setEncodedFormats(java.util.List<audio.policy.configuration.V6_0.AudioFormat>); + method public void setEncodedFormats(java.util.List<java.lang.String>); method public void setGains(audio.policy.configuration.V6_0.Gains); method public void setRole(audio.policy.configuration.V6_0.Role); method public void setTagName(String); @@ -391,10 +391,10 @@ package audio.policy.configuration.V6_0 { public static class SurroundFormats.Format { ctor public SurroundFormats.Format(); - method public audio.policy.configuration.V6_0.AudioFormat getName(); - method public java.util.List<audio.policy.configuration.V6_0.AudioFormat> getSubformats(); - method public void setName(audio.policy.configuration.V6_0.AudioFormat); - method public void setSubformats(java.util.List<audio.policy.configuration.V6_0.AudioFormat>); + method public String getName(); + method public java.util.List<java.lang.String> getSubformats(); + method public void setName(String); + method public void setSubformats(java.util.List<java.lang.String>); } public class SurroundSound { diff --git a/audio/6.0/config/audio_policy_configuration.xsd b/audio/6.0/config/audio_policy_configuration.xsd index ead1cc20c4..c2b8c5dcb4 100644 --- a/audio/6.0/config/audio_policy_configuration.xsd +++ b/audio/6.0/config/audio_policy_configuration.xsd @@ -614,13 +614,13 @@ </xs:sequence> </xs:complexType> <xs:simpleType name="audioFormatsList"> - <xs:list itemType="audioFormat" /> + <xs:list itemType="extendableAudioFormat" /> </xs:simpleType> <xs:complexType name="surroundFormats"> <xs:sequence> <xs:element name="format" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> - <xs:attribute name="name" type="audioFormat" use="required"/> + <xs:attribute name="name" type="extendableAudioFormat" use="required"/> <xs:attribute name="subformats" type="audioFormatsList" /> </xs:complexType> </xs:element> diff --git a/audio/7.0/config/api/current.txt b/audio/7.0/config/api/current.txt index 49cfd388ff..653531d1f1 100644 --- a/audio/7.0/config/api/current.txt +++ b/audio/7.0/config/api/current.txt @@ -212,6 +212,7 @@ package android.audio.policy.configuration.V7_0 { enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_FLAC; enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_HE_AAC_V1; enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_HE_AAC_V2; + enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_IEC60958; enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_IEC61937; enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_LC3; enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_LDAC; diff --git a/audio/7.0/config/audio_policy_configuration.xsd b/audio/7.0/config/audio_policy_configuration.xsd index f20033d239..31ec64bcf1 100644 --- a/audio/7.0/config/audio_policy_configuration.xsd +++ b/audio/7.0/config/audio_policy_configuration.xsd @@ -407,6 +407,7 @@ <xs:enumeration value="AUDIO_FORMAT_MPEGH_BL_L4"/> <xs:enumeration value="AUDIO_FORMAT_MPEGH_LC_L3"/> <xs:enumeration value="AUDIO_FORMAT_MPEGH_LC_L4"/> + <xs:enumeration value="AUDIO_FORMAT_IEC60958"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="extendableAudioFormat"> diff --git a/audio/common/7.0/types.hal b/audio/common/7.0/types.hal index 99c2e5a0eb..bea07059fc 100644 --- a/audio/common/7.0/types.hal +++ b/audio/common/7.0/types.hal @@ -61,6 +61,8 @@ struct Uuid { * Audio stream type describing the intended use case of a stream. * See 'audioStreamType' in audio_policy_configuration.xsd for the * list of allowed values. + * + * An empty string is used to specify the "default" stream type. */ typedef string AudioStreamType; diff --git a/audio/common/all-versions/default/7.0/HidlUtils.cpp b/audio/common/all-versions/default/7.0/HidlUtils.cpp index bb3a5968b5..2949fac293 100644 --- a/audio/common/all-versions/default/7.0/HidlUtils.cpp +++ b/audio/common/all-versions/default/7.0/HidlUtils.cpp @@ -335,25 +335,35 @@ status_t HidlUtils::audioSourceToHal(const AudioSource& source, audio_source_t* return BAD_VALUE; } +// The "default" value of audio_stream_type_t is represented by an empty string. status_t HidlUtils::audioStreamTypeFromHal(audio_stream_type_t halStreamType, AudioStreamType* streamType) { - *streamType = audio_stream_type_to_string(halStreamType); - if (!streamType->empty() && !xsd::isUnknownAudioStreamType(*streamType)) { + if (halStreamType != AUDIO_STREAM_DEFAULT) { + *streamType = audio_stream_type_to_string(halStreamType); + if (!streamType->empty() && !xsd::isUnknownAudioStreamType(*streamType)) { + return NO_ERROR; + } + ALOGE("Unknown audio stream type value 0x%X", halStreamType); + return BAD_VALUE; + } else { + *streamType = ""; return NO_ERROR; } - ALOGE("Unknown audio stream type value 0x%X", halStreamType); - return BAD_VALUE; } status_t HidlUtils::audioStreamTypeToHal(const AudioStreamType& streamType, audio_stream_type_t* halStreamType) { - if (!xsd::isUnknownAudioStreamType(streamType) && - audio_stream_type_from_string(streamType.c_str(), halStreamType)) { + if (!streamType.empty()) { + if (!xsd::isUnknownAudioStreamType(streamType) && + audio_stream_type_from_string(streamType.c_str(), halStreamType)) { + return NO_ERROR; + } + ALOGE("Unknown audio stream type \"%s\"", streamType.c_str()); + return BAD_VALUE; + } else { + *halStreamType = AUDIO_STREAM_DEFAULT; return NO_ERROR; } - ALOGE("Unknown audio stream type \"%s\"", streamType.c_str()); - *halStreamType = AUDIO_STREAM_DEFAULT; - return BAD_VALUE; } status_t HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, bool isInput, diff --git a/audio/common/all-versions/default/Android.bp b/audio/common/all-versions/default/Android.bp index 45f0b8f297..29a3d6f46e 100644 --- a/audio/common/all-versions/default/Android.bp +++ b/audio/common/all-versions/default/Android.bp @@ -114,7 +114,7 @@ cc_library_shared { ], } -cc_library_shared { +cc_library { name: "android.hardware.audio.common@6.0-util", defaults: ["android.hardware.audio.common-util_default"], srcs: [":android.hardware.audio.common-util@2-6"], @@ -152,6 +152,32 @@ cc_library { // Note: this isn't a VTS test, but rather a unit test // to verify correctness of conversion utilities. cc_test { + name: "android.hardware.audio.common@6.0-util_tests", + defaults: ["android.hardware.audio.common-util_default"], + + srcs: ["tests/hidlutils6_tests.cpp"], + + // Use static linking to allow running in presubmit on + // targets that don't have HAL V6. + static_libs: [ + "android.hardware.audio.common@6.0", + "android.hardware.audio.common@6.0-util", + ], + + cflags: [ + "-Werror", + "-Wall", + "-DMAJOR_VERSION=6", + "-DMINOR_VERSION=0", + "-include common/all-versions/VersionMacro.h", + ], + + test_suites: ["device-tests"], +} + +// Note: this isn't a VTS test, but rather a unit test +// to verify correctness of conversion utilities. +cc_test { name: "android.hardware.audio.common@7.0-util_tests", defaults: ["android.hardware.audio.common-util_default"], diff --git a/audio/common/all-versions/default/HidlUtils.h b/audio/common/all-versions/default/HidlUtils.h index 22b7152f99..dd4ca4d8ca 100644 --- a/audio/common/all-versions/default/HidlUtils.h +++ b/audio/common/all-versions/default/HidlUtils.h @@ -210,6 +210,9 @@ status_t HidlUtils::deviceAddressToHalImpl(const DA& device, audio_devices_t* ha *halDeviceType == AUDIO_DEVICE_IN_REMOTE_SUBMIX) { snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%s", device.rSubmixAddress.c_str()); + } else { + // Fall back to bus address for other device types, e.g. for microphones. + snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%s", device.busAddress.c_str()); } return NO_ERROR; } @@ -249,6 +252,7 @@ status_t HidlUtils::deviceAddressFromHalImpl(audio_devices_t halDeviceType, device->rSubmixAddress = halDeviceAddress; return OK; } + // Fall back to bus address for other device types, e.g. for microphones. device->busAddress = halDeviceAddress; return NO_ERROR; } diff --git a/audio/common/all-versions/default/TEST_MAPPING b/audio/common/all-versions/default/TEST_MAPPING index 4316ccf4f5..c96511393f 100644 --- a/audio/common/all-versions/default/TEST_MAPPING +++ b/audio/common/all-versions/default/TEST_MAPPING @@ -1,6 +1,9 @@ { "presubmit": [ { + "name": "android.hardware.audio.common@6.0-util_tests" + }, + { "name": "android.hardware.audio.common@7.0-util_tests" } ] diff --git a/audio/common/all-versions/default/tests/hidlutils6_tests.cpp b/audio/common/all-versions/default/tests/hidlutils6_tests.cpp new file mode 100644 index 0000000000..3a24e752bc --- /dev/null +++ b/audio/common/all-versions/default/tests/hidlutils6_tests.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 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. + */ + +#include <gtest/gtest.h> + +#define LOG_TAG "HidlUtils_Test" +#include <log/log.h> + +#include <HidlUtils.h> +#include <system/audio.h> + +using namespace android; +using namespace ::android::hardware::audio::common::CPP_VERSION; +using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils; + +// Not generated automatically because DeviceAddress contains +// an union. +// +// operator== must be defined in the same namespace as the data type. +namespace android::hardware::audio::common::CPP_VERSION { + +inline bool operator==(const DeviceAddress& lhs, const DeviceAddress& rhs) { + if (lhs.device != rhs.device) return false; + audio_devices_t halDeviceType = static_cast<audio_devices_t>(lhs.device); + if (audio_is_a2dp_out_device(halDeviceType) || audio_is_a2dp_in_device(halDeviceType)) { + return lhs.address.mac == rhs.address.mac; + } else if (halDeviceType == AUDIO_DEVICE_OUT_IP || halDeviceType == AUDIO_DEVICE_IN_IP) { + return lhs.address.ipv4 == rhs.address.ipv4; + } else if (audio_is_usb_out_device(halDeviceType) || audio_is_usb_in_device(halDeviceType)) { + return lhs.address.alsa == rhs.address.alsa; + } else if (halDeviceType == AUDIO_DEVICE_OUT_REMOTE_SUBMIX || + halDeviceType == AUDIO_DEVICE_IN_REMOTE_SUBMIX) { + return lhs.rSubmixAddress == rhs.rSubmixAddress; + } + // busAddress field can be used for types other than bus, e.g. for microphones. + return lhs.busAddress == rhs.busAddress; +} + +} // namespace android::hardware::audio::common::CPP_VERSION + +static void ConvertDeviceAddress(const DeviceAddress& device) { + audio_devices_t halDeviceType; + char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN] = {}; + EXPECT_EQ(NO_ERROR, HidlUtils::deviceAddressToHal(device, &halDeviceType, halDeviceAddress)); + DeviceAddress deviceBack; + EXPECT_EQ(NO_ERROR, + HidlUtils::deviceAddressFromHal(halDeviceType, halDeviceAddress, &deviceBack)); + EXPECT_EQ(device, deviceBack); +} + +TEST(HidlUtils6, ConvertUniqueDeviceAddress) { + DeviceAddress speaker; + speaker.device = AudioDevice::OUT_SPEAKER; + ConvertDeviceAddress(speaker); + + DeviceAddress micWithAddress; + micWithAddress.device = AudioDevice::IN_BUILTIN_MIC; + micWithAddress.busAddress = "bottom"; + ConvertDeviceAddress(micWithAddress); +} + +TEST(HidlUtils6, ConvertA2dpDeviceAddress) { + DeviceAddress a2dpSpeaker; + a2dpSpeaker.device = AudioDevice::OUT_BLUETOOTH_A2DP_SPEAKER; + a2dpSpeaker.address.mac = std::array<uint8_t, 6>{1, 2, 3, 4, 5, 6}; + ConvertDeviceAddress(a2dpSpeaker); +} + +TEST(HidlUtils6, ConvertIpv4DeviceAddress) { + DeviceAddress ipv4; + ipv4.device = AudioDevice::OUT_IP; + ipv4.address.ipv4 = std::array<uint8_t, 4>{1, 2, 3, 4}; + ConvertDeviceAddress(ipv4); +} + +TEST(HidlUtils6, ConvertUsbDeviceAddress) { + DeviceAddress usbHeadset; + usbHeadset.device = AudioDevice::OUT_USB_HEADSET; + usbHeadset.address.alsa = {1, 2}; + ConvertDeviceAddress(usbHeadset); +} + +TEST(HidlUtils6, ConvertBusDeviceAddress) { + DeviceAddress bus; + bus.device = AudioDevice::OUT_BUS; + bus.busAddress = "bus_device"; + ConvertDeviceAddress(bus); +} + +TEST(HidlUtils6, ConvertRSubmixDeviceAddress) { + DeviceAddress rSubmix; + rSubmix.device = AudioDevice::OUT_REMOTE_SUBMIX; + rSubmix.rSubmixAddress = AUDIO_REMOTE_SUBMIX_DEVICE_ADDRESS; + ConvertDeviceAddress(rSubmix); +} diff --git a/audio/common/all-versions/default/tests/hidlutils_tests.cpp b/audio/common/all-versions/default/tests/hidlutils_tests.cpp index 40fc5c81c6..ec6bdf38ef 100644 --- a/audio/common/all-versions/default/tests/hidlutils_tests.cpp +++ b/audio/common/all-versions/default/tests/hidlutils_tests.cpp @@ -44,8 +44,8 @@ static constexpr audio_gain_mode_t kInvalidHalGainMode = static_cast<audio_gain_mode_t>(0xFFFFFFFFU); // AUDIO_SOURCE_INVALID is framework-only. static constexpr audio_source_t kInvalidHalSource = static_cast<audio_source_t>(-1); -static constexpr audio_stream_type_t kInvalidHalStreamType = - static_cast<audio_stream_type_t>(0xFFFFFFFFU); +// AUDIO_STREAM_DEFAULT is framework-only +static constexpr audio_stream_type_t kInvalidHalStreamType = static_cast<audio_stream_type_t>(-2); static constexpr audio_usage_t kInvalidHalUsage = static_cast<audio_usage_t>(0xFFFFFFFFU); TEST(HidlUtils, ConvertInvalidChannelMask) { @@ -476,6 +476,11 @@ TEST(HidlUtils, ConvertUniqueDeviceAddress) { DeviceAddress speaker; speaker.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_SPEAKER); ConvertDeviceAddress(speaker); + + DeviceAddress micWithAddress; + micWithAddress.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_BUILTIN_MIC); + micWithAddress.address.id("bottom"); + ConvertDeviceAddress(micWithAddress); } TEST(HidlUtils, ConvertA2dpDeviceAddress) { @@ -660,10 +665,18 @@ TEST(HidlUtils, ConvertInvalidStreamType) { AudioStreamType invalid; EXPECT_EQ(BAD_VALUE, HidlUtils::audioStreamTypeFromHal(kInvalidHalStreamType, &invalid)); audio_stream_type_t halInvalid; - EXPECT_EQ(BAD_VALUE, HidlUtils::audioStreamTypeToHal("", &halInvalid)); EXPECT_EQ(BAD_VALUE, HidlUtils::audioStreamTypeToHal("random string", &halInvalid)); } +TEST(HidlUtils, ConvertDefaultStreamType) { + AudioStreamType streamDefault = ""; + audio_stream_type_t halStreamDefault; + EXPECT_EQ(NO_ERROR, HidlUtils::audioStreamTypeToHal(streamDefault, &halStreamDefault)); + AudioStreamType streamDefaultBack; + EXPECT_EQ(NO_ERROR, HidlUtils::audioStreamTypeFromHal(halStreamDefault, &streamDefaultBack)); + EXPECT_EQ(streamDefault, streamDefaultBack); +} + TEST(HidlUtils, ConvertStreamType) { for (const auto enumVal : xsdc_enum_range<xsd::AudioStreamType>{}) { const AudioStreamType streamType = toString(enumVal); diff --git a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp index bb7c6d3d3e..f87e5ed565 100644 --- a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp @@ -322,9 +322,9 @@ TEST_P(OutputStreamTest, updateSourceMetadata) { const SourceMetadata metadata = { {{toString(usage), toString(content), - {} /* tags */, + volume, toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO), - volume}}}; + {} /* tags */}}}; ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(metadata)) << "usage=" << toString(usage) << ", content=" << toString(content) << ", volume=" << volume; diff --git a/automotive/OWNERS b/automotive/OWNERS index fb3e3d63bf..43c5f3e30e 100644 --- a/automotive/OWNERS +++ b/automotive/OWNERS @@ -1,6 +1,6 @@ pirozzoj@google.com twasilczyk@google.com -pfg@google.com +krachuri@google.com gurunagarajan@google.com keunyoung@google.com felipeal@google.com diff --git a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp index e56c2d1834..a3dc45bb5b 100644 --- a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp +++ b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp @@ -41,14 +41,14 @@ static const float kNanoToSeconds = 0.000000001f; #include <utils/Errors.h> #include <utils/StrongPointer.h> +#include <android-base/logging.h> #include <android/hardware/automotive/evs/1.1/IEvsCamera.h> #include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h> -#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h> #include <android/hardware/automotive/evs/1.1/IEvsDisplay.h> +#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h> #include <android/hardware/camera/device/3.2/ICameraDevice.h> -#include <android-base/logging.h> #include <system/camera_metadata.h> -#include <ui/DisplayConfig.h> +#include <ui/DisplayMode.h> #include <ui/DisplayState.h> #include <ui/GraphicBuffer.h> #include <ui/GraphicBufferAllocator.h> @@ -622,7 +622,7 @@ TEST_P(EvsHidlTest, CameraToDisplayRoundTrip) { ASSERT_GT(config.size(), 0); ASSERT_GT(state.size(), 0); - android::DisplayConfig* pConfig = (android::DisplayConfig*)config.data(); + android::ui::DisplayMode* pConfig = (android::ui::DisplayMode*)config.data(); const auto width = pConfig->resolution.getWidth(); const auto height = pConfig->resolution.getHeight(); LOG(INFO) << " Resolution: " << width << "x" << height; diff --git a/biometrics/face/1.1/default/Android.bp b/biometrics/face/1.0/default/Android.bp index 360071f3dd..d6ff087ee6 100644 --- a/biometrics/face/1.1/default/Android.bp +++ b/biometrics/face/1.0/default/Android.bp @@ -15,10 +15,10 @@ */ cc_binary { - name: "android.hardware.biometrics.face@1.1-service.example", + name: "android.hardware.biometrics.face@1.0-service.example", defaults: ["hidl_defaults"], vendor: true, - init_rc: ["android.hardware.biometrics.face@1.1-service.rc"], + init_rc: ["android.hardware.biometrics.face@1.0-service.rc"], vintf_fragments: ["manifest_face_default.xml"], relative_install_path: "hw", proprietary: true, @@ -31,6 +31,5 @@ cc_binary { "libutils", "liblog", "android.hardware.biometrics.face@1.0", - "android.hardware.biometrics.face@1.1", ], } diff --git a/biometrics/face/1.1/default/BiometricsFace.cpp b/biometrics/face/1.0/default/BiometricsFace.cpp index 57b3a92690..97dc4690be 100644 --- a/biometrics/face/1.1/default/BiometricsFace.cpp +++ b/biometrics/face/1.0/default/BiometricsFace.cpp @@ -110,20 +110,4 @@ Return<Status> BiometricsFace::resetLockout(const hidl_vec<uint8_t>& /* hat */) return Status::OK; } -// Methods from ::android::hardware::biometrics::face::V1_1::IBiometricsFace follow. -Return<Status> BiometricsFace::enroll_1_1(const hidl_vec<uint8_t>& /* hat */, - uint32_t /* timeoutSec */, - const hidl_vec<Feature>& /* disabledFeatures */, - const hidl_handle& /* windowId */) { - mClientCallback->onError(kDeviceId, mUserId, FaceError::UNABLE_TO_PROCESS, 0 /* vendorCode */); - return Status::OK; -} - -Return<Status> BiometricsFace::enrollRemotely(const hidl_vec<uint8_t>& /* hat */, - uint32_t /* timeoutSec */, - const hidl_vec<Feature>& /* disabledFeatures */) { - mClientCallback->onError(kDeviceId, mUserId, FaceError::UNABLE_TO_PROCESS, 0 /* vendorCode */); - return Status::OK; -} - } // namespace android::hardware::biometrics::face::implementation diff --git a/biometrics/face/1.1/default/BiometricsFace.h b/biometrics/face/1.0/default/BiometricsFace.h index 5ce5771eae..1d99ed26bc 100644 --- a/biometrics/face/1.1/default/BiometricsFace.h +++ b/biometrics/face/1.0/default/BiometricsFace.h @@ -16,7 +16,7 @@ #pragma once -#include <android/hardware/biometrics/face/1.1/IBiometricsFace.h> +#include <android/hardware/biometrics/face/1.0/IBiometricsFace.h> #include <hidl/MQDescriptor.h> #include <hidl/Status.h> #include <random> @@ -34,7 +34,7 @@ using ::android::hardware::biometrics::face::V1_0::Feature; using ::android::hardware::biometrics::face::V1_0::IBiometricsFaceClientCallback; using ::android::hardware::biometrics::face::V1_0::Status; -class BiometricsFace : public V1_1::IBiometricsFace { +class BiometricsFace : public V1_0::IBiometricsFace { public: BiometricsFace(); @@ -71,14 +71,6 @@ class BiometricsFace : public V1_1::IBiometricsFace { Return<Status> resetLockout(const hidl_vec<uint8_t>& hat) override; - // Methods from ::android::hardware::biometrics::face::V1_1::IBiometricsFace follow. - Return<Status> enroll_1_1(const hidl_vec<uint8_t>& hat, uint32_t timeoutSec, - const hidl_vec<Feature>& disabledFeatures, - const hidl_handle& windowId) override; - - Return<Status> enrollRemotely(const hidl_vec<uint8_t>& hat, uint32_t timeoutSec, - const hidl_vec<Feature>& disabledFeatures) override; - private: std::mt19937 mRandom; int32_t mUserId; diff --git a/biometrics/face/1.1/default/android.hardware.biometrics.face@1.1-service.rc b/biometrics/face/1.0/default/android.hardware.biometrics.face@1.0-service.rc index 687e2d8c86..6c7362f2bb 100644 --- a/biometrics/face/1.1/default/android.hardware.biometrics.face@1.1-service.rc +++ b/biometrics/face/1.0/default/android.hardware.biometrics.face@1.0-service.rc @@ -1,4 +1,4 @@ -service vendor.face-hal-1-1-default /vendor/bin/hw/android.hardware.biometrics.face@1.1-service.example +service vendor.face-hal-1-0-default /vendor/bin/hw/android.hardware.biometrics.face@1.0-service.example # "class hal" causes a race condition on some devices due to files created # in /data. As a workaround, postpone startup until later in boot once # /data is mounted. diff --git a/biometrics/face/1.1/default/manifest_face_default.xml b/biometrics/face/1.0/default/manifest_face_default.xml index ec71d9c92b..380ae49e93 100644 --- a/biometrics/face/1.1/default/manifest_face_default.xml +++ b/biometrics/face/1.0/default/manifest_face_default.xml @@ -2,7 +2,7 @@ <hal format="hidl"> <name>android.hardware.biometrics.face</name> <transport>hwbinder</transport> - <version>1.1</version> + <version>1.0</version> <interface> <name>IBiometricsFace</name> <instance>default</instance> diff --git a/biometrics/face/1.1/default/service.cpp b/biometrics/face/1.0/default/service.cpp index 344bdb99b4..9818c959d6 100644 --- a/biometrics/face/1.1/default/service.cpp +++ b/biometrics/face/1.0/default/service.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ -#define LOG_TAG "android.hardware.biometrics.face@1.1-service" +#define LOG_TAG "android.hardware.biometrics.face@1.0-service" #include <android/hardware/biometrics/face/1.0/types.h> -#include <android/hardware/biometrics/face/1.1/IBiometricsFace.h> +#include <android/hardware/biometrics/face/1.0/IBiometricsFace.h> #include <android/log.h> #include <hidl/HidlSupport.h> #include <hidl/HidlTransportSupport.h> @@ -27,7 +27,7 @@ using android::sp; using android::hardware::configureRpcThreadpool; using android::hardware::joinRpcThreadpool; using android::hardware::biometrics::face::implementation::BiometricsFace; -using android::hardware::biometrics::face::V1_1::IBiometricsFace; +using android::hardware::biometrics::face::V1_0::IBiometricsFace; int main() { ALOGI("BiometricsFace HAL is being started."); diff --git a/biometrics/face/1.1/IBiometricsFace.hal b/biometrics/face/1.1/IBiometricsFace.hal deleted file mode 100644 index 84e7443c9c..0000000000 --- a/biometrics/face/1.1/IBiometricsFace.hal +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 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. - */ - -package android.hardware.biometrics.face@1.1; - -import @1.0::IBiometricsFace; -import @1.0::Status; -import @1.0::Feature; - -/** - * The HAL interface for biometric face authentication. - */ -interface IBiometricsFace extends @1.0::IBiometricsFace { - /** - * Enrolls a user's face for a remote client, for example Android Auto. - * - * The HAL implementation is responsible for creating a secure communication - * channel and receiving the enrollment images from a mobile device with - * face authentication hardware. - * - * Note that the Hardware Authentication Token must be valid for the - * duration of enrollment and thus should be explicitly invalidated by a - * call to revokeChallenge() when enrollment is complete, to reduce the - * window of opportunity to re-use the challenge and HAT. For example, - * Settings calls generateChallenge() once to allow the user to enroll one - * or more faces or toggle secure settings without having to re-enter the - * PIN/pattern/password. Once the user completes the operation, Settings - * invokes revokeChallenge() to close the transaction. If the HAT is expired, - * the implementation must invoke onError with UNABLE_TO_PROCESS. - * - * Requirements for using this API: - * - Mobile devices MUST NOT delegate enrollment to another device by calling - * this API. This feature is intended only to allow enrollment on devices - * where it is impossible to enroll locally on the device. - * - The path MUST be protected by a secret key with rollback protection. - * - Synchronizing between devices MUST be accomplished by having both - * devices agree on a secret PIN entered by the user (similar to BT - * pairing procedure) and use a salted version of that PIN plus other secret - * to encrypt traffic. - * - All communication to/from the remote device MUST be encrypted and signed - * to prevent image injection and other man-in-the-middle type attacks. - * - generateChallenge() and revokeChallenge() MUST be implemented on both - * remote and local host (e.g. hash the result of the remote host with a - * local secret before responding to the API call) and any transmission of - * the challenge between hosts MUST be signed to prevent man-in-the-middle - * attacks. - * - In the event of a lost connection, the result of the last - * generateChallenge() MUST be invalidated and the process started over. - * - Both the remote and local host MUST honor the timeout and invalidate the - * challenge. - * - * This method triggers the IBiometricsFaceClientCallback#onEnrollResult() - * method. - * - * @param hat A valid Hardware Authentication Token, generated as a result - * of a generateChallenge() challenge being wrapped by the gatekeeper - * after a successful strong authentication request. - * @param timeoutSec A timeout in seconds, after which this enroll - * attempt is cancelled. Note that the framework can continue - * enrollment by calling this again with a valid HAT. This timeout is - * expected to be used to limit power usage if the device becomes idle - * during enrollment. The implementation is expected to send - * ERROR_TIMEOUT if this happens. - * @param disabledFeatures A list of features to be disabled during - * enrollment. Note that all features are enabled by default. - * @return status The status of this method call. - */ - enrollRemotely(vec<uint8_t> hat, uint32_t timeoutSec, vec<Feature> disabledFeatures) - generates (Status status); - - /** - * Enrolls a user's face. - * - * Note that the Hardware Authentication Token must be valid for the - * duration of enrollment and thus should be explicitly invalidated by a - * call to revokeChallenge() when enrollment is complete, to reduce the - * window of opportunity to re-use the challenge and HAT. For example, - * Settings calls generateChallenge() once to allow the user to enroll one - * or more faces or toggle secure settings without having to re-enter the - * PIN/pattern/password. Once the user completes the operation, Settings - * invokes revokeChallenge() to close the transaction. If the HAT is expired, - * the implementation must invoke onError with UNABLE_TO_PROCESS. - * - * This method triggers the IBiometricsFaceClientCallback#onEnrollResult() - * method. - * - * @param hat A valid Hardware Authentication Token, generated as a result - * of a generateChallenge() challenge being wrapped by the gatekeeper - * after a successful strong authentication request. - * @param timeoutSec A timeout in seconds, after which this enroll - * attempt is cancelled. Note that the framework can continue - * enrollment by calling this again with a valid HAT. This timeout is - * expected to be used to limit power usage if the device becomes idle - * during enrollment. The implementation is expected to send - * ERROR_TIMEOUT if this happens. - * @param disabledFeatures A list of features to be disabled during - * enrollment. Note that all features are enabled by default. - * @param windowId optional ID of a camera preview window for a - * single-camera device. Must be null if not used. - * @return status The status of this method call. - */ - enroll_1_1(vec<uint8_t> hat, uint32_t timeoutSec, vec<Feature> disabledFeatures, - handle windowId) generates (Status status); -}; diff --git a/biometrics/face/1.1/vts/functional/VtsHalBiometricsFaceV1_1TargetTest.cpp b/biometrics/face/1.1/vts/functional/VtsHalBiometricsFaceV1_1TargetTest.cpp deleted file mode 100644 index 0077c8c728..0000000000 --- a/biometrics/face/1.1/vts/functional/VtsHalBiometricsFaceV1_1TargetTest.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 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 "biometrics_face_hidl_hal_test" - -#include <android/hardware/biometrics/face/1.0/IBiometricsFaceClientCallback.h> -#include <android/hardware/biometrics/face/1.1/IBiometricsFace.h> - -#include <VtsHalHidlTargetCallbackBase.h> -#include <android-base/logging.h> -#include <gtest/gtest.h> -#include <hidl/GtestPrinter.h> -#include <hidl/ServiceManagement.h> - -#include <chrono> -#include <cstdint> -#include <random> - -using android::sp; -using android::hardware::hidl_handle; -using android::hardware::hidl_vec; -using android::hardware::Return; -using android::hardware::Void; -using android::hardware::biometrics::face::V1_0::FaceAcquiredInfo; -using android::hardware::biometrics::face::V1_0::FaceError; -using android::hardware::biometrics::face::V1_0::IBiometricsFaceClientCallback; -using android::hardware::biometrics::face::V1_0::OptionalUint64; -using android::hardware::biometrics::face::V1_0::Status; -using android::hardware::biometrics::face::V1_1::IBiometricsFace; - -namespace { - -// Arbitrary, nonexistent userId -constexpr uint32_t kUserId = 9; -constexpr uint32_t kTimeoutSec = 3; -constexpr auto kTimeout = std::chrono::seconds(kTimeoutSec); -constexpr char kFacedataDir[] = "/data/vendor_de/0/facedata"; -constexpr char kCallbackNameOnError[] = "onError"; - -// Callback arguments that need to be captured for the tests. -struct FaceCallbackArgs { - // The error passed to the last onError() callback. - FaceError error; - - // The userId passed to the last callback. - int32_t userId; -}; - -// Test callback class for the BiometricsFace HAL. -// The HAL will call these callback methods to notify about completed operations -// or encountered errors. -class FaceCallback : public ::testing::VtsHalHidlTargetCallbackBase<FaceCallbackArgs>, - public IBiometricsFaceClientCallback { - public: - Return<void> onEnrollResult(uint64_t, uint32_t, int32_t, uint32_t) override { return Void(); } - - Return<void> onAuthenticated(uint64_t, uint32_t, int32_t, const hidl_vec<uint8_t>&) override { - return Void(); - } - - Return<void> onAcquired(uint64_t, int32_t, FaceAcquiredInfo, int32_t) override { - return Void(); - } - - Return<void> onError(uint64_t, int32_t userId, FaceError error, int32_t) override { - FaceCallbackArgs args = {}; - args.error = error; - args.userId = userId; - NotifyFromCallback(kCallbackNameOnError, args); - return Void(); - } - - Return<void> onRemoved(uint64_t, const hidl_vec<uint32_t>&, int32_t) override { return Void(); } - - Return<void> onEnumerate(uint64_t, const hidl_vec<uint32_t>&, int32_t) override { - return Void(); - } - - Return<void> onLockoutChanged(uint64_t) override { return Void(); } -}; - -// Test class for the BiometricsFace HAL. -class FaceHidlTest : public ::testing::TestWithParam<std::string> { - public: - void SetUp() override { - mService = IBiometricsFace::getService(GetParam()); - ASSERT_NE(mService, nullptr); - mCallback = new FaceCallback(); - mCallback->SetWaitTimeoutDefault(kTimeout); - Return<void> ret1 = mService->setCallback(mCallback, [](const OptionalUint64& res) { - ASSERT_EQ(Status::OK, res.status); - // Makes sure the "deviceId" represented by "res.value" is not 0. - // 0 would mean the HIDL is not available. - ASSERT_NE(0UL, res.value); - }); - ASSERT_TRUE(ret1.isOk()); - Return<Status> ret2 = mService->setActiveUser(kUserId, kFacedataDir); - ASSERT_EQ(Status::OK, static_cast<Status>(ret2)); - } - - void TearDown() override {} - - sp<IBiometricsFace> mService; - sp<FaceCallback> mCallback; -}; - -// enroll with an invalid (all zeroes) HAT should fail. -TEST_P(FaceHidlTest, Enroll1_1ZeroHatTest) { - // Filling HAT with zeros - hidl_vec<uint8_t> token(69); - for (size_t i = 0; i < 69; i++) { - token[i] = 0; - } - - hidl_handle windowId = nullptr; - Return<Status> ret = mService->enroll_1_1(token, kTimeoutSec, {}, windowId); - ASSERT_EQ(Status::OK, static_cast<Status>(ret)); - - // onError should be called with a meaningful (nonzero) error. - auto res = mCallback->WaitForCallback(kCallbackNameOnError); - EXPECT_TRUE(res.no_timeout); - EXPECT_EQ(kUserId, res.args->userId); - EXPECT_EQ(FaceError::UNABLE_TO_PROCESS, res.args->error); -} - -// enroll with an invalid HAT should fail. -TEST_P(FaceHidlTest, Enroll1_1GarbageHatTest) { - // Filling HAT with pseudorandom invalid data. - // Using default seed to make the test reproducible. - std::mt19937 gen(std::mt19937::default_seed); - std::uniform_int_distribution<uint8_t> dist; - hidl_vec<uint8_t> token(69); - for (size_t i = 0; i < 69; ++i) { - token[i] = dist(gen); - } - - hidl_handle windowId = nullptr; - Return<Status> ret = mService->enroll_1_1(token, kTimeoutSec, {}, windowId); - ASSERT_EQ(Status::OK, static_cast<Status>(ret)); - - // onError should be called with a meaningful (nonzero) error. - auto res = mCallback->WaitForCallback(kCallbackNameOnError); - EXPECT_TRUE(res.no_timeout); - EXPECT_EQ(kUserId, res.args->userId); - EXPECT_EQ(FaceError::UNABLE_TO_PROCESS, res.args->error); -} - -// enroll with an invalid (all zeroes) HAT should fail. -TEST_P(FaceHidlTest, EnrollRemotelyZeroHatTest) { - // Filling HAT with zeros - hidl_vec<uint8_t> token(69); - for (size_t i = 0; i < 69; i++) { - token[i] = 0; - } - - Return<Status> ret = mService->enrollRemotely(token, kTimeoutSec, {}); - ASSERT_EQ(Status::OK, static_cast<Status>(ret)); - - // onError should be called with a meaningful (nonzero) error. - auto res = mCallback->WaitForCallback(kCallbackNameOnError); - EXPECT_TRUE(res.no_timeout); - EXPECT_EQ(kUserId, res.args->userId); - EXPECT_EQ(FaceError::UNABLE_TO_PROCESS, res.args->error); -} - -// enroll with an invalid HAT should fail. -TEST_P(FaceHidlTest, EnrollRemotelyGarbageHatTest) { - // Filling HAT with pseudorandom invalid data. - // Using default seed to make the test reproducible. - std::mt19937 gen(std::mt19937::default_seed); - std::uniform_int_distribution<uint8_t> dist; - hidl_vec<uint8_t> token(69); - for (size_t i = 0; i < 69; ++i) { - token[i] = dist(gen); - } - - Return<Status> ret = mService->enrollRemotely(token, kTimeoutSec, {}); - ASSERT_EQ(Status::OK, static_cast<Status>(ret)); - - // onError should be called with a meaningful (nonzero) error. - auto res = mCallback->WaitForCallback(kCallbackNameOnError); - EXPECT_TRUE(res.no_timeout); - EXPECT_EQ(kUserId, res.args->userId); - EXPECT_EQ(FaceError::UNABLE_TO_PROCESS, res.args->error); -} - -} // anonymous namespace - -GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(FaceHidlTest); -INSTANTIATE_TEST_SUITE_P( - PerInstance, FaceHidlTest, - testing::ValuesIn(android::hardware::getAllHalInstanceNames(IBiometricsFace::descriptor)), - android::hardware::PrintInstanceNameToString); diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl index 37345ec01b..bfaf90d0e1 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl @@ -35,4 +35,5 @@ package android.hardware.biometrics.face; interface IFace { android.hardware.biometrics.face.SensorProps[] getSensorProps(); android.hardware.biometrics.face.ISession createSession(in int sensorId, in int userId, in android.hardware.biometrics.face.ISessionCallback cb); + void reset(); } diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl index b855a9ede1..c9165e1ab6 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl @@ -45,4 +45,5 @@ interface ISession { void getAuthenticatorId(in int cookie); void invalidateAuthenticatorId(in int cookie); void resetLockout(in int cookie, in android.hardware.keymaster.HardwareAuthToken hat); + void close(in int cookie); } diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl index 46751d03d0..3792eae17f 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl @@ -34,7 +34,7 @@ package android.hardware.biometrics.face; @Backing(type="byte") @VintfStability enum SessionState { IDLING = 0, - TERMINATED = 1, + CLOSED = 1, GENERATING_CHALLENGE = 2, REVOKING_CHALLENGE = 3, ENROLLING = 4, diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/IFace.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/IFace.aidl index f9ed4b1701..afb7c8de65 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/IFace.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/IFace.aidl @@ -35,6 +35,10 @@ interface IFace { * Creates a session that can be used by the framework to perform operations such as * enroll, authenticate, etc. for the given sensorId and userId. * + * Calling this method while there is an active session is considered an error. If the + * framework is in a bad state and for some reason cannot close its session, it should use + * the reset method below. + * * Implementations must store user-specific state or metadata in /data/vendor_de/<user>/facedata * as specified by the SELinux policy. The directory /data/vendor_de is managed by vold (see * vold_prepare_subdirs.cpp). Implementations may store additional user-specific data, such as @@ -47,4 +51,13 @@ interface IFace { */ ISession createSession(in int sensorId, in int userId, in ISessionCallback cb); + /** + * Resets the HAL into a clean state, forcing it to cancel all of the pending operations, close + * its current session, and release all of the acquired resources. + * + * This should be used as a last resort to recover the HAL if the current session becomes + * unresponsive. The implementation might choose to restart the HAL process to get back into a + * good state. + */ + void reset(); } diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl index f540502669..6f2014a61f 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl @@ -17,19 +17,20 @@ package android.hardware.biometrics.face; import android.hardware.biometrics.common.ICancellationSignal; -import android.hardware.biometrics.face.Feature; import android.hardware.biometrics.face.EnrollmentType; -import android.hardware.keymaster.HardwareAuthToken; +import android.hardware.biometrics.face.Feature; import android.hardware.common.NativeHandle; +import android.hardware.keymaster.HardwareAuthToken; -/** * A session is a collection of immutable state (sensorId, userId), mutable state (SessionState), +/** + * A session is a collection of immutable state (sensorId, userId), mutable state (SessionState), * methods available for the framework to call, and a callback (ISessionCallback) to notify the * framework about the events and results. A session is used to establish communication between * the framework and the HAL. */ @VintfStability interface ISession { - /** + /** * generateChallenge: * * Begins a secure transaction request. Note that the challenge by itself is not useful. It only @@ -134,9 +135,9 @@ interface ISession { * @param hat See above documentation. * @param enrollmentType See the EnrollmentType enum. * @param features See the Feature enum. - * @param previewSurface A surface provided by the framework if SensorProps#halControlsPreview is - * set to true. The HAL must send the preview frames to previewSurface if - * it's not null. + * @param previewSurface A surface provided by the framework if SensorProps#halControlsPreview + * is set to true. The HAL must send the preview frames to previewSurface + * if it's not null. * @return ICancellationSignal An object that can be used by the framework to cancel this * operation. */ @@ -420,5 +421,22 @@ interface ISession { * @param hat HardwareAuthToken See above documentation. */ void resetLockout(in int cookie, in HardwareAuthToken hat); -} + /* + * Close this session and allow the HAL to release the resources associated with this session. + * + * A session can only be closed when it's in SessionState::IDLING. Closing a session will + * result in a ISessionCallback#onStateChanged call with SessionState::CLOSED. + * + * If a session is unresponsive or stuck in a state other than SessionState::CLOSED, + * IFace#reset could be used as a last resort to terminate the session and recover the HAL + * from a bad state. + * + * All sessions must be explicitly closed. Calling IFace#createSession while there is an active + * session is considered an error. + * + * @param cookie An identifier used to track subsystem operations related to this call path. The + * client must guarantee that it is unique per ISession. + */ + void close(in int cookie); +} diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl index 354f4a7e19..2e3cd950f6 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl @@ -17,10 +17,10 @@ package android.hardware.biometrics.face; import android.hardware.biometrics.face.AcquiredInfo; -import android.hardware.biometrics.face.Feature; import android.hardware.biometrics.face.AuthenticationFrame; import android.hardware.biometrics.face.EnrollmentFrame; import android.hardware.biometrics.face.Error; +import android.hardware.biometrics.face.Feature; import android.hardware.biometrics.face.SessionState; import android.hardware.keymaster.HardwareAuthToken; @@ -100,9 +100,8 @@ interface ISessionCallback { /** * This method must only be used to notify the framework during SessionState::AUTHENTICATING. * - * Used to notify the framework upon successful authentication. Note that the authentication - * lifecycle ends when either 1) a face is accepted, or 2) an error occurred. The - * authentication lifecycle does NOT end when a face is rejected. + * Used to notify the framework about a successful authentication. This ends the authentication + * lifecycle. * * @param enrollmentId Face that was accepted. * @param hat If the sensor is configured as SensorStrength::STRONG, a non-null attestation that @@ -115,9 +114,8 @@ interface ISessionCallback { /** * This method must only be used to notify the framework during SessionState::AUTHENTICATING. * - * Used to notify the framework upon rejected attempts. Note that the authentication - * lifecycle ends when either 1) a face is accepted, or 2) an occurred. The - * authentication lifecycle does NOT end when a face is rejected. + * Used to notify the framework about a failed authentication. This ends the authentication + * lifecycle. */ void onAuthenticationFailed(); diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/SessionState.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/SessionState.aidl index 76755649e4..afde4eb2d5 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/SessionState.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/SessionState.aidl @@ -25,9 +25,9 @@ enum SessionState { IDLING, /** - * The session has been terminated by the HAL. + * The session has been closed by the client. */ - TERMINATED, + CLOSED, /** * The HAL is processing the ISession#generateChallenge request. @@ -89,4 +89,3 @@ enum SessionState { */ RESETTING_LOCKOUT } - diff --git a/biometrics/face/aidl/default/Face.cpp b/biometrics/face/aidl/default/Face.cpp index 773359e89c..2b4085054b 100644 --- a/biometrics/face/aidl/default/Face.cpp +++ b/biometrics/face/aidl/default/Face.cpp @@ -63,4 +63,8 @@ ndk::ScopedAStatus Face::createSession(int32_t /*sensorId*/, int32_t /*userId*/, return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus Face::reset() { + return ndk::ScopedAStatus::ok(); +} + } // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/face/aidl/default/Face.h b/biometrics/face/aidl/default/Face.h index 786b4f89fe..809b8568c2 100644 --- a/biometrics/face/aidl/default/Face.h +++ b/biometrics/face/aidl/default/Face.h @@ -27,6 +27,8 @@ class Face : public BnFace { ndk::ScopedAStatus createSession(int32_t sensorId, int32_t userId, const std::shared_ptr<ISessionCallback>& cb, std::shared_ptr<ISession>* _aidl_return) override; + + ndk::ScopedAStatus reset() override; }; } // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/face/aidl/default/Session.cpp b/biometrics/face/aidl/default/Session.cpp index 63d1721314..a7130e62d0 100644 --- a/biometrics/face/aidl/default/Session.cpp +++ b/biometrics/face/aidl/default/Session.cpp @@ -15,6 +15,7 @@ */ #include <aidl/android/hardware/biometrics/common/BnCancellationSignal.h> +#include <android-base/logging.h> #include "Session.h" @@ -37,6 +38,7 @@ class CancellationSignal : public common::BnCancellationSignal { Session::Session(std::shared_ptr<ISessionCallback> cb) : cb_(std::move(cb)) {} ndk::ScopedAStatus Session::generateChallenge(int32_t /*cookie*/, int32_t /*timeoutSec*/) { + LOG(INFO) << "generateChallenge"; if (cb_) { cb_->onStateChanged(0, SessionState::GENERATING_CHALLENGE); cb_->onChallengeGenerated(0); @@ -46,6 +48,7 @@ ndk::ScopedAStatus Session::generateChallenge(int32_t /*cookie*/, int32_t /*time } ndk::ScopedAStatus Session::revokeChallenge(int32_t /*cookie*/, int64_t challenge) { + LOG(INFO) << "revokeChallenge"; if (cb_) { cb_->onStateChanged(0, SessionState::REVOKING_CHALLENGE); cb_->onChallengeRevoked(challenge); @@ -59,11 +62,13 @@ ndk::ScopedAStatus Session::enroll( EnrollmentType /*enrollmentType*/, const std::vector<Feature>& /*features*/, const NativeHandle& /*previewSurface*/, std::shared_ptr<biometrics::common::ICancellationSignal>* /*return_val*/) { + LOG(INFO) << "enroll"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::authenticate(int32_t /*cookie*/, int64_t /*keystoreOperationId*/, std::shared_ptr<common::ICancellationSignal>* return_val) { + LOG(INFO) << "authenticate"; if (cb_) { cb_->onStateChanged(0, SessionState::AUTHENTICATING); } @@ -73,10 +78,12 @@ ndk::ScopedAStatus Session::authenticate(int32_t /*cookie*/, int64_t /*keystoreO ndk::ScopedAStatus Session::detectInteraction( int32_t /*cookie*/, std::shared_ptr<common::ICancellationSignal>* /*return_val*/) { + LOG(INFO) << "detectInteraction"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::enumerateEnrollments(int32_t /*cookie*/) { + LOG(INFO) << "enumerateEnrollments"; if (cb_) { cb_->onStateChanged(0, SessionState::ENUMERATING_ENROLLMENTS); cb_->onEnrollmentsEnumerated(std::vector<int32_t>()); @@ -87,6 +94,7 @@ ndk::ScopedAStatus Session::enumerateEnrollments(int32_t /*cookie*/) { ndk::ScopedAStatus Session::removeEnrollments(int32_t /*cookie*/, const std::vector<int32_t>& /*enrollmentIds*/) { + LOG(INFO) << "removeEnrollments"; if (cb_) { cb_->onStateChanged(0, SessionState::REMOVING_ENROLLMENTS); cb_->onEnrollmentsRemoved(std::vector<int32_t>()); @@ -96,6 +104,7 @@ ndk::ScopedAStatus Session::removeEnrollments(int32_t /*cookie*/, } ndk::ScopedAStatus Session::getFeatures(int32_t /*cookie*/, int32_t /*enrollmentId*/) { + LOG(INFO) << "getFeatures"; return ndk::ScopedAStatus::ok(); } @@ -103,10 +112,12 @@ ndk::ScopedAStatus Session::setFeature(int32_t /*cookie*/, const keymaster::HardwareAuthToken& /*hat*/, int32_t /*enrollmentId*/, Feature /*feature*/, bool /*enabled*/) { + LOG(INFO) << "setFeature"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::getAuthenticatorId(int32_t /*cookie*/) { + LOG(INFO) << "getAuthenticatorId"; if (cb_) { cb_->onStateChanged(0, SessionState::GETTING_AUTHENTICATOR_ID); cb_->onAuthenticatorIdRetrieved(0 /* authenticatorId */); @@ -116,11 +127,13 @@ ndk::ScopedAStatus Session::getAuthenticatorId(int32_t /*cookie*/) { } ndk::ScopedAStatus Session::invalidateAuthenticatorId(int32_t /*cookie*/) { + LOG(INFO) << "invalidateAuthenticatorId"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::resetLockout(int32_t /*cookie*/, const keymaster::HardwareAuthToken& /*hat*/) { + LOG(INFO) << "resetLockout"; if (cb_) { cb_->onStateChanged(0, SessionState::RESETTING_LOCKOUT); cb_->onLockoutCleared(); @@ -129,4 +142,8 @@ ndk::ScopedAStatus Session::resetLockout(int32_t /*cookie*/, return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus Session::close(int32_t /*cookie*/) { + return ndk::ScopedAStatus::ok(); +} + } // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/face/aidl/default/Session.h b/biometrics/face/aidl/default/Session.h index 83cb0641d6..0651726d0a 100644 --- a/biometrics/face/aidl/default/Session.h +++ b/biometrics/face/aidl/default/Session.h @@ -63,6 +63,8 @@ class Session : public BnSession { ndk::ScopedAStatus resetLockout(int32_t cookie, const keymaster::HardwareAuthToken& hat) override; + ndk::ScopedAStatus close(int32_t cookie) override; + private: std::shared_ptr<ISessionCallback> cb_; }; diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl index c5a5422d24..2d44528f2e 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl @@ -35,4 +35,5 @@ package android.hardware.biometrics.fingerprint; interface IFingerprint { android.hardware.biometrics.fingerprint.SensorProps[] getSensorProps(); android.hardware.biometrics.fingerprint.ISession createSession(in int sensorId, in int userId, in android.hardware.biometrics.fingerprint.ISessionCallback cb); + void reset(); } diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl index be0029c53b..b583006d46 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl @@ -43,6 +43,7 @@ interface ISession { void getAuthenticatorId(in int cookie); void invalidateAuthenticatorId(in int cookie); void resetLockout(in int cookie, in android.hardware.keymaster.HardwareAuthToken hat); + void close(in int cookie); void onPointerDown(in int pointerId, in int x, in int y, in float minor, in float major); void onPointerUp(in int pointerId); void onUiReady(); diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorLocation.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorLocation.aidl new file mode 100644 index 0000000000..a6e8b4d245 --- /dev/null +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorLocation.aidl @@ -0,0 +1,40 @@ +/* + * Copyright (C) 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.biometrics.fingerprint; +@VintfStability +parcelable SensorLocation { + int displayId; + int sensorLocationX; + int sensorLocationY; + int sensorRadius; +} diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl index c3daacd5de..53ac6dd917 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl @@ -35,10 +35,7 @@ package android.hardware.biometrics.fingerprint; parcelable SensorProps { android.hardware.biometrics.common.CommonProps commonProps; android.hardware.biometrics.fingerprint.FingerprintSensorType sensorType; + android.hardware.biometrics.fingerprint.SensorLocation[] sensorLocations; boolean supportsNavigationGestures; - int sensorLocationX; - int sensorLocationY; - int sensorRadius; - int displayId; boolean supportsDetectInteraction; } diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl index 44323ffb1d..05dd85bbfd 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl @@ -34,14 +34,15 @@ package android.hardware.biometrics.fingerprint; @Backing(type="byte") @VintfStability enum SessionState { IDLING = 0, - GENERATING_CHALLENGE = 1, - REVOKING_CHALLENGE = 2, - ENROLLING = 3, - AUTHENTICATING = 4, - DETECTING_INTERACTION = 5, - ENUMERATING_ENROLLMENTS = 6, - REMOVING_ENROLLMENTS = 7, - GETTING_AUTHENTICATOR_ID = 8, - INVALIDATING_AUTHENTICATOR_ID = 9, - RESETTING_LOCKOUT = 10, + CLOSED = 1, + GENERATING_CHALLENGE = 2, + REVOKING_CHALLENGE = 3, + ENROLLING = 4, + AUTHENTICATING = 5, + DETECTING_INTERACTION = 6, + ENUMERATING_ENROLLMENTS = 7, + REMOVING_ENROLLMENTS = 8, + GETTING_AUTHENTICATOR_ID = 9, + INVALIDATING_AUTHENTICATOR_ID = 10, + RESETTING_LOCKOUT = 11, } diff --git a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/IFingerprint.aidl b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/IFingerprint.aidl index 3675aa4019..37062badae 100644 --- a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/IFingerprint.aidl +++ b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/IFingerprint.aidl @@ -35,6 +35,10 @@ interface IFingerprint { * Creates a session which can then be used by the framework to perform operations such as * enroll, authenticate, etc for the given sensorId and userId. * + * Calling this method while there is an active session is considered an error. If the + * framework is in a bad state and for some reason cannot close its session, it should use + * the reset method below. + * * A physical sensor identified by sensorId typically supports only a single in-flight session * at a time. As such, if a session is currently in a state other than SessionState::IDLING, the * HAL MUST finish or cancel the current operation and return to SessionState::IDLING before the @@ -61,4 +65,14 @@ interface IFingerprint { * @return A new session */ ISession createSession(in int sensorId, in int userId, in ISessionCallback cb); + + /** + * Resets the HAL into a clean state, forcing it to cancel all of the pending operations, close + * its current session, and release all of the acquired resources. + * + * This should be used as a last resort to recover the HAL if the current session becomes + * unresponsive. The implementation might choose to restart the HAL process to get back into a + * good state. + */ + void reset(); } diff --git a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISession.aidl b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISession.aidl index f9c3732ac4..ab7930d81b 100644 --- a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISession.aidl +++ b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISession.aidl @@ -366,6 +366,24 @@ interface ISession { */ void resetLockout(in int cookie, in HardwareAuthToken hat); + /* + * Close this session and allow the HAL to release the resources associated with this session. + * + * A session can only be closed when it's in SessionState::IDLING. Closing a session will + * result in a ISessionCallback#onStateChanged call with SessionState::CLOSED. + * + * If a session is unresponsive or stuck in a state other than SessionState::CLOSED, + * IFingerprint#reset could be used as a last resort to terminate the session and recover the + * HAL from a bad state. + * + * All sessions must be explicitly closed. Calling IFingerprint#createSession while there is an + * active session is considered an error. + * + * @param cookie An identifier used to track subsystem operations related to this call path. The + * client must guarantee that it is unique per ISession. + */ + void close(in int cookie); + /** * Methods for notifying the under-display fingerprint sensor about external events. */ @@ -420,4 +438,3 @@ interface ISession { */ void onUiReady(); } - diff --git a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorLocation.aidl b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorLocation.aidl new file mode 100644 index 0000000000..62a2e8cdbb --- /dev/null +++ b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorLocation.aidl @@ -0,0 +1,54 @@ +/* + * Copyright (C) 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.biometrics.fingerprint; + +@VintfStability +parcelable SensorLocation { + /** + * The display to which the following measurements are relative to. This must correspond to the + * android.hardware.DisplayManager#getDisplay Android API. + * + * A few examples: + * 1) A capacitive rear fingerprint sensor would specify the display to which it is behind. + * 2) An under-display fingerprint sensor would specify the display on which the sensor is + * located. + * 3) A foldable device would specify multiple locations and have a SensorLocation entry + * for each display from which the sensor is accessible from. + */ + int displayId; + + /** + * The location of the center of the sensor if applicable. For example, sensors of + * FingerprintSensorType::UNDER_DISPLAY_* would report this value as the distance in pixels, + * measured from the left edge of the screen. + */ + int sensorLocationX; + + /** + * The location of the center of the sensor if applicable. For example, sensors of + * FingerprintSensorType::UNDER_DISPLAY_* would report this value as the distance in pixels, + * measured from the top edge of the screen. + */ + int sensorLocationY; + + /** + * The radius of the sensor if applicable. For example, sensors of + * FingerprintSensorType::UNDER_DISPLAY_* would report this value as the radius of the sensor, + * in pixels. + */ + int sensorRadius; +}
\ No newline at end of file diff --git a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorProps.aidl b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorProps.aidl index afed175bbb..5222f3ea3a 100644 --- a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorProps.aidl +++ b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorProps.aidl @@ -18,6 +18,7 @@ package android.hardware.biometrics.fingerprint; import android.hardware.biometrics.common.CommonProps; import android.hardware.biometrics.fingerprint.FingerprintSensorType; +import android.hardware.biometrics.fingerprint.SensorLocation; @VintfStability parcelable SensorProps { @@ -32,37 +33,16 @@ parcelable SensorProps { FingerprintSensorType sensorType; /** - * Must be set to true for sensors that support "swipe" gestures via - * android.view.KeyEvent#KEYCODE_SYSTEM_NAVIGATION_*. - */ - boolean supportsNavigationGestures; - - /** - * The location of the center of the sensor if applicable. For example, sensors of - * FingerprintSensorType::UNDER_DISPLAY_* would report this value as the distance in pixels, - * measured from the left edge of the screen. + * A list of display-specific locations from where the sensor is usable from. See SensorLocation + * for more details. */ - int sensorLocationX; + SensorLocation[] sensorLocations; /** - * The location of the center of the sensor if applicable. For example, sensors of - * FingerprintSensorType::UNDER_DISPLAY_* would report this value as the distance in pixels, - * measured from the top edge of the screen. - */ - int sensorLocationY; - - /** - * The radius of the sensor if applicable. For example, sensors of - * FingerprintSensorType::UNDER_DISPLAY_* would report this value as the radius of the sensor, - * in pixels. - */ - int sensorRadius; - - /** - * For sensors of FingerprintSensorType::UNDER_DISPLAY_*, this must correspond to the - * android.hardware.DisplayManager#getDisplay Android API. + * Must be set to true for sensors that support "swipe" gestures via + * android.view.KeyEvent#KEYCODE_SYSTEM_NAVIGATION_*. */ - int displayId; + boolean supportsNavigationGestures; /** * Specifies whether or not the implementation supports ISession#detectInteraction. diff --git a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SessionState.aidl b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SessionState.aidl index 1de01ad73c..19a6ce3682 100644 --- a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SessionState.aidl +++ b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SessionState.aidl @@ -25,6 +25,11 @@ enum SessionState { IDLING, /** + * The session has been closed by the client. + */ + CLOSED, + + /** * The HAL is processing the ISession#generateChallenge request. */ GENERATING_CHALLENGE, @@ -74,4 +79,3 @@ enum SessionState { */ RESETTING_LOCKOUT } - diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp index c8cb663686..24087cf036 100644 --- a/biometrics/fingerprint/aidl/default/Android.bp +++ b/biometrics/fingerprint/aidl/default/Android.bp @@ -1,18 +1,32 @@ cc_binary { name: "android.hardware.biometrics.fingerprint-service.example", + vendor: true, relative_install_path: "hw", init_rc: ["fingerprint-default.rc"], vintf_fragments: ["fingerprint-default.xml"], - vendor: true, + local_include_dirs: ["include"], + srcs: [ + "main.cpp", + "Fingerprint.cpp", + "Session.cpp", + ], shared_libs: [ "libbase", "libbinder_ndk", "android.hardware.biometrics.fingerprint-V1-ndk_platform", "android.hardware.biometrics.common-V1-ndk_platform", ], +} + +cc_test_host { + name: "android.hardware.biometrics.fingerprint.WorkerThreadTest", + local_include_dirs: ["include"], srcs: [ - "main.cpp", - "Fingerprint.cpp", - "Session.cpp", + "tests/WorkerThreadTest.cpp", + "WorkerThread.cpp", + ], + shared_libs: [ + "libcutils", ], + test_suites: ["general-tests"], } diff --git a/biometrics/fingerprint/aidl/default/Fingerprint.cpp b/biometrics/fingerprint/aidl/default/Fingerprint.cpp index 6eb35d970f..6f893468a8 100644 --- a/biometrics/fingerprint/aidl/default/Fingerprint.cpp +++ b/biometrics/fingerprint/aidl/default/Fingerprint.cpp @@ -18,46 +18,49 @@ #include "Session.h" namespace aidl::android::hardware::biometrics::fingerprint { +namespace { -const int kSensorId = 1; -const common::SensorStrength kSensorStrength = common::SensorStrength::STRONG; -const int kMaxEnrollmentsPerUser = 5; -const FingerprintSensorType kSensorType = FingerprintSensorType::REAR; -const bool kSupportsNavigationGestures = true; -const std::string kHwDeviceName = "fingerprintSensor"; -const std::string kHardwareVersion = "vendor/model/revision"; -const std::string kFirmwareVersion = "1.01"; -const std::string kSerialNumber = "00000001"; - -ndk::ScopedAStatus Fingerprint::getSensorProps(std::vector<SensorProps>* return_val) { - *return_val = std::vector<SensorProps>(); - - std::vector<common::HardwareInfo> hardwareInfos = std::vector<common::HardwareInfo>(); - common::HardwareInfo sensorInfo = {kHwDeviceName, - kHardwareVersion, - kFirmwareVersion, - kSerialNumber - }; - hardwareInfos.push_back(sensorInfo); - common::CommonProps commonProps = {kSensorId, - kSensorStrength, - kMaxEnrollmentsPerUser, - hardwareInfos}; - SensorProps props = {commonProps, - kSensorType, - kSupportsNavigationGestures, - 0 /* sensorLocationX */, - 0 /* sensorLocationY */, - 0 /* sensorRadius */, - 0 /* displayId */}; - return_val->push_back(props); +constexpr int SENSOR_ID = 1; +constexpr common::SensorStrength SENSOR_STRENGTH = common::SensorStrength::STRONG; +constexpr int MAX_ENROLLMENTS_PER_USER = 5; +constexpr FingerprintSensorType SENSOR_TYPE = FingerprintSensorType::REAR; +constexpr bool SUPPORTS_NAVIGATION_GESTURES = true; +constexpr char HW_DEVICE_NAME[] = "fingerprintSensor"; +constexpr char HW_VERSION[] = "vendor/model/revision"; +constexpr char FW_VERSION[] = "1.01"; +constexpr char SERIAL_NUMBER[] = "00000001"; + +} // namespace + +Fingerprint::Fingerprint() {} + +ndk::ScopedAStatus Fingerprint::getSensorProps(std::vector<SensorProps>* out) { + std::vector<common::HardwareInfo> hardwareInfos = { + {HW_DEVICE_NAME, HW_VERSION, FW_VERSION, SERIAL_NUMBER}}; + + common::CommonProps commonProps = {SENSOR_ID, SENSOR_STRENGTH, MAX_ENROLLMENTS_PER_USER, + hardwareInfos}; + + SensorLocation sensorLocation = {0 /* displayId */, 0 /* sensorLocationX */, + 0 /* sensorLocationY */, 0 /* sensorRadius */}; + + *out = {{commonProps, + SENSOR_TYPE, + {sensorLocation}, + SUPPORTS_NAVIGATION_GESTURES, + false /* supportsDetectInteraction */}}; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Fingerprint::createSession(int32_t /*sensorId*/, int32_t /*userId*/, const std::shared_ptr<ISessionCallback>& cb, - std::shared_ptr<ISession>* return_val) { - *return_val = SharedRefBase::make<Session>(cb); + std::shared_ptr<ISession>* out) { + *out = SharedRefBase::make<Session>(cb); return ndk::ScopedAStatus::ok(); } + +ndk::ScopedAStatus Fingerprint::reset() { + return ndk::ScopedAStatus::ok(); +} + } // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/Session.cpp b/biometrics/fingerprint/aidl/default/Session.cpp index bf08203707..c928df4dd5 100644 --- a/biometrics/fingerprint/aidl/default/Session.cpp +++ b/biometrics/fingerprint/aidl/default/Session.cpp @@ -15,6 +15,7 @@ */ #include <aidl/android/hardware/biometrics/common/BnCancellationSignal.h> +#include <android-base/logging.h> #include "Session.h" @@ -25,79 +26,97 @@ class CancellationSignal : public common::BnCancellationSignal { ndk::ScopedAStatus cancel() override { return ndk::ScopedAStatus::ok(); } }; -Session::Session(std::shared_ptr<ISessionCallback> cb) : cb_(std::move(cb)) {} +Session::Session(std::shared_ptr<ISessionCallback> cb) : mCb(std::move(cb)) {} ndk::ScopedAStatus Session::generateChallenge(int32_t /*cookie*/, int32_t /*timeoutSec*/) { + LOG(INFO) << "generateChallenge"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::revokeChallenge(int32_t /*cookie*/, int64_t /*challenge*/) { + LOG(INFO) << "revokeChallenge"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::enroll(int32_t /*cookie*/, const keymaster::HardwareAuthToken& /*hat*/, - std::shared_ptr<common::ICancellationSignal>* /*return_val*/) { + std::shared_ptr<common::ICancellationSignal>* /*out*/) { + LOG(INFO) << "enroll"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::authenticate(int32_t /*cookie*/, int64_t /*keystoreOperationId*/, - std::shared_ptr<common::ICancellationSignal>* return_val) { - if (cb_) { - cb_->onStateChanged(0, SessionState::AUTHENTICATING); + std::shared_ptr<common::ICancellationSignal>* out) { + LOG(INFO) << "authenticate"; + if (mCb) { + mCb->onStateChanged(0, SessionState::AUTHENTICATING); } - *return_val = SharedRefBase::make<CancellationSignal>(); + *out = SharedRefBase::make<CancellationSignal>(); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::detectInteraction( - int32_t /*cookie*/, std::shared_ptr<common::ICancellationSignal>* /*return_val*/) { + int32_t /*cookie*/, std::shared_ptr<common::ICancellationSignal>* /*out*/) { + LOG(INFO) << "detectInteraction"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::enumerateEnrollments(int32_t /*cookie*/) { - if (cb_) { - cb_->onStateChanged(0, SessionState::ENUMERATING_ENROLLMENTS); - cb_->onEnrollmentsEnumerated(std::vector<int32_t>()); + LOG(INFO) << "enumerateEnrollments"; + if (mCb) { + mCb->onStateChanged(0, SessionState::ENUMERATING_ENROLLMENTS); + mCb->onEnrollmentsEnumerated(std::vector<int32_t>()); } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::removeEnrollments(int32_t /*cookie*/, const std::vector<int32_t>& /*enrollmentIds*/) { - if (cb_) { - cb_->onStateChanged(0, SessionState::REMOVING_ENROLLMENTS); - cb_->onEnrollmentsRemoved(std::vector<int32_t>()); + LOG(INFO) << "removeEnrollments"; + if (mCb) { + mCb->onStateChanged(0, SessionState::REMOVING_ENROLLMENTS); + mCb->onEnrollmentsRemoved(std::vector<int32_t>()); } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::getAuthenticatorId(int32_t /*cookie*/) { - if (cb_) { - cb_->onStateChanged(0, SessionState::GETTING_AUTHENTICATOR_ID); - cb_->onAuthenticatorIdRetrieved(0 /* authenticatorId */); + LOG(INFO) << "getAuthenticatorId"; + if (mCb) { + mCb->onStateChanged(0, SessionState::GETTING_AUTHENTICATOR_ID); + mCb->onAuthenticatorIdRetrieved(0 /* authenticatorId */); } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::invalidateAuthenticatorId(int32_t /*cookie*/) { + LOG(INFO) << "invalidateAuthenticatorId"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::resetLockout(int32_t /*cookie*/, const keymaster::HardwareAuthToken& /*hat*/) { + LOG(INFO) << "resetLockout"; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Session::close(int32_t /*cookie*/) { + LOG(INFO) << "close"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::onPointerDown(int32_t /*pointerId*/, int32_t /*x*/, int32_t /*y*/, float /*minor*/, float /*major*/) { + LOG(INFO) << "onPointerDown"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::onPointerUp(int32_t /*pointerId*/) { + LOG(INFO) << "onPointerUp"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Session::onUiReady() { + LOG(INFO) << "onUiReady"; return ndk::ScopedAStatus::ok(); } } // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/WorkerThread.cpp b/biometrics/fingerprint/aidl/default/WorkerThread.cpp new file mode 100644 index 0000000000..512efb8d5d --- /dev/null +++ b/biometrics/fingerprint/aidl/default/WorkerThread.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 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. + */ + +#include "WorkerThread.h" + +namespace aidl::android::hardware::biometrics::fingerprint { + +// It's important that mThread is initialized after everything else because it runs a member +// function that may use any member of this class. +WorkerThread::WorkerThread(size_t maxQueueSize) + : mMaxSize(maxQueueSize), + mIsDestructing(false), + mQueue(), + mQueueMutex(), + mQueueCond(), + mThread(&WorkerThread::threadFunc, this) {} + +WorkerThread::~WorkerThread() { + // This is a signal for threadFunc to terminate as soon as possible, and a hint for schedule + // that it doesn't need to do any work. + mIsDestructing = true; + mQueueCond.notify_all(); + mThread.join(); +} + +bool WorkerThread::schedule(Task&& task) { + if (mIsDestructing) { + return false; + } + + std::unique_lock<std::mutex> lock(mQueueMutex); + if (mQueue.size() >= mMaxSize) { + return false; + } + mQueue.push_back(std::move(task)); + lock.unlock(); + mQueueCond.notify_one(); + return true; +} + +void WorkerThread::threadFunc() { + while (!mIsDestructing) { + std::unique_lock<std::mutex> lock(mQueueMutex); + mQueueCond.wait(lock, [this] { return !mQueue.empty() || mIsDestructing; }); + if (mIsDestructing) { + return; + } + Task task = std::move(mQueue.front()); + mQueue.pop_front(); + lock.unlock(); + task(); + } +} + +} // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/Fingerprint.h b/biometrics/fingerprint/aidl/default/include/Fingerprint.h index 4e952ba74e..ce1366cf4c 100644 --- a/biometrics/fingerprint/aidl/default/Fingerprint.h +++ b/biometrics/fingerprint/aidl/default/include/Fingerprint.h @@ -20,13 +20,17 @@ namespace aidl::android::hardware::biometrics::fingerprint { -class Fingerprint : public BnFingerprint { +class Fingerprint final : public BnFingerprint { public: - ndk::ScopedAStatus getSensorProps(std::vector<SensorProps>* _aidl_return) override; + Fingerprint(); + + ndk::ScopedAStatus getSensorProps(std::vector<SensorProps>* out) override; ndk::ScopedAStatus createSession(int32_t sensorId, int32_t userId, const std::shared_ptr<ISessionCallback>& cb, - std::shared_ptr<ISession>* _aidl_return) override; + std::shared_ptr<ISession>* out) override; + + ndk::ScopedAStatus reset() override; }; } // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/Session.h b/biometrics/fingerprint/aidl/default/include/Session.h index ed3ae3fd7e..99d806b5e5 100644 --- a/biometrics/fingerprint/aidl/default/Session.h +++ b/biometrics/fingerprint/aidl/default/include/Session.h @@ -33,14 +33,13 @@ class Session : public BnSession { ndk::ScopedAStatus revokeChallenge(int32_t cookie, int64_t challenge) override; ndk::ScopedAStatus enroll(int32_t cookie, const keymaster::HardwareAuthToken& hat, - std::shared_ptr<common::ICancellationSignal>* return_val) override; + std::shared_ptr<common::ICancellationSignal>* out) override; - ndk::ScopedAStatus authenticate( - int32_t cookie, int64_t keystoreOperationId, - std::shared_ptr<common::ICancellationSignal>* return_val) override; + ndk::ScopedAStatus authenticate(int32_t cookie, int64_t keystoreOperationId, + std::shared_ptr<common::ICancellationSignal>* out) override; ndk::ScopedAStatus detectInteraction( - int32_t cookie, std::shared_ptr<common::ICancellationSignal>* return_val) override; + int32_t cookie, std::shared_ptr<common::ICancellationSignal>* out) override; ndk::ScopedAStatus enumerateEnrollments(int32_t cookie) override; @@ -54,6 +53,8 @@ class Session : public BnSession { ndk::ScopedAStatus resetLockout(int32_t cookie, const keymaster::HardwareAuthToken& hat) override; + ndk::ScopedAStatus close(int32_t cookie) override; + ndk::ScopedAStatus onPointerDown(int32_t pointerId, int32_t x, int32_t y, float minor, float major) override; @@ -62,7 +63,7 @@ class Session : public BnSession { ndk::ScopedAStatus onUiReady() override; private: - std::shared_ptr<ISessionCallback> cb_; + std::shared_ptr<ISessionCallback> mCb; }; } // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/include/WorkerThread.h b/biometrics/fingerprint/aidl/default/include/WorkerThread.h new file mode 100644 index 0000000000..49104c85e2 --- /dev/null +++ b/biometrics/fingerprint/aidl/default/include/WorkerThread.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 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. + */ + +#pragma once + +#include <mutex> +#include <optional> +#include <queue> +#include <thread> + +namespace aidl::android::hardware::biometrics::fingerprint { + +using Task = std::function<void()>; + +// A class that encapsulates a worker thread and a task queue, and provides a convenient interface +// for a Session to schedule its tasks for asynchronous execution. +class WorkerThread final { + public: + // Internally creates a queue that cannot exceed maxQueueSize elements and a new thread that + // polls the queue for tasks until this instance is destructed. + explicit WorkerThread(size_t maxQueueSize); + + // Unblocks the internal queue and calls join on the internal thread allowing it to gracefully + // exit. + ~WorkerThread(); + + // Disallow copying this class. + WorkerThread(const WorkerThread&) = delete; + WorkerThread& operator=(const WorkerThread&) = delete; + + // Also disable moving this class to simplify implementation. + WorkerThread(WorkerThread&&) = delete; + WorkerThread& operator=(WorkerThread&&) = delete; + + // If the internal queue is not full, pushes a task at the end of the queue and returns true. + // Otherwise, returns false. If the queue is busy, blocks until it becomes available. + bool schedule(Task&& task); + + private: + // The function that runs on the internal thread. Sequentially runs the available tasks from + // the queue. If the queue is empty, waits until a new task is added. If the worker is being + // destructed, finishes its current task and gracefully exits. + void threadFunc(); + + // The maximum size that the queue is allowed to expand to. + size_t mMaxSize; + + // Whether the destructor was called. If true, tells threadFunc to exit as soon as possible, and + // tells schedule to avoid doing any work. + std::atomic<bool> mIsDestructing; + + // Queue that's guarded by mQueueMutex and mQueueCond. + std::deque<Task> mQueue; + std::mutex mQueueMutex; + std::condition_variable mQueueCond; + + // The internal thread that works on the tasks from the queue. + std::thread mThread; +}; + +} // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/tests/WorkerThreadTest.cpp b/biometrics/fingerprint/aidl/default/tests/WorkerThreadTest.cpp new file mode 100644 index 0000000000..ba901ad5ca --- /dev/null +++ b/biometrics/fingerprint/aidl/default/tests/WorkerThreadTest.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 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. + */ + +#include <algorithm> +#include <chrono> +#include <future> +#include <thread> + +#include <gtest/gtest.h> + +#include "WorkerThread.h" + +namespace { + +using aidl::android::hardware::biometrics::fingerprint::WorkerThread; +using namespace std::chrono_literals; + +TEST(WorkerThreadTest, ScheduleReturnsTrueWhenQueueHasSpace) { + WorkerThread worker(1 /*maxQueueSize*/); + for (int i = 0; i < 100; ++i) { + EXPECT_TRUE(worker.schedule([] {})); + // Allow enough time for the previous task to be processed. + std::this_thread::sleep_for(2ms); + } +} + +TEST(WorkerThreadTest, ScheduleReturnsFalseWhenQueueIsFull) { + WorkerThread worker(2 /*maxQueueSize*/); + // Add a long-running task. + worker.schedule([] { std::this_thread::sleep_for(1s); }); + + // Allow enough time for the worker to start working on the previous task. + std::this_thread::sleep_for(2ms); + + // Fill the worker's queue to the maximum. + worker.schedule([] {}); + worker.schedule([] {}); + + EXPECT_FALSE(worker.schedule([] {})); +} + +TEST(WorkerThreadTest, TasksExecuteInOrder) { + constexpr int NUM_TASKS = 10000; + WorkerThread worker(NUM_TASKS); + + std::vector<int> results; + for (int i = 0; i < NUM_TASKS; ++i) { + worker.schedule([&results, i] { + // Delay tasks differently to provoke races. + std::this_thread::sleep_for(std::chrono::nanoseconds(100 - i % 100)); + // Unguarded write to results to provoke races. + results.push_back(i); + }); + } + + std::promise<void> promise; + auto future = promise.get_future(); + + // Schedule a special task to signal when all of the tasks are finished. + worker.schedule([&promise] { promise.set_value(); }); + auto status = future.wait_for(1s); + ASSERT_EQ(status, std::future_status::ready); + + ASSERT_EQ(results.size(), NUM_TASKS); + EXPECT_TRUE(std::is_sorted(results.begin(), results.end())); +} + +TEST(WorkerThreadTest, ExecutionStopsAfterWorkerIsDestroyed) { + std::promise<void> promise1; + std::promise<void> promise2; + auto future1 = promise1.get_future(); + auto future2 = promise2.get_future(); + + { + WorkerThread worker(2 /*maxQueueSize*/); + worker.schedule([&promise1] { + promise1.set_value(); + std::this_thread::sleep_for(200ms); + }); + worker.schedule([&promise2] { promise2.set_value(); }); + + // Make sure the first task is executing. + auto status1 = future1.wait_for(1s); + ASSERT_EQ(status1, std::future_status::ready); + } + + // The second task should never execute. + auto status2 = future2.wait_for(1s); + EXPECT_EQ(status2, std::future_status::timeout); +} + +} // namespace diff --git a/bluetooth/audio/2.0/default/Android.bp b/bluetooth/audio/2.0/default/Android.bp index 0db0028879..8ed631e08f 100644 --- a/bluetooth/audio/2.0/default/Android.bp +++ b/bluetooth/audio/2.0/default/Android.bp @@ -23,24 +23,3 @@ cc_library_shared { "libutils", ], } - -cc_library_shared { - name: "libbluetooth_audio_session", - defaults: ["hidl_defaults"], - vendor: true, - srcs: [ - "session/BluetoothAudioSession.cpp", - "session/BluetoothAudioSupportedCodecsDB.cpp", - ], - export_include_dirs: ["session/"], - header_libs: ["libhardware_headers"], - shared_libs: [ - "android.hardware.bluetooth.audio@2.0", - "libbase", - "libcutils", - "libfmq", - "libhidlbase", - "liblog", - "libutils", - ], -} diff --git a/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp b/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp index b4a61b6ce9..3fe1a4dc13 100644 --- a/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp +++ b/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp @@ -22,8 +22,8 @@ #include <fmq/MessageQueue.h> #include <hidl/MQDescriptor.h> -#include "BluetoothAudioSessionReport.h" -#include "BluetoothAudioSupportedCodecsDB.h" +#include "BluetoothAudioSessionReport_2_1.h" +#include "BluetoothAudioSupportedCodecsDB_2_1.h" namespace android { namespace hardware { @@ -32,7 +32,7 @@ namespace audio { namespace V2_1 { namespace implementation { -using ::android::bluetooth::audio::BluetoothAudioSessionReport; +using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1; using ::android::hardware::kSynchronizedReadWrite; using ::android::hardware::MessageQueue; using ::android::hardware::Void; @@ -81,8 +81,8 @@ Return<void> A2dpOffloadAudioProvider::startSession( Return<void> A2dpOffloadAudioProvider::onSessionReady( startSession_cb _hidl_cb) { - BluetoothAudioSessionReport::OnSessionStarted(session_type_, stack_iface_, - nullptr, audio_config_); + BluetoothAudioSessionReport_2_1::OnSessionStarted(session_type_, stack_iface_, + nullptr, audio_config_); _hidl_cb(BluetoothAudioStatus::SUCCESS, DataMQ::Descriptor()); return Void(); } diff --git a/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp b/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp index a67c341abb..a37176ba4d 100644 --- a/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp +++ b/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp @@ -20,8 +20,8 @@ #include <android-base/logging.h> -#include "BluetoothAudioSessionReport.h" -#include "BluetoothAudioSupportedCodecsDB.h" +#include "BluetoothAudioSessionReport_2_1.h" +#include "BluetoothAudioSupportedCodecsDB_2_1.h" namespace android { namespace hardware { @@ -30,7 +30,7 @@ namespace audio { namespace V2_1 { namespace implementation { -using ::android::bluetooth::audio::BluetoothAudioSessionReport; +using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1; using ::android::hardware::Void; using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration; @@ -96,7 +96,7 @@ Return<void> A2dpSoftwareAudioProvider::startSession( Return<void> A2dpSoftwareAudioProvider::onSessionReady( startSession_cb _hidl_cb) { if (mDataMQ && mDataMQ->isValid()) { - BluetoothAudioSessionReport::OnSessionStarted( + BluetoothAudioSessionReport_2_1::OnSessionStarted( session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_); _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc()); } else { diff --git a/bluetooth/audio/2.1/default/Android.bp b/bluetooth/audio/2.1/default/Android.bp index 5381fec43e..c05aa3f04a 100644 --- a/bluetooth/audio/2.1/default/Android.bp +++ b/bluetooth/audio/2.1/default/Android.bp @@ -16,29 +16,7 @@ cc_library_shared { "android.hardware.bluetooth.audio@2.0", "android.hardware.bluetooth.audio@2.1", "libbase", - "libbluetooth_audio_session_2_1", - "libcutils", - "libfmq", - "libhidlbase", - "liblog", - "libutils", - ], -} - -cc_library_shared { - name: "libbluetooth_audio_session_2_1", - defaults: ["hidl_defaults"], - vendor: true, - srcs: [ - "session/BluetoothAudioSession.cpp", - "session/BluetoothAudioSupportedCodecsDB.cpp", - ], - export_include_dirs: ["session/"], - header_libs: ["libhardware_headers"], - shared_libs: [ - "android.hardware.bluetooth.audio@2.0", - "android.hardware.bluetooth.audio@2.1", - "libbase", + "libbluetooth_audio_session", "libcutils", "libfmq", "libhidlbase", diff --git a/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp b/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp index 73fe06c961..38889ae543 100644 --- a/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp +++ b/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp @@ -20,8 +20,8 @@ #include <android-base/logging.h> -#include "BluetoothAudioSessionReport.h" -#include "BluetoothAudioSupportedCodecsDB.h" +#include "BluetoothAudioSessionReport_2_1.h" +#include "BluetoothAudioSupportedCodecsDB_2_1.h" namespace android { namespace hardware { @@ -30,7 +30,7 @@ namespace audio { namespace V2_1 { namespace implementation { -using ::android::bluetooth::audio::BluetoothAudioSessionReport; +using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1; using ::android::hardware::kSynchronizedReadWrite; using ::android::hardware::MessageQueue; using ::android::hardware::Void; @@ -105,8 +105,8 @@ Return<void> BluetoothAudioProvider::streamStarted( * HAL server should start the streaming on data path. */ if (stack_iface_) { - BluetoothAudioSessionReport::ReportControlStatus(session_type_, true, - status); + BluetoothAudioSessionReport_2_1::ReportControlStatus(session_type_, true, + status); } else { LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) << ", status=" << toString(status) << " has NO session"; @@ -125,8 +125,8 @@ Return<void> BluetoothAudioProvider::streamSuspended( * HAL server should suspend the streaming on data path. */ if (stack_iface_) { - BluetoothAudioSessionReport::ReportControlStatus(session_type_, false, - status); + BluetoothAudioSessionReport_2_1::ReportControlStatus(session_type_, false, + status); } else { LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) << ", status=" << toString(status) << " has NO session"; @@ -139,7 +139,7 @@ Return<void> BluetoothAudioProvider::endSession() { LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_); if (stack_iface_) { - BluetoothAudioSessionReport::OnSessionEnded(session_type_); + BluetoothAudioSessionReport_2_1::OnSessionEnded(session_type_); stack_iface_->unlinkToDeath(death_recipient_); } else { LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) diff --git a/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp b/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp index adf27170d7..e1b1ac6793 100644 --- a/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp +++ b/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp @@ -20,7 +20,7 @@ #include <android-base/logging.h> -#include "BluetoothAudioSupportedCodecsDB.h" +#include "BluetoothAudioSupportedCodecsDB_2_1.h" namespace android { namespace hardware { diff --git a/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp b/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp index aded7e157a..712bd4f718 100644 --- a/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp +++ b/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp @@ -20,8 +20,8 @@ #include <android-base/logging.h> -#include "BluetoothAudioSessionReport.h" -#include "BluetoothAudioSupportedCodecsDB.h" +#include "BluetoothAudioSessionReport_2_1.h" +#include "BluetoothAudioSupportedCodecsDB_2_1.h" namespace android { namespace hardware { @@ -30,7 +30,7 @@ namespace audio { namespace V2_1 { namespace implementation { -using ::android::bluetooth::audio::BluetoothAudioSessionReport; +using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1; using ::android::hardware::Void; using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration; @@ -95,7 +95,7 @@ Return<void> HearingAidAudioProvider::startSession( Return<void> HearingAidAudioProvider::onSessionReady(startSession_cb _hidl_cb) { if (mDataMQ && mDataMQ->isValid()) { - BluetoothAudioSessionReport::OnSessionStarted( + BluetoothAudioSessionReport_2_1::OnSessionStarted( session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_); _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc()); } else { diff --git a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp index 9c2b4fe1db..2ebf6c511e 100644 --- a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp +++ b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp @@ -21,8 +21,8 @@ #include <android-base/logging.h> -#include "BluetoothAudioSessionReport.h" -#include "BluetoothAudioSupportedCodecsDB.h" +#include "BluetoothAudioSessionReport_2_1.h" +#include "BluetoothAudioSupportedCodecsDB_2_1.h" namespace android { namespace hardware { @@ -31,7 +31,7 @@ namespace audio { namespace V2_1 { namespace implementation { -using ::android::bluetooth::audio::BluetoothAudioSessionReport; +using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1; using ::android::hardware::Void; using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample; using ::android::hardware::bluetooth::audio::V2_0::ChannelMode; @@ -179,7 +179,7 @@ Return<void> LeAudioAudioProvider::startSession_2_1( Return<void> LeAudioAudioProvider::onSessionReady(startSession_cb _hidl_cb) { if (mDataMQ && mDataMQ->isValid()) { - BluetoothAudioSessionReport::OnSessionStarted( + BluetoothAudioSessionReport_2_1::OnSessionStarted( session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_); _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc()); } else { diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.cpp b/bluetooth/audio/2.1/default/session/BluetoothAudioSession.cpp deleted file mode 100644 index ea2c54afe1..0000000000 --- a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.cpp +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright 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 "BTAudioProviderSession" - -#include "BluetoothAudioSession.h" - -#include <android-base/logging.h> -#include <android-base/stringprintf.h> - -namespace android { -namespace bluetooth { -namespace audio { - -using ::android::hardware::audio::common::V5_0::AudioContentType; -using ::android::hardware::audio::common::V5_0::AudioUsage; -using ::android::hardware::audio::common::V5_0::PlaybackTrackMetadata; -using ::android::hardware::audio::common::V5_0::SourceMetadata; -using ::android::hardware::bluetooth::audio::V2_0::CodecType; -using ::android::hardware::bluetooth::audio::V2_0::TimeSpec; - -const CodecConfiguration BluetoothAudioSession::kInvalidCodecConfiguration = { - .codecType = CodecType::UNKNOWN, - .encodedAudioBitrate = 0x00000000, - .peerMtu = 0xffff, - .isScmstEnabled = false, - .config = {}}; -AudioConfiguration BluetoothAudioSession::invalidSoftwareAudioConfiguration = - {}; -AudioConfiguration BluetoothAudioSession::invalidOffloadAudioConfiguration = {}; - -static constexpr int kFmqSendTimeoutMs = 1000; // 1000 ms timeout for sending -static constexpr int kFmqReceiveTimeoutMs = - 1000; // 1000 ms timeout for receiving -static constexpr int kWritePollMs = 1; // polled non-blocking interval -static constexpr int kReadPollMs = 1; // polled non-blocking interval - -static inline timespec timespec_convert_from_hal(const TimeSpec& TS) { - return {.tv_sec = static_cast<long>(TS.tvSec), - .tv_nsec = static_cast<long>(TS.tvNSec)}; -} - -BluetoothAudioSession::BluetoothAudioSession(const SessionType& session_type) - : session_type_(session_type), stack_iface_(nullptr), mDataMQ(nullptr) { - invalidSoftwareAudioConfiguration.pcmConfig(kInvalidPcmParameters); - invalidOffloadAudioConfiguration.codecConfig(kInvalidCodecConfiguration); -} - -// The report function is used to report that the Bluetooth stack has started -// this session without any failure, and will invoke session_changed_cb_ to -// notify those registered bluetooth_audio outputs -void BluetoothAudioSession::OnSessionStarted( - const sp<IBluetoothAudioPort> stack_iface, const DataMQ::Descriptor* dataMQ, - const AudioConfiguration& audio_config) { - std::lock_guard<std::recursive_mutex> guard(mutex_); - if (stack_iface == nullptr) { - LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) - << ", IBluetoothAudioPort Invalid"; - } else if (!UpdateAudioConfig(audio_config)) { - LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) - << ", AudioConfiguration=" << toString(audio_config) - << " Invalid"; - } else if (!UpdateDataPath(dataMQ)) { - LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) - << " DataMQ Invalid"; - audio_config_ = - (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH - ? kInvalidOffloadAudioConfiguration - : kInvalidSoftwareAudioConfiguration); - } else { - stack_iface_ = stack_iface; - LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) - << ", AudioConfiguration=" << toString(audio_config); - ReportSessionStatus(); - } -} - -// The report function is used to report that the Bluetooth stack has ended the -// session, and will invoke session_changed_cb_ to notify registered -// bluetooth_audio outputs -void BluetoothAudioSession::OnSessionEnded() { - std::lock_guard<std::recursive_mutex> guard(mutex_); - bool toggled = IsSessionReady(); - LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_); - audio_config_ = (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH - ? kInvalidOffloadAudioConfiguration - : kInvalidSoftwareAudioConfiguration); - stack_iface_ = nullptr; - UpdateDataPath(nullptr); - if (toggled) { - ReportSessionStatus(); - } -} - -// invoking the registered session_changed_cb_ -void BluetoothAudioSession::ReportSessionStatus() { - // This is locked already by OnSessionStarted / OnSessionEnded - if (observers_.empty()) { - LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) - << " has NO port state observer"; - return; - } - for (auto& observer : observers_) { - uint16_t cookie = observer.first; - std::shared_ptr<struct PortStatusCallbacks> cb = observer.second; - LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) - << " notify to bluetooth_audio=0x" - << android::base::StringPrintf("%04x", cookie); - cb->session_changed_cb_(cookie); - } -} - -// The report function is used to report that the Bluetooth stack has notified -// the result of startStream or suspendStream, and will invoke -// control_result_cb_ to notify registered bluetooth_audio outputs -void BluetoothAudioSession::ReportControlStatus( - bool start_resp, const BluetoothAudioStatus& status) { - std::lock_guard<std::recursive_mutex> guard(mutex_); - if (observers_.empty()) { - LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) - << " has NO port state observer"; - return; - } - for (auto& observer : observers_) { - uint16_t cookie = observer.first; - std::shared_ptr<struct PortStatusCallbacks> cb = observer.second; - LOG(INFO) << __func__ << " - status=" << toString(status) - << " for SessionType=" << toString(session_type_) - << ", bluetooth_audio=0x" - << android::base::StringPrintf("%04x", cookie) - << (start_resp ? " started" : " suspended"); - cb->control_result_cb_(cookie, start_resp, status); - } -} - -// The function helps to check if this session is ready or not -// @return: true if the Bluetooth stack has started the specified session -bool BluetoothAudioSession::IsSessionReady() { - std::lock_guard<std::recursive_mutex> guard(mutex_); - bool dataMQ_valid = - (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH || - (mDataMQ != nullptr && mDataMQ->isValid())); - return stack_iface_ != nullptr && dataMQ_valid; -} - -bool BluetoothAudioSession::UpdateDataPath(const DataMQ::Descriptor* dataMQ) { - if (dataMQ == nullptr) { - // usecase of reset by nullptr - mDataMQ = nullptr; - return true; - } - std::unique_ptr<DataMQ> tempDataMQ; - tempDataMQ.reset(new DataMQ(*dataMQ)); - if (!tempDataMQ || !tempDataMQ->isValid()) { - mDataMQ = nullptr; - return false; - } - mDataMQ = std::move(tempDataMQ); - return true; -} - -bool BluetoothAudioSession::UpdateAudioConfig( - const AudioConfiguration& audio_config) { - bool is_software_session = - (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || - session_type_ == SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH || - session_type_ == SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH || - session_type_ == SessionType::LE_AUDIO_SOFTWARE_DECODED_DATAPATH); - bool is_offload_session = - (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH); - auto audio_config_discriminator = audio_config.getDiscriminator(); - bool is_software_audio_config = - (is_software_session && - audio_config_discriminator == - AudioConfiguration::hidl_discriminator::pcmConfig); - bool is_offload_audio_config = - (is_offload_session && - audio_config_discriminator == - AudioConfiguration::hidl_discriminator::codecConfig); - if (!is_software_audio_config && !is_offload_audio_config) { - return false; - } - audio_config_ = audio_config; - return true; -} - -// The control function helps the bluetooth_audio module to register -// PortStatusCallbacks -// @return: cookie - the assigned number to this bluetooth_audio output -uint16_t BluetoothAudioSession::RegisterStatusCback( - const PortStatusCallbacks& cbacks) { - std::lock_guard<std::recursive_mutex> guard(mutex_); - uint16_t cookie = ObserversCookieGetInitValue(session_type_); - uint16_t cookie_upper_bound = ObserversCookieGetUpperBound(session_type_); - - while (cookie < cookie_upper_bound) { - if (observers_.find(cookie) == observers_.end()) { - break; - } - ++cookie; - } - if (cookie >= cookie_upper_bound) { - LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) - << " has " << observers_.size() - << " observers already (No Resource)"; - return kObserversCookieUndefined; - } - std::shared_ptr<struct PortStatusCallbacks> cb = - std::make_shared<struct PortStatusCallbacks>(); - *cb = cbacks; - observers_[cookie] = cb; - return cookie; -} - -// The control function helps the bluetooth_audio module to unregister -// PortStatusCallbacks -// @param: cookie - indicates which bluetooth_audio output is -void BluetoothAudioSession::UnregisterStatusCback(uint16_t cookie) { - std::lock_guard<std::recursive_mutex> guard(mutex_); - if (observers_.erase(cookie) != 1) { - LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) - << " no such provider=0x" - << android::base::StringPrintf("%04x", cookie); - } -} - -// The control function is for the bluetooth_audio module to get the current -// AudioConfiguration -const AudioConfiguration& BluetoothAudioSession::GetAudioConfig() { - std::lock_guard<std::recursive_mutex> guard(mutex_); - if (IsSessionReady()) { - return audio_config_; - } else if (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { - return kInvalidOffloadAudioConfiguration; - } else { - return kInvalidSoftwareAudioConfiguration; - } -} - -// Those control functions are for the bluetooth_audio module to start, suspend, -// stop stream, to check position, and to update metadata. -bool BluetoothAudioSession::StartStream() { - std::lock_guard<std::recursive_mutex> guard(mutex_); - if (!IsSessionReady()) { - LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) - << " has NO session"; - return false; - } - auto hal_retval = stack_iface_->startStream(); - if (!hal_retval.isOk()) { - LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" - << toString(session_type_) << " failed"; - return false; - } - return true; -} - -bool BluetoothAudioSession::SuspendStream() { - std::lock_guard<std::recursive_mutex> guard(mutex_); - if (!IsSessionReady()) { - LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) - << " has NO session"; - return false; - } - auto hal_retval = stack_iface_->suspendStream(); - if (!hal_retval.isOk()) { - LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" - << toString(session_type_) << " failed"; - return false; - } - return true; -} - -void BluetoothAudioSession::StopStream() { - std::lock_guard<std::recursive_mutex> guard(mutex_); - if (!IsSessionReady()) { - return; - } - auto hal_retval = stack_iface_->stopStream(); - if (!hal_retval.isOk()) { - LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" - << toString(session_type_) << " failed"; - } -} - -bool BluetoothAudioSession::GetPresentationPosition( - uint64_t* remote_delay_report_ns, uint64_t* total_bytes_readed, - timespec* data_position) { - std::lock_guard<std::recursive_mutex> guard(mutex_); - if (!IsSessionReady()) { - LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) - << " has NO session"; - return false; - } - bool retval = false; - auto hal_retval = stack_iface_->getPresentationPosition( - [&retval, &remote_delay_report_ns, &total_bytes_readed, &data_position]( - BluetoothAudioStatus status, - const uint64_t& remoteDeviceAudioDelayNanos, - uint64_t transmittedOctets, - const TimeSpec& transmittedOctetsTimeStamp) { - if (status == BluetoothAudioStatus::SUCCESS) { - if (remote_delay_report_ns) - *remote_delay_report_ns = remoteDeviceAudioDelayNanos; - if (total_bytes_readed) *total_bytes_readed = transmittedOctets; - if (data_position) - *data_position = - timespec_convert_from_hal(transmittedOctetsTimeStamp); - retval = true; - } - }); - if (!hal_retval.isOk()) { - LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" - << toString(session_type_) << " failed"; - return false; - } - return retval; -} - -void BluetoothAudioSession::UpdateTracksMetadata( - const struct source_metadata* source_metadata) { - std::lock_guard<std::recursive_mutex> guard(mutex_); - if (!IsSessionReady()) { - LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) - << " has NO session"; - return; - } - - ssize_t track_count = source_metadata->track_count; - LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << ", " - << track_count << " track(s)"; - if (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || - session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { - return; - } - - struct playback_track_metadata* track = source_metadata->tracks; - SourceMetadata sourceMetadata; - PlaybackTrackMetadata* halMetadata; - - sourceMetadata.tracks.resize(track_count); - halMetadata = sourceMetadata.tracks.data(); - while (track_count && track) { - halMetadata->usage = static_cast<AudioUsage>(track->usage); - halMetadata->contentType = - static_cast<AudioContentType>(track->content_type); - halMetadata->gain = track->gain; - LOG(VERBOSE) << __func__ << " - SessionType=" << toString(session_type_) - << ", usage=" << toString(halMetadata->usage) - << ", content=" << toString(halMetadata->contentType) - << ", gain=" << halMetadata->gain; - --track_count; - ++track; - ++halMetadata; - } - auto hal_retval = stack_iface_->updateMetadata(sourceMetadata); - if (!hal_retval.isOk()) { - LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" - << toString(session_type_) << " failed"; - } -} - -// The control function writes stream to FMQ -size_t BluetoothAudioSession::OutWritePcmData(const void* buffer, - size_t bytes) { - if (buffer == nullptr || !bytes) return 0; - size_t totalWritten = 0; - int ms_timeout = kFmqSendTimeoutMs; - do { - std::unique_lock<std::recursive_mutex> lock(mutex_); - if (!IsSessionReady()) break; - size_t availableToWrite = mDataMQ->availableToWrite(); - if (availableToWrite) { - if (availableToWrite > (bytes - totalWritten)) { - availableToWrite = bytes - totalWritten; - } - - if (!mDataMQ->write(static_cast<const uint8_t*>(buffer) + totalWritten, - availableToWrite)) { - ALOGE("FMQ datapath writing %zu/%zu failed", totalWritten, bytes); - return totalWritten; - } - totalWritten += availableToWrite; - } else if (ms_timeout >= kWritePollMs) { - lock.unlock(); - usleep(kWritePollMs * 1000); - ms_timeout -= kWritePollMs; - } else { - ALOGD("out data %zu/%zu overflow %d ms", totalWritten, bytes, - (kFmqSendTimeoutMs - ms_timeout)); - return totalWritten; - } - } while (totalWritten < bytes); - return totalWritten; -} - -// The control function reads stream from FMQ -size_t BluetoothAudioSession::InReadPcmData(void* buffer, size_t bytes) { - if (buffer == nullptr || !bytes) return 0; - size_t totalRead = 0; - int ms_timeout = kFmqReceiveTimeoutMs; - do { - std::unique_lock<std::recursive_mutex> lock(mutex_); - if (!IsSessionReady()) break; - size_t availableToRead = mDataMQ->availableToRead(); - if (availableToRead) { - if (availableToRead > (bytes - totalRead)) { - availableToRead = bytes - totalRead; - } - if (!mDataMQ->read(static_cast<uint8_t*>(buffer) + totalRead, - availableToRead)) { - ALOGE("FMQ datapath reading %zu/%zu failed", totalRead, bytes); - return totalRead; - } - totalRead += availableToRead; - } else if (ms_timeout >= kReadPollMs) { - lock.unlock(); - usleep(kReadPollMs * 1000); - ms_timeout -= kReadPollMs; - continue; - } else { - ALOGD("in data %zu/%zu overflow %d ms", totalRead, bytes, - (kFmqReceiveTimeoutMs - ms_timeout)); - return totalRead; - } - } while (totalRead < bytes); - return totalRead; -} - -std::unique_ptr<BluetoothAudioSessionInstance> - BluetoothAudioSessionInstance::instance_ptr = - std::unique_ptr<BluetoothAudioSessionInstance>( - new BluetoothAudioSessionInstance()); - -// API to fetch the session -std::shared_ptr<BluetoothAudioSession> -BluetoothAudioSessionInstance::GetSessionInstance( - const SessionType& session_type) { - std::lock_guard<std::mutex> guard(instance_ptr->mutex_); - if (!instance_ptr->sessions_map_.empty()) { - auto entry = instance_ptr->sessions_map_.find(session_type); - if (entry != instance_ptr->sessions_map_.end()) { - return entry->second; - } - } - std::shared_ptr<BluetoothAudioSession> session_ptr = - std::make_shared<BluetoothAudioSession>(session_type); - instance_ptr->sessions_map_[session_type] = session_ptr; - return session_ptr; -} - -} // namespace audio -} // namespace bluetooth -} // namespace android diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSession.h deleted file mode 100644 index 7bc12e6337..0000000000 --- a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.h +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 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. - */ - -#pragma once - -#include <mutex> -#include <unordered_map> - -#include <android/hardware/bluetooth/audio/2.0/IBluetoothAudioPort.h> -#include <android/hardware/bluetooth/audio/2.1/types.h> -#include <fmq/MessageQueue.h> -#include <hardware/audio.h> -#include <hidl/MQDescriptor.h> - -namespace android { -namespace bluetooth { -namespace audio { - -using ::android::sp; -using ::android::hardware::kSynchronizedReadWrite; -using ::android::hardware::MessageQueue; -using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample; -using ::android::hardware::bluetooth::audio::V2_0::ChannelMode; -using ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration; -using ::android::hardware::bluetooth::audio::V2_0::IBluetoothAudioPort; -using ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration; -using ::android::hardware::bluetooth::audio::V2_1::PcmParameters; -using ::android::hardware::bluetooth::audio::V2_1::SampleRate; -using ::android::hardware::bluetooth::audio::V2_1::SessionType; - -using BluetoothAudioStatus = - ::android::hardware::bluetooth::audio::V2_0::Status; - -using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>; - -static constexpr uint16_t kObserversCookieSize = 0x0010; // 0x0000 ~ 0x000f -constexpr uint16_t kObserversCookieUndefined = - (static_cast<uint16_t>(SessionType::UNKNOWN) << 8 & 0xff00); -inline SessionType ObserversCookieGetSessionType(uint16_t cookie) { - return static_cast<SessionType>(cookie >> 8 & 0x00ff); -} -inline uint16_t ObserversCookieGetInitValue(SessionType session_type) { - return (static_cast<uint16_t>(session_type) << 8 & 0xff00); -} -inline uint16_t ObserversCookieGetUpperBound(SessionType session_type) { - return (static_cast<uint16_t>(session_type) << 8 & 0xff00) + - kObserversCookieSize; -} - -// This presents the callbacks of started / suspended and session changed, -// and the bluetooth_audio module uses to receive the status notification -struct PortStatusCallbacks { - // control_result_cb_ - when the Bluetooth stack reports results of - // streamStarted or streamSuspended, the BluetoothAudioProvider will invoke - // this callback to report to the bluetooth_audio module. - // @param: cookie - indicates which bluetooth_audio output should handle - // @param: start_resp - this report is for startStream or not - // @param: status - the result of startStream - std::function<void(uint16_t cookie, bool start_resp, - const BluetoothAudioStatus& status)> - control_result_cb_; - // session_changed_cb_ - when the Bluetooth stack start / end session, the - // BluetoothAudioProvider will invoke this callback to notify to the - // bluetooth_audio module. - // @param: cookie - indicates which bluetooth_audio output should handle - std::function<void(uint16_t cookie)> session_changed_cb_; -}; - -class BluetoothAudioSession { - private: - // using recursive_mutex to allow hwbinder to re-enter again. - std::recursive_mutex mutex_; - SessionType session_type_; - - // audio control path to use for both software and offloading - sp<IBluetoothAudioPort> stack_iface_; - // Audio path (FMQ) for software encoding/decoded data - std::unique_ptr<DataMQ> mDataMQ; - // audio data configuration for both software and offloading - AudioConfiguration audio_config_; - - static AudioConfiguration invalidSoftwareAudioConfiguration; - static AudioConfiguration invalidOffloadAudioConfiguration; - - // saving those registered bluetooth_audio's callbacks - std::unordered_map<uint16_t, std::shared_ptr<struct PortStatusCallbacks>> - observers_; - - bool UpdateDataPath(const DataMQ::Descriptor* dataMQ); - bool UpdateAudioConfig(const AudioConfiguration& audio_config); - // invoking the registered session_changed_cb_ - void ReportSessionStatus(); - - public: - BluetoothAudioSession(const SessionType& session_type); - - // The function helps to check if this session is ready or not - // @return: true if the Bluetooth stack has started the specified session - bool IsSessionReady(); - - // The report function is used to report that the Bluetooth stack has started - // this session without any failure, and will invoke session_changed_cb_ to - // notify those registered bluetooth_audio outputs - void OnSessionStarted(const sp<IBluetoothAudioPort> stack_iface, - const DataMQ::Descriptor* dataMQ, - const AudioConfiguration& audio_config); - - // The report function is used to report that the Bluetooth stack has ended - // the session, and will invoke session_changed_cb_ to notify registered - // bluetooth_audio outputs - void OnSessionEnded(); - - // The report function is used to report that the Bluetooth stack has notified - // the result of startStream or suspendStream, and will invoke - // control_result_cb_ to notify registered bluetooth_audio outputs - void ReportControlStatus(bool start_resp, const BluetoothAudioStatus& status); - - // The control function helps the bluetooth_audio module to register - // PortStatusCallbacks - // @return: cookie - the assigned number to this bluetooth_audio output - uint16_t RegisterStatusCback(const PortStatusCallbacks& cbacks); - - // The control function helps the bluetooth_audio module to unregister - // PortStatusCallbacks - // @param: cookie - indicates which bluetooth_audio output is - void UnregisterStatusCback(uint16_t cookie); - - // The control function is for the bluetooth_audio module to get the current - // AudioConfiguration - const AudioConfiguration& GetAudioConfig(); - - // Those control functions are for the bluetooth_audio module to start, - // suspend, stop stream, to check position, and to update metadata. - bool StartStream(); - bool SuspendStream(); - void StopStream(); - bool GetPresentationPosition(uint64_t* remote_delay_report_ns, - uint64_t* total_bytes_readed, - timespec* data_position); - void UpdateTracksMetadata(const struct source_metadata* source_metadata); - - // The control function writes stream to FMQ - size_t OutWritePcmData(const void* buffer, size_t bytes); - // The control function read stream from FMQ - size_t InReadPcmData(void* buffer, size_t bytes); - - static constexpr PcmParameters kInvalidPcmParameters = { - .sampleRate = SampleRate::RATE_UNKNOWN, - .channelMode = ChannelMode::UNKNOWN, - .bitsPerSample = BitsPerSample::BITS_UNKNOWN, - .dataIntervalUs = 0, - }; - // can't be constexpr because of non-literal type - static const CodecConfiguration kInvalidCodecConfiguration; - - static constexpr AudioConfiguration& kInvalidSoftwareAudioConfiguration = - invalidSoftwareAudioConfiguration; - static constexpr AudioConfiguration& kInvalidOffloadAudioConfiguration = - invalidOffloadAudioConfiguration; -}; - -class BluetoothAudioSessionInstance { - public: - // The API is to fetch the specified session - static std::shared_ptr<BluetoothAudioSession> GetSessionInstance( - const SessionType& session_type); - - private: - static std::unique_ptr<BluetoothAudioSessionInstance> instance_ptr; - std::mutex mutex_; - std::unordered_map<SessionType, std::shared_ptr<BluetoothAudioSession>> - sessions_map_; -}; - -} // namespace audio -} // namespace bluetooth -} // namespace android diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionControl.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSessionControl.h deleted file mode 100644 index 017a6115cb..0000000000 --- a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionControl.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 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. - */ - -#pragma once - -#include "BluetoothAudioSession.h" - -namespace android { -namespace bluetooth { -namespace audio { - -class BluetoothAudioSessionControl { - public: - // The control API helps to check if session is ready or not - // @return: true if the Bluetooth stack has started th specified session - static bool IsSessionReady(const SessionType& session_type) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - return session_ptr->IsSessionReady(); - } - return false; - } - - // The control API helps the bluetooth_audio module to register - // PortStatusCallbacks - // @return: cookie - the assigned number to this bluetooth_audio output - static uint16_t RegisterControlResultCback( - const SessionType& session_type, const PortStatusCallbacks& cbacks) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - return session_ptr->RegisterStatusCback(cbacks); - } - return kObserversCookieUndefined; - } - - // The control API helps the bluetooth_audio module to unregister - // PortStatusCallbacks - // @param: cookie - indicates which bluetooth_audio output is - static void UnregisterControlResultCback(const SessionType& session_type, - uint16_t cookie) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - session_ptr->UnregisterStatusCback(cookie); - } - } - - // The control API for the bluetooth_audio module to get current - // AudioConfiguration - static const AudioConfiguration& GetAudioConfig( - const SessionType& session_type) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - return session_ptr->GetAudioConfig(); - } else if (session_type == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { - return BluetoothAudioSession::kInvalidOffloadAudioConfiguration; - } else { - return BluetoothAudioSession::kInvalidSoftwareAudioConfiguration; - } - } - - // Those control APIs for the bluetooth_audio module to start / suspend / stop - // stream, to check position, and to update metadata. - static bool StartStream(const SessionType& session_type) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - return session_ptr->StartStream(); - } - return false; - } - - static bool SuspendStream(const SessionType& session_type) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - return session_ptr->SuspendStream(); - } - return false; - } - - static void StopStream(const SessionType& session_type) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - session_ptr->StopStream(); - } - } - - static bool GetPresentationPosition(const SessionType& session_type, - uint64_t* remote_delay_report_ns, - uint64_t* total_bytes_readed, - timespec* data_position) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - return session_ptr->GetPresentationPosition( - remote_delay_report_ns, total_bytes_readed, data_position); - } - return false; - } - - static void UpdateTracksMetadata( - const SessionType& session_type, - const struct source_metadata* source_metadata) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - session_ptr->UpdateTracksMetadata(source_metadata); - } - } - - // The control API writes stream to FMQ - static size_t OutWritePcmData(const SessionType& session_type, - const void* buffer, size_t bytes) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - return session_ptr->OutWritePcmData(buffer, bytes); - } - return 0; - } - - // The control API reads stream from FMQ - static size_t InReadPcmData(const SessionType& session_type, void* buffer, - size_t bytes) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - return session_ptr->InReadPcmData(buffer, bytes); - } - return 0; - } -}; - -} // namespace audio -} // namespace bluetooth -} // namespace android diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionReport.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSessionReport.h deleted file mode 100644 index 267bf8fa92..0000000000 --- a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionReport.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 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. - */ - -#pragma once - -#include "BluetoothAudioSession.h" - -namespace android { -namespace bluetooth { -namespace audio { - -class BluetoothAudioSessionReport { - public: - // The API reports the Bluetooth stack has started the session, and will - // inform registered bluetooth_audio session - static void OnSessionStarted(const SessionType& session_type, - const sp<IBluetoothAudioPort> host_iface, - const DataMQ::Descriptor* dataMQ, - const AudioConfiguration& audio_config) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - session_ptr->OnSessionStarted(host_iface, dataMQ, audio_config); - } - } - // The API reports the Bluetooth stack has ended the session, and will - // inform registered bluetooth_audio outputs - static void OnSessionEnded(const SessionType& session_type) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - session_ptr->OnSessionEnded(); - } - } - // The API reports the Bluetooth stack has replied the result of startStream - // or suspendStream, and will inform registered bluetooth_audio outputs - static void ReportControlStatus(const SessionType& session_type, - const bool& start_resp, - const BluetoothAudioStatus& status) { - std::shared_ptr<BluetoothAudioSession> session_ptr = - BluetoothAudioSessionInstance::GetSessionInstance(session_type); - if (session_ptr != nullptr) { - session_ptr->ReportControlStatus(start_resp, status); - } - } -}; - -} // namespace audio -} // namespace bluetooth -} // namespace android diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp deleted file mode 100644 index 0937f441e3..0000000000 --- a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright 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 "BTAudioProviderSessionCodecsDB" - -#include "BluetoothAudioSupportedCodecsDB.h" - -#include <android-base/logging.h> - -namespace android { -namespace bluetooth { -namespace audio { - -using ::android::hardware::bluetooth::audio::V2_0::AacObjectType; -using ::android::hardware::bluetooth::audio::V2_0::AacParameters; -using ::android::hardware::bluetooth::audio::V2_0::AacVariableBitRate; -using ::android::hardware::bluetooth::audio::V2_0::AptxParameters; -using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample; -using ::android::hardware::bluetooth::audio::V2_0::ChannelMode; -using ::android::hardware::bluetooth::audio::V2_0::CodecType; -using ::android::hardware::bluetooth::audio::V2_0::LdacChannelMode; -using ::android::hardware::bluetooth::audio::V2_0::LdacParameters; -using ::android::hardware::bluetooth::audio::V2_0::LdacQualityIndex; -using ::android::hardware::bluetooth::audio::V2_0::SbcAllocMethod; -using ::android::hardware::bluetooth::audio::V2_0::SbcBlockLength; -using ::android::hardware::bluetooth::audio::V2_0::SbcChannelMode; -using ::android::hardware::bluetooth::audio::V2_0::SbcNumSubbands; -using ::android::hardware::bluetooth::audio::V2_0::SbcParameters; -using ::android::hardware::bluetooth::audio::V2_1::SampleRate; - -// Default Supported PCM Parameters -static const ::android::hardware::bluetooth::audio::V2_0::PcmParameters - kDefaultSoftwarePcmCapabilities = { - .sampleRate = static_cast< - android::hardware::bluetooth::audio::V2_0::SampleRate>( - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 | - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000 | - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_88200 | - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_96000 | - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_16000), - .channelMode = - static_cast<ChannelMode>(ChannelMode::MONO | ChannelMode::STEREO), - .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16 | - BitsPerSample::BITS_24 | - BitsPerSample::BITS_32)}; - -static const PcmParameters kDefaultSoftwarePcmCapabilities_2_1 = { - .sampleRate = static_cast<SampleRate>( - SampleRate::RATE_48000 | SampleRate::RATE_44100 | - SampleRate::RATE_32000 | SampleRate::RATE_24000 | - SampleRate::RATE_16000 | SampleRate::RATE_8000), - .channelMode = - static_cast<ChannelMode>(ChannelMode::MONO | ChannelMode::STEREO), - .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16)}; - -// Default Supported Codecs -// SBC: mSampleRate:(44100), mBitsPerSample:(16), mChannelMode:(MONO|STEREO) -// all blocks | subbands 8 | Loudness -static const SbcParameters kDefaultOffloadSbcCapability = { - .sampleRate = - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100, - .channelMode = static_cast<SbcChannelMode>(SbcChannelMode::MONO | - SbcChannelMode::JOINT_STEREO), - .blockLength = static_cast<SbcBlockLength>( - SbcBlockLength::BLOCKS_4 | SbcBlockLength::BLOCKS_8 | - SbcBlockLength::BLOCKS_12 | SbcBlockLength::BLOCKS_16), - .numSubbands = SbcNumSubbands::SUBBAND_8, - .allocMethod = SbcAllocMethod::ALLOC_MD_L, - .bitsPerSample = BitsPerSample::BITS_16, - .minBitpool = 2, - .maxBitpool = 53}; - -// AAC: mSampleRate:(44100), mBitsPerSample:(16), mChannelMode:(STEREO) -static const AacParameters kDefaultOffloadAacCapability = { - .objectType = AacObjectType::MPEG2_LC, - .sampleRate = - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100, - .channelMode = ChannelMode::STEREO, - .variableBitRateEnabled = AacVariableBitRate::ENABLED, - .bitsPerSample = BitsPerSample::BITS_16}; - -// LDAC: mSampleRate:(44100|48000|88200|96000), mBitsPerSample:(16|24|32), -// mChannelMode:(DUAL|STEREO) -static const LdacParameters kDefaultOffloadLdacCapability = { - .sampleRate = - static_cast<android::hardware::bluetooth::audio::V2_0::SampleRate>( - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 | - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000 | - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_88200 | - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_96000), - .channelMode = static_cast<LdacChannelMode>(LdacChannelMode::DUAL | - LdacChannelMode::STEREO), - .qualityIndex = LdacQualityIndex::QUALITY_HIGH, - .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16 | - BitsPerSample::BITS_24 | - BitsPerSample::BITS_32)}; - -// aptX: mSampleRate:(44100|48000), mBitsPerSample:(16), mChannelMode:(STEREO) -static const AptxParameters kDefaultOffloadAptxCapability = { - .sampleRate = - static_cast<android::hardware::bluetooth::audio::V2_0::SampleRate>( - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 | - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000), - .channelMode = ChannelMode::STEREO, - .bitsPerSample = BitsPerSample::BITS_16, -}; - -// aptX HD: mSampleRate:(44100|48000), mBitsPerSample:(24), -// mChannelMode:(STEREO) -static const AptxParameters kDefaultOffloadAptxHdCapability = { - .sampleRate = - static_cast<android::hardware::bluetooth::audio::V2_0::SampleRate>( - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 | - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000), - .channelMode = ChannelMode::STEREO, - .bitsPerSample = BitsPerSample::BITS_24, -}; - -const std::vector<CodecCapabilities> kDefaultOffloadA2dpCodecCapabilities = { - {.codecType = CodecType::SBC, .capabilities = {}}, - {.codecType = CodecType::AAC, .capabilities = {}}, - {.codecType = CodecType::LDAC, .capabilities = {}}, - {.codecType = CodecType::APTX, .capabilities = {}}, - {.codecType = CodecType::APTX_HD, .capabilities = {}}}; - -static bool IsSingleBit(uint32_t bitmasks, uint32_t bitfield) { - bool single = false; - uint32_t test_bit = 0x00000001; - while (test_bit <= bitmasks && test_bit <= bitfield) { - if (bitfield & test_bit && bitmasks & test_bit) { - if (single) return false; - single = true; - } - if (test_bit == 0x80000000) break; - test_bit <<= 1; - } - return single; -} - -static bool IsOffloadSbcConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific); -static bool IsOffloadAacConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific); -static bool IsOffloadLdacConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific); -static bool IsOffloadAptxConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific); -static bool IsOffloadAptxHdConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific); - -static bool IsOffloadSbcConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific) { - if (codec_specific.getDiscriminator() != - CodecConfiguration::CodecSpecific::hidl_discriminator::sbcConfig) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } - const SbcParameters sbc_data = codec_specific.sbcConfig(); - if (!IsSingleBit(static_cast<uint32_t>(sbc_data.sampleRate), 0xff) || - !IsSingleBit(static_cast<uint32_t>(sbc_data.channelMode), 0x0f) || - !IsSingleBit(static_cast<uint32_t>(sbc_data.blockLength), 0xf0) || - !IsSingleBit(static_cast<uint32_t>(sbc_data.numSubbands), 0x0c) || - !IsSingleBit(static_cast<uint32_t>(sbc_data.allocMethod), 0x03) || - !IsSingleBit(static_cast<uint32_t>(sbc_data.bitsPerSample), 0x07) || - sbc_data.minBitpool > sbc_data.maxBitpool) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } else if ((sbc_data.sampleRate & kDefaultOffloadSbcCapability.sampleRate) && - (sbc_data.channelMode & - kDefaultOffloadSbcCapability.channelMode) && - (sbc_data.blockLength & - kDefaultOffloadSbcCapability.blockLength) && - (sbc_data.numSubbands & - kDefaultOffloadSbcCapability.numSubbands) && - (sbc_data.allocMethod & - kDefaultOffloadSbcCapability.allocMethod) && - (sbc_data.bitsPerSample & - kDefaultOffloadSbcCapability.bitsPerSample) && - (kDefaultOffloadSbcCapability.minBitpool <= sbc_data.minBitpool && - sbc_data.maxBitpool <= kDefaultOffloadSbcCapability.maxBitpool)) { - return true; - } - LOG(WARNING) << __func__ - << ": Unsupported CodecSpecific=" << toString(codec_specific); - return false; -} - -static bool IsOffloadAacConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific) { - if (codec_specific.getDiscriminator() != - CodecConfiguration::CodecSpecific::hidl_discriminator::aacConfig) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } - const AacParameters aac_data = codec_specific.aacConfig(); - if (!IsSingleBit(static_cast<uint32_t>(aac_data.objectType), 0xf0) || - !IsSingleBit(static_cast<uint32_t>(aac_data.sampleRate), 0xff) || - !IsSingleBit(static_cast<uint32_t>(aac_data.channelMode), 0x03) || - !IsSingleBit(static_cast<uint32_t>(aac_data.bitsPerSample), 0x07)) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } else if ((aac_data.objectType & kDefaultOffloadAacCapability.objectType) && - (aac_data.sampleRate & kDefaultOffloadAacCapability.sampleRate) && - (aac_data.channelMode & - kDefaultOffloadAacCapability.channelMode) && - (aac_data.variableBitRateEnabled == AacVariableBitRate::DISABLED || - kDefaultOffloadAacCapability.variableBitRateEnabled == - AacVariableBitRate::ENABLED) && - (aac_data.bitsPerSample & - kDefaultOffloadAacCapability.bitsPerSample)) { - return true; - } - LOG(WARNING) << __func__ - << ": Unsupported CodecSpecific=" << toString(codec_specific); - return false; -} - -static bool IsOffloadLdacConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific) { - if (codec_specific.getDiscriminator() != - CodecConfiguration::CodecSpecific::hidl_discriminator::ldacConfig) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } - const LdacParameters ldac_data = codec_specific.ldacConfig(); - if (!IsSingleBit(static_cast<uint32_t>(ldac_data.sampleRate), 0xff) || - !IsSingleBit(static_cast<uint32_t>(ldac_data.channelMode), 0x07) || - (ldac_data.qualityIndex > LdacQualityIndex::QUALITY_LOW && - ldac_data.qualityIndex != LdacQualityIndex::QUALITY_ABR) || - !IsSingleBit(static_cast<uint32_t>(ldac_data.bitsPerSample), 0x07)) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } else if ((ldac_data.sampleRate & - kDefaultOffloadLdacCapability.sampleRate) && - (ldac_data.channelMode & - kDefaultOffloadLdacCapability.channelMode) && - (ldac_data.bitsPerSample & - kDefaultOffloadLdacCapability.bitsPerSample)) { - return true; - } - LOG(WARNING) << __func__ - << ": Unsupported CodecSpecific=" << toString(codec_specific); - return false; -} - -static bool IsOffloadAptxConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific) { - if (codec_specific.getDiscriminator() != - CodecConfiguration::CodecSpecific::hidl_discriminator::aptxConfig) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } - const AptxParameters aptx_data = codec_specific.aptxConfig(); - if (!IsSingleBit(static_cast<uint32_t>(aptx_data.sampleRate), 0xff) || - !IsSingleBit(static_cast<uint32_t>(aptx_data.channelMode), 0x03) || - !IsSingleBit(static_cast<uint32_t>(aptx_data.bitsPerSample), 0x07)) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } else if ((aptx_data.sampleRate & - kDefaultOffloadAptxCapability.sampleRate) && - (aptx_data.channelMode & - kDefaultOffloadAptxCapability.channelMode) && - (aptx_data.bitsPerSample & - kDefaultOffloadAptxCapability.bitsPerSample)) { - return true; - } - LOG(WARNING) << __func__ - << ": Unsupported CodecSpecific=" << toString(codec_specific); - return false; -} - -static bool IsOffloadAptxHdConfigurationValid( - const CodecConfiguration::CodecSpecific& codec_specific) { - if (codec_specific.getDiscriminator() != - CodecConfiguration::CodecSpecific::hidl_discriminator::aptxConfig) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } - const AptxParameters aptx_data = codec_specific.aptxConfig(); - if (!IsSingleBit(static_cast<uint32_t>(aptx_data.sampleRate), 0xff) || - !IsSingleBit(static_cast<uint32_t>(aptx_data.channelMode), 0x03) || - !IsSingleBit(static_cast<uint32_t>(aptx_data.bitsPerSample), 0x07)) { - LOG(WARNING) << __func__ - << ": Invalid CodecSpecific=" << toString(codec_specific); - return false; - } else if ((aptx_data.sampleRate & - kDefaultOffloadAptxHdCapability.sampleRate) && - (aptx_data.channelMode & - kDefaultOffloadAptxHdCapability.channelMode) && - (aptx_data.bitsPerSample & - kDefaultOffloadAptxHdCapability.bitsPerSample)) { - return true; - } - LOG(WARNING) << __func__ - << ": Unsupported CodecSpecific=" << toString(codec_specific); - return false; -} - -std::vector<::android::hardware::bluetooth::audio::V2_0::PcmParameters> -GetSoftwarePcmCapabilities() { - return std::vector< - ::android::hardware::bluetooth::audio::V2_0::PcmParameters>( - 1, kDefaultSoftwarePcmCapabilities); -} - -std::vector<PcmParameters> GetSoftwarePcmCapabilities_2_1() { - return std::vector<PcmParameters>(1, kDefaultSoftwarePcmCapabilities_2_1); -} - -std::vector<CodecCapabilities> GetOffloadCodecCapabilities( - const ::android::hardware::bluetooth::audio::V2_0::SessionType& - session_type) { - return GetOffloadCodecCapabilities(static_cast<SessionType>(session_type)); -} - -std::vector<CodecCapabilities> GetOffloadCodecCapabilities( - const SessionType& session_type) { - if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { - return std::vector<CodecCapabilities>(0); - } - std::vector<CodecCapabilities> offload_a2dp_codec_capabilities = - kDefaultOffloadA2dpCodecCapabilities; - for (auto& codec_capability : offload_a2dp_codec_capabilities) { - switch (codec_capability.codecType) { - case CodecType::SBC: - codec_capability.capabilities.sbcCapabilities( - kDefaultOffloadSbcCapability); - break; - case CodecType::AAC: - codec_capability.capabilities.aacCapabilities( - kDefaultOffloadAacCapability); - break; - case CodecType::LDAC: - codec_capability.capabilities.ldacCapabilities( - kDefaultOffloadLdacCapability); - break; - case CodecType::APTX: - codec_capability.capabilities.aptxCapabilities( - kDefaultOffloadAptxCapability); - break; - case CodecType::APTX_HD: - codec_capability.capabilities.aptxCapabilities( - kDefaultOffloadAptxHdCapability); - break; - case CodecType::UNKNOWN: - codec_capability = {}; - break; - } - } - return offload_a2dp_codec_capabilities; -} - -bool IsSoftwarePcmConfigurationValid( - const ::android::hardware::bluetooth::audio::V2_0::PcmParameters& - pcm_config) { - if ((pcm_config.sampleRate != - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 && - pcm_config.sampleRate != - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000 && - pcm_config.sampleRate != - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_88200 && - pcm_config.sampleRate != - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_96000 && - pcm_config.sampleRate != - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_16000 && - pcm_config.sampleRate != - android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_24000) || - (pcm_config.bitsPerSample != BitsPerSample::BITS_16 && - pcm_config.bitsPerSample != BitsPerSample::BITS_24 && - pcm_config.bitsPerSample != BitsPerSample::BITS_32) || - (pcm_config.channelMode != ChannelMode::MONO && - pcm_config.channelMode != ChannelMode::STEREO)) { - LOG(WARNING) << __func__ - << ": Invalid PCM Configuration=" << toString(pcm_config); - return false; - } else if (pcm_config.sampleRate & - kDefaultSoftwarePcmCapabilities.sampleRate && - pcm_config.bitsPerSample & - kDefaultSoftwarePcmCapabilities.bitsPerSample && - pcm_config.channelMode & - kDefaultSoftwarePcmCapabilities.channelMode) { - return true; - } - LOG(WARNING) << __func__ - << ": Unsupported PCM Configuration=" << toString(pcm_config); - return false; -} - -bool IsSoftwarePcmConfigurationValid_2_1(const PcmParameters& pcm_config) { - if ((pcm_config.sampleRate != SampleRate::RATE_96000 && - pcm_config.sampleRate != SampleRate::RATE_88200 && - pcm_config.sampleRate != SampleRate::RATE_48000 && - pcm_config.sampleRate != SampleRate::RATE_44100 && - pcm_config.sampleRate != SampleRate::RATE_32000 && - pcm_config.sampleRate != SampleRate::RATE_24000 && - pcm_config.sampleRate != SampleRate::RATE_16000 && - pcm_config.sampleRate != SampleRate::RATE_8000) || - (pcm_config.bitsPerSample != BitsPerSample::BITS_16 && - pcm_config.bitsPerSample != BitsPerSample::BITS_24 && - pcm_config.bitsPerSample != BitsPerSample::BITS_32) || - (pcm_config.channelMode != ChannelMode::MONO && - pcm_config.channelMode != ChannelMode::STEREO)) { - LOG(WARNING) << __func__ - << ": Invalid PCM Configuration=" << toString(pcm_config); - return false; - } else if (pcm_config.sampleRate & - kDefaultSoftwarePcmCapabilities_2_1.sampleRate && - pcm_config.bitsPerSample & - kDefaultSoftwarePcmCapabilities_2_1.bitsPerSample && - pcm_config.channelMode & - kDefaultSoftwarePcmCapabilities_2_1.channelMode && - pcm_config.dataIntervalUs != 0) { - return true; - } - LOG(WARNING) << __func__ - << ": Unsupported PCM Configuration=" << toString(pcm_config); - return false; -} - -bool IsOffloadCodecConfigurationValid(const SessionType& session_type, - const CodecConfiguration& codec_config) { - if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { - LOG(ERROR) << __func__ - << ": Invalid SessionType=" << toString(session_type); - return false; - } else if (codec_config.encodedAudioBitrate < 0x00000001 || - 0x00ffffff < codec_config.encodedAudioBitrate) { - LOG(ERROR) << __func__ << ": Unsupported Codec Configuration=" - << toString(codec_config); - return false; - } - const CodecConfiguration::CodecSpecific& codec_specific = codec_config.config; - switch (codec_config.codecType) { - case CodecType::SBC: - if (IsOffloadSbcConfigurationValid(codec_specific)) { - return true; - } - return false; - case CodecType::AAC: - if (IsOffloadAacConfigurationValid(codec_specific)) { - return true; - } - return false; - case CodecType::LDAC: - if (IsOffloadLdacConfigurationValid(codec_specific)) { - return true; - } - return false; - case CodecType::APTX: - if (IsOffloadAptxConfigurationValid(codec_specific)) { - return true; - } - return false; - case CodecType::APTX_HD: - if (IsOffloadAptxHdConfigurationValid(codec_specific)) { - return true; - } - return false; - case CodecType::UNKNOWN: - return false; - } - return false; -} - -} // namespace audio -} // namespace bluetooth -} // namespace android diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.h deleted file mode 100644 index 9b2f68092f..0000000000 --- a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 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. - */ - -#pragma once - -#include <android/hardware/bluetooth/audio/2.0/types.h> -#include <android/hardware/bluetooth/audio/2.1/types.h> - -namespace android { -namespace bluetooth { -namespace audio { - -using ::android::hardware::bluetooth::audio::V2_0::CodecCapabilities; -using ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration; -using ::android::hardware::bluetooth::audio::V2_1::PcmParameters; -using ::android::hardware::bluetooth::audio::V2_1::SessionType; - -std::vector<::android::hardware::bluetooth::audio::V2_0::PcmParameters> -GetSoftwarePcmCapabilities(); -std::vector<PcmParameters> GetSoftwarePcmCapabilities_2_1(); -std::vector<CodecCapabilities> GetOffloadCodecCapabilities( - const SessionType& session_type); -std::vector<CodecCapabilities> GetOffloadCodecCapabilities( - const ::android::hardware::bluetooth::audio::V2_0::SessionType& - session_type); - -bool IsSoftwarePcmConfigurationValid_2_1(const PcmParameters& pcm_config); -bool IsSoftwarePcmConfigurationValid( - const ::android::hardware::bluetooth::audio::V2_0::PcmParameters& - pcm_config); -bool IsOffloadCodecConfigurationValid(const SessionType& session_type, - const CodecConfiguration& codec_config); - -} // namespace audio -} // namespace bluetooth -} // namespace android diff --git a/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp b/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp index 95903d1f82..57fa07b787 100644 --- a/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp +++ b/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp @@ -1032,7 +1032,7 @@ TEST_P(BluetoothAudioProviderLeAudioOutputSoftwareHidlTest, * stopped with different PCM config */ TEST_P(BluetoothAudioProviderLeAudioOutputSoftwareHidlTest, - StartAndEndLeAudioOutputSessionWithPossiblePcmConfig) { + DISABLED_StartAndEndLeAudioOutputSessionWithPossiblePcmConfig) { bool is_codec_config_valid; std::unique_ptr<DataMQ> tempDataMQ; auto hidl_cb = [&is_codec_config_valid, &tempDataMQ]( @@ -1126,7 +1126,7 @@ TEST_P(BluetoothAudioProviderLeAudioInputSoftwareHidlTest, * stopped with different PCM config */ TEST_P(BluetoothAudioProviderLeAudioInputSoftwareHidlTest, - StartAndEndLeAudioInputSessionWithPossiblePcmConfig) { + DISABLED_StartAndEndLeAudioInputSessionWithPossiblePcmConfig) { bool is_codec_config_valid; std::unique_ptr<DataMQ> tempDataMQ; auto hidl_cb = [&is_codec_config_valid, &tempDataMQ]( diff --git a/bluetooth/audio/utils/Android.bp b/bluetooth/audio/utils/Android.bp new file mode 100644 index 0000000000..35476d285f --- /dev/null +++ b/bluetooth/audio/utils/Android.bp @@ -0,0 +1,23 @@ +cc_library_shared { + name: "libbluetooth_audio_session", + defaults: ["hidl_defaults"], + vendor: true, + srcs: [ + "session/BluetoothAudioSession.cpp", + "session/BluetoothAudioSession_2_1.cpp", + "session/BluetoothAudioSupportedCodecsDB.cpp", + "session/BluetoothAudioSupportedCodecsDB_2_1.cpp", + ], + export_include_dirs: ["session/"], + header_libs: ["libhardware_headers"], + shared_libs: [ + "android.hardware.bluetooth.audio@2.0", + "android.hardware.bluetooth.audio@2.1", + "libbase", + "libcutils", + "libfmq", + "libhidlbase", + "liblog", + "libutils", + ], +} diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.cpp b/bluetooth/audio/utils/session/BluetoothAudioSession.cpp index 50119bf399..50119bf399 100644 --- a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.cpp +++ b/bluetooth/audio/utils/session/BluetoothAudioSession.cpp diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.h b/bluetooth/audio/utils/session/BluetoothAudioSession.h index 838d1ccad0..838d1ccad0 100644 --- a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.h +++ b/bluetooth/audio/utils/session/BluetoothAudioSession.h diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSessionControl.h b/bluetooth/audio/utils/session/BluetoothAudioSessionControl.h index 6707765638..6707765638 100644 --- a/bluetooth/audio/2.0/default/session/BluetoothAudioSessionControl.h +++ b/bluetooth/audio/utils/session/BluetoothAudioSessionControl.h diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h new file mode 100644 index 0000000000..86af4680a5 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h @@ -0,0 +1,148 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include "BluetoothAudioSession_2_1.h" + +namespace android { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSessionControl_2_1 { + using SessionType_2_1 = + ::android::hardware::bluetooth::audio::V2_1::SessionType; + + public: + // The control API helps to check if session is ready or not + // @return: true if the Bluetooth stack has started th specified session + static bool IsSessionReady(const SessionType_2_1& session_type) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->IsSessionReady(); + } + return false; + } + + // The control API helps the bluetooth_audio module to register + // PortStatusCallbacks + // @return: cookie - the assigned number to this bluetooth_audio output + static uint16_t RegisterControlResultCback( + const SessionType_2_1& session_type, const PortStatusCallbacks& cbacks) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->RegisterStatusCback(cbacks); + } + return kObserversCookieUndefined; + } + + // The control API helps the bluetooth_audio module to unregister + // PortStatusCallbacks + // @param: cookie - indicates which bluetooth_audio output is + static void UnregisterControlResultCback(const SessionType_2_1& session_type, + uint16_t cookie) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->UnregisterStatusCback(cookie); + } + } + + // The control API for the bluetooth_audio module to get current + // AudioConfiguration + static const AudioConfiguration& GetAudioConfig( + const SessionType_2_1& session_type) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + // TODO: map 2.1 to 2.0 audio config inside GetAudioConfig? + return session_ptr->GetAudioSession()->GetAudioConfig(); + } else if (session_type == + SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH) { + return BluetoothAudioSession::kInvalidOffloadAudioConfiguration; + } else { + return BluetoothAudioSession::kInvalidSoftwareAudioConfiguration; + } + } + + // Those control APIs for the bluetooth_audio module to start / suspend / stop + // stream, to check position, and to update metadata. + static bool StartStream(const SessionType_2_1& session_type) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->StartStream(); + } + return false; + } + + static bool SuspendStream(const SessionType_2_1& session_type) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->SuspendStream(); + } + return false; + } + + static void StopStream(const SessionType_2_1& session_type) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->StopStream(); + } + } + + static bool GetPresentationPosition(const SessionType_2_1& session_type, + uint64_t* remote_delay_report_ns, + uint64_t* total_bytes_readed, + timespec* data_position) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->GetPresentationPosition( + remote_delay_report_ns, total_bytes_readed, data_position); + } + return false; + } + + static void UpdateTracksMetadata( + const SessionType_2_1& session_type, + const struct source_metadata* source_metadata) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->UpdateTracksMetadata(source_metadata); + } + } + + // The control API writes stream to FMQ + static size_t OutWritePcmData(const SessionType_2_1& session_type, + const void* buffer, size_t bytes) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->OutWritePcmData(buffer, bytes); + } + return 0; + } +}; + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSessionReport.h b/bluetooth/audio/utils/session/BluetoothAudioSessionReport.h index 5a83ae2d1f..5a83ae2d1f 100644 --- a/bluetooth/audio/2.0/default/session/BluetoothAudioSessionReport.h +++ b/bluetooth/audio/utils/session/BluetoothAudioSessionReport.h diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h new file mode 100644 index 0000000000..ab30536d0a --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h @@ -0,0 +1,69 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include "BluetoothAudioSession_2_1.h" + +namespace android { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSessionReport_2_1 { + public: + // The API reports the Bluetooth stack has started the session, and will + // inform registered bluetooth_audio outputs + static void OnSessionStarted( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const sp<IBluetoothAudioPort> host_iface, + const DataMQ::Descriptor* dataMQ, + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + audio_config) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->OnSessionStarted(host_iface, dataMQ, audio_config); + } + } + // The API reports the Bluetooth stack has ended the session, and will + // inform registered bluetooth_audio outputs + static void OnSessionEnded( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->OnSessionEnded(); + } + } + // The API reports the Bluetooth stack has replied the result of startStream + // or suspendStream, and will inform registered bluetooth_audio outputs + static void ReportControlStatus( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const bool& start_resp, const BluetoothAudioStatus& status) { + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->ReportControlStatus(start_resp, status); + } + } +}; + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp new file mode 100644 index 0000000000..9e1baf4f6e --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2018 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 "BTAudioProviderSession_2_1" + +#include "BluetoothAudioSession_2_1.h" + +#include <android-base/logging.h> +#include <android-base/stringprintf.h> + +namespace android { +namespace bluetooth { +namespace audio { +using SessionType_2_1 = + ::android::hardware::bluetooth::audio::V2_1::SessionType; +using SessionType_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SessionType; + +namespace { +bool is_2_0_session_type( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) { + if (session_type == SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH || + session_type == SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH) { + return true; + } else { + return false; + } +} +} // namespace + +BluetoothAudioSession_2_1::BluetoothAudioSession_2_1( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) + : audio_session(BluetoothAudioSessionInstance::GetSessionInstance( + static_cast<SessionType_2_0>(session_type))) { + if (is_2_0_session_type(session_type)) { + session_type_2_1_ = (SessionType_2_1::UNKNOWN); + } else { + session_type_2_1_ = (session_type); + } +} + +std::shared_ptr<BluetoothAudioSession> +BluetoothAudioSession_2_1::GetAudioSession() { + return audio_session; +} + +// The report function is used to report that the Bluetooth stack has started +// this session without any failure, and will invoke session_changed_cb_ to +// notify those registered bluetooth_audio outputs +void BluetoothAudioSession_2_1::OnSessionStarted( + const sp<IBluetoothAudioPort> stack_iface, const DataMQ::Descriptor* dataMQ, + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + audio_config) { + if (session_type_2_1_ == SessionType_2_1::UNKNOWN) { + ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration config; + if (audio_config.getDiscriminator() == + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration:: + hidl_discriminator::codecConfig) { + config.codecConfig(audio_config.codecConfig()); + } else { + auto& tmpPcm = audio_config.pcmConfig(); + config.pcmConfig( + ::android::hardware::bluetooth::audio::V2_0::PcmParameters{ + .sampleRate = static_cast<SampleRate>(tmpPcm.sampleRate), + .channelMode = tmpPcm.channelMode, + .bitsPerSample = tmpPcm.bitsPerSample + /*dataIntervalUs is not passed to 2.0 */ + }); + } + + audio_session->OnSessionStarted(stack_iface, dataMQ, config); + } else { + LOG(FATAL) << " Not implemented yet!!"; + } +} + +std::unique_ptr<BluetoothAudioSessionInstance_2_1> + BluetoothAudioSessionInstance_2_1::instance_ptr = + std::unique_ptr<BluetoothAudioSessionInstance_2_1>( + new BluetoothAudioSessionInstance_2_1()); + +// API to fetch the session of A2DP / Hearing Aid +std::shared_ptr<BluetoothAudioSession_2_1> +BluetoothAudioSessionInstance_2_1::GetSessionInstance( + const SessionType_2_1& session_type) { + std::lock_guard<std::mutex> guard(instance_ptr->mutex_); + if (!instance_ptr->sessions_map_.empty()) { + auto entry = instance_ptr->sessions_map_.find(session_type); + if (entry != instance_ptr->sessions_map_.end()) { + return entry->second; + } + } + std::shared_ptr<BluetoothAudioSession_2_1> session_ptr = + std::make_shared<BluetoothAudioSession_2_1>(session_type); + instance_ptr->sessions_map_[session_type] = session_ptr; + return session_ptr; +} + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h new file mode 100644 index 0000000000..0e9c12b849 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h @@ -0,0 +1,82 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include <android/hardware/bluetooth/audio/2.1/types.h> +#include "BluetoothAudioSession.h" + +#include <mutex> +#include <unordered_map> + +namespace android { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSession_2_1 { + private: + std::shared_ptr<BluetoothAudioSession> audio_session; + + ::android::hardware::bluetooth::audio::V2_1::SessionType session_type_2_1_; + + // audio data configuration for both software and offloading + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration + audio_config_2_1_; + + bool UpdateAudioConfig( + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + audio_config); + + public: + BluetoothAudioSession_2_1( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type); + + std::shared_ptr<BluetoothAudioSession> GetAudioSession(); + + // The report function is used to report that the Bluetooth stack has started + // this session without any failure, and will invoke session_changed_cb_ to + // notify those registered bluetooth_audio outputs + void OnSessionStarted( + const sp<IBluetoothAudioPort> stack_iface, + const DataMQ::Descriptor* dataMQ, + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + audio_config); + + // The control function is for the bluetooth_audio module to get the current + // AudioConfiguration + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + GetAudioConfig(); +}; + +class BluetoothAudioSessionInstance_2_1 { + public: + // The API is to fetch the specified session of A2DP / Hearing Aid + static std::shared_ptr<BluetoothAudioSession_2_1> GetSessionInstance( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type); + + private: + static std::unique_ptr<BluetoothAudioSessionInstance_2_1> instance_ptr; + std::mutex mutex_; + std::unordered_map<::android::hardware::bluetooth::audio::V2_1::SessionType, + std::shared_ptr<BluetoothAudioSession_2_1>> + sessions_map_; +}; + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.cpp index c368197467..c368197467 100644 --- a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp +++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.cpp diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.h b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.h index e71dc8a183..e71dc8a183 100644 --- a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.h +++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.h diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp new file mode 100644 index 0000000000..8b0b0f743a --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp @@ -0,0 +1,127 @@ +/* + * Copyright 2018 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 "BTAudioProviderSessionCodecsDB_2_1" + +#include "BluetoothAudioSupportedCodecsDB_2_1.h" + +#include <android-base/logging.h> + +namespace android { +namespace bluetooth { +namespace audio { + +using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample; +using ::android::hardware::bluetooth::audio::V2_0::ChannelMode; + +using SampleRate_2_0 = ::android::hardware::bluetooth::audio::V2_0::SampleRate; +using SampleRate_2_1 = ::android::hardware::bluetooth::audio::V2_1::SampleRate; + +using SessionType_2_1 = + ::android::hardware::bluetooth::audio::V2_1::SessionType; +using SessionType_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SessionType; + +namespace { +bool is_2_0_session_type( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) { + if (session_type == SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH || + session_type == SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH) { + return true; + } else { + return false; + } +} +} // namespace + +static const ::android::hardware::bluetooth::audio::V2_1::PcmParameters + kDefaultSoftwarePcmCapabilities_2_1 = { + .sampleRate = static_cast<SampleRate_2_1>( + SampleRate_2_1::RATE_44100 | SampleRate_2_1::RATE_48000 | + SampleRate_2_1::RATE_88200 | SampleRate_2_1::RATE_96000 | + SampleRate_2_1::RATE_16000 | SampleRate_2_1::RATE_24000), + .channelMode = + static_cast<ChannelMode>(ChannelMode::MONO | ChannelMode::STEREO), + .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16 | + BitsPerSample::BITS_24 | + BitsPerSample::BITS_32)}; + +std::vector<::android::hardware::bluetooth::audio::V2_1::PcmParameters> +GetSoftwarePcmCapabilities_2_1() { + return std::vector< + ::android::hardware::bluetooth::audio::V2_1::PcmParameters>( + 1, kDefaultSoftwarePcmCapabilities_2_1); +} + +std::vector<CodecCapabilities> GetOffloadCodecCapabilities( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) { + if (is_2_0_session_type(session_type)) { + return GetOffloadCodecCapabilities( + static_cast<SessionType_2_0>(session_type)); + } + return std::vector<CodecCapabilities>(0); +} + +bool IsSoftwarePcmConfigurationValid_2_1( + const ::android::hardware::bluetooth::audio::V2_1::PcmParameters& + pcm_config) { + if ((pcm_config.sampleRate != SampleRate_2_1::RATE_44100 && + pcm_config.sampleRate != SampleRate_2_1::RATE_48000 && + pcm_config.sampleRate != SampleRate_2_1::RATE_88200 && + pcm_config.sampleRate != SampleRate_2_1::RATE_96000 && + pcm_config.sampleRate != SampleRate_2_1::RATE_16000 && + pcm_config.sampleRate != SampleRate_2_1::RATE_24000) || + (pcm_config.bitsPerSample != BitsPerSample::BITS_16 && + pcm_config.bitsPerSample != BitsPerSample::BITS_24 && + pcm_config.bitsPerSample != BitsPerSample::BITS_32) || + (pcm_config.channelMode != ChannelMode::MONO && + pcm_config.channelMode != ChannelMode::STEREO)) { + LOG(WARNING) << __func__ + << ": Invalid PCM Configuration=" << toString(pcm_config); + return false; + } else if (pcm_config.sampleRate & + kDefaultSoftwarePcmCapabilities_2_1.sampleRate && + pcm_config.bitsPerSample & + kDefaultSoftwarePcmCapabilities_2_1.bitsPerSample && + pcm_config.channelMode & + kDefaultSoftwarePcmCapabilities_2_1.channelMode && + pcm_config.dataIntervalUs != 0) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported PCM Configuration=" << toString(pcm_config); + return false; +} + +bool IsOffloadCodecConfigurationValid( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration& + codec_config) { + if (is_2_0_session_type(session_type)) { + return IsOffloadCodecConfigurationValid( + static_cast<SessionType_2_0>(session_type), codec_config); + } + + return false; +} + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h new file mode 100644 index 0000000000..746d9c05ed --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h @@ -0,0 +1,46 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include "BluetoothAudioSupportedCodecsDB.h" + +#include <android/hardware/bluetooth/audio/2.1/types.h> + +namespace android { +namespace bluetooth { +namespace audio { + +std::vector<::android::hardware::bluetooth::audio::V2_1::PcmParameters> +GetSoftwarePcmCapabilities_2_1(); +std::vector<::android::hardware::bluetooth::audio::V2_0::CodecCapabilities> +GetOffloadCodecCapabilities( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type); + +bool IsSoftwarePcmConfigurationValid_2_1( + const ::android::hardware::bluetooth::audio::V2_1::PcmParameters& + pcm_config); + +bool IsOffloadCodecConfigurationValid( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration& + codec_config); + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/camera/metadata/3.6/Android.bp b/camera/metadata/3.6/Android.bp new file mode 100644 index 0000000000..d9f3fb82e8 --- /dev/null +++ b/camera/metadata/3.6/Android.bp @@ -0,0 +1,16 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.camera.metadata@3.6", + root: "android.hardware", + srcs: [ + "types.hal", + ], + interfaces: [ + "android.hardware.camera.metadata@3.2", + "android.hardware.camera.metadata@3.3", + "android.hardware.camera.metadata@3.4", + "android.hardware.camera.metadata@3.5", + ], + gen_java: true, +} diff --git a/camera/metadata/3.6/types.hal b/camera/metadata/3.6/types.hal new file mode 100644 index 0000000000..fb957367f8 --- /dev/null +++ b/camera/metadata/3.6/types.hal @@ -0,0 +1,51 @@ +/* + * Copyright (C) 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. + */ + +/* + * Autogenerated from camera metadata definitions in + * /system/media/camera/docs/metadata_definitions.xml + * *** DO NOT EDIT BY HAND *** + */ + +package android.hardware.camera.metadata@3.6; + +import android.hardware.camera.metadata@3.2; +import android.hardware.camera.metadata@3.3; +import android.hardware.camera.metadata@3.4; +import android.hardware.camera.metadata@3.5; + +// No new metadata sections added in this revision + +/** + * Main enumeration for defining camera metadata tags added in this revision + * + * <p>Partial documentation is included for each tag; for complete documentation, reference + * '/system/media/camera/docs/docs.html' in the corresponding Android source tree.</p> + */ +enum CameraMetadataTag : @3.5::CameraMetadataTag { + /** android.scaler.defaultSecureImageSize [static, int32[], public] + * + * <p>Default YUV/PRIVATE size to use for requesting secure image buffers.</p> + */ + ANDROID_SCALER_DEFAULT_SECURE_IMAGE_SIZE = android.hardware.camera.metadata@3.5::CameraMetadataTag:ANDROID_SCALER_END_3_5, + + ANDROID_SCALER_END_3_6, + +}; + +/* + * Enumeration definitions for the various entries that need them + */ diff --git a/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp b/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp index df0c85934f..a1d5930386 100644 --- a/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp +++ b/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp @@ -256,12 +256,19 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { ::testing::AssertionResult MediaCasHidlTest::createCasPlugin(int32_t caSystemId) { auto status = mService->isSystemIdSupported(caSystemId); + bool skipDescrambler = false; if (!status.isOk() || !status) { return ::testing::AssertionFailure(); } status = mService->isDescramblerSupported(caSystemId); if (!status.isOk() || !status) { - return ::testing::AssertionFailure(); + if (mIsTestDescrambler) { + return ::testing::AssertionFailure(); + } else { + ALOGI("Skip Descrambler test since it's not required in cas@1.2."); + mDescramblerBase = nullptr; + skipDescrambler = true; + } } mCasListener = new MediaCasListener(); @@ -274,16 +281,15 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { return ::testing::AssertionFailure(); } + if (skipDescrambler) { + return ::testing::AssertionSuccess(); + } + auto descramblerStatus = mService->createDescrambler(caSystemId); if (!descramblerStatus.isOk()) { - if (mIsTestDescrambler) { - return ::testing::AssertionFailure(); - } else { - ALOGI("Skip Descrambler test since it's not required in cas@1.2."); - return ::testing::AssertionSuccess(); - } + return ::testing::AssertionFailure(); } - mIsTestDescrambler = true; + mDescramblerBase = descramblerStatus; return ::testing::AssertionResult(mDescramblerBase != nullptr); } @@ -506,7 +512,7 @@ TEST_P(MediaCasHidlTest, TestClearKeyApis) { returnStatus = mMediaCas->setSessionPrivateData(streamSessionId, hidlPvtData); EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { returnStatus = mDescramblerBase->setMediaCasSession(sessionId); EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); @@ -556,7 +562,7 @@ TEST_P(MediaCasHidlTest, TestClearKeyApis) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc")); sp<IDescrambler> descrambler; @@ -606,7 +612,7 @@ TEST_P(MediaCasHidlTest, TestClearKeySessionClosedAfterRelease) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { returnStatus = mDescramblerBase->setMediaCasSession(sessionId); EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus); @@ -672,7 +678,7 @@ TEST_P(MediaCasHidlTest, TestClearKeyErrors) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::ERROR_CAS_UNKNOWN, returnStatus); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { /* * Test MediaDescrambler error codes */ @@ -720,7 +726,7 @@ TEST_P(MediaCasHidlTest, TestClearKeyOobFails) { std::vector<uint8_t> sessionId; ASSERT_TRUE(openCasSession(&sessionId)); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { returnStatus = mDescramblerBase->setMediaCasSession(sessionId); EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); @@ -732,7 +738,7 @@ TEST_P(MediaCasHidlTest, TestClearKeyOobFails) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { sp<IDescrambler> descrambler = IDescrambler::castFrom(mDescramblerBase); ASSERT_NE(nullptr, descrambler.get()); diff --git a/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp b/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp index 6797506642..42d70cfeb9 100644 --- a/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp +++ b/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp @@ -297,12 +297,19 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { ::testing::AssertionResult MediaCasHidlTest::createCasPlugin(int32_t caSystemId) { auto status = mService->isSystemIdSupported(caSystemId); + bool skipDescrambler = false; if (!status.isOk() || !status) { return ::testing::AssertionFailure(); } status = mService->isDescramblerSupported(caSystemId); if (!status.isOk() || !status) { - return ::testing::AssertionFailure(); + if (mIsTestDescrambler) { + return ::testing::AssertionFailure(); + } else { + ALOGI("Skip Descrambler test since it's not required in cas@1.2."); + mDescramblerBase = nullptr; + skipDescrambler = true; + } } mCasListener = new MediaCasListener(); @@ -315,16 +322,14 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { return ::testing::AssertionFailure(); } + if (skipDescrambler) { + return ::testing::AssertionSuccess(); + } + auto descramblerStatus = mService->createDescrambler(caSystemId); if (!descramblerStatus.isOk()) { - if (mIsTestDescrambler) { - return ::testing::AssertionFailure(); - } else { - ALOGI("Skip Descrambler test since it's not required in cas@1.2."); - return ::testing::AssertionSuccess(); - } + return ::testing::AssertionFailure(); } - mIsTestDescrambler = true; mDescramblerBase = descramblerStatus; return ::testing::AssertionResult(mDescramblerBase != nullptr); @@ -481,7 +486,7 @@ TEST_P(MediaCasHidlTest, TestClearKeyApisWithSession) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { returnStatus = mDescramblerBase->setMediaCasSession(sessionId); EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); @@ -533,7 +538,7 @@ TEST_P(MediaCasHidlTest, TestClearKeyApisWithSession) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc")); sp<IDescrambler> descrambler; diff --git a/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp b/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp index 333dea61db..0d75f5b812 100644 --- a/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp +++ b/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp @@ -311,7 +311,6 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { sp<ICas> mMediaCas; sp<IDescramblerBase> mDescramblerBase; sp<MediaCasListener> mCasListener; - bool mIsTestDescrambler = false; typedef struct _OobInputTestParams { const SubSample* subSamples; uint32_t numSubSamples; @@ -336,12 +335,15 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { ::testing::AssertionResult MediaCasHidlTest::createCasPlugin(int32_t caSystemId) { auto status = mService->isSystemIdSupported(caSystemId); + bool skipDescrambler = false; if (!status.isOk() || !status) { return ::testing::AssertionFailure(); } status = mService->isDescramblerSupported(caSystemId); if (!status.isOk() || !status) { - return ::testing::AssertionFailure(); + ALOGI("Skip Descrambler test since it's not required in cas@1.2."); + mDescramblerBase = nullptr; + skipDescrambler = true; } mCasListener = new MediaCasListener(); @@ -354,12 +356,14 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { return ::testing::AssertionFailure(); } + if (skipDescrambler) { + return ::testing::AssertionSuccess(); + } + auto descramblerStatus = mService->createDescrambler(caSystemId); if (!descramblerStatus.isOk()) { - ALOGI("Skip Descrambler test since it's not required in cas@1.2."); - return ::testing::AssertionSuccess(); + return ::testing::AssertionFailure(); } - mIsTestDescrambler = true; mDescramblerBase = descramblerStatus; return ::testing::AssertionResult(mDescramblerBase != nullptr); @@ -516,7 +520,7 @@ TEST_P(MediaCasHidlTest, TestClearKeyApisWithSession) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { returnStatus = mDescramblerBase->setMediaCasSession(sessionId); EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); @@ -571,7 +575,7 @@ TEST_P(MediaCasHidlTest, TestClearKeyApisWithSession) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - if (mIsTestDescrambler) { + if (mDescramblerBase != nullptr) { EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc")); sp<IDescrambler> descrambler; diff --git a/compatibility_matrices/compatibility_matrix.5.xml b/compatibility_matrices/compatibility_matrix.5.xml index 53b9be83b5..ade00ffd0a 100644 --- a/compatibility_matrices/compatibility_matrix.5.xml +++ b/compatibility_matrices/compatibility_matrix.5.xml @@ -86,7 +86,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.biometrics.face</name> - <version>1.0-1</version> + <version>1.0</version> <interface> <name>IBiometricsFace</name> <instance>default</instance> diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index 8b94636c15..540e0178ec 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -9,7 +9,6 @@ </hal> <hal format="hidl" optional="false"> <name>android.hardware.audio</name> - <!-- TODO(b/142480271): remove 6.0 when implemented on reference device. --> <version>6.0</version> <version>7.0</version> <interface> @@ -19,7 +18,6 @@ </hal> <hal format="hidl" optional="false"> <name>android.hardware.audio.effect</name> - <!-- TODO(b/142480271): remove 6.0 when implemented on reference device. --> <version>6.0</version> <version>7.0</version> <interface> @@ -97,7 +95,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.biometrics.face</name> - <version>1.0-1</version> + <version>1.0</version> <interface> <name>IBiometricsFace</name> <instance>default</instance> @@ -191,7 +189,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.contexthub</name> - <version>1.0-2</version> + <version>1.2</version> <interface> <name>IContexthub</name> <instance>default</instance> @@ -345,6 +343,13 @@ </interface> </hal> <hal format="aidl" optional="true"> + <name>android.hardware.security.keymint</name> + <interface> + <name>IRemotelyProvisionedComponent</name> + <instance>default</instance> + </interface> + </hal> + <hal format="aidl" optional="true"> <name>android.hardware.light</name> <version>1</version> <interface> @@ -397,6 +402,13 @@ <regex-instance>.*</regex-instance> </interface> </hal> + <hal format="aidl" optional="true"> + <name>android.hardware.neuralnetworks</name> + <interface> + <name>IDevice</name> + <regex-instance>.*</regex-instance> + </interface> + </hal> <hal format="hidl" optional="true"> <name>android.hardware.nfc</name> <version>1.2</version> @@ -549,7 +561,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.tv.cec</name> - <version>1.0</version> + <version>1.0-1</version> <interface> <name>IHdmiCec</name> <instance>default</instance> @@ -573,7 +585,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.usb</name> - <version>1.0-2</version> + <version>1.0-3</version> <interface> <name>IUsb</name> <instance>default</instance> diff --git a/contexthub/1.0/vts/functional/VtsHalContexthubV1_0TargetTest.cpp b/contexthub/1.0/vts/functional/VtsHalContexthubV1_0TargetTest.cpp index 8a90173bf1..356ad97327 100644 --- a/contexthub/1.0/vts/functional/VtsHalContexthubV1_0TargetTest.cpp +++ b/contexthub/1.0/vts/functional/VtsHalContexthubV1_0TargetTest.cpp @@ -52,40 +52,17 @@ using ::android::hardware::contexthub::vts_utils::ContexthubCallbackBase; using ::android::hardware::contexthub::vts_utils::ContexthubHidlTestBase; using ::android::hardware::contexthub::vts_utils::getHalAndHubIdList; using ::android::hardware::contexthub::vts_utils::getHubsSync; +using ::android::hardware::contexthub::vts_utils::kNonExistentAppId; +using ::android::hardware::contexthub::vts_utils::waitForCallback; namespace { -// App ID with vendor "GoogT" (Google Testing), app identifier 0x555555. This -// app ID is reserved and must never appear in the list of loaded apps. -constexpr uint64_t kNonExistentAppId = 0x476f6f6754555555; - const std::vector<std::tuple<std::string, std::string>> kTestParameters = getHalAndHubIdList<IContexthub>(); class ContexthubHidlTest : public ContexthubHidlTestBase<IContexthub> {}; -// Wait for a callback to occur (signaled by the given future) up to the -// provided timeout. If the future is invalid or the callback does not come -// within the given time, returns false. -template <class ReturnType> -bool waitForCallback(std::future<ReturnType> future, ReturnType* result, - std::chrono::milliseconds timeout = std::chrono::seconds(5)) { - auto expiration = std::chrono::system_clock::now() + timeout; - - EXPECT_NE(result, nullptr); - EXPECT_TRUE(future.valid()); - if (result != nullptr && future.valid()) { - std::future_status status = future.wait_until(expiration); - EXPECT_NE(status, std::future_status::timeout) << "Timed out waiting for callback"; - - if (status == std::future_status::ready) { - *result = future.get(); - return true; - } - } - - return false; -} +class ContexthubCallbackV1_0 : public ContexthubCallbackBase<IContexthubCallback> {}; // Ensures that the metadata reported in getHubs() is sane TEST_P(ContexthubHidlTest, TestGetHubs) { @@ -110,7 +87,7 @@ TEST_P(ContexthubHidlTest, TestGetHubs) { TEST_P(ContexthubHidlTest, TestRegisterCallback) { ALOGD("TestRegisterCallback called, hubId %" PRIu32, getHubId()); - ASSERT_OK(registerCallback(new ContexthubCallbackBase())); + ASSERT_OK(registerCallback(new ContexthubCallbackV1_0())); } TEST_P(ContexthubHidlTest, TestRegisterNullCallback) { @@ -119,7 +96,7 @@ TEST_P(ContexthubHidlTest, TestRegisterNullCallback) { } // Helper callback that puts the async appInfo callback data into a promise -class QueryAppsCallback : public ContexthubCallbackBase { +class QueryAppsCallback : public ContexthubCallbackV1_0 { public: virtual Return<void> handleAppsInfo(const hidl_vec<HubAppInfo>& appInfo) override { ALOGD("Got app info callback with %zu apps", appInfo.size()); @@ -150,7 +127,7 @@ TEST_P(ContexthubHidlTest, TestQueryApps) { // Helper callback that puts the TransactionResult for the expectedTxnId into a // promise -class TxnResultCallback : public ContexthubCallbackBase { +class TxnResultCallback : public ContexthubCallbackV1_0 { public: virtual Return<void> handleTxnResult(uint32_t txnId, TransactionResult result) override { ALOGD("Got transaction result callback for txnId %" PRIu32 " (expecting %" PRIu32 diff --git a/contexthub/1.1/vts/functional/VtsHalContexthubV1_1TargetTest.cpp b/contexthub/1.1/vts/functional/VtsHalContexthubV1_1TargetTest.cpp index 5f1dad97cb..acf4be0c45 100644 --- a/contexthub/1.1/vts/functional/VtsHalContexthubV1_1TargetTest.cpp +++ b/contexthub/1.1/vts/functional/VtsHalContexthubV1_1TargetTest.cpp @@ -31,6 +31,7 @@ #include <cinttypes> +using ::android::hardware::contexthub::V1_0::IContexthubCallback; using ::android::hardware::contexthub::V1_1::IContexthub; using ::android::hardware::contexthub::V1_1::Setting; using ::android::hardware::contexthub::V1_1::SettingValue; @@ -45,10 +46,12 @@ const std::vector<std::tuple<std::string, std::string>> kTestParameters = class ContexthubHidlTest : public ContexthubHidlTestBase<IContexthub> {}; +class ContexthubCallbackV1_0 : public ContexthubCallbackBase<IContexthubCallback> {}; + TEST_P(ContexthubHidlTest, TestOnSettingChanged) { // In VTS, we only test that sending the values doesn't cause things to blow up - other test // suites verify the expected E2E behavior in CHRE - ASSERT_OK(registerCallback(new ContexthubCallbackBase())); + ASSERT_OK(registerCallback(new ContexthubCallbackV1_0())); hubApi->onSettingChanged(Setting::LOCATION, SettingValue::DISABLED); hubApi->onSettingChanged(Setting::LOCATION, SettingValue::ENABLED); ASSERT_OK(registerCallback(nullptr)); diff --git a/contexthub/1.2/IContexthub.hal b/contexthub/1.2/IContexthub.hal index 3488b7446c..4bb9361bc1 100644 --- a/contexthub/1.2/IContexthub.hal +++ b/contexthub/1.2/IContexthub.hal @@ -16,6 +16,7 @@ package android.hardware.contexthub@1.2; +import @1.0::ContextHub; import @1.0::Result; import @1.1::IContexthub; import @1.1::SettingValue; @@ -23,6 +24,17 @@ import IContexthubCallback; interface IContexthub extends @1.1::IContexthub { /** + * Enumerate all available context hubs on the system. + * + * @return hubs list of hubs on this system. + * @return supportedPermissions list of Android permissions all hubs + * support for nanoapps to enforce host + * endpoints are granted in order to + * communicate with them. + */ + getHubs_1_2() generates (vec<ContextHub> hubs, vec<string> supportedPermissions); + + /** * Register a callback for the HAL implementation to send asynchronous * messages to the service from a context hub. There can be a maximum of * one callback registered with the HAL. A call to this function when a diff --git a/contexthub/1.2/IContexthubCallback.hal b/contexthub/1.2/IContexthubCallback.hal index 0236160305..1a405128b7 100644 --- a/contexthub/1.2/IContexthubCallback.hal +++ b/contexthub/1.2/IContexthubCallback.hal @@ -24,10 +24,18 @@ interface IContexthubCallback extends @1.0::IContexthubCallback { * implementation to allow the HAL to send asynchronous messages back * to the service and registered clients of the ContextHub service. * - * @param msg message that should be delivered to host app clients - * + * @param msg message that should be delivered to host app + * clients + * @param msgContentPerms list of Android permissions that cover the + * contents of the message being sent from the app. + * This is different from the permissions stored + * inside of ContextHubMsg in that these must be a + * subset of those permissions and are meant to + * assist in properly attributing the message + * contents when delivering to a ContextHub service + * client. */ - handleClientMsg_1_2(ContextHubMsg msg); + handleClientMsg_1_2(ContextHubMsg msg, vec<string> msgContentPerms); /** * This callback is passed by the Contexthub service to the HAL diff --git a/contexthub/1.2/default/Contexthub.cpp b/contexthub/1.2/default/Contexthub.cpp index db0c5bc3df..601eccd399 100644 --- a/contexthub/1.2/default/Contexthub.cpp +++ b/contexthub/1.2/default/Contexthub.cpp @@ -23,10 +23,36 @@ namespace contexthub { namespace V1_2 { namespace implementation { +using ::android::hardware::hidl_string; using ::android::hardware::contexthub::V1_0::Result; using ::android::hardware::contexthub::V1_X::implementation::IContextHubCallbackWrapperV1_0; using ::android::hardware::contexthub::V1_X::implementation::IContextHubCallbackWrapperV1_2; +Return<void> Contexthub::getHubs_1_2(getHubs_1_2_cb _hidl_cb) { + ::android::hardware::contexthub::V1_0::ContextHub hub = {}; + hub.name = "Mock Context Hub"; + hub.vendor = "AOSP"; + hub.toolchain = "n/a"; + hub.platformVersion = 1; + hub.toolchainVersion = 1; + hub.hubId = kMockHubId; + hub.peakMips = 1; + hub.peakPowerDrawMw = 1; + hub.maxSupportedMsgLen = 4096; + hub.chrePlatformId = UINT64_C(0x476f6f6754000000); + hub.chreApiMajorVersion = 1; + hub.chreApiMinorVersion = 4; + + // Report a single mock hub + std::vector<::android::hardware::contexthub::V1_0::ContextHub> hubs; + hubs.push_back(hub); + + std::vector<hidl_string> hubPermissionList; + + _hidl_cb(hubs, hubPermissionList); + return Void(); +} + Return<Result> Contexthub::registerCallback(uint32_t hubId, const sp<V1_0::IContexthubCallback>& cb) { if (hubId == kMockHubId) { diff --git a/contexthub/1.2/default/Contexthub.h b/contexthub/1.2/default/Contexthub.h index 8b89824d6b..32b862dc3f 100644 --- a/contexthub/1.2/default/Contexthub.h +++ b/contexthub/1.2/default/Contexthub.h @@ -35,6 +35,7 @@ class Contexthub using Result = ::android::hardware::contexthub::V1_0::Result; using SettingValue = ::android::hardware::contexthub::V1_1::SettingValue; using SettingV1_1 = ::android::hardware::contexthub::V1_1::Setting; + using getHubs_1_2_cb = ::android::hardware::contexthub::V1_2::IContexthub::getHubs_1_2_cb; public: // Methods from V1_0::IContexthub @@ -47,6 +48,8 @@ class Contexthub Return<void> onSettingChanged(SettingV1_1 setting, SettingValue newValue) override; // Methods from V1_2::IContexthub + Return<void> getHubs_1_2(getHubs_1_2_cb _hidl_cb) override; + Return<void> onSettingChanged_1_2(Setting setting, SettingValue newValue) override; Return<Result> registerCallback_1_2(uint32_t hubId, diff --git a/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp b/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp index 782edae610..c50d43c137 100644 --- a/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp +++ b/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp @@ -32,45 +32,202 @@ #include <cinttypes> +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::contexthub::V1_0::ContextHub; +using ::android::hardware::contexthub::V1_0::Result; +using ::android::hardware::contexthub::V1_0::TransactionResult; using ::android::hardware::contexthub::V1_1::SettingValue; +using ::android::hardware::contexthub::V1_2::ContextHubMsg; +using ::android::hardware::contexthub::V1_2::HubAppInfo; using ::android::hardware::contexthub::V1_2::IContexthub; +using ::android::hardware::contexthub::V1_2::IContexthubCallback; using ::android::hardware::contexthub::V1_2::Setting; +using ::android::hardware::contexthub::vts_utils::asBaseType; using ::android::hardware::contexthub::vts_utils::ContexthubCallbackBase; using ::android::hardware::contexthub::vts_utils::ContexthubHidlTestBase; using ::android::hardware::contexthub::vts_utils::getHalAndHubIdList; +using ::android::hardware::contexthub::vts_utils::kNonExistentAppId; +using ::android::hardware::contexthub::vts_utils::waitForCallback; namespace { const std::vector<std::tuple<std::string, std::string>> kTestParameters = getHalAndHubIdList<IContexthub>(); -class ContexthubHidlTest : public ContexthubHidlTestBase<IContexthub> {}; +class ContexthubCallbackV1_2 : public ContexthubCallbackBase<IContexthubCallback> { + public: + virtual Return<void> handleClientMsg_1_2( + const ContextHubMsg& /*msg*/, + const hidl_vec<hidl_string>& /*msgContentPerms*/) override { + ALOGD("Got client message callback"); + return Void(); + } + + virtual Return<void> handleAppsInfo_1_2(const hidl_vec<HubAppInfo>& /*appInfo*/) override { + ALOGD("Got app info callback"); + return Void(); + } +}; + +class ContexthubHidlTest : public ContexthubHidlTestBase<IContexthub> { + public: + Result registerCallback_1_2(sp<IContexthubCallback> cb) { + return hubApi->registerCallback_1_2(getHubId(), cb); + } +}; + +// Ensures that the metadata reported in getHubs_1_2() is valid +TEST_P(ContexthubHidlTest, TestGetHubs_1_2) { + hidl_vec<ContextHub> hubList; + hubApi->getHubs_1_2( + [&hubList](const hidl_vec<ContextHub>& hubs, + const hidl_vec<hidl_string>& /*hubPermissions*/) { hubList = hubs; }); + + ALOGD("System reports %zu hubs", hubList.size()); + + for (const ContextHub& hub : hubList) { + ALOGD("Checking hub ID %" PRIu32, hub.hubId); + + EXPECT_FALSE(hub.name.empty()); + EXPECT_FALSE(hub.vendor.empty()); + EXPECT_FALSE(hub.toolchain.empty()); + EXPECT_GT(hub.peakMips, 0); + EXPECT_GE(hub.stoppedPowerDrawMw, 0); + EXPECT_GE(hub.sleepPowerDrawMw, 0); + EXPECT_GT(hub.peakPowerDrawMw, 0); + + // Minimum 128 byte MTU as required by CHRE API v1.0 + EXPECT_GE(hub.maxSupportedMsgLen, UINT32_C(128)); + } +} + +TEST_P(ContexthubHidlTest, TestRegisterCallback) { + ALOGD("TestRegisterCallback called, hubId %" PRIu32, getHubId()); + ASSERT_OK(registerCallback_1_2(new ContexthubCallbackV1_2())); +} + +TEST_P(ContexthubHidlTest, TestRegisterNullCallback) { + ALOGD("TestRegisterNullCallback called, hubId %" PRIu32, getHubId()); + ASSERT_OK(registerCallback_1_2(nullptr)); +} // In VTS, we only test that sending the values doesn't cause things to blow up - other test // suites verify the expected E2E behavior in CHRE TEST_P(ContexthubHidlTest, TestOnWifiSettingChanged) { - ASSERT_OK(registerCallback(new ContexthubCallbackBase())); + ASSERT_OK(registerCallback_1_2(new ContexthubCallbackV1_2())); hubApi->onSettingChanged_1_2(Setting::WIFI_AVAILABLE, SettingValue::DISABLED); hubApi->onSettingChanged_1_2(Setting::WIFI_AVAILABLE, SettingValue::ENABLED); - ASSERT_OK(registerCallback(nullptr)); + ASSERT_OK(registerCallback_1_2(nullptr)); } TEST_P(ContexthubHidlTest, TestOnAirplaneModeSettingChanged) { - ASSERT_OK(registerCallback(new ContexthubCallbackBase())); + ASSERT_OK(registerCallback_1_2(new ContexthubCallbackV1_2())); hubApi->onSettingChanged_1_2(Setting::AIRPLANE_MODE, SettingValue::DISABLED); hubApi->onSettingChanged_1_2(Setting::AIRPLANE_MODE, SettingValue::ENABLED); - ASSERT_OK(registerCallback(nullptr)); + ASSERT_OK(registerCallback_1_2(nullptr)); } TEST_P(ContexthubHidlTest, TestOnGlobalMicDisableSettingChanged) { - ASSERT_OK(registerCallback(new ContexthubCallbackBase())); + ASSERT_OK(registerCallback_1_2(new ContexthubCallbackV1_2())); hubApi->onSettingChanged_1_2(Setting::GLOBAL_MIC_DISABLE, SettingValue::DISABLED); hubApi->onSettingChanged_1_2(Setting::GLOBAL_MIC_DISABLE, SettingValue::ENABLED); - ASSERT_OK(registerCallback(nullptr)); + ASSERT_OK(registerCallback_1_2(nullptr)); +} + +// Helper callback that puts the async appInfo callback data into a promise +class QueryAppsCallback : public ContexthubCallbackV1_2 { + public: + virtual Return<void> handleAppsInfo_1_2(const hidl_vec<HubAppInfo>& appInfo) override { + ALOGD("Got app info callback with %zu apps", appInfo.size()); + promise.set_value(appInfo); + return Void(); + } + + std::promise<hidl_vec<HubAppInfo>> promise; +}; + +// Calls queryApps() and checks the returned metadata +TEST_P(ContexthubHidlTest, TestQueryApps) { + hidl_vec<hidl_string> hubPerms; + hubApi->getHubs_1_2([&hubPerms](const hidl_vec<ContextHub>& /*hubs*/, + const hidl_vec<hidl_string>& hubPermissions) { + hubPerms = hubPermissions; + }); + + ALOGD("TestQueryApps called, hubId %u", getHubId()); + sp<QueryAppsCallback> cb = new QueryAppsCallback(); + ASSERT_OK(registerCallback_1_2(cb)); + + Result result = hubApi->queryApps(getHubId()); + ASSERT_OK(result); + + ALOGD("Waiting for app info callback"); + hidl_vec<HubAppInfo> appList; + ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &appList)); + for (const HubAppInfo& appInfo : appList) { + EXPECT_NE(appInfo.info_1_0.appId, UINT64_C(0)); + EXPECT_NE(appInfo.info_1_0.appId, kNonExistentAppId); + for (std::string permission : appInfo.permissions) { + ASSERT_TRUE(hubPerms.contains(permission)); + } + } +} + +// Helper callback that puts the TransactionResult for the expectedTxnId into a +// promise +class TxnResultCallback : public ContexthubCallbackV1_2 { + public: + virtual Return<void> handleTxnResult(uint32_t txnId, TransactionResult result) override { + ALOGD("Got transaction result callback for txnId %" PRIu32 " (expecting %" PRIu32 + ") with result %" PRId32, + txnId, expectedTxnId, result); + if (txnId == expectedTxnId) { + promise.set_value(result); + } + return Void(); + } + + uint32_t expectedTxnId = 0; + std::promise<TransactionResult> promise; +}; + +// Parameterized fixture that sets the callback to TxnResultCallback +class ContexthubTxnTest : public ContexthubHidlTest { + public: + virtual void SetUp() override { + ContexthubHidlTest::SetUp(); + ASSERT_OK(registerCallback_1_2(cb)); + } + + sp<TxnResultCallback> cb = new TxnResultCallback(); +}; + +TEST_P(ContexthubTxnTest, TestSendMessageToNonExistentNanoApp) { + ContextHubMsg msg; + msg.msg_1_0.appName = kNonExistentAppId; + msg.msg_1_0.msgType = 1; + msg.msg_1_0.msg.resize(4); + std::fill(msg.msg_1_0.msg.begin(), msg.msg_1_0.msg.end(), 0); + + ALOGD("Sending message to non-existent nanoapp"); + Result result = hubApi->sendMessageToHub_1_2(getHubId(), msg); + if (result != Result::OK && result != Result::BAD_PARAMS && + result != Result::TRANSACTION_FAILED) { + FAIL() << "Got result " << asBaseType(result) << ", expected OK, BAD_PARAMS" + << ", or TRANSACTION_FAILED"; + } } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ContexthubHidlTest); INSTANTIATE_TEST_SUITE_P(HubIdSpecificTests, ContexthubHidlTest, testing::ValuesIn(kTestParameters), android::hardware::PrintInstanceTupleNameToString<>); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ContexthubTxnTest); +INSTANTIATE_TEST_SUITE_P(HubIdSpecificTests, ContexthubTxnTest, testing::ValuesIn(kTestParameters), + android::hardware::PrintInstanceTupleNameToString<>); + } // anonymous namespace diff --git a/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h b/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h index df78438750..d9459b7381 100644 --- a/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h +++ b/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h @@ -54,7 +54,8 @@ inline hidl_vec<V1_0::HubAppInfo> convertToOldAppInfo(hidl_vec<V1_2::HubAppInfo> */ class IContextHubCallbackWrapperBase : public VirtualLightRefBase { public: - virtual Return<void> handleClientMsg(V1_2::ContextHubMsg msg) = 0; + virtual Return<void> handleClientMsg(V1_2::ContextHubMsg msg, + hidl_vec<hidl_string> msgContentPerms) = 0; virtual Return<void> handleTxnResult(uint32_t txnId, V1_0::TransactionResult result) = 0; @@ -63,6 +64,11 @@ class IContextHubCallbackWrapperBase : public VirtualLightRefBase { virtual Return<void> handleAppAbort(uint64_t appId, uint32_t abortCode) = 0; virtual Return<void> handleAppsInfo(hidl_vec<V1_2::HubAppInfo> appInfo) = 0; + + virtual Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t cookie) = 0; + + virtual Return<bool> unlinkToDeath(const sp<hidl_death_recipient>& recipient) = 0; }; template <typename T> @@ -70,7 +76,8 @@ class ContextHubCallbackWrapper : public IContextHubCallbackWrapperBase { public: ContextHubCallbackWrapper(sp<T> callback) : mCallback(callback){}; - virtual Return<void> handleClientMsg(V1_2::ContextHubMsg msg) override { + virtual Return<void> handleClientMsg(V1_2::ContextHubMsg msg, + hidl_vec<hidl_string> /* msgContentPerms */) override { return mCallback->handleClientMsg(convertToOldMsg(msg)); } @@ -90,6 +97,14 @@ class ContextHubCallbackWrapper : public IContextHubCallbackWrapperBase { return mCallback->handleAppsInfo(convertToOldAppInfo(appInfo)); } + Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t cookie) override { + return mCallback->linkToDeath(recipient, cookie); + } + + Return<bool> unlinkToDeath(const sp<hidl_death_recipient>& recipient) override { + return mCallback->unlinkToDeath(recipient); + } + protected: sp<T> mCallback; }; @@ -105,8 +120,9 @@ class IContextHubCallbackWrapperV1_2 : public ContextHubCallbackWrapper<V1_2::IC IContextHubCallbackWrapperV1_2(sp<V1_2::IContexthubCallback> callback) : ContextHubCallbackWrapper(callback){}; - Return<void> handleClientMsg(V1_2::ContextHubMsg msg) override { - return mCallback->handleClientMsg_1_2(msg); + Return<void> handleClientMsg(V1_2::ContextHubMsg msg, + hidl_vec<hidl_string> msgContentPerms) override { + return mCallback->handleClientMsg_1_2(msg, msgContentPerms); } Return<void> handleAppsInfo(hidl_vec<V1_2::HubAppInfo> appInfo) override { diff --git a/contexthub/common/vts/ContexthubCallbackBase.h b/contexthub/common/vts/ContexthubCallbackBase.h index 124a11601a..24d6c52705 100644 --- a/contexthub/common/vts/ContexthubCallbackBase.h +++ b/contexthub/common/vts/ContexthubCallbackBase.h @@ -27,7 +27,8 @@ namespace vts_utils { // Base callback implementation that just logs all callbacks by default, but // records a failure if -class ContexthubCallbackBase : public V1_0::IContexthubCallback { +template <class CallbackType> +class ContexthubCallbackBase : public CallbackType { public: virtual Return<void> handleClientMsg(const V1_0::ContextHubMsg& /*msg*/) override { ALOGD("Got client message callback"); diff --git a/contexthub/common/vts/VtsHalContexthubUtils.h b/contexthub/common/vts/VtsHalContexthubUtils.h index 8f9b6946d9..dff1865f4c 100644 --- a/contexthub/common/vts/VtsHalContexthubUtils.h +++ b/contexthub/common/vts/VtsHalContexthubUtils.h @@ -30,6 +30,10 @@ namespace hardware { namespace contexthub { namespace vts_utils { +// App ID with vendor "GoogT" (Google Testing), app identifier 0x555555. This +// app ID is reserved and must never appear in the list of loaded apps. +constexpr uint64_t kNonExistentAppId = 0x476f6f6754555555; + #define ASSERT_OK(result) ASSERT_EQ(result, ::android::hardware::contexthub::V1_0::Result::OK) #define EXPECT_OK(result) EXPECT_EQ(result, ::android::hardware::contexthub::V1_0::Result::OK) @@ -64,6 +68,29 @@ static std::vector<std::tuple<std::string, std::string>> getHalAndHubIdList() { return parameters; } +// Wait for a callback to occur (signaled by the given future) up to the +// provided timeout. If the future is invalid or the callback does not come +// within the given time, returns false. +template <class ReturnType> +bool waitForCallback(std::future<ReturnType> future, ReturnType* result, + std::chrono::milliseconds timeout = std::chrono::seconds(5)) { + auto expiration = std::chrono::system_clock::now() + timeout; + + EXPECT_NE(result, nullptr); + EXPECT_TRUE(future.valid()); + if (result != nullptr && future.valid()) { + std::future_status status = future.wait_until(expiration); + EXPECT_NE(status, std::future_status::timeout) << "Timed out waiting for callback"; + + if (status == std::future_status::ready) { + *result = future.get(); + return true; + } + } + + return false; +} + } // namespace vts_utils } // namespace contexthub } // namespace hardware diff --git a/current.txt b/current.txt index bf6829a353..5e9a34c5f6 100644 --- a/current.txt +++ b/current.txt @@ -769,7 +769,7 @@ a64467bae843569f0d465c5be7f0c7a5b987985b55a3ef4794dd5afc68538650 android.hardwar # ABI preserving changes to HALs during Android S e042522daa4b5f7fd4a0a19bcdadb93c79a1b04c09ef2c9813a3a8941032f3f5 android.hardware.contexthub@1.0::IContexthub c2f64133b83ede65c9939ef97ab5bd867b73faf3dba0e7e69f77c3c43d9e487e android.hardware.contexthub@1.0::IContexthubCallback -1ca372cd67d197df099e87616a613ba6ede6552638a603e18f86c8834302c3d1 android.hardware.gnss@1.0::IGnssMeasurementCallback +bda492ec4021d13869de72bd6f8c15c5837b78d6136b8d538efec5320573a5ec android.hardware.gnss@1.0::IGnssMeasurementCallback 6a271e493907e8ba20912e42771bd0d99ae45431a851d5675ef9496d02510a34 android.hardware.gnss@1.1::IGnssMeasurementCallback 2c331a9605f3a08d9c1e0a36169ca57758bc43c11a78ef3f3730509885e52c15 android.hardware.graphics.composer@2.4::IComposerClient 3da3ce039247872d95c6bd48621dbfdfa1c2d2a91a90f257862f87ee2bc46300 android.hardware.health@2.1::types @@ -779,8 +779,9 @@ c2f64133b83ede65c9939ef97ab5bd867b73faf3dba0e7e69f77c3c43d9e487e android.hardwar 6017b4f2481feb0fffceae81c62bc372c898998b2d8fe69fbd39859d3a315e5e android.hardware.keymaster@4.0::IKeymasterDevice dabe23dde7c9e3ad65c61def7392f186d7efe7f4216f9b6f9cf0863745b1a9f4 android.hardware.keymaster@4.1::IKeymasterDevice cd84ab19c590e0e73dd2307b591a3093ee18147ef95e6d5418644463a6620076 android.hardware.neuralnetworks@1.2::IDevice -9625e85f56515ad2cf87b6a1847906db669f746ea4ab02cd3d4ca25abc9b0109 android.hardware.neuralnetworks@1.2::types -9e758e208d14f7256e0885d6d8ad0b61121b21d8c313864f981727ae55bffd16 android.hardware.neuralnetworks@1.3::types +f729ee6a5f136b25d79ea6895d24700fce413df555baaecf2c39e4440d15d043 android.hardware.neuralnetworks@1.0::types +c6ae443608502339aec4256feef48e7b2d36f7477ca5361cc95cd27a8ed9c612 android.hardware.neuralnetworks@1.2::types +9fe5a4093043c2b5da4e9491aed1646c388a5d3059b8fd77d5b6a9807e6d3a3e android.hardware.neuralnetworks@1.3::types e8c86c69c438da8d1549856c1bb3e2d1b8da52722f8235ff49a30f2cce91742c android.hardware.soundtrigger@2.1::ISoundTriggerHwCallback b9fbb6e2e061ed0960939d48b785e9700210add1f13ed32ecd688d0f1ca20ef7 android.hardware.renderscript@1.0::types 0f53d70e1eadf8d987766db4bf6ae2048004682168f4cab118da576787def3fa android.hardware.radio@1.0::types diff --git a/drm/1.4/types.hal b/drm/1.4/types.hal index 706c3aa92a..17eba8a08f 100644 --- a/drm/1.4/types.hal +++ b/drm/1.4/types.hal @@ -19,11 +19,14 @@ package android.hardware.drm@1.4; import @1.2::Status; enum LogPriority : uint32_t { - ERROR, - WARN, - INFO, - DEBUG, - VERBOSE + UNKNOWN, + DEFAULT, + VERBOSE, + DEBUG, + INFO, + WARN, + ERROR, + FATAL, }; /** @@ -37,15 +40,100 @@ struct LogMessage { }; enum Status : @1.2::Status { - + /** + * queueSecureInput buffer called with 0 subsamples. + */ + CANNOT_DECRYPT_ZERO_SUBSAMPLES, + /** + * An error happened within the crypto library used by the drm plugin. + */ + CRYPTO_LIBRARY_ERROR, /** * Non-specific error reported by the device OEM subsystem. */ GENERAL_OEM_ERROR, - /** * Unexpected internal failure in the drm/crypto plugin. */ GENERAL_PLUGIN_ERROR, - + /** + * The init data parameter passed to getKeyRequest is empty or invalid. + */ + INIT_DATA_INVALID, + /** + * Either the key was not loaded from the license before attempting the + * operation, or the key ID parameter provided by the app is incorrect. + */ + KEY_NOT_LOADED, + /** + * The license response was empty, fields are missing or otherwise unable + * to be parsed. + */ + LICENSE_PARSE_ERROR, + /** + * The operation (e.g. to renew or persist a license) is prohibited by the + * license policy. + */ + LICENSE_POLICY_ERROR, + /** + * Failed to generate a release request because a field in the stored + * license is empty or malformed. + */ + LICENSE_RELEASE_ERROR, + /** + * The license server detected an error in the license request. + */ + LICENSE_REQUEST_REJECTED, + /** + * Failed to restore an offline license because a field is empty or + * malformed. + */ + LICENSE_RESTORE_ERROR, + /** + * License is in an invalid state for the attempted operation. + */ + LICENSE_STATE_ERROR, + /** + * Certificate is malformed or is of the wrong type. + */ + MALFORMED_CERTIFICATE, + /** + * Failure in the media framework. + */ + MEDIA_FRAMEWORK_ERROR, + /** + * Certificate has not been set. + */ + MISSING_CERTIFICATE, + /** + * There was an error loading the provisioned certificate. + */ + PROVISIONING_CERTIFICATE_ERROR, + /** + * Required steps where not performed before provisioning was attempted. + */ + PROVISIONING_CONFIGURATION_ERROR, + /** + * The provisioning response was empty, fields are missing or otherwise + * unable to be parsed. + */ + PROVISIONING_PARSE_ERROR, + /** + * Provisioning failed in a way that is likely to succeed on a subsequent + * attempt. + */ + RETRYABLE_PROVISIONING_ERROR, + /** + * Failed to generate a secure stop request because a field in the stored + * license is empty or malformed. + */ + SECURE_STOP_RELEASE_ERROR, + /** + * The plugin was unable to read data from the filesystem. + */ + STORAGE_READ_FAILURE, + /** + * The plugin was unable to write data to the filesystem. + */ + STORAGE_WRITE_FAILURE, }; diff --git a/drm/1.4/vts/OWNERS b/drm/1.4/vts/OWNERS new file mode 100644 index 0000000000..3a0672e5c6 --- /dev/null +++ b/drm/1.4/vts/OWNERS @@ -0,0 +1,9 @@ +conglin@google.com +edwinwong@google.com +fredgc@google.com +jtinker@google.com +juce@google.com +kylealexander@google.com +rfrias@google.com +robertshih@google.com +sigquit@google.com diff --git a/drm/1.4/vts/functional/Android.bp b/drm/1.4/vts/functional/Android.bp new file mode 100644 index 0000000000..80b1dd1825 --- /dev/null +++ b/drm/1.4/vts/functional/Android.bp @@ -0,0 +1,95 @@ +// +// Copyright (C) 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. +// + +cc_library_static { + name: "android.hardware.drm@1.4-vts", + defaults: ["VtsHalTargetTestDefaults"], + local_include_dirs: [ + "include", + ], + srcs: [ + "drm_hal_test.cpp", + ], + shared_libs: [ + "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "android.hardware.drm@1.2", + "android.hardware.drm@1.3", + "android.hardware.drm@1.4", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libhidlmemory", + "libnativehelper", + ], + static_libs: [ + "android.hardware.drm@1.0-helper", + "android.hardware.drm@1.2-vts", + "libdrmvtshelper", + ], + export_static_lib_headers: [ + "android.hardware.drm@1.2-vts", + ], + export_include_dirs: [ + "include", + ], +} + +cc_test { + name: "VtsHalDrmV1_4TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + include_dirs: ["hardware/interfaces/drm/1.0/vts/functional"], + srcs: [ + "drm_hal_test_main.cpp", + ], + whole_static_libs: [ + "android.hardware.drm@1.4-vts", + ], + shared_libs: [ + "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "android.hardware.drm@1.2", + "android.hardware.drm@1.3", + "android.hardware.drm@1.4", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libcrypto", + "libhidlmemory", + "libnativehelper", + ], + static_libs: [ + "android.hardware.drm@1.0-helper", + "android.hardware.drm@1.2-vts", + "libdrmvtshelper", + ], + arch: { + arm: { + data: [":libvtswidevine-arm-prebuilts"], + }, + arm64: { + data: [":libvtswidevine-arm64-prebuilts"], + }, + x86: { + data: [":libvtswidevine-x86-prebuilts"], + }, + x86_64: { + data: [":libvtswidevine-x86_64-prebuilts"], + }, + }, + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/drm/1.4/vts/functional/AndroidTest.xml b/drm/1.4/vts/functional/AndroidTest.xml new file mode 100644 index 0000000000..b18da49a8c --- /dev/null +++ b/drm/1.4/vts/functional/AndroidTest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 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. +--> +<configuration description="Runs VtsHalDrmV1_4TargetTest."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + <option name="not-shardable" value="true" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + + <target_preparer class="com.android.tradefed.targetprep.WifiPreparer" > + <option name="verify-only" value="true" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push-file" key="VtsHalDrmV1_4TargetTest" value="/data/local/tmp/VtsHalDrmV1_4TargetTest" /> + <option name="push-file" key="libvtswidevine64.so" value="/data/local/tmp/64/lib/libvtswidevine.so" /> + <option name="push-file" key="libvtswidevine32.so" value="/data/local/tmp/32/lib/libvtswidevine.so" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="VtsHalDrmV1_4TargetTest" /> + </test> +</configuration> diff --git a/drm/1.4/vts/functional/drm_hal_test.cpp b/drm/1.4/vts/functional/drm_hal_test.cpp new file mode 100644 index 0000000000..f9fa0bde3b --- /dev/null +++ b/drm/1.4/vts/functional/drm_hal_test.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "drm_hal_test@1.4" + +#include "android/hardware/drm/1.4/vts/drm_hal_test.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_4 { +namespace vts { + +const char* const DrmHalTest::kVideoMp4 = "video/mp4"; +const char* const DrmHalTest::kAudioMp4 = "audio/mp4"; +const uint32_t DrmHalTest::kSecLevelDefault = DrmHalTest::kSecLevelMax + 1; + +sp<drm::V1_4::IDrmPlugin> DrmHalTest::DrmPluginV1_4() const { + sp<drm::V1_4::IDrmPlugin> plugin(drm::V1_4::IDrmPlugin::castFrom(drmPlugin)); + EXPECT_NE(nullptr, plugin.get()); + return plugin; +} + +sp<V1_0::ICryptoPlugin> DrmHalTest::CryptoPlugin(const SessionId& sid) { + sp<V1_0::ICryptoPlugin> crypto; + auto res = cryptoFactory->createPlugin( + getUUID(), sid, + [&](V1_0::Status status, const sp<V1_0::ICryptoPlugin>& plugin) { + EXPECT_EQ(V1_0::Status::OK, status); + EXPECT_NE(nullptr, plugin.get()); + crypto = plugin; + }); + EXPECT_OK(res); + return crypto; +} + +SessionId DrmHalTest::OpenSession(uint32_t level = kSecLevelDefault) { + V1_0::Status err; + SessionId sessionId; + bool attemptedProvision = false; + + V1_0::IDrmPlugin::openSession_cb cb = [&]( + V1_0::Status status, + const hidl_vec<unsigned char> &id) { + err = status; + sessionId = id; + }; + + while (true) { + Return<void> res; + if (level > kSecLevelMax) { + res = drmPlugin->openSession(cb); + } else if (level >= kSecLevelMin) { + auto securityLevel = static_cast<SecurityLevel>(level); + res = drmPlugin->openSession_1_1(securityLevel, cb); + } + EXPECT_OK(res); + if (V1_0::Status::ERROR_DRM_NOT_PROVISIONED == err + && !attemptedProvision) { + // provision once if necessary + provision(); + attemptedProvision = true; + continue; + } else if (V1_0::Status::ERROR_DRM_CANNOT_HANDLE == err) { + // must be able to handle default level + EXPECT_NE(kSecLevelDefault, level); + sessionId = {}; + } else { + EXPECT_EQ(V1_0::Status::OK, err); + EXPECT_NE(sessionId.size(), 0u); + } + break; + } + + return sessionId; +} + +TEST_P(DrmHalTest, RequiresSecureDecoder) { + for (uint32_t level : {kSecLevelMin, kSecLevelMax, kSecLevelDefault}) { + for (auto mime : {kVideoMp4, kAudioMp4}) { + auto sid = OpenSession(level); + if (sid.size() == 0u) { + continue; + } + auto drm = DrmPluginV1_4(); + sp<V1_0::ICryptoPlugin> crypto(CryptoPlugin(sid)); + if (drm == nullptr || crypto == nullptr) { + continue; + } + bool r1 = crypto->requiresSecureDecoderComponent(mime); + bool r2; + if (level == kSecLevelDefault) { + r2 = drm->requiresSecureDecoderDefault(mime); + } else { + auto sL = static_cast<SecurityLevel>(level); + r2 = drm->requiresSecureDecoder(mime, sL); + } + EXPECT_EQ(r1, r2); + closeSession(sid); + } + } +} + +TEST_P(DrmHalTest, SetPlaybackId) { + auto testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); + auto testName = testInfo->name(); + const hidl_string& pbId{testName}; + auto sid = OpenSession(); + auto drm = DrmPluginV1_4(); + if (drm == nullptr) { + return; + } + V1_0::Status err = drm->setPlaybackId(sid, pbId); + EXPECT_EQ(V1_0::Status::OK, err); + closeSession(sid); + + // search for playback id among metric attributes/values + bool foundPbId = false; + auto res = drmPlugin->getMetrics([&]( + V1_0::Status status, + hidl_vec<V1_1::DrmMetricGroup> metricGroups) { + EXPECT_EQ(V1_0::Status::OK, status); + for (const auto& group : metricGroups) { + for (const auto& metric : group.metrics) { + for (const auto& value : metric.values) { + if (value.stringValue == pbId) { + foundPbId = true; + break; + } + } + for (const auto& attr : metric.attributes) { + if (attr.stringValue == pbId) { + foundPbId = true; + break; + } + } + } + } + }); + EXPECT_OK(res); + EXPECT_TRUE(foundPbId); +} + +TEST_P(DrmHalTest, GetLogMessages) { + auto drm = DrmPluginV1_4(); + auto sid = OpenSession(); + auto crypto_1_0 = CryptoPlugin(sid); + sp<V1_4::ICryptoPlugin> crypto(V1_4::ICryptoPlugin::castFrom(crypto_1_0)); + + hidl_vec<uint8_t> initData; + hidl_string mime{"text/plain"}; + V1_0::KeyedVector optionalParameters; + auto res = drmPlugin->getKeyRequest_1_2( + sid, initData, mime, V1_0::KeyType::STREAMING, + optionalParameters, [&](V1_2::Status status, const hidl_vec<uint8_t>&, + V1_1::KeyRequestType, const hidl_string&) { + EXPECT_NE(V1_2::Status::OK, status); + }); + EXPECT_OK(res); + + V1_4::IDrmPlugin::getLogMessages_cb cb = [&]( + V1_4::Status status, + hidl_vec<V1_4::LogMessage> logs) { + EXPECT_EQ(V1_4::Status::OK, status); + EXPECT_NE(0, logs.size()); + for (auto log: logs) { + ALOGI("priority=[%u] message='%s'", log.priority, log.message.c_str()); + } + }; + + auto res2 = drm->getLogMessages(cb); + EXPECT_OK(res2); + + auto res3 = crypto->getLogMessages(cb); + EXPECT_OK(res3); + + closeSession(sid); +} + +} // namespace vts +} // namespace V1_4 +} // namespace drm +} // namespace hardware +} // namespace android diff --git a/drm/1.4/vts/functional/drm_hal_test_main.cpp b/drm/1.4/vts/functional/drm_hal_test_main.cpp new file mode 100644 index 0000000000..65d1b76038 --- /dev/null +++ b/drm/1.4/vts/functional/drm_hal_test_main.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 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. + */ + +/** + * Instantiate the set of test cases for each vendor module + */ + +#define LOG_TAG "drm_hal_test@1.4" + +#include <android/hardware/drm/1.4/ICryptoFactory.h> +#include <android/hardware/drm/1.4/IDrmFactory.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidl/ServiceManagement.h> +#include <log/log.h> + +#include <algorithm> +#include <iterator> +#include <string> +#include <utility> +#include <vector> + +#include "android/hardware/drm/1.4/vts/drm_hal_test.h" + +using drm_vts::DrmHalTestParam; +using drm_vts::PrintParamInstanceToString; + +using android::hardware::drm::V1_4::vts::DrmHalTest; + +static const std::vector<DrmHalTestParam> kAllInstances = [] { + using ::android::hardware::hidl_array; + using ::android::hardware::hidl_vec; + using ::android::hardware::drm::V1_4::ICryptoFactory; + using ::android::hardware::drm::V1_4::IDrmFactory; + + std::vector<std::string> drmInstances = + android::hardware::getAllHalInstanceNames(IDrmFactory::descriptor); + std::vector<std::string> cryptoInstances = + android::hardware::getAllHalInstanceNames(ICryptoFactory::descriptor); + std::set<std::string> allInstances; + allInstances.insert(drmInstances.begin(), drmInstances.end()); + allInstances.insert(cryptoInstances.begin(), cryptoInstances.end()); + + std::vector<DrmHalTestParam> firstInstanceUuidCombos; + for (const auto &instance : allInstances) { + auto drmFactory = IDrmFactory::getService(instance); + if (drmFactory == nullptr) { + continue; + } + drmFactory->getSupportedCryptoSchemes( + [&](const hidl_vec<hidl_array<uint8_t, 16>>& schemes) { + if (schemes.size() > 0) { + firstInstanceUuidCombos.push_back(DrmHalTestParam(instance, schemes[0])); + } + }); + } + return firstInstanceUuidCombos; +}(); + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DrmHalTest); +INSTANTIATE_TEST_SUITE_P(PerInstance, DrmHalTest, + testing::ValuesIn(kAllInstances), + PrintParamInstanceToString); + +int main(int argc, char** argv) { +#if defined(__LP64__) + const char* kModulePath = "/data/local/tmp/64/lib"; +#else + const char* kModulePath = "/data/local/tmp/32/lib"; +#endif + DrmHalTest::gVendorModules + = new drm_vts::VendorModules(kModulePath); + if (DrmHalTest::gVendorModules->getPathList().size() == 0) { + std::cerr << "WARNING: No vendor modules found in " << kModulePath << + ", all vendor tests will be skipped" << std::endl; + } + ::testing::InitGoogleTest(&argc, argv); + int status = RUN_ALL_TESTS(); + ALOGI("Test result = %d", status); + return status; +} diff --git a/drm/1.4/vts/functional/include/android/hardware/drm/1.4/vts/drm_hal_test.h b/drm/1.4/vts/functional/include/android/hardware/drm/1.4/vts/drm_hal_test.h new file mode 100644 index 0000000000..ed49a6141b --- /dev/null +++ b/drm/1.4/vts/functional/include/android/hardware/drm/1.4/vts/drm_hal_test.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 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. + */ + +#ifndef DRM_HAL_TEST_V1_4_H +#define DRM_HAL_TEST_V1_4_H + +#include <android/hardware/drm/1.0/IDrmPlugin.h> +#include <android/hardware/drm/1.3/IDrmFactory.h> +#include <android/hardware/drm/1.4/ICryptoFactory.h> +#include <android/hardware/drm/1.4/ICryptoPlugin.h> +#include <android/hardware/drm/1.4/IDrmFactory.h> +#include <android/hardware/drm/1.4/IDrmPlugin.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidl/ServiceManagement.h> +#include <log/log.h> + +#include <algorithm> +#include <cstdint> +#include <iterator> +#include <string> +#include <utility> +#include <vector> + +#include "drm_hal_vendor_module_api.h" +#include "drm_vts_helper.h" +#include "vendor_modules.h" +#include "VtsHalHidlTargetCallbackBase.h" + +#include "android/hardware/drm/1.2/vts/drm_hal_common.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_4 { +namespace vts { + +namespace drm = ::android::hardware::drm; +using android::hardware::hidl_array; +using android::hardware::hidl_string; +using V1_0::SessionId; +using V1_1::SecurityLevel; + +using drm_vts::DrmHalTestParam; + +class DrmHalTest : public drm::V1_2::vts::DrmHalTest { +public: + using drm::V1_2::vts::DrmHalTest::DrmHalTest; + static const char* const kVideoMp4; + static const char* const kAudioMp4; + static const uint32_t kSecLevelMin = static_cast<uint32_t>(SecurityLevel::SW_SECURE_CRYPTO); + static const uint32_t kSecLevelMax = static_cast<uint32_t>(SecurityLevel::HW_SECURE_ALL); + static const uint32_t kSecLevelDefault; + +protected: + sp<V1_4::IDrmPlugin> DrmPluginV1_4() const; + sp<V1_0::ICryptoPlugin> CryptoPlugin(const SessionId& sid); + SessionId OpenSession(uint32_t level); + +private: + void DoProvisioning(); +}; + +} // namespace vts +} // namespace V1_4 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // DRM_HAL_TEST_V1_4_H diff --git a/gnss/1.0/IGnssMeasurementCallback.hal b/gnss/1.0/IGnssMeasurementCallback.hal index d219af0bd1..603680d0b4 100644 --- a/gnss/1.0/IGnssMeasurementCallback.hal +++ b/gnss/1.0/IGnssMeasurementCallback.hal @@ -644,22 +644,19 @@ interface IGnssMeasurementCallback { */ double snrDb; - /** - * Automatic gain control (AGC) level. AGC acts as a variable gain - * amplifier adjusting the power of the incoming signal. The AGC level - * may be used to indicate potential interference. When AGC is at a - * nominal level, this value must be set as 0. Higher gain (and/or lower - * input power) must be output as a positive number. Hence in cases of - * strong jamming, in the band of this signal, this value must go more - * negative. - * - * Note: Different hardware designs (e.g. antenna, pre-amplification, or - * other RF HW components) may also affect the typical output of of this - * value on any given hardware design in an open sky test - the - * important aspect of this output is that changes in this value are - * indicative of changes on input signal power in the frequency band for - * this measurement. - */ + + /** + * Automatic gain control (AGC) level. AGC acts as a variable gain amplifier adjusting the power + * of the incoming signal. The AGC level may be used to indicate potential interference. Higher + * gain (and/or lower input power) must be output as a positive number. Hence in cases of strong + * jamming, in the band of this signal, this value must go more negative. This value must be + * consistent given the same level of the incoming signal power. + * + * Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW components) + * may also affect the typical output of this value on any given hardware design in an open sky + * test - the important aspect of this output is that changes in this value are indicative of + * changes on input signal power in the frequency band for this measurement. + */ double agcLevelDb; }; diff --git a/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl b/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl index 2c56a41643..4468b63e26 100644 --- a/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl +++ b/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl @@ -547,20 +547,16 @@ parcelable GnssMeasurement { double snrDb; /** - * Automatic gain control (AGC) level. AGC acts as a variable gain - * amplifier adjusting the power of the incoming signal. The AGC level - * may be used to indicate potential interference. When AGC is at a - * nominal level, this value must be set as 0. Higher gain (and/or lower - * input power) must be output as a positive number. Hence in cases of - * strong jamming, in the band of this signal, this value must go more - * negative. - * - * Note: Different hardware designs (e.g. antenna, pre-amplification, or - * other RF HW components) may also affect the typical output of this - * value on any given hardware design in an open sky test - the - * important aspect of this output is that changes in this value are - * indicative of changes on input signal power in the frequency band for - * this measurement. + * Automatic gain control (AGC) level. AGC acts as a variable gain amplifier adjusting the power + * of the incoming signal. The AGC level may be used to indicate potential interference. Higher + * gain (and/or lower input power) must be output as a positive number. Hence in cases of strong + * jamming, in the band of this signal, this value must go more negative. This value must be + * consistent given the same level of the incoming signal power. + * + * Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW components) + * may also affect the typical output of this value on any given hardware design in an open sky + * test - the important aspect of this output is that changes in this value are indicative of + * changes on input signal power in the frequency band for this measurement. */ double agcLevelDb; diff --git a/health/utils/libhealth2impl/Health.cpp b/health/utils/libhealth2impl/Health.cpp index f4684ae6f6..035b36f747 100644 --- a/health/utils/libhealth2impl/Health.cpp +++ b/health/utils/libhealth2impl/Health.cpp @@ -80,14 +80,14 @@ Return<Result> Health::unregisterCallback(const sp<V2_0::IHealthInfoCallback>&) Return<Result> Health::update() { Result result = Result::UNKNOWN; - getHealthInfo_2_1([&](auto res, const auto& /* health_info */) { + getHealthInfo_2_1([&](auto res, const auto& health_info) { result = res; if (res != Result::SUCCESS) { LOG(ERROR) << "Cannot call getHealthInfo_2_1: " << toString(res); return; } - battery_monitor_.logValues(); + BatteryMonitor::logValues(health_info, *healthd_config_); }); return result; } diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp index 38348ac1b0..6418028c6a 100644 --- a/identity/support/src/IdentityCredentialSupport.cpp +++ b/identity/support/src/IdentityCredentialSupport.cpp @@ -833,9 +833,16 @@ bool parseAsn1Time(const ASN1_TIME* asn1Time, time_t* outTime) { optional<vector<vector<uint8_t>>> createAttestation( const EVP_PKEY* key, const vector<uint8_t>& applicationId, const vector<uint8_t>& challenge, uint64_t activeTimeMilliSeconds, uint64_t expireTimeMilliSeconds, bool isTestCredential) { + // Pretend to be implemented in a trusted environment just so we can pass + // the VTS tests. Of course, this is a pretend-only game since hopefully no + // relying party is ever going to trust our batch key and those keys above + // it. + ::keymaster::PureSoftKeymasterContext context(::keymaster::KmVersion::KEYMASTER_4_1, + KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT); + keymaster_error_t error; ::keymaster::CertificateChain attestation_chain = - ::keymaster::getAttestationChain(KM_ALGORITHM_EC, &error); + context.GetAttestationChain(KM_ALGORITHM_EC, &error); if (KM_ERROR_OK != error) { LOG(ERROR) << "Error getting attestation chain " << error; return {}; @@ -855,12 +862,6 @@ optional<vector<vector<uint8_t>>> createAttestation( } expireTimeMilliSeconds = bcNotAfter * 1000; } - const keymaster_key_blob_t* attestation_signing_key = - ::keymaster::getAttestationKey(KM_ALGORITHM_EC, nullptr); - if (attestation_signing_key == nullptr) { - LOG(ERROR) << "Error getting attestation key"; - return {}; - } ::keymaster::X509_NAME_Ptr subjectName; if (KM_ERROR_OK != @@ -874,8 +875,11 @@ optional<vector<vector<uint8_t>>> createAttestation( i2d_X509_NAME(subjectName.get(), &subjectPtr); + uint64_t nowMilliSeconds = time(nullptr) * 1000; ::keymaster::AuthorizationSet auth_set( ::keymaster::AuthorizationSetBuilder() + .Authorization(::keymaster::TAG_CERTIFICATE_NOT_BEFORE, nowMilliSeconds) + .Authorization(::keymaster::TAG_CERTIFICATE_NOT_AFTER, expireTimeMilliSeconds) .Authorization(::keymaster::TAG_ATTESTATION_CHALLENGE, challenge.data(), challenge.size()) .Authorization(::keymaster::TAG_ACTIVE_DATETIME, activeTimeMilliSeconds) @@ -914,19 +918,11 @@ optional<vector<vector<uint8_t>>> createAttestation( } ::keymaster::AuthorizationSet hwEnforced(hwEnforcedBuilder); - // Pretend to be implemented in a trusted environment just so we can pass - // the VTS tests. Of course, this is a pretend-only game since hopefully no - // relying party is ever going to trust our batch key and those keys above - // it. - ::keymaster::PureSoftKeymasterContext context(::keymaster::KmVersion::KEYMASTER_4_1, - KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT); - - ::keymaster::CertificateChain cert_chain_out = generate_attestation_from_EVP( - key, swEnforced, hwEnforced, auth_set, context, move(attestation_chain), - *attestation_signing_key, &error); + ::keymaster::CertificateChain cert_chain_out = generate_attestation( + key, swEnforced, hwEnforced, auth_set, {} /* attest_key */, context, &error); if (KM_ERROR_OK != error) { - LOG(ERROR) << "Error generate attestation from EVP key" << error; + LOG(ERROR) << "Error generating attestation from EVP key: " << error; return {}; } diff --git a/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp b/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp index 9184c56e70..ea29f038de 100644 --- a/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp +++ b/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp @@ -215,6 +215,7 @@ void allocateGraphicBuffers(sp<IOmxNode> omxNode, OMX_U32 portIndex, ASSERT_NE(handle, nullptr); *nStride = static_cast<int32_t>(stride); + buffer->handle = handle; buffer->omxBuffer.nativeHandle = handle; buffer->omxBuffer.attr.anwBuffer.width = nFrameWidth; buffer->omxBuffer.attr.anwBuffer.height = nFrameHeight; @@ -335,6 +336,18 @@ void allocatePortBuffers(sp<IOmxNode> omxNode, } } +// free buffers needed on a component port +void freePortBuffers(android::Vector<BufferInfo>* buffArray, PortMode portMode, bool allocGrap) { + for (size_t i = 0; i < buffArray->size(); i++) { + if (portMode == PortMode::PRESET_ANW_BUFFER || + (allocGrap && portMode == PortMode::DYNAMIC_ANW_BUFFER)) { + android::GraphicBufferAllocator& allocator = android::GraphicBufferAllocator::get(); + android::status_t error = allocator.free((*buffArray)[i].handle); + ASSERT_EQ(error, android::NO_ERROR); + } + } +} + // State Transition : Loaded -> Idle // Note: This function does not make any background checks for this transition. // The callee holds the reponsibility to ensure the legality of the transition. @@ -399,11 +412,15 @@ void changeStateLoadedtoIdle(sp<IOmxNode> omxNode, sp<CodecObserver> observer, // The callee holds the reponsibility to ensure the legality of the transition. void changeStateIdletoLoaded(sp<IOmxNode> omxNode, sp<CodecObserver> observer, android::Vector<BufferInfo>* iBuffer, - android::Vector<BufferInfo>* oBuffer, - OMX_U32 kPortIndexInput, - OMX_U32 kPortIndexOutput) { + android::Vector<BufferInfo>* oBuffer, OMX_U32 kPortIndexInput, + OMX_U32 kPortIndexOutput, PortMode* portMode, bool allocGrap) { android::hardware::media::omx::V1_0::Status status; Message msg; + PortMode defaultPortMode[2], *pm; + + defaultPortMode[0] = PortMode::PRESET_BYTE_BUFFER; + defaultPortMode[1] = PortMode::PRESET_BYTE_BUFFER; + pm = portMode ? portMode : defaultPortMode; // set state to Loaded status = omxNode->sendCommand(toRawCommandType(OMX_CommandStateSet), @@ -446,6 +463,8 @@ void changeStateIdletoLoaded(sp<IOmxNode> omxNode, sp<CodecObserver> observer, ASSERT_EQ(msg.data.eventData.data1, OMX_CommandStateSet); ASSERT_EQ(msg.data.eventData.data2, OMX_StateLoaded); + ASSERT_NO_FATAL_FAILURE(freePortBuffers(iBuffer, pm[0], allocGrap)); + ASSERT_NO_FATAL_FAILURE(freePortBuffers(oBuffer, pm[1], allocGrap)); return; } diff --git a/media/omx/1.0/vts/functional/common/media_hidl_test_common.h b/media/omx/1.0/vts/functional/common/media_hidl_test_common.h index b16c772512..eddf83f02d 100644 --- a/media/omx/1.0/vts/functional/common/media_hidl_test_common.h +++ b/media/omx/1.0/vts/functional/common/media_hidl_test_common.h @@ -115,6 +115,7 @@ inline uint32_t toRawCommandType(OMX_COMMANDTYPE l) { struct BufferInfo { uint32_t id; bufferOwner owner; + buffer_handle_t handle; android::hardware::media::omx::V1_0::CodecBuffer omxBuffer; ::android::sp<IMemory> mMemory; int32_t slot; @@ -329,6 +330,9 @@ void allocatePortBuffers(sp<IOmxNode> omxNode, PortMode portMode = PortMode::PRESET_BYTE_BUFFER, bool allocGrap = false); +void freePortBuffers(android::Vector<BufferInfo>* buffArray, PortMode portMode, + bool allocGrap = false); + void changeStateLoadedtoIdle(sp<IOmxNode> omxNode, sp<CodecObserver> observer, android::Vector<BufferInfo>* iBuffer, android::Vector<BufferInfo>* oBuffer, @@ -338,8 +342,9 @@ void changeStateLoadedtoIdle(sp<IOmxNode> omxNode, sp<CodecObserver> observer, void changeStateIdletoLoaded(sp<IOmxNode> omxNode, sp<CodecObserver> observer, android::Vector<BufferInfo>* iBuffer, - android::Vector<BufferInfo>* oBuffer, - OMX_U32 kPortIndexInput, OMX_U32 kPortIndexOutput); + android::Vector<BufferInfo>* oBuffer, OMX_U32 kPortIndexInput, + OMX_U32 kPortIndexOutput, PortMode* portMode = nullptr, + bool allocGrap = false); void changeStateIdletoExecute(sp<IOmxNode> omxNode, sp<CodecObserver> observer); diff --git a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp index 67b9895d06..d35ce652fb 100644 --- a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp +++ b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp @@ -451,6 +451,7 @@ void portReconfiguration(sp<IOmxNode> omxNode, sp<CodecObserver> observer, status, android::hardware::media::omx::V1_0::Status::TIMED_OUT); + ASSERT_NO_FATAL_FAILURE(freePortBuffers(oBuffer, oPortMode, true)); ASSERT_NO_FATAL_FAILURE(allocatePortBuffers( omxNode, oBuffer, kPortIndexOutput, oPortMode, true)); status = observer->dequeueMessage(&msg, DEFAULT_TIMEOUT, @@ -853,9 +854,9 @@ TEST_P(VideoDecHidlTest, DecodeTest) { ASSERT_NO_FATAL_FAILURE( changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, - &oBuffer, kPortIndexInput, - kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, + kPortIndexInput, kPortIndexOutput, portMode, + true)); } // Test for adaptive playback support @@ -1001,9 +1002,9 @@ TEST_P(VideoDecHidlTest, AdaptivePlaybackTest) { ASSERT_NO_FATAL_FAILURE( changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, - &oBuffer, kPortIndexInput, - kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, + kPortIndexInput, kPortIndexOutput, portMode, + true)); } // end of sequence test @@ -1067,9 +1068,9 @@ TEST_P(VideoDecHidlTest, EOSTest_M) { ASSERT_NO_FATAL_FAILURE( changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, - &oBuffer, kPortIndexInput, - kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, + kPortIndexInput, kPortIndexOutput, portMode, + true)); } // end of sequence test @@ -1188,9 +1189,9 @@ TEST_P(VideoDecHidlTest, ThumbnailTest) { ASSERT_NO_FATAL_FAILURE( changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, - &oBuffer, kPortIndexInput, - kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, + kPortIndexInput, kPortIndexOutput, portMode, + true)); } // end of sequence test @@ -1295,9 +1296,9 @@ TEST_P(VideoDecHidlTest, SimpleEOSTest) { ASSERT_NO_FATAL_FAILURE( changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, - &oBuffer, kPortIndexInput, - kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, + kPortIndexInput, kPortIndexOutput, portMode, + true)); } // test input/output port flush @@ -1414,9 +1415,9 @@ TEST_P(VideoDecHidlTest, FlushTest) { ASSERT_NO_FATAL_FAILURE( changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, - &oBuffer, kPortIndexInput, - kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, + kPortIndexInput, kPortIndexOutput, portMode, + true)); } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VideoDecHidlTest); diff --git a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp index 3c0734ebeb..f24c6d1be2 100644 --- a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp +++ b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp @@ -1057,9 +1057,9 @@ TEST_P(VideoEncHidlTest, BufferSourceCallBacks) { ASSERT_NO_FATAL_FAILURE(changeStateExecutetoIdle( omxNode, observer, &buffersource->iBuffer, &buffersource->oBuffer)); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded( - omxNode, observer, &buffersource->iBuffer, &buffersource->oBuffer, - kPortIndexInput, kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &buffersource->iBuffer, + &buffersource->oBuffer, kPortIndexInput, + kPortIndexOutput, portMode)); // test for callbacks EXPECT_EQ(buffersource->callback, 31); } @@ -1174,9 +1174,8 @@ TEST_P(VideoEncHidlTest, EncodeTest) { ASSERT_NO_FATAL_FAILURE( changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, - &oBuffer, kPortIndexInput, - kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, + kPortIndexInput, kPortIndexOutput, portMode)); } // test raw stream encode (input is ANW buffers) @@ -1337,9 +1336,8 @@ TEST_P(VideoEncHidlTest, EncodeTestBufferMetaModes) { changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); EXPECT_EQ(portDef.nBufferCountActual, listener->freeBuffers); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, - &oBuffer, kPortIndexInput, - kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, + kPortIndexInput, kPortIndexOutput, portMode)); returnval = producer->disconnect( NATIVE_WINDOW_API_CPU, IGraphicBufferProducer::DisconnectMode::API); @@ -1452,9 +1450,8 @@ TEST_P(VideoEncHidlTest, EncodeTestEOS) { changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); EXPECT_EQ(portDef.nBufferCountActual, listener->freeBuffers); // set state to executing - ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, - &oBuffer, kPortIndexInput, - kPortIndexOutput)); + ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, + kPortIndexInput, kPortIndexOutput, portMode)); returnval = producer->disconnect( NATIVE_WINDOW_API_CPU, IGraphicBufferProducer::DisconnectMode::API); diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl index 00abff9edc..3e25c56e43 100644 --- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl +++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl @@ -1,14 +1,29 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// // 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 diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl index 844a1bb3d4..2e2b68e2f1 100644 --- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl +++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl @@ -1,14 +1,29 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// // 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 diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl index 09ecefc6c4..0e15ce32dd 100644 --- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl +++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl @@ -1,14 +1,29 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// // 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 diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl index 7f3f93904f..b19869e61f 100644 --- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl +++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl @@ -1,14 +1,29 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// // 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 @@ -23,5 +38,4 @@ enum MemtrackType { GRAPHICS = 2, MULTIMEDIA = 3, CAMERA = 4, - NUM_TYPES = 5, } diff --git a/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl b/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl index 715c6bff43..5db735a2e5 100644 --- a/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl +++ b/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl @@ -27,5 +27,4 @@ enum MemtrackType { GRAPHICS = 2, MULTIMEDIA = 3, CAMERA = 4, - NUM_TYPES, } diff --git a/memtrack/aidl/default/Memtrack.cpp b/memtrack/aidl/default/Memtrack.cpp index 000b25c030..49a6582989 100644 --- a/memtrack/aidl/default/Memtrack.cpp +++ b/memtrack/aidl/default/Memtrack.cpp @@ -26,7 +26,8 @@ ndk::ScopedAStatus Memtrack::getMemory(int pid, MemtrackType type, if (pid < 0) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT)); } - if (type < MemtrackType::OTHER || type >= MemtrackType::NUM_TYPES) { + if (type != MemtrackType::OTHER && type != MemtrackType::GL && type != MemtrackType::GRAPHICS && + type != MemtrackType::MULTIMEDIA && type != MemtrackType::CAMERA) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); } _aidl_return->clear(); diff --git a/memtrack/aidl/vts/Android.bp b/memtrack/aidl/vts/Android.bp index df87db843f..eff2a563af 100644 --- a/memtrack/aidl/vts/Android.bp +++ b/memtrack/aidl/vts/Android.bp @@ -13,6 +13,6 @@ cc_test { "android.hardware.memtrack-V1-ndk_platform", ], test_suites: [ - "vts-core", + "vts", ], } diff --git a/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp b/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp index d5f4612ca2..8905f50eaf 100644 --- a/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp +++ b/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp @@ -46,17 +46,19 @@ class MemtrackAidlTest : public testing::TestWithParam<std::string> { TEST_P(MemtrackAidlTest, GetMemoryInvalidPid) { int pid = -1; - MemtrackType type = MemtrackType::OTHER; - std::vector<MemtrackRecord> records; - auto status = memtrack_->getMemory(pid, type, &records); + for (MemtrackType type : ndk::enum_range<MemtrackType>()) { + std::vector<MemtrackRecord> records; - EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT); + auto status = memtrack_->getMemory(pid, type, &records); + + EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT); + } } TEST_P(MemtrackAidlTest, GetMemoryInvalidType) { int pid = 1; - MemtrackType type = MemtrackType::NUM_TYPES; + MemtrackType type = static_cast<MemtrackType>(-1); std::vector<MemtrackRecord> records; auto status = memtrack_->getMemory(pid, type, &records); @@ -66,12 +68,13 @@ TEST_P(MemtrackAidlTest, GetMemoryInvalidType) { TEST_P(MemtrackAidlTest, GetMemory) { int pid = 1; - MemtrackType type = MemtrackType::OTHER; - std::vector<MemtrackRecord> records; + for (MemtrackType type : ndk::enum_range<MemtrackType>()) { + std::vector<MemtrackRecord> records; - auto status = memtrack_->getMemory(pid, type, &records); + auto status = memtrack_->getMemory(pid, type, &records); - EXPECT_TRUE(status.isOk()); + EXPECT_TRUE(status.isOk()); + } } TEST_P(MemtrackAidlTest, GetGpuDeviceInfo) { @@ -87,7 +90,7 @@ TEST_P(MemtrackAidlTest, GetGpuDeviceInfo) { ->getRuntimeInfo(RuntimeInfo::FetchFlag::CPU_VERSION) ->kernelVersion(); EXPECT_LT(kernel_version, min_kernel_version) - << "Devices with 5.10 or later kernels must implement getGpuDeviceInfo()"; + << "Devices with 5.4 or later kernels must implement getGpuDeviceInfo()"; return; } diff --git a/neuralnetworks/1.0/types.hal b/neuralnetworks/1.0/types.hal index 620eefb48b..5bfadd33bb 100644 --- a/neuralnetworks/1.0/types.hal +++ b/neuralnetworks/1.0/types.hal @@ -308,8 +308,9 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth_out]. - * For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, - * the following condition must be satisfied: output_scale > input_scale * filter_scale + * For output tensor of + * {@link OperandType::TENSOR_QUANT8_ASYMM}, the following condition must + * be satisfied: output_scale > input_scale * filter_scale */ CONV_2D = 3, diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h index f2cbe93c7c..832930359e 100644 --- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h +++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h @@ -41,7 +41,7 @@ class Burst final : public nn::IBurst { Burst(PrivateConstructorTag tag, nn::SharedPreparedModel preparedModel); - OptionalCacheHold cacheMemory(const nn::Memory& memory) const override; + OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override; nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( const nn::Request& request, nn::MeasureTiming measure) const override; diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h index d3d933b22c..5d4bdbce82 100644 --- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h +++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h @@ -36,7 +36,7 @@ GeneralResult<Operand> unvalidatedConvert(const hal::V1_0::Operand& operand); GeneralResult<Operation> unvalidatedConvert(const hal::V1_0::Operation& operation); GeneralResult<Model::OperandValues> unvalidatedConvert( const hardware::hidl_vec<uint8_t>& operandValues); -GeneralResult<Memory> unvalidatedConvert(const hardware::hidl_memory& memory); +GeneralResult<SharedMemory> unvalidatedConvert(const hardware::hidl_memory& memory); GeneralResult<Model> unvalidatedConvert(const hal::V1_0::Model& model); GeneralResult<Request::Argument> unvalidatedConvert( const hal::V1_0::RequestArgument& requestArgument); @@ -65,7 +65,7 @@ nn::GeneralResult<Operand> unvalidatedConvert(const nn::Operand& operand); nn::GeneralResult<Operation> unvalidatedConvert(const nn::Operation& operation); nn::GeneralResult<hidl_vec<uint8_t>> unvalidatedConvert( const nn::Model::OperandValues& operandValues); -nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory); +nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory); nn::GeneralResult<Model> unvalidatedConvert(const nn::Model& model); nn::GeneralResult<RequestArgument> unvalidatedConvert(const nn::Request::Argument& requestArgument); nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Request::MemoryPool& memoryPool); diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h index 4cec545cf0..b695f48550 100644 --- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h +++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h @@ -44,6 +44,12 @@ bool valid(const Type& halObject) { return result.has_value(); } +template <typename Type> +auto convertFromNonCanonical(const Type& nonCanonicalObject) + -> decltype(convert(nn::convert(nonCanonicalObject).value())) { + return convert(NN_TRY(nn::convert(nonCanonicalObject))); +} + } // namespace android::hardware::neuralnetworks::V1_0::utils #endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_H diff --git a/neuralnetworks/1.0/utils/src/Burst.cpp b/neuralnetworks/1.0/utils/src/Burst.cpp index 384bd9b699..971ad08015 100644 --- a/neuralnetworks/1.0/utils/src/Burst.cpp +++ b/neuralnetworks/1.0/utils/src/Burst.cpp @@ -43,7 +43,7 @@ Burst::Burst(PrivateConstructorTag /*tag*/, nn::SharedPreparedModel preparedMode CHECK(kPreparedModel != nullptr); } -Burst::OptionalCacheHold Burst::cacheMemory(const nn::Memory& /*memory*/) const { +Burst::OptionalCacheHold Burst::cacheMemory(const nn::SharedMemory& /*memory*/) const { return nullptr; } diff --git a/neuralnetworks/1.0/utils/src/Conversions.cpp b/neuralnetworks/1.0/utils/src/Conversions.cpp index fde7346470..7a099cfd49 100644 --- a/neuralnetworks/1.0/utils/src/Conversions.cpp +++ b/neuralnetworks/1.0/utils/src/Conversions.cpp @@ -153,8 +153,8 @@ GeneralResult<Model::OperandValues> unvalidatedConvert(const hidl_vec<uint8_t>& return Model::OperandValues(operandValues.data(), operandValues.size()); } -GeneralResult<Memory> unvalidatedConvert(const hidl_memory& memory) { - return createSharedMemoryFromHidlMemory(memory); +GeneralResult<SharedMemory> unvalidatedConvert(const hidl_memory& memory) { + return hal::utils::createSharedMemoryFromHidlMemory(memory); } GeneralResult<Model> unvalidatedConvert(const hal::V1_0::Model& model) { @@ -346,9 +346,8 @@ nn::GeneralResult<hidl_vec<uint8_t>> unvalidatedConvert( return hidl_vec<uint8_t>(operandValues.data(), operandValues.data() + operandValues.size()); } -nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) { - return hidl_memory(memory.name, NN_TRY(hal::utils::hidlHandleFromSharedHandle(memory.handle)), - memory.size); +nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) { + return hal::utils::createHidlMemoryFromSharedMemory(memory); } nn::GeneralResult<Model> unvalidatedConvert(const nn::Model& model) { @@ -392,7 +391,7 @@ nn::GeneralResult<RequestArgument> unvalidatedConvert( } nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Request::MemoryPool& memoryPool) { - return unvalidatedConvert(std::get<nn::Memory>(memoryPool)); + return unvalidatedConvert(std::get<nn::SharedMemory>(memoryPool)); } nn::GeneralResult<Request> unvalidatedConvert(const nn::Request& request) { diff --git a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h index 052d88e922..09597a31f8 100644 --- a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h +++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h @@ -47,6 +47,12 @@ bool valid(const Type& halObject) { return result.has_value(); } +template <typename Type> +auto convertFromNonCanonical(const Type& nonCanonicalObject) + -> decltype(convert(nn::convert(nonCanonicalObject).value())) { + return convert(NN_TRY(nn::convert(nonCanonicalObject))); +} + } // namespace android::hardware::neuralnetworks::V1_1::utils #endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_H diff --git a/neuralnetworks/1.1/utils/src/Conversions.cpp b/neuralnetworks/1.1/utils/src/Conversions.cpp index b47f25a68c..07bf7bc88f 100644 --- a/neuralnetworks/1.1/utils/src/Conversions.cpp +++ b/neuralnetworks/1.1/utils/src/Conversions.cpp @@ -175,7 +175,7 @@ nn::GeneralResult<hidl_vec<uint8_t>> unvalidatedConvert( return V1_0::utils::unvalidatedConvert(operandValues); } -nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) { +nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) { return V1_0::utils::unvalidatedConvert(memory); } diff --git a/neuralnetworks/1.2/types.hal b/neuralnetworks/1.2/types.hal index 7441a54dba..7cec49e0e2 100644 --- a/neuralnetworks/1.2/types.hal +++ b/neuralnetworks/1.2/types.hal @@ -314,7 +314,8 @@ enum OperationType : int32_t { * tensors. The output shape is [D0, D1, ..., sum(Daxis(i)), ..., Dm]. * Since HAL version 1.2, for a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, * the scale and zeroPoint values can be different from - * input tensors. Before HAL version 1.2 they have to be the same as for the input tensors. + * input tensors. Before HAL version 1.2 they have to be the same as for the + * input tensors. */ CONCATENATION = @1.1::OperationType:CONCATENATION, @@ -460,8 +461,9 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth_out]. - * Before HAL version 1.2, for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, - * the following condition must be satisfied: output_scale > input_scale * filter_scale + * Before HAL version 1.2, for output tensor of + * {@link OperandType::TENSOR_QUANT8_ASYMM}, the following condition must + * be satisfied: output_scale > input_scale * filter_scale */ CONV_2D = @1.1::OperationType:CONV_2D, diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h index c289fc89ab..323311439f 100644 --- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h +++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h @@ -54,6 +54,12 @@ bool valid(const Type& halObject) { return result.has_value(); } +template <typename Type> +auto convertFromNonCanonical(const Type& nonCanonicalObject) + -> decltype(convert(nn::convert(nonCanonicalObject).value())) { + return convert(NN_TRY(nn::convert(nonCanonicalObject))); +} + } // namespace android::hardware::neuralnetworks::V1_2::utils #endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_H diff --git a/neuralnetworks/1.2/utils/src/Callbacks.cpp b/neuralnetworks/1.2/utils/src/Callbacks.cpp index fefa122101..9f54bb12dd 100644 --- a/neuralnetworks/1.2/utils/src/Callbacks.cpp +++ b/neuralnetworks/1.2/utils/src/Callbacks.cpp @@ -43,6 +43,15 @@ namespace android::hardware::neuralnetworks::V1_2::utils { namespace { +nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback( + V1_0::ErrorStatus status, const sp<V1_0::IPreparedModel>& preparedModel) { + if (const auto dynamicPreparedModel = + V1_2::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)) { + return V1_2::utils::prepareModelCallback(status, dynamicPreparedModel); + } + return V1_0::utils::prepareModelCallback(status, preparedModel); +} + nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> convertExecutionGeneralResultsHelper(const hidl_vec<OutputShape>& outputShapes, const Timing& timing) { @@ -72,7 +81,7 @@ nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executi Return<void> PreparedModelCallback::notify(V1_0::ErrorStatus status, const sp<V1_0::IPreparedModel>& preparedModel) { - mData.put(V1_0::utils::prepareModelCallback(status, preparedModel)); + mData.put(prepareModelCallback(status, preparedModel)); return Void(); } diff --git a/neuralnetworks/1.2/utils/src/Conversions.cpp b/neuralnetworks/1.2/utils/src/Conversions.cpp index 062f6f712f..7ae483ede2 100644 --- a/neuralnetworks/1.2/utils/src/Conversions.cpp +++ b/neuralnetworks/1.2/utils/src/Conversions.cpp @@ -304,7 +304,11 @@ GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert( } GeneralResult<SharedHandle> unvalidatedConvert(const hidl_handle& hidlHandle) { - return hal::utils::sharedHandleFromNativeHandle(hidlHandle.getNativeHandle()); + if (hidlHandle.getNativeHandle() == nullptr) { + return nullptr; + } + auto handle = NN_TRY(hal::utils::sharedHandleFromNativeHandle(hidlHandle.getNativeHandle())); + return std::make_shared<const Handle>(std::move(handle)); } GeneralResult<DeviceType> convert(const hal::V1_2::DeviceType& deviceType) { @@ -365,7 +369,7 @@ nn::GeneralResult<hidl_vec<uint8_t>> unvalidatedConvert( return V1_0::utils::unvalidatedConvert(operandValues); } -nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) { +nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) { return V1_0::utils::unvalidatedConvert(memory); } @@ -588,7 +592,10 @@ nn::GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert( } nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle) { - return hal::utils::hidlHandleFromSharedHandle(handle); + if (handle == nullptr) { + return {}; + } + return hal::utils::hidlHandleFromSharedHandle(*handle); } nn::GeneralResult<DeviceType> convert(const nn::DeviceType& deviceType) { diff --git a/neuralnetworks/1.3/types.hal b/neuralnetworks/1.3/types.hal index 5f5ee0360c..51837fe858 100644 --- a/neuralnetworks/1.3/types.hal +++ b/neuralnetworks/1.3/types.hal @@ -263,7 +263,8 @@ enum OperationType : int32_t { * tensors. The output shape is [D0, D1, ..., sum(Daxis(i)), ..., Dm]. * Since HAL version 1.2, for a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, * the scale and zeroPoint values can be different from - * input tensors. Before HAL version 1.2 they have to be the same as for the input tensors. + * input tensors. Before HAL version 1.2 they have to be the same as for the + * input tensors. * For a {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, * the scale and zeroPoint values can be different from input tensors. */ @@ -312,7 +313,8 @@ enum OperationType : int32_t { * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to * * * input.scale * filter.scale). * - * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3): + * * Quantized signed with filter symmetric per channel quantization + * (since HAL version 1.3): * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output. * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, @@ -425,8 +427,9 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth_out]. - * Before HAL version 1.2, for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, - * the following condition must be satisfied: output_scale > input_scale * filter_scale + * Before HAL version 1.2, for output tensor of + * {@link OperandType::TENSOR_QUANT8_ASYMM}, the following condition must + * be satisfied: output_scale > input_scale * filter_scale */ CONV_2D = @1.2::OperationType:CONV_2D, @@ -477,7 +480,8 @@ enum OperationType : int32_t { * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to * * * input.scale * filter.scale). * - * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3): + * * Quantized signed with filter symmetric per channel quantization + * (since HAL version 1.3): * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output. * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, @@ -3354,7 +3358,8 @@ enum OperationType : int32_t { * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, * * * each value scaling is separate and equal to input.scale * filter.scales[channel]). * - * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3): + * * Quantized signed with filter symmetric per channel quantization + * (since HAL version 1.3): * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output. * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, @@ -4615,7 +4620,8 @@ enum OperationType : int32_t { * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to * * * input.scale * filter.scale). * - * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3): + * * Quantized signed with filter symmetric per channel quantization + * (since HAL version 1.3): * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output. * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h index fda79c88c1..69e87f7566 100644 --- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h +++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h @@ -42,8 +42,8 @@ class Buffer final : public nn::IBuffer { nn::Request::MemoryDomainToken getToken() const override; - nn::GeneralResult<void> copyTo(const nn::Memory& dst) const override; - nn::GeneralResult<void> copyFrom(const nn::Memory& src, + nn::GeneralResult<void> copyTo(const nn::SharedMemory& dst) const override; + nn::GeneralResult<void> copyFrom(const nn::SharedMemory& src, const nn::Dimensions& dimensions) const override; private: diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h index 74a6534aff..8e1cdb82c9 100644 --- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h +++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h @@ -59,7 +59,7 @@ GeneralResult<OptionalDuration> convert( GeneralResult<ErrorStatus> convert(const hal::V1_3::ErrorStatus& errorStatus); GeneralResult<SharedHandle> convert(const hardware::hidl_handle& handle); -GeneralResult<Memory> convert(const hardware::hidl_memory& memory); +GeneralResult<SharedMemory> convert(const hardware::hidl_memory& memory); GeneralResult<std::vector<BufferRole>> convert( const hardware::hidl_vec<hal::V1_3::BufferRole>& bufferRoles); @@ -100,7 +100,7 @@ nn::GeneralResult<OptionalTimeoutDuration> convert( nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus); nn::GeneralResult<hidl_handle> convert(const nn::SharedHandle& handle); -nn::GeneralResult<hidl_memory> convert(const nn::Memory& memory); +nn::GeneralResult<hidl_memory> convert(const nn::SharedMemory& memory); nn::GeneralResult<hidl_vec<BufferRole>> convert(const std::vector<nn::BufferRole>& bufferRoles); nn::GeneralResult<V1_0::DeviceStatus> convert(const nn::DeviceStatus& deviceStatus); diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h index 29b0c806ff..3ce412cde6 100644 --- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h +++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h @@ -49,6 +49,12 @@ bool valid(const Type& halObject) { return result.has_value(); } +template <typename Type> +auto convertFromNonCanonical(const Type& nonCanonicalObject) + -> decltype(convert(nn::convert(nonCanonicalObject).value())) { + return convert(NN_TRY(nn::convert(nonCanonicalObject))); +} + } // namespace android::hardware::neuralnetworks::V1_3::utils #endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_H diff --git a/neuralnetworks/1.3/utils/src/Buffer.cpp b/neuralnetworks/1.3/utils/src/Buffer.cpp index 614033e268..ada526573b 100644 --- a/neuralnetworks/1.3/utils/src/Buffer.cpp +++ b/neuralnetworks/1.3/utils/src/Buffer.cpp @@ -61,7 +61,7 @@ nn::Request::MemoryDomainToken Buffer::getToken() const { return kToken; } -nn::GeneralResult<void> Buffer::copyTo(const nn::Memory& dst) const { +nn::GeneralResult<void> Buffer::copyTo(const nn::SharedMemory& dst) const { const auto hidlDst = NN_TRY(convert(dst)); const auto ret = kBuffer->copyTo(hidlDst); @@ -71,7 +71,7 @@ nn::GeneralResult<void> Buffer::copyTo(const nn::Memory& dst) const { return {}; } -nn::GeneralResult<void> Buffer::copyFrom(const nn::Memory& src, +nn::GeneralResult<void> Buffer::copyFrom(const nn::SharedMemory& src, const nn::Dimensions& dimensions) const { const auto hidlSrc = NN_TRY(convert(src)); const auto hidlDimensions = hidl_vec<uint32_t>(dimensions); diff --git a/neuralnetworks/1.3/utils/src/Callbacks.cpp b/neuralnetworks/1.3/utils/src/Callbacks.cpp index af76e6a87e..8e9fb833b1 100644 --- a/neuralnetworks/1.3/utils/src/Callbacks.cpp +++ b/neuralnetworks/1.3/utils/src/Callbacks.cpp @@ -28,6 +28,7 @@ #include <nnapi/IPreparedModel.h> #include <nnapi/Result.h> #include <nnapi/Types.h> +#include <nnapi/hal/1.0/Callbacks.h> #include <nnapi/hal/1.0/Conversions.h> #include <nnapi/hal/1.0/PreparedModel.h> #include <nnapi/hal/1.2/Callbacks.h> @@ -46,6 +47,20 @@ namespace android::hardware::neuralnetworks::V1_3::utils { namespace { +nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback( + V1_0::ErrorStatus status, const sp<V1_0::IPreparedModel>& preparedModel) { + if (const auto dynamicPreparedModel = + V1_3::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)) { + const auto currentVersionStatus = NN_TRY(convertFromNonCanonical(status)); + return V1_3::utils::prepareModelCallback(currentVersionStatus, dynamicPreparedModel); + } + if (const auto dynamicPreparedModel = + V1_2::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)) { + return V1_2::utils::prepareModelCallback(status, dynamicPreparedModel); + } + return V1_0::utils::prepareModelCallback(status, preparedModel); +} + nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> convertExecutionGeneralResultsHelper(const hidl_vec<V1_2::OutputShape>& outputShapes, const V1_2::Timing& timing) { @@ -82,13 +97,13 @@ nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executi Return<void> PreparedModelCallback::notify(V1_0::ErrorStatus status, const sp<V1_0::IPreparedModel>& preparedModel) { - mData.put(V1_0::utils::prepareModelCallback(status, preparedModel)); + mData.put(prepareModelCallback(status, preparedModel)); return Void(); } Return<void> PreparedModelCallback::notify_1_2(V1_0::ErrorStatus status, const sp<V1_2::IPreparedModel>& preparedModel) { - mData.put(V1_2::utils::prepareModelCallback(status, preparedModel)); + mData.put(prepareModelCallback(status, preparedModel)); return Void(); } diff --git a/neuralnetworks/1.3/utils/src/Conversions.cpp b/neuralnetworks/1.3/utils/src/Conversions.cpp index 8b7db2b90e..6e74a6239d 100644 --- a/neuralnetworks/1.3/utils/src/Conversions.cpp +++ b/neuralnetworks/1.3/utils/src/Conversions.cpp @@ -261,7 +261,7 @@ GeneralResult<Request::MemoryPool> unvalidatedConvert( using Discriminator = hal::V1_3::Request::MemoryPool::hidl_discriminator; switch (memoryPool.getDiscriminator()) { case Discriminator::hidlMemory: - return createSharedMemoryFromHidlMemory(memoryPool.hidlMemory()); + return hal::utils::createSharedMemoryFromHidlMemory(memoryPool.hidlMemory()); case Discriminator::token: return static_cast<Request::MemoryDomainToken>(memoryPool.token()); } @@ -352,7 +352,7 @@ GeneralResult<SharedHandle> convert(const hardware::hidl_handle& handle) { return validatedConvert(handle); } -GeneralResult<Memory> convert(const hardware::hidl_memory& memory) { +GeneralResult<SharedMemory> convert(const hardware::hidl_memory& memory) { return validatedConvert(memory); } @@ -386,7 +386,7 @@ nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle return V1_2::utils::unvalidatedConvert(handle); } -nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) { +nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) { return V1_0::utils::unvalidatedConvert(memory); } @@ -424,7 +424,7 @@ nn::GeneralResult<hidl_vec<unvalidatedConvertOutput<Type>>> unvalidatedConvert( return unvalidatedConvertVec(arguments); } -nn::GeneralResult<Request::MemoryPool> makeMemoryPool(const nn::Memory& memory) { +nn::GeneralResult<Request::MemoryPool> makeMemoryPool(const nn::SharedMemory& memory) { Request::MemoryPool ret; ret.hidlMemory(NN_TRY(unvalidatedConvert(memory))); return ret; @@ -677,7 +677,7 @@ nn::GeneralResult<hidl_handle> convert(const nn::SharedHandle& handle) { return validatedConvert(handle); } -nn::GeneralResult<hidl_memory> convert(const nn::Memory& memory) { +nn::GeneralResult<hidl_memory> convert(const nn::SharedMemory& memory) { return validatedConvert(memory); } diff --git a/neuralnetworks/1.3/vts/functional/Android.bp b/neuralnetworks/1.3/vts/functional/Android.bp index b17d44559b..ee753bb951 100644 --- a/neuralnetworks/1.3/vts/functional/Android.bp +++ b/neuralnetworks/1.3/vts/functional/Android.bp @@ -57,6 +57,7 @@ cc_test { "VtsHalNeuralNetworksV1_0_utils", "VtsHalNeuralNetworksV1_2_utils", "VtsHalNeuralNetworksV1_3_utils", + "android.hardware.neuralnetworks-V1-ndk_platform", "android.hardware.neuralnetworks@1.0", "android.hardware.neuralnetworks@1.1", "android.hardware.neuralnetworks@1.2", diff --git a/neuralnetworks/aidl/Android.bp b/neuralnetworks/aidl/Android.bp new file mode 100644 index 0000000000..0557e43a5a --- /dev/null +++ b/neuralnetworks/aidl/Android.bp @@ -0,0 +1,27 @@ +aidl_interface { + name: "android.hardware.neuralnetworks", + vendor_available: true, + srcs: [ + "android/hardware/neuralnetworks/*.aidl", + ], + stability: "vintf", + imports: [ + "android.hardware.common", + ], + backend: { + java: { + enabled: false, + }, + cpp: { + enabled: false, + }, + ndk: { + apex_available: [ + "//apex_available:platform", + "com.android.neuralnetworks", + "test_com.android.neuralnetworks", + ], + min_sdk_version: "30", + }, + }, +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferDesc.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferDesc.aidl new file mode 100644 index 0000000000..71b7758f20 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferDesc.aidl @@ -0,0 +1,37 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable BufferDesc { + int[] dimensions; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferRole.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferRole.aidl new file mode 100644 index 0000000000..c2d636c53d --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferRole.aidl @@ -0,0 +1,39 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable BufferRole { + int modelIndex; + int ioIndex; + float frequency; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Capabilities.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Capabilities.aidl new file mode 100644 index 0000000000..01cc753b58 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Capabilities.aidl @@ -0,0 +1,41 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable Capabilities { + android.hardware.neuralnetworks.PerformanceInfo relaxedFloat32toFloat16PerformanceScalar; + android.hardware.neuralnetworks.PerformanceInfo relaxedFloat32toFloat16PerformanceTensor; + android.hardware.neuralnetworks.OperandPerformance[] operandPerformance; + android.hardware.neuralnetworks.PerformanceInfo ifPerformance; + android.hardware.neuralnetworks.PerformanceInfo whilePerformance; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl new file mode 100644 index 0000000000..074cc0922d --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl @@ -0,0 +1,39 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable DataLocation { + int poolIndex; + long offset; + long length; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceBuffer.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceBuffer.aidl new file mode 100644 index 0000000000..7bc8aa739a --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceBuffer.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable DeviceBuffer { + android.hardware.neuralnetworks.IBuffer buffer; + int token; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceType.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceType.aidl new file mode 100644 index 0000000000..1abacc8a6f --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceType.aidl @@ -0,0 +1,40 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@Backing(type="int") @VintfStability +enum DeviceType { + OTHER = 1, + CPU = 2, + GPU = 3, + ACCELERATOR = 4, +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ErrorStatus.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ErrorStatus.aidl new file mode 100644 index 0000000000..873c584f7c --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ErrorStatus.aidl @@ -0,0 +1,45 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@Backing(type="int") @VintfStability +enum ErrorStatus { + NONE = 0, + DEVICE_UNAVAILABLE = 1, + GENERAL_FAILURE = 2, + OUTPUT_INSUFFICIENT_SIZE = 3, + INVALID_ARGUMENT = 4, + MISSED_DEADLINE_TRANSIENT = 5, + MISSED_DEADLINE_PERSISTENT = 6, + RESOURCE_EXHAUSTED_TRANSIENT = 7, + RESOURCE_EXHAUSTED_PERSISTENT = 8, +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionPreference.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionPreference.aidl new file mode 100644 index 0000000000..c4badc0ad9 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionPreference.aidl @@ -0,0 +1,39 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@Backing(type="int") @VintfStability +enum ExecutionPreference { + LOW_POWER = 0, + FAST_SINGLE_ANSWER = 1, + SUSTAINED_SPEED = 2, +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionResult.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionResult.aidl new file mode 100644 index 0000000000..b99bb3131e --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionResult.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 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.neuralnetworks; +@VintfStability +parcelable ExecutionResult { + boolean outputSufficientSize; + android.hardware.neuralnetworks.OutputShape[] outputShapes; + android.hardware.neuralnetworks.Timing timing; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Extension.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Extension.aidl new file mode 100644 index 0000000000..a7ae9421e9 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Extension.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable Extension { + String name; + android.hardware.neuralnetworks.ExtensionOperandTypeInformation[] operandTypes; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl new file mode 100644 index 0000000000..4c255387d1 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable ExtensionNameAndPrefix { + String name; + char prefix; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl new file mode 100644 index 0000000000..b32b217960 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl @@ -0,0 +1,39 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable ExtensionOperandTypeInformation { + char type; + boolean isTensor; + int byteSize; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/FusedActivationFunc.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/FusedActivationFunc.aidl new file mode 100644 index 0000000000..2fee136e19 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/FusedActivationFunc.aidl @@ -0,0 +1,40 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@Backing(type="int") @VintfStability +enum FusedActivationFunc { + NONE = 0, + RELU = 1, + RELU1 = 2, + RELU6 = 3, +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IBuffer.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IBuffer.aidl new file mode 100644 index 0000000000..2860692378 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IBuffer.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +interface IBuffer { + void copyFrom(in android.hardware.neuralnetworks.Memory src, in int[] dimensions); + void copyTo(in android.hardware.neuralnetworks.Memory dst); +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IDevice.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IDevice.aidl new file mode 100644 index 0000000000..4c5fd2fa47 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IDevice.aidl @@ -0,0 +1,51 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +interface IDevice { + android.hardware.neuralnetworks.DeviceBuffer allocate(in android.hardware.neuralnetworks.BufferDesc desc, in android.hardware.neuralnetworks.IPreparedModelParcel[] preparedModels, in android.hardware.neuralnetworks.BufferRole[] inputRoles, in android.hardware.neuralnetworks.BufferRole[] outputRoles); + android.hardware.neuralnetworks.Capabilities getCapabilities(); + android.hardware.neuralnetworks.NumberOfCacheFiles getNumberOfCacheFilesNeeded(); + android.hardware.neuralnetworks.Extension[] getSupportedExtensions(); + boolean[] getSupportedOperations(in android.hardware.neuralnetworks.Model model); + android.hardware.neuralnetworks.DeviceType getType(); + String getVersionString(); + void prepareModel(in android.hardware.neuralnetworks.Model model, in android.hardware.neuralnetworks.ExecutionPreference preference, in android.hardware.neuralnetworks.Priority priority, in long deadline, in ParcelFileDescriptor[] modelCache, in ParcelFileDescriptor[] dataCache, in byte[] token, in android.hardware.neuralnetworks.IPreparedModelCallback callback); + void prepareModelFromCache(in long deadline, in ParcelFileDescriptor[] modelCache, in ParcelFileDescriptor[] dataCache, in byte[] token, in android.hardware.neuralnetworks.IPreparedModelCallback callback); + const int BYTE_SIZE_OF_CACHE_TOKEN = 32; + const int MAX_NUMBER_OF_CACHE_FILES = 32; + const int EXTENSION_TYPE_HIGH_BITS_PREFIX = 15; + const int EXTENSION_TYPE_LOW_BITS_TYPE = 16; + const int OPERAND_TYPE_BASE_MAX = 65535; + const int OPERATION_TYPE_BASE_MAX = 65535; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl new file mode 100644 index 0000000000..abe67b8801 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl @@ -0,0 +1,37 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +interface IFencedExecutionCallback { + android.hardware.neuralnetworks.ErrorStatus getExecutionInfo(out android.hardware.neuralnetworks.Timing timingLaunched, out android.hardware.neuralnetworks.Timing timingFenced); +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModel.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModel.aidl new file mode 100644 index 0000000000..3ca155009a --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModel.aidl @@ -0,0 +1,40 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +interface IPreparedModel { + android.hardware.neuralnetworks.ExecutionResult executeSynchronously(in android.hardware.neuralnetworks.Request request, in boolean measureTiming, in long deadline, in long loopTimeoutDuration); + android.hardware.neuralnetworks.IFencedExecutionCallback executeFenced(in android.hardware.neuralnetworks.Request request, in ParcelFileDescriptor[] waitFor, in boolean measureTiming, in long deadline, in long loopTimeoutDuration, in long duration, out @nullable ParcelFileDescriptor syncFence); + const long DEFAULT_LOOP_TIMEOUT_DURATION_NS = 2000000000; + const long MAXIMUM_LOOP_TIMEOUT_DURATION_NS = 15000000000; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelCallback.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelCallback.aidl new file mode 100644 index 0000000000..8eaaab68e4 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelCallback.aidl @@ -0,0 +1,37 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +interface IPreparedModelCallback { + void notify(in android.hardware.neuralnetworks.ErrorStatus status, in android.hardware.neuralnetworks.IPreparedModel preparedModel); +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelParcel.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelParcel.aidl new file mode 100644 index 0000000000..8388fdabb4 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelParcel.aidl @@ -0,0 +1,37 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable IPreparedModelParcel { + android.hardware.neuralnetworks.IPreparedModel preparedModel; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Memory.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Memory.aidl new file mode 100644 index 0000000000..3b2f240b48 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Memory.aidl @@ -0,0 +1,39 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable Memory { + android.hardware.common.NativeHandle handle; + long size; + String name; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Model.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Model.aidl new file mode 100644 index 0000000000..9d12e58f98 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Model.aidl @@ -0,0 +1,42 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable Model { + android.hardware.neuralnetworks.Subgraph main; + android.hardware.neuralnetworks.Subgraph[] referenced; + byte[] operandValues; + android.hardware.neuralnetworks.Memory[] pools; + boolean relaxComputationFloat32toFloat16; + android.hardware.neuralnetworks.ExtensionNameAndPrefix[] extensionNameToPrefix; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl new file mode 100644 index 0000000000..c1e87da229 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable NumberOfCacheFiles { + int numModelCache; + int numDataCache; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operand.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operand.aidl new file mode 100644 index 0000000000..bb78caaa17 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operand.aidl @@ -0,0 +1,43 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable Operand { + android.hardware.neuralnetworks.OperandType type; + int[] dimensions; + float scale; + int zeroPoint; + android.hardware.neuralnetworks.OperandLifeTime lifetime; + android.hardware.neuralnetworks.DataLocation location; + @nullable android.hardware.neuralnetworks.OperandExtraParams extraParams; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandExtraParams.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandExtraParams.aidl new file mode 100644 index 0000000000..3f6d93b3bf --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandExtraParams.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +union OperandExtraParams { + android.hardware.neuralnetworks.SymmPerChannelQuantParams channelQuant; + byte[] extension; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandLifeTime.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandLifeTime.aidl new file mode 100644 index 0000000000..d581cedf54 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandLifeTime.aidl @@ -0,0 +1,43 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@Backing(type="int") @VintfStability +enum OperandLifeTime { + TEMPORARY_VARIABLE = 0, + SUBGRAPH_INPUT = 1, + SUBGRAPH_OUTPUT = 2, + CONSTANT_COPY = 3, + CONSTANT_POOL = 4, + NO_VALUE = 5, + SUBGRAPH = 6, +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandPerformance.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandPerformance.aidl new file mode 100644 index 0000000000..87fd3a64bd --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandPerformance.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable OperandPerformance { + android.hardware.neuralnetworks.OperandType type; + android.hardware.neuralnetworks.PerformanceInfo info; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandType.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandType.aidl new file mode 100644 index 0000000000..186c13dfe9 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandType.aidl @@ -0,0 +1,52 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@Backing(type="int") @VintfStability +enum OperandType { + FLOAT32 = 0, + INT32 = 1, + UINT32 = 2, + TENSOR_FLOAT32 = 3, + TENSOR_INT32 = 4, + TENSOR_QUANT8_ASYMM = 5, + BOOL = 6, + TENSOR_QUANT16_SYMM = 7, + TENSOR_FLOAT16 = 8, + TENSOR_BOOL8 = 9, + FLOAT16 = 10, + TENSOR_QUANT8_SYMM_PER_CHANNEL = 11, + TENSOR_QUANT16_ASYMM = 12, + TENSOR_QUANT8_SYMM = 13, + TENSOR_QUANT8_ASYMM_SIGNED = 14, + SUBGRAPH = 15, +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operation.aidl new file mode 100644 index 0000000000..fec83a89a8 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operation.aidl @@ -0,0 +1,39 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable Operation { + android.hardware.neuralnetworks.OperationType type; + int[] inputs; + int[] outputs; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperationType.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperationType.aidl new file mode 100644 index 0000000000..ad42b02055 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperationType.aidl @@ -0,0 +1,138 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@Backing(type="int") @VintfStability +enum OperationType { + ADD = 0, + AVERAGE_POOL_2D = 1, + CONCATENATION = 2, + CONV_2D = 3, + DEPTHWISE_CONV_2D = 4, + DEPTH_TO_SPACE = 5, + DEQUANTIZE = 6, + EMBEDDING_LOOKUP = 7, + FLOOR = 8, + FULLY_CONNECTED = 9, + HASHTABLE_LOOKUP = 10, + L2_NORMALIZATION = 11, + L2_POOL_2D = 12, + LOCAL_RESPONSE_NORMALIZATION = 13, + LOGISTIC = 14, + LSH_PROJECTION = 15, + LSTM = 16, + MAX_POOL_2D = 17, + MUL = 18, + RELU = 19, + RELU1 = 20, + RELU6 = 21, + RESHAPE = 22, + RESIZE_BILINEAR = 23, + RNN = 24, + SOFTMAX = 25, + SPACE_TO_DEPTH = 26, + SVDF = 27, + TANH = 28, + BATCH_TO_SPACE_ND = 29, + DIV = 30, + MEAN = 31, + PAD = 32, + SPACE_TO_BATCH_ND = 33, + SQUEEZE = 34, + STRIDED_SLICE = 35, + SUB = 36, + TRANSPOSE = 37, + ABS = 38, + ARGMAX = 39, + ARGMIN = 40, + AXIS_ALIGNED_BBOX_TRANSFORM = 41, + BIDIRECTIONAL_SEQUENCE_LSTM = 42, + BIDIRECTIONAL_SEQUENCE_RNN = 43, + BOX_WITH_NMS_LIMIT = 44, + CAST = 45, + CHANNEL_SHUFFLE = 46, + DETECTION_POSTPROCESSING = 47, + EQUAL = 48, + EXP = 49, + EXPAND_DIMS = 50, + GATHER = 51, + GENERATE_PROPOSALS = 52, + GREATER = 53, + GREATER_EQUAL = 54, + GROUPED_CONV_2D = 55, + HEATMAP_MAX_KEYPOINT = 56, + INSTANCE_NORMALIZATION = 57, + LESS = 58, + LESS_EQUAL = 59, + LOG = 60, + LOGICAL_AND = 61, + LOGICAL_NOT = 62, + LOGICAL_OR = 63, + LOG_SOFTMAX = 64, + MAXIMUM = 65, + MINIMUM = 66, + NEG = 67, + NOT_EQUAL = 68, + PAD_V2 = 69, + POW = 70, + PRELU = 71, + QUANTIZE = 72, + QUANTIZED_16BIT_LSTM = 73, + RANDOM_MULTINOMIAL = 74, + REDUCE_ALL = 75, + REDUCE_ANY = 76, + REDUCE_MAX = 77, + REDUCE_MIN = 78, + REDUCE_PROD = 79, + REDUCE_SUM = 80, + ROI_ALIGN = 81, + ROI_POOLING = 82, + RSQRT = 83, + SELECT = 84, + SIN = 85, + SLICE = 86, + SPLIT = 87, + SQRT = 88, + TILE = 89, + TOPK_V2 = 90, + TRANSPOSE_CONV_2D = 91, + UNIDIRECTIONAL_SEQUENCE_LSTM = 92, + UNIDIRECTIONAL_SEQUENCE_RNN = 93, + RESIZE_NEAREST_NEIGHBOR = 94, + QUANTIZED_LSTM = 95, + IF = 96, + WHILE = 97, + ELU = 98, + HARD_SWISH = 99, + FILL = 100, + RANK = 101, +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OutputShape.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OutputShape.aidl new file mode 100644 index 0000000000..09a43f7b6b --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OutputShape.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable OutputShape { + int[] dimensions; + boolean isSufficient; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/PerformanceInfo.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/PerformanceInfo.aidl new file mode 100644 index 0000000000..178946c217 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/PerformanceInfo.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable PerformanceInfo { + float execTime; + float powerUsage; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Priority.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Priority.aidl new file mode 100644 index 0000000000..d9b77fa917 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Priority.aidl @@ -0,0 +1,39 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@Backing(type="int") @VintfStability +enum Priority { + LOW = 0, + MEDIUM = 1, + HIGH = 2, +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Request.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Request.aidl new file mode 100644 index 0000000000..599b3f4bfa --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Request.aidl @@ -0,0 +1,39 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable Request { + android.hardware.neuralnetworks.RequestArgument[] inputs; + android.hardware.neuralnetworks.RequestArgument[] outputs; + android.hardware.neuralnetworks.RequestMemoryPool[] pools; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestArgument.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestArgument.aidl new file mode 100644 index 0000000000..91b9aa7751 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestArgument.aidl @@ -0,0 +1,39 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable RequestArgument { + boolean hasNoValue; + android.hardware.neuralnetworks.DataLocation location; + int[] dimensions; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestMemoryPool.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestMemoryPool.aidl new file mode 100644 index 0000000000..3813b51578 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestMemoryPool.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +union RequestMemoryPool { + android.hardware.neuralnetworks.Memory pool; + int token; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Subgraph.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Subgraph.aidl new file mode 100644 index 0000000000..dec976f8e8 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Subgraph.aidl @@ -0,0 +1,40 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable Subgraph { + android.hardware.neuralnetworks.Operand[] operands; + android.hardware.neuralnetworks.Operation[] operations; + int[] inputIndexes; + int[] outputIndexes; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl new file mode 100644 index 0000000000..66fdfe7d82 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable SymmPerChannelQuantParams { + float[] scales; + int channelDim; +} diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Timing.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Timing.aidl new file mode 100644 index 0000000000..d0de34a123 --- /dev/null +++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Timing.aidl @@ -0,0 +1,38 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.neuralnetworks; +@VintfStability +parcelable Timing { + long timeOnDevice; + long timeInDriver; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferDesc.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferDesc.aidl new file mode 100644 index 0000000000..bec7e860b9 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferDesc.aidl @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * A buffer descriptor. Describes the properties of a buffer. + */ +@VintfStability +parcelable BufferDesc { + /** + * Dimensions of the buffer. May have unknown dimensions or rank. A buffer with some number of + * unspecified dimensions is represented by setting each unspecified dimension to 0. A buffer + * with unspecified rank is represented by providing an empty dimensions vector. + */ + int[] dimensions; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferRole.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferRole.aidl new file mode 100644 index 0000000000..0d7f678006 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferRole.aidl @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Describes a role of an input or output to a prepared model. + */ +@VintfStability +parcelable BufferRole { + /** + * The index of the IPreparedModel within the "preparedModel" argument passed in + * IDevice::allocate. + */ + int modelIndex; + /** + * The index of the input or output operand. + */ + int ioIndex; + /** + * A floating-point value within the range (0.0, 1.0]. Describes how likely the buffer is to be + * used in the specified role. This is provided as a hint to optimize the case when multiple + * roles prefer different buffer locations or data layouts. + */ + float frequency; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Capabilities.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Capabilities.aidl new file mode 100644 index 0000000000..3802f1fe15 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Capabilities.aidl @@ -0,0 +1,62 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.OperandPerformance; +import android.hardware.neuralnetworks.PerformanceInfo; + +/** + * The capabilities of a driver. + * + * This represents performance of non-extension operations. + * + * Performance of an operation other than {@link OperationType::IF} and {@link OperationType::WHILE} + * comes from the type of its first operand. + */ +@VintfStability +parcelable Capabilities { + /** + * Driver performance when operating on float32 data but performing calculations with range + * and/or precision as low as that of the IEEE 754 16-bit floating-point format. + */ + PerformanceInfo relaxedFloat32toFloat16PerformanceScalar; + PerformanceInfo relaxedFloat32toFloat16PerformanceTensor; + /** + * Performance by operand type. Must be sorted by OperandType. + * + * If a particular {@link OperandType} is not present in operandPerformance, its performance is + * treated as { .execTime = FLT_MAX, .powerUsage = FLT_MAX }. + * + * Performance does not apply to {@link OperandType::SUBGRAPH}, and a driver must not report + * operand performance for {@link OperandType::SUBGRAPH}. + */ + OperandPerformance[] operandPerformance; + /** + * Performance of an {@link OperationType::IF} operation is the sum of + * {@link Capabilities::ifPerformance} and the mean of performance for the two branch subgraphs, + * where performance for a subgraph is the sum of the performance of all operations within the + * subgraph. + */ + PerformanceInfo ifPerformance; + /** + * Performance of a {@link OperationType::WHILE} operation is the sum of + * {@link Capabilities::whilePerformance}, performance for the condition subgraph and + * performance for the body subgraph, where performance for a subgraph is the sum of the + * performance of all operations within the subgraph. + */ + PerformanceInfo whilePerformance; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl new file mode 100644 index 0000000000..f6b5e0d2ac --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl @@ -0,0 +1,36 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Describes the location of a data object. + */ +@VintfStability +parcelable DataLocation { + /** + * The index of the memory pool where this location is found. + */ + int poolIndex; + /** + * Offset in bytes from the start of the pool. + */ + long offset; + /** + * The length of the data in bytes. + */ + long length; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceBuffer.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceBuffer.aidl new file mode 100644 index 0000000000..07930a6b92 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceBuffer.aidl @@ -0,0 +1,36 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.IBuffer; + +/** + * A type that is used to represent a driver allocated buffer and token that corresponds to it. + */ +@VintfStability +parcelable DeviceBuffer { + /** + * An IBuffer object used to interact with the device allocated buffer. + */ + IBuffer buffer; + /** + * A positive token identifying the allocated buffer. The token is provided when referencing the + * buffer as one of the memory pools in the request of an execution. The token must not collide + * with the tokens of other IBuffer objects that are currently alive in the same driver service. + */ + int token; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceType.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceType.aidl new file mode 100644 index 0000000000..815be64d8f --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceType.aidl @@ -0,0 +1,44 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Device types. + * + * The type of NNAPI device. + */ +@VintfStability +@Backing(type="int") +enum DeviceType { + /** + * The device does not fall into any category below. + */ + OTHER = 1, + /** + * The device runs NNAPI models on single or multi-core CPU. + */ + CPU = 2, + /** + * The device can run NNAPI models and also accelerate graphics APIs such as OpenGL ES and + * Vulkan. + */ + GPU = 3, + /** + * Dedicated accelerator for Machine Learning workloads. + */ + ACCELERATOR = 4, +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ErrorStatus.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ErrorStatus.aidl new file mode 100644 index 0000000000..c2752d98cc --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ErrorStatus.aidl @@ -0,0 +1,51 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Calls to neural networks AIDL interfaces may return a ServiceSpecificException with the following + * error codes. + */ +@VintfStability +@Backing(type="int") +enum ErrorStatus { + NONE, + DEVICE_UNAVAILABLE, + GENERAL_FAILURE, + OUTPUT_INSUFFICIENT_SIZE, + INVALID_ARGUMENT, + /** + * Failure because a deadline could not be met for a task, but future deadlines may still be met + * for the same task after a short delay. + */ + MISSED_DEADLINE_TRANSIENT, + /** + * Failure because a deadline could not be met for a task, and future deadlines will likely also + * not be met for the same task even after a short delay. + */ + MISSED_DEADLINE_PERSISTENT, + /** + * Failure because of a resource limitation within the driver, but future calls for the same + * task may still succeed after a short delay. + */ + RESOURCE_EXHAUSTED_TRANSIENT, + /** + * Failure because of a resource limitation within the driver, and future calls for the same + * task will likely also fail even after a short delay. + */ + RESOURCE_EXHAUSTED_PERSISTENT, +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionPreference.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionPreference.aidl new file mode 100644 index 0000000000..a3e3ce3ee9 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionPreference.aidl @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Execution preferences. + */ +@VintfStability +@Backing(type="int") +enum ExecutionPreference { + /** + * Prefer executing in a way that minimizes battery drain. This is desirable for compilations + * that will be executed often. + */ + LOW_POWER, + /** + * Prefer returning a single answer as fast as possible, even if this causes more power + * consumption. + */ + FAST_SINGLE_ANSWER, + /** + * Prefer maximizing the throughput of successive frames, for example when processing successive + * frames coming from the camera. + */ + SUSTAINED_SPEED, +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionResult.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionResult.aidl new file mode 100644 index 0000000000..1f88207ffd --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionResult.aidl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 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.neuralnetworks; + +import android.hardware.neuralnetworks.ErrorStatus; +import android.hardware.neuralnetworks.OutputShape; +import android.hardware.neuralnetworks.Timing; + +/** + * A result from running a synchronous execution of a prepared model. + */ +@VintfStability +parcelable ExecutionResult { + /** + * A value of "true" indicates that the execution was successful. A value of "false" indicates + * the execution failed because at least one output operand buffer was not large enough to store + * the corresponding output. + */ + boolean outputSufficientSize; + /** + * A list of shape information of model output operands. The index in "outputShapes" corresponds + * to the index of the output operand in the Request outputs vector. + */ + OutputShape[] outputShapes; + /** + * Duration of execution. Unless measure is true and the execution is successful, all times must + * be reported as -1. A driver may choose to report any time as -1, indicating that measurement + * is not available. + */ + Timing timing; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Extension.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Extension.aidl new file mode 100644 index 0000000000..20109bd584 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Extension.aidl @@ -0,0 +1,41 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.ExtensionOperandTypeInformation; + +/** + * Information about an extension. + */ +@VintfStability +parcelable Extension { + /** + * The extension name. + * + * The name must consist of lowercase latin letters, numbers, periods, and underscore signs. The + * name must contain at least one period. + * + * The name must start with the reverse domain name of the vendor. + * + * Example: com.google.test_extension + */ + String name; + /** + * Information about operand types defined by the extension. + */ + ExtensionOperandTypeInformation[] operandTypes; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl new file mode 100644 index 0000000000..29be93f549 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl @@ -0,0 +1,48 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * The mapping between extension names and prefixes of operand and operation type values. + * + * An operand or operation whose numeric type value is above {@link IDevice::OPERAND_TYPE_BASE_MAX} + * or {@link IDevice::OPERATION_TYPE_BASE_MAX} respectively should be interpreted as an extension + * operand/operation. The low {@link IDevice::EXTENSION_TYPE_LOW_BITS_TYPE} bits of the value + * correspond to the type ID within the extension and the high + * {@link IDevice::EXTENSION_TYPE_HIGH_BITS_PREFIX} bits encode the "prefix", which maps uniquely to + * the extension name. The sign bit is always 0. + * + * For example, if a model contains an operation whose value is 0x7AAABBBB and extensionNameToPrefix + * contains an entry with prefix=0x7AAA and name="vendor.test.test_extension", then the operation + * should be interpreted as the operation 0xBBBB of the extension named vendor.test.test_extension. + * + * This is a one-to-one correspondence. That is, there must be at most one prefix corresponding to + * each extension name and at most one extension name corresponding to each prefix. + */ +@VintfStability +parcelable ExtensionNameAndPrefix { + /** + * The extension name. + * + * See {@link Extension::name} for the format specification. + */ + String name; + /** + * The extension prefix. Only the lowest 15 bits are used, so the value must be less than 32768. + */ + char prefix; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl new file mode 100644 index 0000000000..b8e344936d --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl @@ -0,0 +1,36 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Information about an extension operand type. + */ +@VintfStability +parcelable ExtensionOperandTypeInformation { + /** + * The extension operand type. + */ + char type; + /** + * Indicates whether the extension operand type represents a tensor or a scalar. + */ + boolean isTensor; + /** + * The byte size of the operand (if scalar) or of a single element (if tensor). + */ + int byteSize; +} diff --git a/biometrics/face/1.1/vts/functional/Android.bp b/neuralnetworks/aidl/android/hardware/neuralnetworks/FusedActivationFunc.aidl index aa0b1fa082..861b6f056a 100644 --- a/biometrics/face/1.1/vts/functional/Android.bp +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/FusedActivationFunc.aidl @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Android Open Source Project + * 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. @@ -14,16 +14,16 @@ * limitations under the License. */ -cc_test { - name: "VtsHalBiometricsFaceV1_1TargetTest", - defaults: ["VtsHalTargetTestDefaults"], - srcs: ["VtsHalBiometricsFaceV1_1TargetTest.cpp"], - static_libs: [ - "android.hardware.biometrics.face@1.0", - "android.hardware.biometrics.face@1.1", - ], - test_suites: [ - "general-tests", - "vts", - ], +package android.hardware.neuralnetworks; + +/** + * Fused activation function types. + */ +@VintfStability +@Backing(type="int") +enum FusedActivationFunc { + NONE, + RELU, + RELU1, + RELU6, } diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IBuffer.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IBuffer.aidl new file mode 100644 index 0000000000..2915a22185 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IBuffer.aidl @@ -0,0 +1,57 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.Memory; + +/** + * This interface represents a device memory buffer. + */ +@VintfStability +interface IBuffer { + /** + * Sets the content of this buffer from a shared memory region. + * + * @param src The source shared memory region. + * @param dimensions Updated dimensional information. If the dimensions of the IBuffer object + * are not fully specified, then the dimensions must be fully specified here. + * If the dimensions of the IBuffer object are fully specified, then the + * dimensions may be empty here. If dimensions.size() > 0, then all dimensions + * must be specified here, and any dimension that was specified in the IBuffer + * object must have the same value here. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if provided memory is invalid, or if the dimensions is invalid + */ + void copyFrom(in Memory src, in int[] dimensions); + + /** + * Retrieves the content of this buffer to a shared memory region. + * + * The IBuffer object must have been initialized before the call to IBuffer::copyTo. For more + * information on the state of the IBuffer object, refer to IDevice::allocate. + * + * @param dst The destination shared memory region. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if the IBuffer object is uninitialized, or there is an unspecified + * error + * - INVALID_ARGUMENT if provided memory is invalid + */ + void copyTo(in Memory dst); +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl new file mode 100644 index 0000000000..e17e0cd765 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl @@ -0,0 +1,432 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.BufferDesc; +import android.hardware.neuralnetworks.BufferRole; +import android.hardware.neuralnetworks.Capabilities; +import android.hardware.neuralnetworks.DeviceBuffer; +import android.hardware.neuralnetworks.DeviceType; +import android.hardware.neuralnetworks.ExecutionPreference; +import android.hardware.neuralnetworks.Extension; +import android.hardware.neuralnetworks.IPreparedModel; +import android.hardware.neuralnetworks.IPreparedModelCallback; +import android.hardware.neuralnetworks.IPreparedModelParcel; +import android.hardware.neuralnetworks.Model; +import android.hardware.neuralnetworks.NumberOfCacheFiles; +import android.hardware.neuralnetworks.Priority; + +/** + * This interface represents a device driver. + */ +@VintfStability +interface IDevice { + /** + * The byte size of the cache token. + */ + const int BYTE_SIZE_OF_CACHE_TOKEN = 32; + /** + * The maximum number of files for each type of cache in compilation caching. + */ + const int MAX_NUMBER_OF_CACHE_FILES = 32; + + /** + * Numeric values of extension operand and operation types have the following structure: + * - The sign bit is always 0. + * - 15 high bits represent the "prefix", which corresponds uniquely to the extension name. + * - 16 low bits represent the type ID within the extension. + */ + const int EXTENSION_TYPE_HIGH_BITS_PREFIX = 15; + const int EXTENSION_TYPE_LOW_BITS_TYPE = 16; + /** + * OperandType with any value above {@link IDevice::OPERAND_TYPE_BASE_MAX} must be interpreted + * as an extension type according to {@link Model::extensionNameToPrefix}. + */ + const int OPERAND_TYPE_BASE_MAX = 0xFFFF; + /** + * OperationType with any value above {@link IDevice::OPERATION_TYPE_BASE_MAX} must be + * interpreted as an extension type according to {@link Model::extensionNameToPrefix}. + */ + const int OPERATION_TYPE_BASE_MAX = 0xFFFF; + + /** + * Allocates a driver-managed buffer with the properties specified by the buffer descriptor as + * well as the input and output roles. + * + * The allocate function must verify its inputs are correct. If there is an error, or if a + * certain role or property is not supported by the driver, the allocate function must return a + * service specific exception with an appropriate ErrorStatus. If the allocation is successful, + * this method must return a DeviceBuffer object with the produced IBuffer and a positive token + * identifying the allocated buffer. A successful allocation must accommodate all of the + * specified roles and buffer properties. + * + * The buffer is allocated to an uninitialized state. An uninitialized buffer may only be used + * in ways that are specified by outputRoles. A buffer is initialized after it is used as an + * output in a successful execution, or after a successful invocation of IBuffer::copyFrom on + * the buffer. An initialized buffer may be used according to all roles specified in inputRoles + * and outputRoles. A buffer will return to the uninitialized state if it is used as an output + * in a failed execution, or after a failed invocation of IBuffer::copyFrom on the buffer. + * + * The dimensions of the buffer can be deduced from the buffer descriptor as well as the + * dimensions of the corresponding model operands of the input and output roles. The dimensions + * or rank of the buffer may be unknown at this stage. As such, some driver services may only + * create a placeholder and defer the actual allocation until execution time. Note that the same + * buffer may be used for different shapes of outputs on different executions. When the buffer + * is used as an input, the input shape must be the same as the output shape from the last + * execution using this buffer as an output. + * + * The driver must apply proper validatation upon every usage of the buffer, and must fail the + * execution immediately if the usage is illegal. + * + * @param desc A buffer descriptor specifying the properties of the buffer to allocate. + * @param preparedModels A vector of IPreparedModel objects. Must only contain IPreparedModel + * objects from the same IDevice as this method is being invoked on. + * @param inputRoles A vector of roles with each specifying an input to a prepared model. + * @param outputRoles A vector of roles with each specifying an output to a prepared model. Each + * role specified in inputRoles and outputRoles must be unique. The + * corresponding model operands of the roles must have the same OperandType, + * scale, zero point, and ExtraParams. The dimensions of the operands and the + * dimensions specified in the buffer descriptor must be compatible with each + * other. Two dimensions are incompatible if there is at least one axis that + * is fully specified in both but has different values. + * @return DeviceBuffer object containing the allocated IBuffer object and a positive token that + * can be used to reference the buffer as one of the memory pools. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if a certain buffer property or a certain role is not supported, + * or if there is an unspecified error + * - INVALID_ARGUMENT if one of the input arguments is invalid + * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver + */ + DeviceBuffer allocate(in BufferDesc desc, in IPreparedModelParcel[] preparedModels, + in BufferRole[] inputRoles, in BufferRole[] outputRoles); + + /** + * Gets the capabilities of a driver. + * + * @return Capabilities of the driver. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + */ + Capabilities getCapabilities(); + + /** + * Gets the caching requirements of the driver implementation. + * + * There are two types of cache file descriptors provided to the driver: model cache and data + * cache. + * + * The data cache is for caching constant data, possibly including preprocessed and transformed + * tensor buffers. Any modification to the data cache should have no worse effect than + * generating bad output values at execution time. + * + * The model cache is for caching security-sensitive data such as compiled executable machine + * code in the device's native binary format. A modification to the model cache may affect the + * driver's execution behavior, and a malicious client could make use of this to execute beyond + * the granted permission. Thus, the driver must always check whether the model cache is + * corrupted before preparing the model from cache. + * + * getNumberOfCacheFilesNeeded returns how many of each type of cache files the driver + * implementation needs to cache a single prepared model. Returning 0 for both types indicates + * compilation caching is not supported by this driver. The driver may still choose not to cache + * certain compiled models even if it reports that caching is supported. + * + * If the device reports that caching is not supported, the user may avoid calling + * IDevice::prepareModelFromCache or providing cache file descriptors to + * IDevice::prepareModel. + * + * @return NumberOfCacheFiles structure indicating how many files for model and data cache the + * driver needs to cache a single prepared model. It must be less than or equal to + * MAX_NUMBER_OF_CACHE_FILES. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + */ + NumberOfCacheFiles getNumberOfCacheFilesNeeded(); + + /** + * Gets information about extensions supported by the driver implementation. + * + * All extension operations and operands must be fully supported for the extension to appear in + * the list of supported extensions. + * + * @return A list of supported extensions. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + */ + Extension[] getSupportedExtensions(); + + /** + * Gets the supported operations in a model. + * + * getSupportedOperations indicates which operations of the top-level subgraph are fully + * supported by the vendor driver. If an operation may not be supported for any reason, + * getSupportedOperations must return false for that operation. + * + * The {@link OperationType::IF} and {@link OperationType::WHILE} operations may only be fully + * supported if the vendor driver fully supports all operations in the referenced subgraphs. + * + * @param model A model whose operations -- and their corresponding operands -- are to be + * verified by the driver. + * @return A list of supported operations, where true indicates the operation is supported and + * false indicates the operation is not supported. The index of "supported" corresponds with + * the index of the operation it is describing in the main subgraph. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if provided model is invalid + */ + boolean[] getSupportedOperations(in Model model); + + /** + * Get the type of a given device. + * + * The device type can be used to help application developers to distribute Machine Learning + * workloads and other workloads such as graphical rendering. E.g., for an app which renders AR + * scenes based on real time object detection results, the developer could choose an ACCELERATOR + * type device for ML workloads, and reserve GPU for graphical rendering. + * + * @return The DeviceType of the device. Please note, this is not a bitfield of DeviceTypes. + * Each device must only be of a single DeviceType. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if the query resulted in an unspecified error + */ + DeviceType getType(); + + /** + * Get the version string of the driver implementation. + * + * The version string must be a unique token among the set of version strings of drivers of a + * specific device. The token identifies the device driver's implementation. The token must not + * be confused with the feature level which is solely defined by the interface version. This API + * is opaque to the Android framework, but the Android framework may use the information for + * debugging or to pass on to NNAPI applications. + * + * Application developers sometimes have specific requirements to ensure good user experiences, + * and they need more information to make intelligent decisions when the Android framework + * cannot. For example, combined with the device name and other information, the token can help + * NNAPI applications filter devices based on their needs: + * - An application demands a certain level of performance, but a specific version of the + * driver cannot meet that requirement because of a performance regression. + * The application can disallow the driver based on the version provided. + * - An application has a minimum precision requirement, but certain versions of + * the driver cannot meet that requirement because of bugs or certain optimizations. + * The application can filter out versions of these drivers. + * + * @return The version string of the device implementation. Must have nonzero length. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if the query resulted in an unspecified error + */ + String getVersionString(); + + /** + * Asynchronously creates a prepared model for execution and optionally saves it into cache + * files. + * + * prepareModel is used to make any necessary transformations to or alternative representations + * to a model for execution, possibly including transformations on the constant data, + * optimization on the model's graph, or compilation into the device's native binary format. The + * model itself is not changed. + * + * Optionally, caching information may be provided for the driver to save the prepared model to + * cache files for faster model compilation time when the same model preparation is requested in + * the future. There are two types of cache file descriptors provided to the driver: model cache + * and data cache. For more information on the two types of cache, refer to + * getNumberOfCacheFilesNeeded. + * + * The file descriptors must be opened with read and write permission. A file may have any size, + * and the corresponding file descriptor may have any offset. The driver must truncate a file to + * zero size before writing to that file. The file descriptors may be closed by the client once + * the asynchronous preparation has finished. The driver must dup a file descriptor if it wants + * to get access to the cache file later. + * + * The model is prepared asynchronously with respect to the caller. The prepareModel function + * must verify the inputs to the preparedModel function related to preparing the model (as + * opposed to saving the prepared model to cache) are correct. If there is an error, + * prepareModel must immediately invoke the callback with the appropriate ErrorStatus value and + * nullptr for the IPreparedModel, then return a status with a service specific exception with + * the same ErrorStatus. If the inputs to the prepareModel function that are related to + * preparing the model are valid and there is no error, prepareModel must launch an asynchronous + * task to prepare the model in the background, and immediately return from prepareModel. If the + * asynchronous task fails to launch, prepareModel must immediately invoke the callback with + * ErrorStatus::GENERAL_FAILURE and nullptr for the IPreparedModel, then return a service + * specific exception with ErrorStatus::GENERAL_FAILURE. + * + * When the asynchronous task has finished preparing the model, it must immediately invoke the + * callback function provided as an input to prepareModel. If the model was prepared + * successfully, the callback object must be invoked with an error status of ErrorStatus::NONE + * and the produced IPreparedModel object. If an error occurred preparing the model, the + * callback object must be invoked with the appropriate ErrorStatus value and nullptr for the + * IPreparedModel. + * + * The model is prepared with a priority. This priority is relative to other prepared models + * owned by the same client. Higher priority executions may use more compute resources than + * lower priority executions, and may preempt or starve lower priority executions. + * + * prepareModel can be called with an optional deadline. If the model is not able to be prepared + * before the provided deadline, the model preparation may be aborted, and either + * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link + * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort must be + * sent the same way as other errors, described above. + * + * Optionally, the driver may save the prepared model to cache during the asynchronous + * preparation. Any error that occurs when saving to cache must not affect the status of + * preparing the model. Even if the input arguments related to the cache may be invalid, or the + * driver may fail to save to cache, the prepareModel function must finish preparing the model. + * The driver may choose not to save to cache even if the caching information is provided and + * valid. + * + * The only information that may be unknown to the model at this stage is the shape of the + * tensors, which may only be known at execution time. As such, some driver services may return + * partially prepared models, where the prepared model may only be finished when it is paired + * with a set of inputs to the model. Note that the same prepared model object may be used with + * different shapes of inputs on different (possibly concurrent) executions. + * + * Multiple threads may call prepareModel on the same model concurrently. + * + * @param model The model to be prepared for execution. + * @param preference Indicates the intended execution behavior of a prepared model. + * @param priority The priority of the prepared model relative to other prepared models owned by + * the client. + * @param deadline The time by which the model is expected to be prepared. The time is measured + * in nanoseconds since epoch of the steady clock (as from + * std::chrono::steady_clock). If the model cannot be prepared by the deadline, + * the preparation may be aborted. Passing -1 means the deadline is omitted. + * Other negative values are invalid. + * @param modelCache A vector of file descriptors for the security-sensitive cache. The length + * of the vector must either be 0 indicating that caching information is not + * provided, or match the numModelCache returned from + * getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in + * the same order when retrieving the preparedModel from cache files with + * prepareModelFromCache. + * @param dataCache A vector of file descriptors for the constants' cache. The length of the + * vector must either be 0 indicating that caching information is not provided, + * or match the numDataCache returned from getNumberOfCacheFilesNeeded. The + * cache file descriptors will be provided in the same order when retrieving + * the preparedModel from cache files with prepareModelFromCache. + * @param token A caching token of length BYTE_SIZE_OF_CACHE_TOKEN identifying the prepared + * model. The same token will be provided when retrieving the prepared model from + * the cache files with prepareModelFromCache. Tokens should be chosen to have a + * low rate of collision for a particular application. The driver cannot detect a + * collision; a collision will result in a failed execution or in a successful + * execution that produces incorrect output values. If both modelCache and + * dataCache are empty indicating that caching information is not provided, this + * token must be ignored. + * @param callback A callback object used to return the error status of preparing the model for + * execution and the prepared model if successful, nullptr otherwise. The + * callback object's notify function must be called exactly once, even if the + * model could not be prepared. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if one of the input arguments related to preparing the model is + * invalid + * - MISSED_DEADLINE_* if the preparation is aborted because the model cannot be prepared by + * the deadline + * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver + */ + void prepareModel(in Model model, in ExecutionPreference preference, in Priority priority, + in long deadline, in ParcelFileDescriptor[] modelCache, + in ParcelFileDescriptor[] dataCache, in byte[] token, + in IPreparedModelCallback callback); + + /** + * Creates a prepared model from cache files for execution. + * + * prepareModelFromCache is used to retrieve a prepared model directly from cache files to avoid + * slow model compilation time. There are two types of cache file descriptors provided to the + * driver: model cache and data cache. For more information on the two types of cache files, + * refer to getNumberOfCacheFilesNeeded. + * + * The file descriptors must be opened with read and write permission. A file may have any size, + * and the corresponding file descriptor may have any offset. The driver must truncate a file to + * zero size before writing to that file. The file descriptors may be closed by the client once + * the asynchronous preparation has finished. The driver must dup a file descriptor if it wants + * to get access to the cache file later. + * + * The model is prepared asynchronously with respect to the caller. The prepareModelFromCache + * function must verify the inputs to the prepareModelFromCache function are correct, and that + * the security-sensitive cache has not been modified since it was last written by the driver. + * If there is an error, or if compilation caching is not supported, or if the + * security-sensitive cache has been modified, prepareModelFromCache must immediately invoke the + * callback with the appropriate ErrorStatus value and nullptr for the IPreparedModel, then + * return a status with a service specific exception with the same ErrorStatus. If the inputs to + * the prepareModelFromCache function are valid, the security-sensitive cache is not modified, + * and there is no error, prepareModelFromCache must launch an asynchronous task to prepare the + * model in the background, and immediately return from prepareModelFromCache. If the + * asynchronous task fails to launch, prepareModelFromCache must immediately invoke the callback + * with ErrorStatus::GENERAL_FAILURE and nullptr for the IPreparedModel, then return a service + * specific exception with ErrorStatus::GENERAL_FAILURE. + * + * When the asynchronous task has finished preparing the model, it must immediately invoke the + * callback function provided as an input to prepareModelFromCache. If the model was prepared + * successfully, the callback object must be invoked with an error status of ErrorStatus::NONE + * and the produced IPreparedModel object. If an error occurred preparing the model, the + * callback object must be invoked with the appropriate ErrorStatus value and nullptr for the + * IPreparedModel. + * + * prepareModelFromCache can be called with an optional deadline. If the model is not able to + * prepared before the provided deadline, the model preparation may be aborted, and either + * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or + * {@link ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort + * must be sent the same way as other errors, described above. + * + * The only information that may be unknown to the model at this stage is the shape of the + * tensors, which may only be known at execution time. As such, some driver services may return + * partially prepared models, where the prepared model may only be finished when it is paired + * with a set of inputs to the model. Note that the same prepared model object may be used with + * different shapes of inputs on different (possibly concurrent) executions. + * + * @param deadline The time by which the model is expected to be prepared. The time is measured + * in nanoseconds since epoch of the steady clock (as from + * std::chrono::steady_clock). If the model cannot be prepared by the deadline, + * the preparation may be aborted. Passing -1 means the deadline is omitted. + * Other negative values are invalid. + * @param modelCache A vector of file descriptors for the security-sensitive cache. The length + * of the vector must match the numModelCache returned from + * getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in + * the same order as with prepareModel. + * @param dataCache A vector of file descriptors for the constants' cache. The length of the + * vector must match the numDataCache returned from + * getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in + * the same order as with prepareModel. + * @param token A caching token of length BYTE_SIZE_OF_CACHE_TOKEN identifying the prepared + * model. It is the same token provided when saving the cache files with + * prepareModel. Tokens should be chosen to have a low rate of collision for a + * particular application. The driver cannot detect a collision; a collision will + * result in a failed execution or in a successful execution that produces + * incorrect output values. + * @param callback A callback object used to return the error status of preparing the model for + * execution and the prepared model if successful, nullptr otherwise. The + * callback object's notify function must be called exactly once, even if the + * model could not be prepared. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if caching is not supported or if there is an unspecified error + * - INVALID_ARGUMENT if one of the input arguments is invalid + * - MISSED_DEADLINE_* if the preparation is aborted because the model cannot be prepared by + * the deadline + * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver + */ + void prepareModelFromCache(in long deadline, in ParcelFileDescriptor[] modelCache, + in ParcelFileDescriptor[] dataCache, in byte[] token, + in IPreparedModelCallback callback); +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl new file mode 100644 index 0000000000..cb6db11af7 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.ErrorStatus; +import android.hardware.neuralnetworks.Timing; + +/** + * IFencedExecutionCallback can be used to query the error status result and duration information + * from an IPreparedModel::executeFenced call. + */ +@VintfStability +interface IFencedExecutionCallback { + /** + * The getExecutionInfo method is used by the clients to query error status result and duration + * information. The method must only be called after the actual evaluation has finished or + * resulted in an runtime error, as indicated by the status of the sync fence returned by the + * IPreparedModel::executeFenced call, otherwise GENERAL_FAILURE must be returned. + * + * @param out timingLaunched The duration starts when executeFenced is called and ends when + * executeFenced signals the returned syncFence. Unless measureTiming + * was set to true when launching the execution and status is NONE, + * all times must be reported as -1. A driver may choose to report any + * time as -1, indicating that particular measurement is not + * available. + * @param out timingFenced The duration starts when all waitFor sync fences have been signaled + * and ends when executeFenced signals the returned syncFence. Unless + * measureTiming was set to true when launching the execution and status + * is NONE, all times must be reported as -1. A driver may choose to + * report any time as -1, indicating that particular measurement is not + * available. + * @return Error status returned from the asynchronously dispatched execution must be: + * - NONE if the asynchronous execution was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if the asynchronous task resulted in an unspecified error + * - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the + * deadline + * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver + */ + ErrorStatus getExecutionInfo(out Timing timingLaunched, out Timing timingFenced); +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl new file mode 100644 index 0000000000..2414a4a1b6 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl @@ -0,0 +1,172 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.common.NativeHandle; +import android.hardware.neuralnetworks.ErrorStatus; +import android.hardware.neuralnetworks.ExecutionResult; +import android.hardware.neuralnetworks.IFencedExecutionCallback; +import android.hardware.neuralnetworks.Request; + +/** + * IPreparedModel describes a model that has been prepared for execution and is used to launch + * executions. + */ +@VintfStability +interface IPreparedModel { + /** + * Each {@link OperationType::WHILE} operation in the model has an implicit execution timeout + * duration associated with it ("loop timeout duration"). This duration is configurable on a + * per-execution basis and must not exceed 15 seconds. The default value is 2 seconds. The units + * are nanoseconds. + */ + const long DEFAULT_LOOP_TIMEOUT_DURATION_NS = 2000000000; + const long MAXIMUM_LOOP_TIMEOUT_DURATION_NS = 15000000000; + + /** + * Performs a synchronous execution on a prepared model. + * + * The execution is performed synchronously with respect to the caller. executeSynchronously + * must verify the inputs to the function are correct, and the usages of memory pools allocated + * by IDevice::allocate are valid. If there is an error, executeSynchronously must immediately + * return a service specific exception with the appropriate ErrorStatus value. If the inputs to + * the function are valid and there is no error, executeSynchronously must perform the + * execution, and must not return until the execution is complete. + * + * The caller must not change the content of any data object referenced by 'request' (described + * by the {@link DataLocation} of a {@link RequestArgument}) until executeSynchronously returns. + * executeSynchronously must not change the content of any of the data objects corresponding to + * 'request' inputs. + * + * If the prepared model was prepared from a model wherein all tensor operands have fully + * specified dimensions, and the inputs to the function are valid, and at execution time every + * operation's input operands have legal values, then the execution should complete + * successfully: there must be no failure unless the device itself is in a bad state. + * + * executeSynchronously may be called with an optional deadline. If the execution is not able to + * be completed before the provided deadline, the execution may be aborted, and either + * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link + * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort must be + * sent the same way as other errors, described above. + * + * Any number of calls to the execute* functions, in any combination, may be made concurrently, + * even on the same IPreparedModel object. + * + * @param request The input and output information on which the prepared model is to be + * executed. + * @param measure Specifies whether or not to measure duration of the execution. The duration + * runs from the time the driver sees the call to the executeSynchronously + * function to the time the driver returns from the function. + * @param deadline The time by which the execution is expected to complete. The time is measured + * in nanoseconds since epoch of the steady clock (as from + * std::chrono::steady_clock). If the execution cannot be finished by the + * deadline, the execution may be aborted. Passing -1 means the deadline is + * omitted. Other negative values are invalid. + * @param loopTimeoutDuration The maximum amount of time in nanoseconds that should be spent + * executing a {@link OperationType::WHILE} operation. If a loop + * condition model does not output false within this duration, the + * execution must be aborted. If -1 is provided, the maximum amount + * of time is {@link DEFAULT_LOOP_TIMEOUT_DURATION_NS}. Other + * negative values are invalid. When provided, the duration must not + * exceed {@link MAXIMUM_LOOP_TIMEOUT_DURATION_NS}. + * @return ExecutionResult parcelable, containing the status of the execution, output shapes and + * timing information. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if one of the input arguments is invalid + * - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the + * deadline + * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver + */ + ExecutionResult executeSynchronously(in Request request, in boolean measureTiming, + in long deadline, in long loopTimeoutDuration); + + /** + * Launch a fenced asynchronous execution on a prepared model. + * + * The execution is performed asynchronously with respect to the caller. executeFenced must + * verify the inputs to the function are correct, and the usages of memory pools allocated by + * IDevice::allocate are valid. If there is an error, executeFenced must immediately return a + * service specific exception with the corresponding ErrorStatus. If the inputs to the function + * are valid and there is no error, executeFenced must dispatch an asynchronous task to perform + * the execution in the background, assign a sync fence that will be signaled once the execution + * is completed and immediately return a callback that can be used by the client to query the + * duration and runtime error status. If the task has finished before the call returns, + * syncFence file descriptor may be set to -1. The execution must wait for all the sync fences + * (if any) in waitFor to be signaled before starting the actual execution. + * + * When the asynchronous task has finished its execution, it must immediately signal the + * syncFence returned from the executeFenced call. After the syncFence is signaled, the task + * must not modify the content of any data object referenced by 'request' (described by the + * {@link DataLocation} of a {@link RequestArgument}). + * + * executeFenced may be called with an optional deadline and an optional duration. If the + * execution is not able to be completed before the provided deadline or within the timeout + * duration (measured from when all sync fences in waitFor are signaled), whichever comes + * earlier, the execution may be aborted, and either + * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link + * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort must be + * sent the same way as other errors, described above. + * + * If any of the sync fences in waitFor changes to error status after the executeFenced call + * succeeds, or the execution is aborted because it cannot finish before the deadline has been + * reached or the duration has elapsed, the driver must immediately set the returned syncFence + * to error status. + * + * Any number of calls to the execute* functions, in any combination, may be made concurrently, + * even on the same IPreparedModel object. + * + * @param request The input and output information on which the prepared model is to be + * executed. The outputs in the request must have fully specified dimensions. + * @param waitFor A vector of sync fence file descriptors. Execution must not start until all + * sync fences have been signaled. + * @param measure Specifies whether or not to measure duration of the execution. + * @param deadline The time by which the execution is expected to complete. The time is measured + * in nanoseconds since epoch of the steady clock (as from + * std::chrono::steady_clock).If the execution cannot be finished by the + * deadline, the execution may be aborted. Passing -1 means the deadline is + * omitted. Other negative values are invalid. + * @param loopTimeoutDuration The maximum amount of time in nanoseconds that should be spent + * executing a {@link OperationType::WHILE} operation. If a loop + * condition model does not output false within this duration, the + * execution must be aborted. If -1 is provided, the maximum amount + * of time is {@link DEFAULT_LOOP_TIMEOUT_DURATION_NS}. Other + * negative values are invalid. When provided, the duration must not + * exceed {@link MAXIMUM_LOOP_TIMEOUT_DURATION_NS}. + * @param duration The length of time in nanoseconds within which the execution is expected to + * complete after all sync fences in waitFor are signaled. If the execution + * cannot be finished within the duration, the execution may be aborted. Passing + * -1 means the duration is omitted. Other negative values are invalid. + * @param out syncFence The sync fence that will be signaled when the task is completed. The + * sync fence will be set to error if a critical error, e.g. hardware + * failure or kernel panic, occurs when doing execution. + * @return The IFencedExecutionCallback can be used to query information like duration and error + * status when the execution is completed. + * @throws ServiceSpecificException with one of the following ErrorStatus values: + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if one of the input arguments is invalid, including fences in error + * states. + * - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the + * deadline + * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver + */ + IFencedExecutionCallback executeFenced(in Request request, in ParcelFileDescriptor[] waitFor, + in boolean measureTiming, in long deadline, in long loopTimeoutDuration, + in long duration, out @nullable ParcelFileDescriptor syncFence); +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelCallback.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelCallback.aidl new file mode 100644 index 0000000000..29cca6d906 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelCallback.aidl @@ -0,0 +1,50 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.ErrorStatus; +import android.hardware.neuralnetworks.IPreparedModel; + +/** + * IPreparedModelCallback must be used to return a prepared model produced by an asynchronous task + * launched from IDevice::prepareModel*. + */ +@VintfStability +interface IPreparedModelCallback { + /** + * Notify must be invoked immediately after the asynchronous task holding this callback has + * finished preparing the model. If the model was successfully prepared, the method must be + * invoked with ErrorStatus::NONE and the prepared model. If the model was not able to be + * successfully prepared, the method must be invoked with the appropriate ErrorStatus and + * nullptr as the IPreparedModel. If the asynchronous task holding this callback fails to launch + * or if the model provided to IDevice::prepareModel is invalid, notify method must be invoked + * with the appropriate error as well as nullptr for the IPreparedModel. + * + * @param status Error status returned from the asynchronous model preparation task; must be: + * - NONE if the asynchronous task successfully prepared the model + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if the asynchronous task resulted in an unspecified error + * - INVALID_ARGUMENT if one of the input arguments to prepareModel is invalid + * - MISSED_DEADLINE_* if the preparation is aborted because the model cannot be + * prepared by the deadline + * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver + * @param preparedModel A model that has been asynchronously prepared for execution. If the + * model was unable to be prepared due to an error, nullptr must be passed + * in place of the IPreparedModel object. + */ + void notify(in ErrorStatus status, in IPreparedModel preparedModel); +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelParcel.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelParcel.aidl new file mode 100644 index 0000000000..878b0ad6eb --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelParcel.aidl @@ -0,0 +1,27 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.IPreparedModel; + +/** + * A parcelable for passing a vector of IPreparedModel objects. + */ +@VintfStability +parcelable IPreparedModelParcel { + IPreparedModel preparedModel; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Memory.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Memory.aidl new file mode 100644 index 0000000000..870f0aeb08 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Memory.aidl @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; +import android.hardware.common.NativeHandle; +import android.os.ParcelFileDescriptor; + +/** + * A type that is used to pass pieces of shared memory between processes. + * The type structure mimics hidl_memory type from HIDL. + */ +@VintfStability +parcelable Memory { + NativeHandle handle; + long size; + String name; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Model.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Model.aidl new file mode 100644 index 0000000000..2f11dec07d --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Model.aidl @@ -0,0 +1,69 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.ExtensionNameAndPrefix; +import android.hardware.neuralnetworks.Memory; +import android.hardware.neuralnetworks.Subgraph; + +/** + * A Neural Network Model. + * + * This includes not only the execution graph, but also constant data such as weights or scalars + * added at construction time. The only information that may not be known is the shape of the input + * tensors. + */ +@VintfStability +parcelable Model { + /** + * The top-level subgraph. + */ + Subgraph main; + /** + * Referenced subgraphs. + * + * Each subgraph is referenced by the main subgraph or at least one other referenced subgraph. + * + * There must be no reference cycles. + */ + Subgraph[] referenced; + /** + * A byte buffer containing operand data that were copied into the model. + * + * An operand's value must be located here if and only if Operand::lifetime equals + * OperandLifeTime::CONSTANT_COPY. + */ + byte[] operandValues; + /** + * A collection of shared memory pools containing operand values. + * + * An operand's value must be located here if and only if Operand::lifetime equals + * OperandLifeTime::CONSTANT_POOL. + */ + Memory[] pools; + /** + * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or precision as low as that + * of the IEEE 754 16-bit floating-point format. + * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the range and precision of + * the IEEE 754 32-bit floating-point format. + */ + boolean relaxComputationFloat32toFloat16; + /** + * The mapping between extension names and prefixes of operand and operation type values. + */ + ExtensionNameAndPrefix[] extensionNameToPrefix; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl new file mode 100644 index 0000000000..1ca2676646 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl @@ -0,0 +1,27 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Structure indicating how many files for model and numDataCache cache the driver needs to cache a + * single prepared model. + */ +@VintfStability +parcelable NumberOfCacheFiles { + int numModelCache; + int numDataCache; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Operand.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operand.aidl new file mode 100644 index 0000000000..4d2260feb2 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operand.aidl @@ -0,0 +1,112 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.DataLocation; +import android.hardware.neuralnetworks.OperandExtraParams; +import android.hardware.neuralnetworks.OperandLifeTime; +import android.hardware.neuralnetworks.OperandType; + +/** + * Describes one operand of the model's graph. + */ +@VintfStability +parcelable Operand { + /** + * The data type. + * + * Besides the values listed in {@link OperandType}, any value above + * {@link IDevice::OPERAND_TYPE_BASE_MAX} is possible and should be interpreted as an extension + * type according to {@link Model::extensionNameToPrefix}. + */ + OperandType type; + /** + * Dimensions of the operand. + * + * For a scalar operand, dimensions.size() must be 0. + * + * A tensor operand with all dimensions specified has "fully specified" dimensions. Whenever + * possible (i.e., whenever the dimensions are known at model construction time), a tensor + * operand should have (but is not required to have) fully specified dimensions, in order to + * enable the best possible performance. + * + * If a tensor operand's dimensions are not fully specified, the dimensions of the operand are + * deduced from the operand dimensions and values of the operation for which that operand is an + * output or from the corresponding {@link OperationType::IF} or {@link OperationType::WHILE} + * operation input operand dimensions in the case of referenced subgraph input operands. + * + * In the following situations, a tensor operand's dimensions must be fully specified: + * + * . The operand has lifetime CONSTANT_COPY or CONSTANT_POOL. + * + * . The operand has lifetime SUBGRAPH_INPUT and belongs to the main subgraph. Fully + * specified dimensions must either be present in the Operand or they must be provided in + * the corresponding RequestArgument. + * EXCEPTION: If the input is optional and omitted (by setting the hasNoValue field of the + * corresponding RequestArgument to true) then it need not have fully specified + * dimensions. + * + * A tensor operand with some number of unspecified dimensions is represented by setting each + * unspecified dimension to 0. + * + * A tensor operand with unspecified rank is represented by providing an empty dimensions + * vector. + */ + int[] dimensions; + /** + * Quantized scale of the operand. + * + * Must be 0 when not applicable to an operand type. + * + * See {@link OperandType}. + */ + float scale; + /** + * Quantized zero-point offset of the operand. + * + * Must be 0 when not applicable to an operand type. + * + * See {@link OperandType}. + */ + int zeroPoint; + /** + * How the operand is used. + */ + OperandLifeTime lifetime; + /** + * Where to find the data for this operand. + * If the lifetime is TEMPORARY_VARIABLE, SUBGRAPH_INPUT, SUBGRAPH_OUTPUT, or NO_VALUE: + * - All the fields must be 0. + * If the lifetime is CONSTANT_COPY: + * - location.poolIndex is 0. + * - location.offset is the offset in bytes into Model.operandValues. + * - location.length is set. + * If the lifetime is CONSTANT_POOL: + * - location.poolIndex is set. + * - location.offset is the offset in bytes into the specified pool. + * - location.length is set. + * If the lifetime is SUBGRAPH: + * - location.poolIndex is 0. + * - location.offset is the index of the referenced subgraph in {@link Model::referenced}. + * - location.length is 0. + */ + DataLocation location; + /** + * Additional parameters specific to a particular operand type. + */ + @nullable OperandExtraParams extraParams; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandExtraParams.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandExtraParams.aidl new file mode 100644 index 0000000000..229754ab4b --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandExtraParams.aidl @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.SymmPerChannelQuantParams; + +/** + * Parameters specific to a particular operand type. + */ +@VintfStability +union OperandExtraParams { + /** + * Symmetric per-channel quantization parameters. + * + * Only applicable to operands of type TENSOR_QUANT8_SYMM_PER_CHANNEL. + */ + SymmPerChannelQuantParams channelQuant; + /** + * Extension operand parameters. + * + * The framework treats this as an opaque data blob. + * The format is up to individual extensions. + */ + byte[] extension; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandLifeTime.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandLifeTime.aidl new file mode 100644 index 0000000000..1d18149603 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandLifeTime.aidl @@ -0,0 +1,62 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * How an operand is used. + */ +@VintfStability +@Backing(type="int") +enum OperandLifeTime { + /** + * The operand is internal to the model. It's created by an operation and consumed by other + * operations. It must be an output operand of exactly one operation. + */ + TEMPORARY_VARIABLE, + /** + * The operand is an input of a subgraph. It must not be an output operand of any operation. + * + * An operand can't be both input and output of a subgraph. + */ + SUBGRAPH_INPUT, + /** + * The operand is an output of a subgraph. It must be an output operand of exactly one + * operation. + * + * An operand can't be both input and output of a subgraph. + */ + SUBGRAPH_OUTPUT, + /** + * The operand is a constant found in Model.operandValues. It must not be an output operand of + * any operation. + */ + CONSTANT_COPY, + /** + * The operand is a constant that was specified via a Memory object. It must not be an output + * operand of any operation. + */ + CONSTANT_POOL, + /** + * The operand does not have a value. This is valid only for optional arguments of operations. + */ + NO_VALUE, + /** + * The operand is a reference to a subgraph. It must be an input to one or more + * {@link OperationType::IF} or {@link OperationType::WHILE} operations. + */ + SUBGRAPH, +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandPerformance.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandPerformance.aidl new file mode 100644 index 0000000000..7fd86f90a0 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandPerformance.aidl @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.OperandType; +import android.hardware.neuralnetworks.PerformanceInfo; + +/** + * Driver performance when operating on a particular data type. In the case of float32 data, this is + * used when the calculations are not relaxed. + */ +@VintfStability +parcelable OperandPerformance { + OperandType type; + PerformanceInfo info; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandType.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandType.aidl new file mode 100644 index 0000000000..12edc0f261 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandType.aidl @@ -0,0 +1,153 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Operand types. + * + * The type of an operand in a model. + * + * Types prefaced with TENSOR_* must be used for tensor data (i.e., tensors + * with at least one dimension). Types not prefaced by TENSOR_* represent + * scalar values and must have no dimensions. + */ +@VintfStability +@Backing(type="int") +enum OperandType { + /** + * A 32 bit floating point scalar value. + */ + FLOAT32 = 0, + /** + * A signed 32 bit integer scalar value. + */ + INT32 = 1, + /** + * An unsigned 32 bit integer scalar value. + */ + UINT32 = 2, + /** + * A tensor of 32 bit floating point values. + */ + TENSOR_FLOAT32 = 3, + /** + * A tensor of 32 bit integer values. + */ + TENSOR_INT32 = 4, + /** + * A tensor of 8 bit unsigned integers that represent real numbers. + * + * Attached to this tensor are two numbers that can be used to convert the 8 bit integer to the + * real value and vice versa. These two numbers are: + * - scale: a 32 bit floating point value greater than zero. + * - zeroPoint: a 32 bit integer, in range [0, 255]. + * + * The formula is: + * real_value = (integer_value - zeroPoint) * scale. + */ + TENSOR_QUANT8_ASYMM = 5, + /** + * An 8 bit boolean scalar value. + * + * Values of this operand type are either true or false. A zero value represents false; any + * other value represents true. + */ + BOOL = 6, + /** + * A tensor of 16 bit signed integers that represent real numbers. + * + * Attached to this tensor is a number representing real value scale that is used to convert the + * 16 bit number to a real value in the following way: + * realValue = integerValue * scale. + * + * scale is a 32 bit floating point with value greater than zero. + */ + TENSOR_QUANT16_SYMM = 7, + /** + * A tensor of IEEE 754 16 bit floating point values. + */ + TENSOR_FLOAT16 = 8, + /** + * A tensor of 8 bit boolean values. + * + * Values of this operand type are either true or false. A zero value represents false; any + * other value represents true. + */ + TENSOR_BOOL8 = 9, + /** + * An IEEE 754 16 bit floating point scalar value. + */ + FLOAT16 = 10, + /** + * A tensor of 8 bit signed integers that represent real numbers. + * + * This tensor is associated with additional fields that can be used to convert the 8 bit signed + * integer to the real value and vice versa. These fields are: + * - channelDim: a 32 bit unsigned integer indicating channel dimension. + * - scales: an array of positive 32 bit floating point values. + * The size of the scales array must be equal to dimensions[channelDim]. + * + * {@link SymmPerChannelQuantParams} must hold the parameters for an Operand of this type. + * The channel dimension of this tensor must not be unknown (dimensions[channelDim] != 0). + * + * The formula is: + * realValue[..., C, ...] = + * integerValue[..., C, ...] * scales[C] + * where C is an index in the Channel dimension. + */ + TENSOR_QUANT8_SYMM_PER_CHANNEL = 11, + /** + * A tensor of 16 bit unsigned integers that represent real numbers. + * + * Attached to this tensor are two numbers that can be used to convert the 16 bit integer to the + * real value and vice versa. These two numbers are: + * - scale: a 32 bit floating point value greater than zero. + * - zeroPoint: a 32 bit integer, in range [0, 65535]. + * + * The formula is: + * real_value = (integer_value - zeroPoint) * scale. + */ + TENSOR_QUANT16_ASYMM = 12, + /** + * A tensor of 8 bit signed integers that represent real numbers. + * + * Attached to this tensor is a number representing real value scale that is used to convert the + * 8 bit number to a real value in the following way: + * realValue = integerValue * scale. + * + * scale is a 32 bit floating point with value greater than zero. + */ + TENSOR_QUANT8_SYMM = 13, + /** + * A tensor of 8 bit signed integers that represent real numbers. + * + * Attached to this tensor are two numbers that can be used to convert the 8 bit integer to the + * real value and vice versa. These two numbers are: + * - scale: a 32 bit floating point value greater than zero. + * - zeroPoint: a 32 bit integer, in range [-128, 127]. + * + * The formula is: + * real_value = (integer_value - zeroPoint) * scale. + */ + TENSOR_QUANT8_ASYMM_SIGNED = 14, + /** + * A reference to a subgraph. + * + * Must have the lifetime {@link OperandLifeTime::SUBGRAPH}. + */ + SUBGRAPH = 15, +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Operation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operation.aidl new file mode 100644 index 0000000000..0c6032f625 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operation.aidl @@ -0,0 +1,44 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.OperationType; + +/** + * Describes one operation of the model's graph. + */ +@VintfStability +parcelable Operation { + /** + * The operation type. + * + * Besides the values listed in {@link OperationType}, any value above + * {@link IDevice::OPERATION_TYPE_BASE_MAX} is possible and should be interpreted as an + * extension type according to {@link Model::extensionNameToPrefix}. + */ + OperationType type; + /** + * Describes the table that contains the indexes of the inputs of the operation. The offset is + * the index in the operandIndexes table. + */ + int[] inputs; + /** + * Describes the table that contains the indexes of the outputs of the operation. The offset is + * the index in the operandIndexes table. + */ + int[] outputs; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperationType.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperationType.aidl new file mode 100644 index 0000000000..3f491540fd --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperationType.aidl @@ -0,0 +1,5131 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Operation types. + * + * The type of an operation in a model. + */ +@VintfStability +@Backing(type="int") +enum OperationType { + /** + * Adds two tensors, element-wise. + * + * Takes two input tensors of identical {@link OperandType} and compatible + * dimensions. The output is the sum of both input tensors, optionally + * modified by an activation function. + * + * Two dimensions are compatible when: + * 1. they are equal, or + * 2. one of them is 1 + * + * The size of the output is the maximum size along each dimension of the + * input operands. It starts with the trailing dimensions, and works its + * way forward. + * + * Example: + * + * input1.dimension = {4, 1, 2} + * input2.dimension = {5, 4, 3, 1} + * output.dimension = {5, 4, 3, 2} + * + * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero + * dimension is only compatible with 0 or 1. The size of the output + * dimension is zero if either of corresponding input dimension is zero. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType}, and compatible dimensions + * as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scales and zeroPoint can be different from input0 scale and zeroPoint. + * * 2: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * For a {@link OperandType::TENSOR_INT32} tensor, + * the {@link FusedActivationFunc} must be "NONE". + * + * Outputs: + * * 0: The sum, a tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. + */ + ADD = 0, + /** + * Performs a 2-D average pooling operation. + * + * The output dimensions are functions of the filter dimensions, stride, and + * padding. + * + * The values in the output tensor are computed as: + * + * output[b, i, j, channel] = + * sum_{di, dj}( + * input[b, strides[1] * i + di, strides[2] * j + dj, channel] + * ) / sum(1) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. + * * 1: An {@link OperandType::INT32} scalar, specifying the padding on + * the left, in the ‘width’ dimension. + * * 2: An {@link OperandType::INT32} scalar, specifying the padding on + * the right, in the ‘width’ dimension. + * * 3: An {@link OperandType::INT32} scalar, specifying the padding on + * the top, in the ‘height’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the padding on + * the bottom, in the ‘height’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 7: An {@link OperandType::INT32} scalar, specifying the filter + * width. + * * 8: An {@link OperandType::INT32} scalar, specifying the filter + * height. + * * 9: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 10: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. + * * 1: An {@link OperandType::INT32} scalar, specifying the implicit + * padding scheme, has to be one of the + * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. + * * 2: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 3: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the filter + * width. + * * 5: An {@link OperandType::INT32} scalar, specifying the filter + * height. + * * 6: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 7: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * + * Outputs: + * * 0: The output 4-D tensor, of shape + * [batches, out_height, out_width, depth]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + AVERAGE_POOL_2D = 1, + /** + * Concatenates the input tensors along the given dimension. + * + * The input tensors must have identical {@link OperandType} and the same + * dimensions except the dimension along the concatenation axis. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * (full support since HAL version 1.2, see the input section) + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0 ~ n-1: The list of n input tensors, of shape + * [D0, D1, ..., Daxis(i), ..., Dm]. + * Before HAL version 1.2, all input tensors of + * {@link OperandType::TENSOR_QUANT8_ASYMM} + * must have the same scale and zeroPoint as the output tensor. + * Input tensors of + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * are allowed to have different scale and zeroPoint. + * Since HAL version 1.2, zero-sized tensors are supported. + * * n: An {@link OperandType::INT32} scalar, specifying the + * concatenation axis. + * + * Outputs: + * * 0: The output, a tensor of the same {@link OperandType} as the input + * tensors. The output shape is [D0, D1, ..., sum(Daxis(i)), ..., Dm]. + * Since HAL version 1.2, for a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint values can be different from + * input tensors. Before HAL version 1.2 they have to be the same as for the input tensors. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint values can be different from input tensors. + */ + CONCATENATION = 2, + /** + * Performs a 2-D convolution operation. + * + * The CONV_2D op sweeps a 2-D filter that can mix channels together over a + * batch of images, applying the filter to each window of each image of the + * appropriate size. + * + * The output dimensions are functions of the filter dimensions, stride, and + * padding. + * + * The values in the output tensor are computed as: + * + * output[b, i, j, channel] = + * sum_{di, dj, k} ( + * input[b, strides[1] * i + di, strides[2] * j + dj, k] * + * filter[channel, di, dj, k] + * ) + bias[channel] + * + * Supported tensor {@link OperandType} configurations: + * * 32 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias. + * + * * Quantized: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * Available since HAL version 1.2: + * * 16 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias. + * + * * Quantized with symmetric per channel quantization for the filter: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output. + * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. + * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, + * * * each value scaling is separate and equal to input.scale * filter.scales[channel]). + * + * Available since HAL version 1.3: + * * Quantized signed (since HAL version 1.3): + * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3): + * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output. + * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. + * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, + * * * each value scaling is separate and equal to input.scale * filter.scales[channel]). + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input. + * Since HAL version 1.2, zero batches is supported for this tensor. + * * 1: A 4-D tensor, of shape + * [depth_out, filter_height, filter_width, depth_in], specifying the + * filter. + * For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} + * the channel dimension (SymmPerChannelQuantParams::channelDim) + * must be set to 0. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same type. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} + * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to + * bias_scale[i] = input_scale * filter_scale[i]. + * * 3: An {@link OperandType::INT32} scalar, specifying the padding on + * the left, in the ‘width’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the padding on + * the right, in the ‘width’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the padding on + * the top, in the ‘height’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the padding on + * the bottom, in the ‘height’ dimension. + * * 7: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 8: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 9: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 10: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * * 11: An optional {@link OperandType::INT32} scalar, specifying the dilation + * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped + * cells between each filter element on width dimension. If this input is set, + * input 12 (dilation factor for height) must be specified as well. + * Available since HAL version 1.2. + * * 12: An optional {@link OperandType::INT32} scalar, specifying the dilation + * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped + * cells between each filter element on height dimension. If this input is set, + * input 11 (dilation factor for width) must be specified as well. + * Available since HAL version 1.2. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input. + * Since HAL version 1.2, zero batches is supported for this tensor. + * * 1: A 4-D tensor, of shape + * [depth_out, filter_height, filter_width, depth_in], specifying the + * filter. + * For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} + * the channel dimension (SymmPerChannelQuantParams::channelDim) + * must be set to 0. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same + * type. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} + * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to + * bias_scale[i] = input_scale * filter_scale[i]. + * * 3: An {@link OperandType::INT32} scalar, specifying the implicit + * padding scheme, has to be one of the + * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. + * * 4: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 6: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 7: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * * 8: An optional {@link OperandType::INT32} scalar, specifying the dilation + * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped + * cells between each filter element on width dimension. If this input is set, + * input 9 (dilation factor for height) must be specified as well. + * Available since HAL version 1.2. + * * 9: An optional {@link OperandType::INT32} scalar, specifying the dilation + * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped + * cells between each filter element on height dimension. If this input is set, + * input 8 (dilation factor for width) must be specified as well. + * Available since HAL version 1.2. + * + * Outputs: + * * 0: The output 4-D tensor, of shape + * [batches, out_height, out_width, depth_out]. + * Before HAL version 1.2, for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the following condition must be satisfied: output_scale > input_scale * filter_scale + */ + CONV_2D = 3, + /** + * Performs a depthwise 2-D convolution operation. + * + * Given an input tensor of shape [batches, height, width, depth_in] and a + * filter tensor of shape [1, filter_height, filter_width, depth_out] + * containing depth_out convolutional filters of depth 1, DEPTHWISE_CONV + * applies a different filter to each input channel (expanding from 1 + * channel to channel_multiplier channels for each), then concatenates the + * results together. + * + * The output has depth_out = depth_in * depth_multiplier channels. + * The output dimensions are functions of the filter dimensions, stride, and + * padding. + * + * The values in the output tensor are computed as: + * + * output[b, i, j, k * channel_multiplier + q] = + * sum_{di, dj} ( + * input[b, strides[1] * i + di, strides[2] * j + dj, k] * + * filter[1, di, dj, k * channel_multiplier + q] + * ) + bias[k * channel_multiplier + q] + * + * Supported tensor {@link OperandType} configurations: + * * 32 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias. + * + * * Quantized: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * Available since HAL version 1.2: + * * 16 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias. + * + * * Quantized with symmetric per channel quantization for the filter: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output. + * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. + * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, + * * * each value scaling is separate and equal to input.scale * filter.scales[channel]). + * + * Available since HAL version 1.3: + * * Quantized signed (since HAL version 1.3): + * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3): + * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output. + * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. + * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, + * * * each value scaling is separate and equal to input.scale * filter.scales[channel]). + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input. + * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out], + * specifying the filter. + * For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} + * the channel dimension (SymmPerChannelQuantParams::channelDim) + * must be set to 3. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same type. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} + * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to + * bias_scale[i] = input_scale * filter_scale[i]. + * * 3: An {@link OperandType::INT32} scalar, specifying the padding on + * the left, in the ‘width’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the padding on + * the right, in the ‘width’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the padding on + * the top, in the ‘height’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the padding on + * the bottom, in the ‘height’ dimension. + * * 7: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 8: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 9: An {@link OperandType::INT32} scalar, specifying the depthwise + * multiplier. + * * 10: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 11: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * * 12: An optional {@link OperandType::INT32} scalar, specifying the dilation + * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped + * cells between each filter element on width dimension. If this input is set, + * input 13 (dilation factor for height) must be specified as well. + * Available since HAL version 1.2. + * * 13: An optional {@link OperandType::INT32} scalar, specifying the dilation + * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped + * cells between each filter element on height dimension. If this input is set, + * input 12 (dilation factor for width) must be specified as well. + * Available since HAL version 1.2. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input. + * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out], + * specifying the filter. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same type. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} + * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to + * bias_scale[i] = input_scale * filter_scale[i]. + * * 3: An {@link OperandType::INT32} scalar, specifying the implicit + * padding scheme, has to be one of the + * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. + * * 4: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the depthwise + * multiplier. + * * 7: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 8: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * * 9: An optional {@link OperandType::INT32} scalar, specifying the dilation + * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped + * cells between each filter element on width dimension. If this input is set, + * input 10 (dilation factor for height) must be specified as well. + * Available since HAL version 1.2. + * * 10: An optional {@link OperandType::INT32} scalar, specifying the dilation + * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped + * cells between each filter element on height dimension. If this input is set, + * input 9 (dilation factor for width) must be specified as well. + * Available since HAL version 1.2. + * + * Outputs: + * * 0: The output 4-D tensor, of shape + * [batches, out_height, out_width, depth_out]. Before HAL version 1.2, for + * output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the following condition must be satisfied: + * output_scale > input_scale * filter_scale + */ + DEPTHWISE_CONV_2D = 4, + /** + * Rearranges data from depth into blocks of spatial data. + * + * More specifically, this op outputs a copy of the input tensor where + * values from the depth dimension are moved in spatial blocks to the height + * and width dimensions. The value block_size indicates the input block size + * and how the data is moved. + * + * Chunks of data of size block_size * block_size from depth are rearranged + * into non-overlapping blocks of size block_size x block_size. + * + * The width of the output tensor is input_depth * block_size, whereas the + * height is input_height * block_size. The depth of the input tensor must + * be divisible by block_size * block_size + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Inputs: + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input. + * * 1: An {@link OperandType::INT32} scalar, specifying the block_size. + * block_size must be >=1 and block_size * block_size must be a divisor + * of the input depth. + * * 2: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batch, height*block_size, + * width*block_size, depth/(block_size*block_size)]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + DEPTH_TO_SPACE = 5, + /** + * Dequantizes the input tensor. + * + * The formula is: + * + * output = (input - zeroPoint) * scale. + * + * Supported input tensor {@link OperandType}: + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_SYMM} (since HAL version 1.2) + * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} (since HAL version 1.2) + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported output tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32}. + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: A tensor. + * Since HAL version 1.2, this tensor may be zero-sized. + * + * Outputs: + * * 0: A tensor with the same shape as input0. + */ + DEQUANTIZE = 6, + /** + * Looks up sub-tensors in the input tensor. + * + * This operator takes for input a tensor of values (Values) and + * a one-dimensional tensor of selection indices (Lookups). + * The output tensor is the concatenation of sub-tensors of Values as + * selected by Lookups. + * + * Think of Values as being sliced along its first dimension: + * The entries in Lookups select which slices are concatenated together + * to create the output tensor. + * + * For example, if Values has shape of [40, 200, 300] and + * Lookups has shape of [3], all three values found in Lookups are + * expected to be between 0 and 39. The resulting tensor must + * have shape of [3, 200, 300]. + * + * If a value in Lookups is out of bounds, the operation must fail + * and an error must be reported. + * + * Supported value tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.3) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} (since HAL version 1.2) + * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2) + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported value tensor rank: from 2 + * + * Inputs: + * * 0: Lookups. A 1-D tensor of {@link OperandType::TENSOR_INT32}. + * The values are indices into the first dimension of Values. + * * 1: Values. An n-D tensor, where n >= 2, from which sub-tensors are + * extracted. + * + * Output: + * * 0: A n-D tensor with the same rank and shape as the Values + * tensor, except for the first dimension which has the same size + * as Lookups' only dimension. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input1. + */ + EMBEDDING_LOOKUP = 7, + /** + * Computes element-wise floor() on the input tensor. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor, of the same {@link OperandType} and dimensions as + * the input tensor. + */ + FLOOR = 8, + /** + * Denotes a fully (densely) connected layer, which connects all elements + * in the input tensor with each element in the output tensor. + * + * This layer implements the operation: + * + * outputs = activation(inputs * weights’ + bias) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor of at least rank 2, specifying the input. If rank is + * greater than 2, then it gets flattened to a 2-D Tensor. The + * (flattened) 2-D Tensor is reshaped (if necessary) to + * [batch_size, input_size], where "input_size" corresponds to the + * number of inputs to the layer, matching the second dimension of + * weights, and "batch_size" is calculated by dividing the number of + * elements by "input_size". + * Since HAL version 1.2, zero batch_size is supported for this tensor. + * * 1: A 2-D tensor, specifying the weights, of shape + * [num_units, input_size], where "num_units" corresponds to the number + * of output nodes. + * * 2: A 1-D tensor, of shape [num_units], specifying the bias. For input + * tensor of {@link OperandType::TENSOR_FLOAT32}, the bias should + * also be of {@link OperandType::TENSOR_FLOAT32}. + * For input tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} + * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the bias should be of {@link OperandType::TENSOR_INT32}, + * with zeroPoint of 0 and bias_scale == input_scale * filter_scale. + * * 3: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * + * Outputs: + * * 0: The output tensor, of shape [batch_size, num_units]. Before HAL version 1.2, for + * output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the following + * condition must be satisfied: output_scale > input_scale * filter_scale. + */ + FULLY_CONNECTED = 9, + /** + * Looks up sub-tensors in the input tensor using a key-value map. + * + * This operator takes for input a tensor of values (Values), + * a one-dimensional tensor of selection values (Lookups) and + * a one-dimensional tensor that maps these values to Values + * indexes. The output tensor is the concatenation of sub-tensors of + * Values as selected by Lookups via Keys. + * + * Think of Values as being sliced along its outer-most dimension. + * The output is a concatenation of selected slices, with one slice + * for each entry of Lookups. The slice selected is the one at the + * same index as the Maps entry that matches the value in Lookups. + * + * For a hit, the corresponding sub-tensor of Values is included + * in the Output tensor. For a miss, the corresponding sub-tensor in + * Output must have zero values. + * + * For example, if Values has shape of [40, 200, 300], + * Keys should have a shape of [40]. If Lookups tensor has shape + * of [3], three slices are being concatenated, so the resulting tensor + * must have the shape of [3, 200, 300]. If the first entry in Lookups + * has the value 123456, that value must be located in Keys tensor. + * If the sixth entry of Keys contains 123456, the sixth slice of Values + * must be selected. If no entry in Keys has 123456, a slice of zeroes + * must be concatenated. + * + * Supported value tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * + * Supported value tensor rank: from 2 + * + * Inputs: + * * 0: Lookups. A 1-D {@link OperandType::TENSOR_INT32} tensor with + * shape [ k ]. + * * 1: Keys. A 1-D {@link OperandType::TENSOR_INT32} tensor with shape + * [ n ]; Keys and Values pair represent a map, i.e., the ith element + * in Keys (Keys[i]) is the key to select the ith sub-tensor in Values + * (Values[i]), where 0 <= i <= n-1. Keys tensor *MUST* be sorted in + * ascending order. + * * 2: Values. A tensor with shape of [ n, … ]; i.e., the first dimension + * must be n. + * + * Outputs: + * * 0: Output. A tensor with shape [ k …]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input2. + * * 1: Hits. A boolean tensor with shape [ k ] indicates whether the lookup + * hits (True) or not (False). + * Stored as {@link OperandType::TENSOR_QUANT8_ASYMM} with offset 0 + * and scale 1.0f. + * A non-zero byte represents True, a hit. A zero indicates otherwise. + */ + HASHTABLE_LOOKUP = 10, + /** + * Applies L2 normalization along the axis dimension. + * + * The values in the output tensor are computed as: + * + * output[batch, row, col, channel] = + * input[batch, row, col, channel] / + * sqrt(sum_{c} pow(input[batch, row, col, c], 2)) + * + * By default the axis dimension is the last dimension of the input tensor. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2) + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * Tensors with rank less than 4 are only supported since HAL version 1.2. + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be normalized. + * * 1: An optional {@link OperandType::INT32} scalar, default to -1, + * specifying the dimension normalization would be performed on. + * Negative index is used to specify axis from the end (e.g. -1 for + * the last axis). Must be in the range [-n, n). + * Available since HAL version 1.2. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} and same shape as input0. + * For {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the scale must be 1.f / 128 and the zeroPoint must be 128. + * For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the scale must be 1.f / 128 and the zeroPoint must be 0. + * + * NOTE: Before HAL version 1.3, if the elements along an axis are all zeros, + * the result is undefined. Since HAL version 1.3, if the elements along an axis + * are all zeros, the result is logical zero. + */ + L2_NORMALIZATION = 11, + /** + * Performs an 2-D L2 pooling operation. + * + * The output dimensions are functions of the filter dimensions, stride, and + * padding. + * + * The values in the output tensor are computed as: + * + * output[b, i, j, c] = + * sqrt(sum_{di, dj} pow(input[b, strides[1] * i + di, strides[2] * j + dj, c], 2) / + * sum(1)) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. + * * 1: An {@link OperandType::INT32} scalar, specifying the padding on + * the left, in the ‘width’ dimension. + * * 2: An {@link OperandType::INT32} scalar, specifying the padding on + * the right, in the ‘width’ dimension. + * * 3: An {@link OperandType::INT32} scalar, specifying the padding on + * the top, in the ‘height’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the padding on + * the bottom, in the ‘height’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 7: An {@link OperandType::INT32} scalar, specifying the filter + * width. + * * 8: An {@link OperandType::INT32} scalar, specifying the filter + * height. + * * 9: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 10: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. + * * 1: An {@link OperandType::INT32} scalar, specifying the implicit + * padding scheme, has to be one of the + * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. + * * 2: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 3: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the filter + * width. + * * 5: An {@link OperandType::INT32} scalar, specifying the filter + * height. + * * 6: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 7: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * + * Outputs: + * * 0: The output 4-D tensor, of shape + * [batches, out_height, out_width, depth]. + */ + L2_POOL_2D = 12, + /** + * Applies Local Response Normalization along the depth dimension. + * + * The 4-D input tensor is treated as a 3-D array of 1-D vectors (along the + * last dimension), and each vector is normalized independently. Within a + * given vector, each component is divided by the weighted, squared sum of + * inputs within depth_radius. + * + * The output is calculated using this formula: + * + * sqr_sum[a, b, c, d] = sum( + * pow(input[a, b, c, d - depth_radius : d + depth_radius + 1], 2)) + * output = input / pow((bias + alpha * sqr_sum), beta) + * + * For input tensor with rank less than 4, independently normalizes each + * 1-D slice along specified dimension. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: up to 4 + * Tensors with rank less than 4 are only supported since HAL version 1.2. + * + * Inputs: + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. + * * 1: An {@link OperandType::INT32} scalar, specifying the radius of + * the normalization window. + * * 2: A scalar, specifying the bias, must not be zero. + * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the bias + * value must be of {@link OperandType::FLOAT16}. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the bias + * value must be of {@link OperandType::FLOAT32}. + * * 3: A scalar, specifying the scale factor, alpha. + * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the + * alpha value must be of {@link OperandType::FLOAT16}. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the + * alpha value must be of {@link OperandType::FLOAT32}. + * * 4: A scalar, specifying the exponent, beta. + * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the beta + * value must be of {@link OperandType::FLOAT16}. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta + * value must be of {@link OperandType::FLOAT32}. + * * 5: An optional {@link OperandType::INT32} scalar, default to -1, + * specifying the dimension normalization would be performed on. + * Negative index is used to specify axis from the end (e.g. -1 for + * the last axis). Must be in the range [-n, n). + * Available since HAL version 1.2. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + LOCAL_RESPONSE_NORMALIZATION = 13, + /** + * Computes sigmoid activation on the input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = 1 / (1 + exp(-input)) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + * For {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the scale must be 1.f / 256 and the zeroPoint must be 0. + * For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the scale must be 1.f / 256 and the zeroPoint must be -128. + */ + LOGISTIC = 14, + /** + * Projects an input to a bit vector via locality senstive hashing. + * + * Supported input tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * + * Supported input tensor rank: from 1 + * + * Inputs: + * * 0: Hash functions. Dim.size == 2, DataType: Float. + * Tensor[0].Dim[0]: 15 of hash functions. + * Tensor[0].Dim[1]: 16 of projected output bits generated by each + * hash function. + * If the projection type is Sparse: + * Tensor[0].Dim[1] + ceil(log2(Tensor[0].Dim[0])) <= 32 + * + * * 1: Input. Dim.size >= 1, no restriction on DataType. + * * 2: Weight. Optional. Dim.size == 1, DataType: Float. + * If not set, each input element is considered to have the same weight + * of 1.0. + * Tensor[1].Dim[0] == Tensor[2].Dim[0] + * * 3: Type: + * Sparse: + * Value LSHProjectionType_SPARSE(=3) (since HAL version 1.2). + * Computed bit vector is considered to be sparse. + * Each output element is an int32 made up of multiple bits + * computed from hash functions. + * + * NOTE: To avoid collisions across hash functions, an offset value + * of k * (1 << Tensor[0].Dim[1]) will be added to each signature, + * where k is the index of the hash function. + * + * Value LSHProjectionType_SPARSE_DEPRECATED(=1). + * Legacy behavior that does not include the offset value. + * + * Dense: + * Value LSHProjectionType_DENSE(=2). + * Computed bit vector is considered to be dense. Each output + * element represents a bit and can take the value of either + * 0 or 1. + * + * Outputs: + * * 0: If the projection type is Sparse: + * Output.Dim == { Tensor[0].Dim[0] } + * A tensor of int32 that represents hash signatures. + * + * If the projection type is Dense: + * Output.Dim == { Tensor[0].Dim[0] * Tensor[0].Dim[1] } + * A flattened tensor that represents projected bit vectors. + * The offset value for sparse projections was added in HAL version 1.2. + */ + LSH_PROJECTION = 15, + /** + * Performs a single time step in a Long Short-Term Memory (LSTM) layer + * + * The LSTM operation is described by the following equations. + * + * \f{eqnarray*}{ + * i_t =& \sigma(W_{xi}x_t+W_{hi}h_{t-1}+W_{ci}C_{t-1}+b_i) & \\ + * f_t =& \sigma(W_{xf}x_t+W_{hf}h_{t-1}+W_{cf}C_{t-1}+b_f) & \\ + * C_t =& clip(f_t \odot C_{t-1} + i_t \odot + * g(W_{xc}x_t+W_{hc}h_{t-1}+b_c),\ t_{cell}) & \\ + * o_t =& \sigma(W_{xo}x_t+W_{ho}h_{t-1}+W_{co}C_t+b_o) & \\ + * & & \\ + * & clip(W_{proj}(o_t \odot g(C_t))+b_{proj},\ t_{proj}) + * & if\ there\ is\ a\ projection; \\ + * h_t =& & \\ + * & o_t \odot g(C_t) & otherwise. \\ + * \f} + * Where: + * * \f$x_t\f$ is the input, + * * \f$i_t\f$ is the input gate, + * * \f$f_t\f$ is the forget gate, + * * \f$C_t\f$ is the cell state, + * * \f$o_t\f$ is the output, + * * \f$h_t\f$ is the output state, + * * \f$\sigma\f$ is the logistic sigmoid function, + * * \f$g\f$ is the cell input and cell output activation function, usually + * \f$tahn\f$, + * * \f$W_{xi}\f$ is the input-to-input weight matrix, + * * \f$W_{hi}\f$ is the recurrent to input weight matrix, + * * \f$W_{ci}\f$ is the cell-to-input weight matrix, + * * \f$b_i\f$ is the input gate bias, + * * \f$W_{xf}\f$ is the input-to-forget weight matrix, + * * \f$W_{hf}\f$ is the recurrent-to-forget weight matrix, + * * \f$W_{cf}\f$ is the cell-to-forget weight matrix, + * * \f$b_f\f$ is the forget gate bias, + * * \f$W_{xc}\f$ is the input-to-cell weight matrix, + * * \f$W_{hc}\f$ is the recurrent-to-cell weight matrix, + * * \f$b_c\f$ is the cell bias, + * * \f$W_{xo}\f$ is the input-to-output weight matrix, + * * \f$W_{ho}\f$ is the recurrent-to-output weight matrix, + * * \f$W_{co}\f$ is the cell-to-output weight matrix, + * * \f$b_o\f$ is the output gate bias, + * * \f$W_{proj}\f$ is the projection weight matrix, + * * \f$b_{proj}\f$ is the projection bias, + * * \f$t_{cell}\f$ is the threshold for clipping the cell state, and + * * \f$t_{proj}\f$ is the threshold for clipping the projected output. + * * \f$\odot\f$ is the + * <a href="https://en.wikipedia.org/wiki/Hadamard_product_(matrices)"> + * Hadamard product</a> that takes two matrices and produces another + * matrix, each element of which is the product of the corresponding + * elements of the input matrices. + * + * Since HAL version 1.2 LSTM supports layer normalization. + * In case layer normalization is used, the inputs to internal activation + * functions (sigmoid and \f$g\f$) are normalized, rescaled and recentered + * following an approach from section 3.1 from + * https://arxiv.org/pdf/1607.06450.pdf + * + * The operation has the following independently optional inputs: + * * The cell-to-input weights (\f$W_{ci}\f$), cell-to-forget weights + * (\f$W_{cf}\f$) and cell-to-output weights (\f$W_{co}\f$) either all + * have values or neither of them have values (i.e., all set to null). If + * they have values, the peephole optimization is used. + * * The input-to-input weights (\f$W_{xi}\f$), recurrent-to-input weights + * (\f$W_{hi}\f$) and input gate bias (\f$b_i\f$) either all have values, + * or none of them have values. If they have no values, coupling of input + * and forget gates (CIFG) is used, in which case the input gate + * (\f$i_t\f$) is calculated using the following equation instead. + * \f{eqnarray*}{ + * i_t = 1 - f_t + * \f} + * In case peephole optimization is used and CIFG is not used + * cell-to-input (\f$W_{ci}\f$) weights must be present. Otherwise, the + * cell-to-input weights must have no value. + * * The projection weights (\f$W_{proj}\f$) is required only for the + * recurrent projection layer, and should otherwise have no value. + * * The projection bias (\f$b_{proj}\f$) may (but not required to) have a + * value if the recurrent projection layer exists, and should otherwise + * have no value. + * * (HAL version 1.2 or later) The four layer normalization weights either all have + * values or none of them have values. Additionally, if CIFG is used, + * input layer normalization weights tensor is omitted and the other layer + * normalization weights either all have values or none of them have + * values. Layer normalization is used when the values of all the layer + * normalization weights are present. + * + * References: + * + * The default non-peephole non-CIFG implementation is based on: + * http://www.bioinf.jku.at/publications/older/2604.pdf + * S. Hochreiter and J. Schmidhuber. "Long Short-Term Memory". Neural + * Computation, 9(8):1735-1780, 1997. + * + * The peephole implementation and projection layer is based on: + * https://research.google.com/pubs/archive/43905.pdf + * Hasim Sak, Andrew Senior, and Francoise Beaufays. "Long short-term memory + * recurrent neural network architectures for large scale acoustic + * modeling." INTERSPEECH, 2014. + * (However, the concept of peephole optimization was introduced in work + * prior to this paper.) + * + * The coupling of input and forget gate (CIFG) is based on: + * http://arxiv.org/pdf/1503.04069.pdf + * Greff et al. "LSTM: A Search Space Odyssey" + * + * The layer normalization is based on: + * https://arxiv.org/pdf/1607.06450.pdf + * Jimmy Ba et al. "Layer Normalization" + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * + * All input and output tensors must be of the same type. + * + * Inputs: + * * 0: The input (\f$x_t\f$). + * A 2-D tensor of shape [batch_size, input_size], where “batch_size” + * corresponds to the batching dimension, and “input_size” is the size + * of the input. + * * 1: The input-to-input weights (\f$W_{xi}\f$). Optional. + * A 2-D tensor of shape [num_units, input_size], where “num_units” + * corresponds to the number of cell units. + * * 2: The input-to-forget weights (\f$W_{xf}\f$). + * A 2-D tensor of shape [num_units, input_size]. + * * 3: The input-to-cell weights (\f$W_{xc}\f$). + * A 2-D tensor of shape [num_units, input_size]. + * * 4: The input-to-output weights (\f$W_{xo}\f$). + * A 2-D tensor of shape [num_units, input_size]. + * * 5: The recurrent-to-input weights (\f$W_{hi}\f$). Optional. + * A 2-D tensor of shape [num_units, output_size], where “output_size” + * corresponds to either the number of cell units (i.e., “num_units”), + * or the second dimension of the “projection_weights”, if defined. + * * 6: The recurrent-to-forget weights (\f$W_{hf}\f$). + * A 2-D tensor of shape [num_units, output_size]. + * * 7: The recurrent-to-cell weights (\f$W_{hc}\f$). + * A 2-D tensor of shape [num_units, output_size]. + * * 8: The recurrent-to-output weights (\f$W_{ho}\f$). + * A 2-D tensor of shape [num_units, output_size]. + * * 9: The cell-to-input weights (\f$W_{ci}\f$). Optional. + * A 1-D tensor of shape [num_units]. + * * 10:The cell-to-forget weights (\f$W_{cf}\f$). Optional. + * A 1-D tensor of shape [num_units]. + * * 11:The cell-to-output weights (\f$W_{co}\f$). Optional. + * A 1-D tensor of shape [num_units]. + * * 12:The input gate bias (\f$b_i\f$). Optional. + * A 1-D tensor of shape [num_units]. + * * 13:The forget gate bias (\f$b_f\f$). + * A 1-D tensor of shape [num_units]. + * * 14:The cell bias (\f$b_c\f$). + * A 1-D tensor of shape [num_units]. + * * 15:The output gate bias (\f$b_o\f$). + * A 1-D tensor of shape [num_units]. + * * 16:The projection weights (\f$W_{proj}\f$). Optional. + * A 2-D tensor of shape [output_size, num_units]. + * * 17:The projection bias (\f$b_{proj}\f$). Optional. + * A 1-D tensor of shape [output_size]. + * * 18:The output state (in) (\f$h_{t-1}\f$). + * A 2-D tensor of shape [batch_size, output_size]. + * * 19:The cell state (in) (\f$C_{t-1}\f$). + * A 2-D tensor of shape [batch_size, num_units]. + * * 20:The activation function (\f$g\f$). + * A value indicating the activation function: + * <ul> + * <li>0: None; + * <li>1: Relu; + * <li>3: Relu6; + * <li>4: Tanh; + * <li>6: Sigmoid. + * </ul> + * * 21:The clipping threshold (\f$t_{cell}\f$) for the cell state, such + * that values are bound within [-cell_clip, cell_clip]. If set to 0.0 + * then clipping is disabled. + * Until HAL version 1.2 this scalar must be of type {@link + * OperandType::FLOAT32}. Since HAL version 1.2, if all the input + * tensors have type {@link OperandType::TENSOR_FLOAT32}, this + * scalar must be of the type {@link OperandType::FLOAT32}, + * otherwise if all the input tensors have the type {@link + * OperandType::TENSOR_FLOAT16}, this scalar must be of type {@link + * OperandType::FLOAT16}. + * * 22:The clipping threshold (\f$t_{proj}\f$) for the output from the + * projection layer, such that values are bound within + * [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled. + * Until HAL version 1.2 this scalar must be of type {@link + * OperandType::FLOAT32}. Since HAL version 1.2, if all the input + * tensors have type {@link OperandType::TENSOR_FLOAT32}, this + * scalar must be of the type {@link OperandType::FLOAT32}, + * otherwise if all the input tensors have the type {@link + * OperandType::TENSOR_FLOAT16}, this scalar must be of type {@link + * OperandType::FLOAT16}. + * Since HAL version 1.2 there are additional inputs to this op: + * * 23:The input layer normalization weights. + * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs + * to activation at input gate. + * * 24:The forget layer normalization weights. + * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs + * to activation at forget gate. + * * 25:The cell layer normalization weights. + * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs + * to activation at cell gate. + * * 26:The output layer normalization weights. + * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs + * to activation at output gate. + * + * Outputs: + * * 0: The scratch buffer. + * A 2-D tensor of shape [batch_size, num_units * 3] with CIFG, or + * [batch_size, num_units * 4] without CIFG. + * * 1: The output state (out) (\f$h_t\f$). + * A 2-D tensor of shape [batch_size, output_size]. + * * 2: The cell state (out) (\f$C_t\f$). + * A 2-D tensor of shape [batch_size, num_units]. + * * 3: The output (\f$o_t\f$). + * A 2-D tensor of shape [batch_size, output_size]. This is effectively + * the same as the current “output state (out)” value. + */ + LSTM = 16, + /** + * Performs an 2-D max pooling operation. + * + * The output dimensions are functions of the filter dimensions, stride, and + * padding. + * + * The values in the output tensor are computed as: + * + * output[b, i, j, channel] = + * max_{di, dj} ( + * input[b, strides[1] * i + di, strides[2] * j + dj, channel] + * ) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. + * * 1: An {@link OperandType::INT32} scalar, specifying the padding on + * the left, in the ‘width’ dimension. + * * 2: An {@link OperandType::INT32} scalar, specifying the padding on + * the right, in the ‘width’ dimension. + * * 3: An {@link OperandType::INT32} scalar, specifying the padding on + * the top, in the ‘height’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the padding on + * the bottom, in the ‘height’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 7: An {@link OperandType::INT32} scalar, specifying the filter + * width. + * * 8: An {@link OperandType::INT32} scalar, specifying the filter + * height. + * * 9: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 10: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. + * * 1: An {@link OperandType::INT32} scalar, specifying the implicit + * padding scheme, has to be one of the + * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. + * * 2: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 3: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the filter + * width. + * * 5: An {@link OperandType::INT32} scalar, specifying the filter + * height. + * * 6: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 7: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * + * Outputs: + * * 0: The output 4-D tensor, of shape + * [batches, out_height, out_width, depth]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + MAX_POOL_2D = 17, + /** + * Multiplies two tensors, element-wise. + * + * Takes two input tensors of identical {@link OperandType} and compatible + * dimensions. The output is the product of both input tensors, optionally + * modified by an activation function. + * + * Two dimensions are compatible when: + * 1. they are equal, or + * 2. one of them is 1 + * + * The size of the resulting output is the maximum size along each dimension + * of the input operands. It starts with the trailing dimensions, and works + * its way forward. + * + * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero + * dimension is only compatible with 0 or 1. The size of the output + * dimension is zero if either of corresponding input dimension is zero. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType}, and compatible dimensions + * as input0. + * * 2: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * For a {@link OperandType::TENSOR_INT32} tensor, + * the {@link FusedActivationFunc} must be "NONE". + * + * Outputs: + * * 0: The product, a tensor of the same {@link OperandType} as input0. + * For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} + * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the following condition must be satisfied: + * output_scale > input1_scale * input2_scale. + */ + MUL = 18, + /** + * Computes rectified linear activation on the input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = max(0, input) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + RELU = 19, + /** + * Computes rectified linear 1 activation on the input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = min(1.f, max(-1.f, input)) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. + * + * Outputs: + * * 0: The output tensor of the same shape as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + RELU1 = 20, + /** + * Computes rectified linear 6 activation on the input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = min(6, max(0, input)) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + RELU6 = 21, + /** + * Reshapes a tensor. + * + * Given tensor, this operation returns a tensor that has the same values as + * tensor, but with a newly specified shape. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the tensor to be reshaped. + * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}, defining the + * shape of the output tensor. The number of elements implied by shape + * must be the same as the number of elements in the input tensor. + * + * If one component of shape is the special value -1, the size of that + * dimension is computed so that the total size remains constant. In + * particular, a shape of [-1] flattens into 1-D. At most one component + * of shape can be -1. + * + * Outputs: + * * 0: The output tensor, of shape specified by the input shape. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + RESHAPE = 22, + /** + * Resizes images to given size using the bilinear interpretation. + * + * Resized images must be distorted if their output aspect ratio is not the + * same as input aspect ratio. The corner pixels of output may not be the + * same as corner pixels of input. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2) + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Both resizing by shape and resizing by scale are supported. + * + * Inputs (resizing by shape): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. + * * 1: An {@link OperandType::INT32} scalar, specifying the output + * width of the output tensor. + * * 2: An {@link OperandType::INT32} scalar, specifying the output + * height of the output tensor. + * * 3: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * * 4: Align corners. An optional {@link OperandType::BOOL} + * scalar, default to false. If True, the centers of the 4 corner + * pixels of the input and output tensors are aligned, preserving the + * values at the corner pixels. + * Available since HAL version 1.3. + * * 5: Half pixel centers. An optional {@link OperandType::BOOL} + * scalar, default to false. If True, the pixel centers are assumed to + * be at (0.5, 0.5). This is the default behavior of image.resize in + * TF 2.0. If this parameter is True, then align_corners parameter + * must be False. + * Available since HAL version 1.3. + * + * Inputs (resizing by scale, since HAL version 1.2): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. Zero batches is supported for this tensor. + * * 1: A scalar, specifying width_scale, the scaling factor of the width + * dimension from the input tensor to the output tensor. The output + * width is calculated as new_width = floor(width * width_scale). + * The scalar must be of {@link OperandType::FLOAT16} if input0 is + * of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} otherwise. + * * 2: A scalar, specifying height_scale, the scaling factor of the height + * dimension from the input tensor to the output tensor. The output + * height is calculated as new_height = floor(height * height_scale). + * The scalar must be of {@link OperandType::FLOAT16} if input0 is + * of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} otherwise. + * * 3: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * * 4: Align corners. An optional {@link OperandType::BOOL} + * scalar, default to false. If True, the centers of the 4 corner + * pixels of the input and output tensors are aligned, preserving the + * values at the corner pixels. + * Available since HAL version 1.3. + * * 5: Half pixel centers. An optional {@link OperandType::BOOL} + * scalar, default to false. If True, the pixel centers are assumed to + * be at (0.5, 0.5). This is the default behavior of image.resize in + * TF 2.0. If this parameter is True, then align_corners parameter + * must be False. + * Available since HAL version 1.3. + * + * Outputs: + * * 0: The output 4-D tensor, of shape + * [batches, new_height, new_width, depth]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + RESIZE_BILINEAR = 23, + /** + * A basic recurrent neural network layer. + * + * This layer implements the operation: + * outputs = state = activation(inputs * input_weights + + * state * recurrent_weights + bias) + * + * Where: + * * “input_weights” is a weight matrix that multiplies the inputs; + * * “recurrent_weights” is a weight matrix that multiplies the current + * “state” which itself is the output from the previous time step + * computation; + * * “bias” is a bias vector (added to each output vector in the batch); + * * “activation” is the function passed as the “fused_activation_function” + * argument (if not “NONE”). + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * + * The input tensors must all be the same type. + * + * Inputs: + * * 0: input. + * A 2-D tensor of shape [batch_size, input_size], where “batch_size” + * corresponds to the batching dimension, and “input_size” is the size + * of the input. + * * 1: weights. + * A 2-D tensor of shape [num_units, input_size], where “num_units” + * corresponds to the number of units. + * * 2: recurrent_weights. + * A 2-D tensor of shape [num_units, num_units], with columns + * corresponding to the weights from each unit. + * * 3: bias. + * A 1-D tensor of shape [num_units]. + * * 4: hidden state (in). + * A 2-D tensor of shape [batch_size, num_units]. + * * 5: fused_activation_function. + * An optional {@link FusedActivationFunc} value indicating the + * activation function. If “NONE” is specified then it results in a + * linear activation. + * + * Outputs: + * * 0: hidden state (out). + * A 2-D tensor of shape [batch_size, num_units]. + * + * * 1: output. + * A 2-D tensor of shape [batch_size, num_units]. This is effectively + * the same as the current state value. + */ + RNN = 24, + /** + * Computes the softmax activation on the input tensor element-wise, per + * batch, by normalizing the input vector so the maximum coefficient is + * zero. + * + * The output is calculated using this formula: + * + * output[batch, i] = + * exp((input[batch, i] - max(input[batch, :])) * beta) / + * sum_{k}{exp((input[batch, k] - max(input[batch, :])) * beta)} + * + * For input tensor with rank other than 2, the activation will be applied + * independently on each 1-D slice along specified dimension. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4. + * Tensors with rank other than 2 or 4 are only supported since HAL version 1.2. + * + * Inputs: + * * 0: A 2-D or 4-D tensor, specifying the tensor to be reshaped. + * Since HAL version 1.2, this tensor may be zero-sized. + * * 1: A scalar, specifying the positive scaling factor for the exponent, + * beta. If input0 is of {@link OperandType::TENSOR_FLOAT32}, + * {@link OperandType::TENSOR_QUANT8_ASYMM} or + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, the scalar + * must be of {@link OperandType::FLOAT32}. + * If input0 is of {@link OperandType::TENSOR_FLOAT16}, then the + * scalar must be of {@link OperandType::FLOAT16}. + * * 2: An optional {@link OperandType::INT32} scalar, default to -1, + * specifying the dimension the activation would be performed on. + * Negative index is used to specify axis from the end (e.g. -1 for + * the last axis). Must be in the range [-n, n). + * Available since HAL version 1.2. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + * For {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the scale must be 1.f / 256 and the zeroPoint must be 0. + * For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the scale must be 1.f / 256 and the zeroPoint must be -128. + */ + SOFTMAX = 25, + /** + * Rearranges blocks of spatial data, into depth. + * + * More specifically, this op outputs a copy of the input tensor where + * values from the height and width dimensions are moved to the depth + * dimension. The value block_size indicates the input block size and how + * the data is moved. + * + * Chunks of data of size block_size * block_size from depth are rearranged + * into non-overlapping blocks of size block_size x block_size. + * + * The depth of the output tensor is input_depth * block_size * block_size. + * The input tensor's height and width must be divisible by block_size. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Inputs: + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input. + * * 1: An {@link OperandType::INT32} scalar, specifying the block_size. + * block_size must be >=1 and block_size must be a divisor of both the + * input height and width. + * * 2: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batches, height/block_size, + * width/block_size, depth_in*block_size*block_size]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + SPACE_TO_DEPTH = 26, + /** + * SVDF op is a kind of stateful layer derived from the notion that a + * densely connected layer that's processing a sequence of input frames can + * be approximated by using a singular value decomposition of each of its + * nodes. The implementation is based on: + * + * https://research.google.com/pubs/archive/43813.pdf + * + * P. Nakkiran, R. Alvarez, R. Prabhavalkar, C. Parada. + * “Compressing Deep Neural Networks using a Rank-Constrained Topology”. + * INTERSPEECH, 2015. + * + * It processes the incoming input using a 2-stage filtering mechanism: + * * stage 1 performs filtering on the "features" dimension, whose outputs + * get pushed into a memory of fixed-size memory_size. + * * stage 2 performs filtering on the "time" dimension of the memory_size + * memoized outputs of stage 1. + * + * Specifically, for rank 1, this layer implements the operation: + * + * memory = push(conv1d(inputs, weights_feature, feature_dim, + * "PADDING_VALID")); + * outputs = activation(memory * weights_time + bias); + * + * Where: + * * “weights_feature” is a weights matrix that processes the inputs (by + * convolving the input with every “feature filter”), and whose outputs + * get pushed, stacked in order, into the fixed-size “memory” (the oldest + * entry gets dropped); + * * “weights_time” is a weights matrix that processes the “memory” (by a + * batched matrix multiplication on the num_units); + * * “bias” is an optional bias vector (added to each output vector in the + * batch); and + * * “activation” is the function passed as the “fused_activation_function” + * argument (if not “NONE”). + * + * Each rank adds a dimension to the weights matrices by means of stacking + * the filters. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * + * All input tensors must be the same type. + * + * Inputs: + * * 0: input. + * A 2-D tensor of shape [batch_size, input_size], where “batch_size” + * corresponds to the batching dimension, and “input_size” is the size + * of the input. + * * 1: weights_feature. + * A 2-D tensor of shape [num_units, input_size], where “num_units” + * corresponds to the number of units. + * * 2: weights_time. + * A 2-D tensor of shape [num_units, memory_size], where “memory_size” + * corresponds to the fixed-size of the memory. + * * 3: bias. + * An optional 1-D tensor of shape [num_units]. + * * 4: state (in). + * A 2-D tensor of shape [batch_size, (memory_size - 1) * num_units * rank]. + * * 5: rank. + * The rank of the SVD approximation. + * * 6: fused_activation_function. + * An optional {@link FusedActivationFunc} value indicating the + * activation function. If “NONE” is specified then it results in a + * linear activation. + * + * Outputs: + * * 0: state (out). + * A 2-D tensor of the same {@link OperandType} as the inputs, with shape + * [batch_size, (memory_size - 1) * num_units * rank]. + * * 1: output. + * A 2-D tensor of the same {@link OperandType} as the inputs, with shape + * [batch_size, num_units]. + */ + SVDF = 27, + /** + * Computes hyperbolic tangent of input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = tanh(input) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2) + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + * For {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the scale must be 1.f / 128 and the zeroPoint must be 128. + * For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the scale must be 1.f / 128 and the zeroPoint must be 0. + */ + TANH = 28, + /** + * BatchToSpace for N-dimensional tensors. + * + * This operation reshapes the batch dimension (dimension 0) into M + 1 + * dimensions of shape block_shape + [batch], interleaves these blocks back + * into the grid defined by the spatial dimensions [1, ..., M], to obtain a + * result with the same rank as the input. + * + * This is the reverse of SpaceToBatch. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be reshaped + * * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}, the block + * sizes for each spatial dimension of the input tensor. All values + * must be >= 1. + * * 2: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since API level 29. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + BATCH_TO_SPACE_ND = 29, + /** + * Element-wise division of two tensors. + * + * Takes two input tensors of identical {@link OperandType} and compatible + * dimensions. The output is the result of dividing the first input tensor + * by the second, optionally modified by an activation function. + * + * For inputs of {@link OperandType::TENSOR_INT32}, performs + * "floor division" ("//" in Python). For example, + * 5 // 2 = 2 + * -5 // 2 = -3 + * + * Two dimensions are compatible when: + * 1. they are equal, or + * 2. one of them is 1 + * + * The size of the output is the maximum size along each dimension of the + * input operands. It starts with the trailing dimensions, and works its way + * forward. + * + * Example: + * input1.dimension = {4, 1, 2} + * input2.dimension = {5, 4, 3, 1} + * output.dimension = {5, 4, 3, 2} + * + * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero + * dimension is only compatible with 0 or 1. The size of the output + * dimension is zero if either of corresponding input dimension is zero. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, specifying the first input. + * * 1: A tensor of the same {@link OperandType}, and compatible dimensions + * as input0. + * * 2: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * For a {@link OperandType::TENSOR_INT32} tensor, + * the {@link FusedActivationFunc} must be "NONE". + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + */ + DIV = 30, + /** + * Computes the mean of elements across dimensions of a tensor. + * + * Reduces the input tensor along the given dimensions to reduce. Unless + * keep_dims is true, the rank of the tensor is reduced by 1 for each entry + * in axis. If keep_dims is true, the reduced dimensions are retained with + * length 1. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: A tensor, specifying the input. + * * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}. The dimensions + * to reduce. Must be in the range + * [-rank(input_tensor), rank(input_tensor)). + * + * NOTE: When the operation was introduced, the documentation + * incorrectly stated that if dimensions were empty, the operation + * would reduce across all dimensions. This behavior was never + * implemented. + * + * * 2: An {@link OperandType::INT32} scalar, keep_dims. If positive, + * retains reduced dimensions with length 1. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + * If all dimensions are reduced and keep_dims is false, the output + * shape is [1]. + */ + MEAN = 31, + /** + * Pads a tensor. + * + * This operation pads a tensor according to the specified paddings. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * (full support since HAL version 1.2, see the output section) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be padded. + * * 1: A 2-D Tensor of {@link OperandType::TENSOR_INT32}, the paddings + * for each spatial dimension of the input tensor. The shape of the + * tensor must be {rank(input0), 2}. + * padding[i, 0] specifies the number of elements to be padded in the + * front of dimension i. + * padding[i, 1] specifies the number of elements to be padded after the + * end of dimension i. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. The + * output tensor has the same rank as input0, and each + * dimension of the output tensor has the same size as the + * corresponding dimension of the input tensor plus the size + * of the padding: + * output0.dimension[i] = + * padding[i, 0] + input0.dimension[i] + padding[i, 1] + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + * + * NOTE: Before HAL version 1.2, the pad value for + * {@link OperandType::TENSOR_QUANT8_ASYMM} is undefined. + * Since HAL version 1.2, the pad value is always the logical zero. + */ + PAD = 32, + /** + * SpaceToBatch for N-Dimensional tensors. + * + * This operation divides "spatial" dimensions [1, ..., M] of the input into + * a grid of blocks of shape block_shape, and interleaves these blocks with + * the "batch" dimension (0) such that in the output, the spatial dimensions + * [1, ..., M] correspond to the position within the grid, and the batch + * dimension combines both the position within a spatial block and the + * original batch position. Prior to division into blocks, the spatial + * dimensions of the input are optionally zero padded according to paddings. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * (full support since HAL version 1.2, see the output section) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. + * + * Inputs: + * * 0: An n-D tensor, specifying the input. + * * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}, the block + * sizes for each spatial dimension of the input tensor. All values + * must be >= 1. + * * 2: A 2-D Tensor of {@link OperandType::TENSOR_INT32}, the paddings + * for each spatial dimension of the input tensor. All values must be + * >= 0. The shape of the tensor must be {M, 2}, where M is the number + * of spatial dimensions. + * padding[i, 0] specifies the number of element to be padded in the + * front of dimension i. + * padding[i, 1] specifies the number of element to be padded after the + * end of dimension i. + * * 3: An optional {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * Available since HAL version 1.2. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + * + * NOTE: Before HAL version 1.2, the pad value for + * {@link OperandType::TENSOR_QUANT8_ASYMM} is undefined. + * Since HAL version 1.2, the pad value is always the logical zero. + */ + SPACE_TO_BATCH_ND = 33, + /** + * Removes dimensions of size 1 from the shape of a tensor. + * + * Given a tensor input, this operation returns a tensor of the same + * {@link OperandType} with all dimensions of size 1 removed. If you don't + * want to remove all size 1 dimensions, you can remove specific size 1 + * dimensions by specifying the axes (input1). + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, the tensor to be squeezed. + * * 1: An optional 1-D tensor of {@link OperandType::TENSOR_INT32}. The + * dimensions to squeeze. If specified only squeezes the dimensions + * listed. Otherwise, squeezes all dimensions. The dimension index + * starts at 0. An error must be reported if squeezing a dimension that + * is not 1. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. Contains the + * same data as input, but has one or more dimensions of size 1 + * removed. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + * If all input dimensions are equal to 1 and are to be squeezed, the + * output shape is [1]. + */ + SQUEEZE = 34, + /** + * Extracts a strided slice of a tensor. + * + * Roughly speaking, this op extracts a slice of size (end - begin) / stride + * from the given input tensor. Starting at the location specified by begin + * the slice continues by adding stride to the index until all dimensions + * are not less than end. Note that a stride can be negative, which causes a + * reverse slice. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be sliced. + * * 1: begin, a 1-D tensor of {@link OperandType::TENSOR_INT32}. The + * starts of the dimensions of the input tensor to be sliced. The + * length must be of rank(input0). + * * 2: end, a 1-D tensor of {@link OperandType::TENSOR_INT32}. The + * ends of the dimensions of the input tensor to be sliced. The length + * must be of rank(input0). + * * 3: strides, a 1-D tensor of {@link OperandType::TENSOR_INT32}. The + * strides of the dimensions of the input tensor to be sliced. The + * length must be of rank(input0). The entries must be non-zero. + * * 4: begin_mask, an {@link OperandType::INT32} scalar. If the ith bit + * of begin_mask is set, begin[i] is ignored and the fullest possible + * range in that dimension is used instead. + * * 5: end_mask, an {@link OperandType::INT32} scalar. If the ith bit of + * end_mask is set, end[i] is ignored and the fullest possible range in + * that dimension is used instead. + * * 6: shrink_axis_mask, an {@link OperandType::INT32} scalar. If the + * ith bit of shrink_axis_mask is set, the ith dimension specification + * shrinks the dimensionality by 1, taking on the value at index + * begin[i]. In this case, the ith specification must define a + * slice of size 1, e.g. begin[i] = x, end[i] = x + 1. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0 and rank (n - k), + * where k is the number of bits set in shrink_axis_mask. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + * If shrink_axis_mask is true for all input dimensions, the output + * shape is [1]. + */ + STRIDED_SLICE = 35, + /** + * Element-wise subtraction of two tensors. + * + * Takes two input tensors of identical {@link OperandType} and compatible + * dimensions. The output is the result of subtracting the second input + * tensor from the first one, optionally modified by an activation function. + * + * Two dimensions are compatible when: + * 1. they are equal, or + * 2. one of them is 1 + * + * The size of the output is the maximum size along each dimension of the + * input operands. It starts with the trailing dimensions, and works its way + * forward. + * + * Example: + * input1.dimension = {4, 1, 2} + * input2.dimension = {5, 4, 3, 1} + * output.dimension = {5, 4, 3, 2} + * + * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero + * dimension is only compatible with 0 or 1. The size of the output + * dimension is zero if either of corresponding input dimension is zero. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2) + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, specifying the first input. + * * 1: A tensor of the same {@link OperandType}, and compatible dimensions + * as input0. + * * 2: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * For a {@link OperandType::TENSOR_INT32} tensor, + * the {@link FusedActivationFunc} must be "NONE". + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. + */ + SUB = 36, + /** + * Transposes the input tensor, permuting the dimensions according to the + * perm tensor. + * + * The returned tensor's dimension i corresponds to the input dimension + * perm[i]. If perm is not given, it is set to (n-1...0), where n is the + * rank of the input tensor. Hence by default, this operation performs a + * regular matrix transpose on 2-D input Tensors. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be transposed. + * Since HAL version 1.2, this tensor may be zero-sized. + * * 1: An optional 1-D Tensor of {@link OperandType::TENSOR_INT32}, + * the permutation of the dimensions of the input tensor. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + TRANSPOSE = 37, + /** + * Computes the absolute value of a tensor, element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3) + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + ABS = 38, + /** + * Returns the index of the largest element along an axis. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: An n-D tensor specifying the input. Must be non-empty. + * * 1: An {@link OperandType::INT32} scalar specifying the axis to + * reduce across. Negative index is used to specify axis from the + * end (e.g. -1 for the last axis). Must be in the range [-n, n). + * + * Outputs: + * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor. + * If input is 1-dimensional, the output shape is [1]. + */ + ARGMAX = 39, + /** + * Returns the index of the smallest element along an axis. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: An n-D tensor specifying the input. Must be non-empty. + * * 1: An {@link OperandType::INT32} scalar specifying the axis to + * reduce across. Negative index is used to specify axis from the + * end (e.g. -1 for the last axis). Must be in the range [-n, n). + * + * Outputs: + * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor. + * If input is 1-dimensional, the output shape is [1]. + */ + ARGMIN = 40, + /** + * Transform axis-aligned bounding box proposals using bounding box deltas. + * + * Given the positions of bounding box proposals and the corresponding + * bounding box deltas for each class, return the refined bounding box + * regions. The resulting bounding boxes are cliped against the edges of + * the image. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT16_ASYMM} + * + * Inputs: + * * 0: A 2-D Tensor of shape [num_rois, 4], specifying the locations of the + * bounding box proposals, each line with format [x1, y1, x2, y2]. + * For tensor of type {@link OperandType::TENSOR_QUANT16_ASYMM}, + * the zeroPoint must be 0 and the scale must be 0.125. Zero num_rois + * is supported for this tensor. + * * 1: A 2-D Tensor of shape [num_rois, num_classes * 4], specifying the + * bounding box delta for each region of interest and each class. The + * bounding box deltas are organized in the following order + * [dx, dy, dw, dh], where dx and dy is the relative correction factor + * for the center position of the bounding box with respect to the width + * and height, dw and dh is the log-scale relative correction factor + * for the width and height. For input0 of type + * {@link OperandType::TENSOR_QUANT16_ASYMM}, this tensor should be + * of {@link OperandType::TENSOR_QUANT8_ASYMM} or + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}. Zero num_rois is + * supported for this tensor. + * * 2: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape + * [num_rois], specifying the batch index of each box. Boxes with + * the same batch index are grouped together. Zero num_rois is + * supported for this tensor. + * * 3: A 2-D Tensor of shape [batches, 2], specifying the information of + * each image in the batch, each line with format + * [image_height, image_width]. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0, with shape + * [num_rois, num_classes * 4], specifying the coordinates of each + * output bounding box for each class, with format [x1, y1, x2, y2]. + * For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the + * scale must be 0.125 and the zero point must be 0. + */ + AXIS_ALIGNED_BBOX_TRANSFORM = 41, + /** + * A recurrent neural network layer that applies an LSTM cell to a + * sequence of inputs in forward and backward directions. + * + * The op supports cross-linking via an auxiliary input. Regular cell feeds + * one input into the two RNN cells in the following way: + * + * INPUT (INPUT_REVERSED) + * | | + * --------------------- + * | FW_LSTM BW_LSTM | + * --------------------- + * | | + * FW_OUT BW_OUT + * + * An op with cross-linking takes two inputs and feeds them into the RNN + * cells in the following way: + * + * AUX_INPUT (AUX_INPUT_REVERSED) + * | | + * INPUT | (INPUT_R'D.)| + * | | | | + * ----------------------- + * | \ / \ / | + * | FW_LSTM BW_LSTM | + * ----------------------- + * | | + * FW_OUT BW_OUT + * + * The cross-linking mode is enabled iff auxiliary input and auxiliary + * weights are present. While stacking this op on top of itself, this + * allows to connect both forward and backward outputs from previous cell + * to the next cell's input. + * + * Since HAL version 1.3 parallel linking mode is supported. The mode is + * enabled if auxiliary input is present but auxiliary weights are omitted. + * In this case, the cell feeds inputs into the RNN in the following way: + * + * INPUT (AUX_INPUT_REVERSED) + * | | + * --------------------- + * | FW_LSTM BW_LSTM | + * --------------------- + * | | + * FW_OUT BW_OUT + * + * While stacking this op on top of itself, this allows to connect both + * forward and backward outputs from previous cell to the next cell's + * corresponding inputs. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: 3, either time-major or batch-major. + * + * All input and output tensors must be of the same type. + * + * Inputs: + * * 0: The input. + * A 3-D tensor of shape: + * If time-major: [max_time, batch_size, input_size] + * If batch-major: [batch_size, max_time, input_size] + * where "max_time" is the number of timesteps (sequence length), + * "batch_size" corresponds to the batching dimension, and + * "input_size" is the size of the input. + * * 1: The forward input-to-input weights. Optional. + * A 2-D tensor of shape [fw_num_units, input_size], where “fw_num_units” + * corresponds to the number of forward cell units. + * * 2: The forward input-to-forget weights. + * A 2-D tensor of shape [fw_num_units, input_size]. + * * 3: The forward input-to-cell weights. + * A 2-D tensor of shape [fw_num_units, input_size]. + * * 4: The forward input-to-output weights. + * A 2-D tensor of shape [fw_num_units, input_size]. + * * 5: The forward recurrent-to-input weights. Optional. + * A 2-D tensor of shape [fw_num_units, fw_output_size], where “fw_output_size” + * corresponds to either the number of cell units (i.e., fw_num_units), + * or the second dimension of the “fw_projection_weights”, if defined. + * * 6: The forward recurrent-to-forget weights. + * A 2-D tensor of shape [fw_num_units, fw_output_size]. + * * 7: The forward recurrent-to-cell weights. + * A 2-D tensor of shape [fw_num_units, fw_output_size]. + * * 8: The forward recurrent-to-output weights. + * A 2-D tensor of shape [fw_num_units, fw_output_size]. + * * 9: The forward cell-to-input weights. Optional. + * A 1-D tensor of shape [fw_num_units]. + * * 10: The forward cell-to-forget weights. Optional. + * A 1-D tensor of shape [fw_num_units]. + * * 11: The forward cell-to-output weights. Optional. + * A 1-D tensor of shape [fw_num_units]. + * * 12: The forward input gate bias. Optional. + * A 1-D tensor of shape [fw_num_units]. + * * 13: The forward forget gate bias. + * A 1-D tensor of shape [fw_num_units]. + * * 14: The forward cell gate bias. + * A 1-D tensor of shape [fw_num_units]. + * * 15: The forward output gate bias. + * A 1-D tensor of shape [fw_num_units]. + * * 16: The forward projection weights. Optional. + * A 2-D tensor of shape [fw_output_size, fw_num_units]. + * * 17: The forward projection bias. Optional. + * A 1-D tensor of shape [fw_output_size]. + * * 18: The backward input-to-input weights. Optional. + * A 2-D tensor of shape [bw_num_units, input_size], where “bw_num_units” + * corresponds to the number of backward cell units. + * * 19: The backward input-to-forget weights. + * A 2-D tensor of shape [bw_num_units, input_size]. + * * 20: The backward input-to-cell weights. + * A 2-D tensor of shape [bw_num_units, input_size]. + * * 21: The backward input-to-output weights. + * A 2-D tensor of shape [bw_num_units, input_size]. + * * 22: The backward recurrent-to-input weights. Optional. + * A 2-D tensor of shape [bw_num_units, bw_output_size], where “bw_output_size” + * corresponds to either the number of cell units (i.e., “bw_num_units”), + * or the second dimension of the “bw_projection_weights”, if defined. + * * 23: The backward recurrent-to-forget weights. + * A 2-D tensor of shape [bw_num_units, bw_output_size]. + * * 24: The backward recurrent-to-cell weights. + * A 2-D tensor of shape [bw_num_units, bw_output_size]. + * * 25: The backward recurrent-to-output weights. + * A 2-D tensor of shape [bw_num_units, bw_output_size]. + * * 26: The backward cell-to-input weights. Optional. + * A 1-D tensor of shape [bw_num_units]. + * * 27: The backward cell-to-forget weights. Optional. + * A 1-D tensor of shape [bw_num_units]. + * * 28: The backward cell-to-output weights. Optional. + * A 1-D tensor of shape [bw_num_units]. + * * 29: The backward input gate bias. Optional. + * A 1-D tensor of shape [bw_num_units]. + * * 30: The backward forget gate bias. + * A 1-D tensor of shape [bw_num_units]. + * * 31: The backward cell gate bias. + * A 1-D tensor of shape [bw_num_units]. + * * 32: The backward output gate bias. + * A 1-D tensor of shape [bw_num_units]. + * * 33: The backward projection weights. Optional. + * A 2-D tensor of shape [bw_output_size, bw_num_units]. + * * 34: The backward projection bias. Optional. + * A 1-D tensor of shape [bw_output_size]. + * * 35: The forward input activation state. + * A 2-D tensor of shape [batch_size, bw_output_size]. + * * 36: The forward input cell state. + * A 2-D tensor of shape [batch_size, bw_num_units]. + * * 37: The backward input activation state. + * A 2-D tensor of shape [batch_size, bw_output_size]. + * * 38: The backward input cell state. + * A 2-D tensor of shape [batch_size, bw_num_units]. + * * 39: The auxiliary input. Optional. + * A 3-D tensor of shape [max_time, batch_size, aux_input_size], + * where “batch_size” corresponds to the batching dimension, and + * “aux_input_size” is the size of the auxiliary input. Optional. See + * the docs above for the usage modes explanation. + * * 40: The forward auxiliary input-to-input weights. + * Optional. See the docs above for the usage modes explanation. + * A 2-D tensor of shape [fw_num_units, aux_input_size]. + * * 41: The forward auxiliary input-to-forget weights. + * Optional. See the docs above for the usage modes explanation. + * A 2-D tensor of shape [fw_num_units, aux_input_size]. + * * 42: The forward auxiliary input-to-cell weights. + * Optional. See the docs above for the usage modes explanation. + * A 2-D tensor of shape [fw_num_units, aux_input_size]. + * * 43: The forward auxiliary input-to-output weights. + * Optional. See the docs above for the usage modes explanation. + * A 2-D tensor of shape [fw_num_units, aux_input_size]. + * * 44: The backward auxiliary input-to-input weights. + * Optional. See the docs above for the usage modes explanation. + * A 2-D tensor of shape [bw_num_units, aux_input_size]. + * * 45: The backward auxiliary input-to-forget weights. + * Optional. See the docs above for the usage modes explanation. + * A 2-D tensor of shape [bw_num_units, aux_input_size]. + * * 46: The backward auxiliary input-to-cell weights. + * Optional. See the docs above for the usage modes explanation. + * A 2-D tensor of shape [bw_num_units, aux_input_size]. + * * 47: The backward auxiliary input-to-output weights. + * Optional. See the docs above for the usage modes explanation. + * A 2-D tensor of shape [bw_num_units, aux_input_size]. + * * 48: The activation function. + * A value indicating the activation function: + * <ul> + * <li>0: None; + * <li>1: Relu; + * <li>3: Relu6; + * <li>4: Tanh; + * <li>6: Sigmoid. + * </ul> + * * 49: The clipping threshold for the cell state, such + * that values are bound within [-cell_clip, cell_clip]. If set to 0.0 + * then clipping is disabled. + * If all the input tensors have type {@link OperandType::TENSOR_FLOAT32}, + * this scalar must be of the type {@link OperandType::FLOAT32}, + * otherwise if all the input tensors have the type + * {@link OperandType::TENSOR_FLOAT16}, this scalar must be + * of type {@link OperandType::FLOAT16}. + * * 50: The clipping threshold for the output from the + * projection layer, such that values are bound within + * [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled. + * If all the input tensors have type {@link OperandType::TENSOR_FLOAT32}, + * this scalar must be of the type {@link OperandType::FLOAT32}, + * otherwise if all the input tensors have the type + * {@link OperandType::TENSOR_FLOAT16}, this scalar must be + * of type {@link OperandType::FLOAT16}. + * * 51: merge_outputs + * An {@link OperandType::BOOL} scalar specifying if the outputs + * from forward and backward cells should be merged. + * * 52: time_major + * An {@link OperandType::BOOL} scalar specifying the shape format + * of input and output tensors. + * * 53: The forward input layer normalization weights. Optional. + * A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs + * to activation at input gate. + * * 54: The forward forget layer normalization weights. Optional. + * A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs + * to activation at forget gate. + * * 55: The forward cell layer normalization weights. Optional. + * A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs + * to activation at cell gate. + * * 56: The forward output layer normalization weights. Optional. + * A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs + * to activation at output gate. + * * 57: The backward input layer normalization weights. Optional. + * A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs + * to activation at input gate. + * * 58: The backward forget layer normalization weights. Optional. + * A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs + * to activation at forget gate. + * * 59: The backward cell layer normalization weights. Optional. + * A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs + * to activation at cell gate. + * * 60: The backward output layer normalization weights. Optional. + * A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs + * to activation at output gate. + * + * Outputs: + * * 0: The forward output. + * A 3-D tensor of shape: + * If time-major and not merge_outputs: + * [max_time, batch_size, fw_output_size] + * If time-major and merge_outputs: + * [max_time, batch_size, fw_output_size + bw_output_size] + * If batch-major and not merge_outputs: + * [batch_size, max_time, fw_output_size] + * If batch-major and merge_outputs: + * [batch_size, max_time, fw_output_size + bw_output_size] + * * 1: The backward output. Unused if merge_outputs is true. + * A 3-D tensor of shape: + * If time-major: [max_time, batch_size, bw_output_size] + * If batch-major: [batch_size, max_time, bw_output_size] + * * 2: The forward activation state output. + * A 2-D tensor of shape [batch_size, fw_output_size] containing an + * activation state from the last time step in the sequence. This + * output is optional and can be omitted. If this output is present + * then outputs 3-5 must be present as well. + * Available since HAL version 1.3. + * * 3: The forward cell state output. + * A tensor of shape [batch_size, fw_cell_size] containing a cell state + * from the last time step in the sequence. This output is optional + * and can be omitted. If this output is present + * then outputs 2, 4, 5 must be present as well. + * Available since HAL version 1.3. + * * 4: The backward activation state output. + * A 2-D tensor of shape [batch_size, bw_output_size] containing an + * activation state from the last time step in the sequence. This + * output is optional and can be omitted. If this output is present + * then outputs 2, 3, 5 must be present as well. + * Available since HAL version 1.3. + * * 5: The backward cell state output. + * A tensor of shape [batch_size, bw_cell_size] containing a cell state + * from the last time step in the sequence. This output is optional + * and can be omitted. If this output is present + * then outputs 2-4 must be present as well. + * Available since HAL version 1.3. + */ + BIDIRECTIONAL_SEQUENCE_LSTM = 42, + /** + * A recurrent neural network layer that applies a basic RNN cell to a + * sequence of inputs in forward and backward directions. + * + * This Op unrolls the input along the sequence dimension, and implements + * the following operation for each element in the sequence s = + * 1...sequence_length: + * fw_outputs[s] = fw_state = activation(inputs[s] * fw_input_weights’ + + * fw_state * fw_recurrent_weights’ + fw_bias) + * + * And for each element in sequence t = sequence_length : 1 + * bw_outputs[t] = bw_state = activation(inputs[t] * bw_input_weights’ + + * bw_state * bw_recurrent_weights’ + bw_bias) + * + * Where: + * * “{fw,bw}_input_weights” is a weight matrix that multiplies the inputs; + * * “{fw,bw}_recurrent_weights” is a weight matrix that multiplies the + * current “state” which itself is the output from the previous time step + * computation; + * * “{fw,bw}_bias” is a bias vector (added to each output vector in the + * batch); + * * “activation” is the function passed as the “fused_activation_function” + * argument (if not “NONE”). + * + * The op supports cross-linking via an auxiliary input. Regular cell feeds + * one input into the two RNN cells in the following way: + * + * INPUT (INPUT_REVERSED) + * | | + * --------------------- + * | FW_RNN BW_RNN | + * --------------------- + * | | + * FW_OUT BW_OUT + * + * An op with cross-linking takes two inputs and feeds them into the RNN + * cells in the following way: + * + * AUX_INPUT (AUX_INPUT_REVERSED) + * | | + * INPUT | (INPUT_R'D.)| + * | | | | + * ----------------------- + * | \ / \ / | + * | FW_RNN BW_RNN | + * ----------------------- + * | | + * FW_OUT BW_OUT + * + * The cross-linking mode is enabled iff auxiliary input and auxiliary + * weights are present. While stacking this op on top of itself, this + * allows to connect both forward and backward outputs from previous cell + * to the next cell's input. + * + * Since HAL version 1.3 parallel linking mode is supported. The mode is + * enabled if auxiliary input is present but auxiliary weights are omitted. + * In this case, the cell feeds inputs into the RNN in the following way: + * + * INPUT (AUX_INPUT_REVERSED) + * | | + * --------------------- + * | FW_RNN BW_RNN | + * --------------------- + * | | + * FW_OUT BW_OUT + * + * While stacking this op on top of itself, this allows to connect both + * forward and backward outputs from previous cell to the next cell's + * corresponding inputs. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * The input tensors must all be the same type. + * + * Inputs: + * * 0: input. + * A 3-D tensor. The shape is defined by the input 6 (timeMajor). If + * it is set to true, then the input has a shape [maxTime, batchSize, + * inputSize], otherwise the input has a shape [batchSize, maxTime, + * inputSize]. + * * 1: fwWeights. + * A 2-D tensor of shape [fwNumUnits, inputSize]. + * * 2: fwRecurrentWeights. + * A 2-D tensor of shape [fwNumUnits, fwNumUnits]. + * * 3: fwBias. + * A 1-D tensor of shape [fwNumUnits]. + * * 4: fwHiddenState. + * A 2-D tensor of shape [batchSize, fwNumUnits]. Specifies a hidden + * state input for the first time step of the computation. + * * 5: bwWeights. + * A 2-D tensor of shape [bwNumUnits, inputSize]. + * * 6: bwRecurrentWeights. + * A 2-D tensor of shape [bwNumUnits, bwNumUnits]. + * * 7: bwBias. + * A 1-D tensor of shape [bwNumUnits]. + * * 8: bwHiddenState + * A 2-D tensor of shape [batchSize, bwNumUnits]. Specifies a hidden + * state input for the first time step of the computation. + * * 9: auxInput. + * A 3-D tensor. The shape is defined by the input 6 (timeMajor). If + * it is set to true, then the input has a shape [maxTime, batchSize, + * auxInputSize], otherwise the input has a shape [batchSize, maxTime, + * auxInputSize]. Can be omitted. See the docs above for the usage + * modes explanation. + * * 10:fwAuxWeights. + * A 2-D tensor of shape [fwNumUnits, auxInputSize]. Can be omitted. + * See the docs above for the usage modes explanation. + * * 11:bwAuxWeights. + * A 2-D tensor of shape [bwNumUnits, auxInputSize]. Can be omitted. + * See the docs above for the usage modes explanation. + * * 12:fusedActivationFunction. + * A {@link FusedActivationFunc} value indicating the activation function. If + * “NONE” is specified then it results in a linear activation. + * * 13:timeMajor + * An {@link OperandType::BOOL} scalar specifying the shape format + * of input and output tensors. + * * 14:mergeOutputs + * An {@link OperandType::BOOL} scalar specifying if the outputs + * from forward and backward cells are separate (if set to false) or + * concatenated (if set to true). + * Outputs: + * * 0: fwOutput. + * A 3-D tensor. The first two dimensions of the shape are defined by + * the input 6 (timeMajor) and the third dimension is defined by the + * input 14 (mergeOutputs). If timeMajor is set to true, then the first + * two dimensions are [maxTime, batchSize], otherwise they are set to + * [batchSize, maxTime]. If mergeOutputs is set to true, then the third + * dimension is equal to (fwNumUnits + bwNumUnits), otherwise it is set + * to fwNumUnits. + * * 1: bwOutput. + * A 3-D tensor. If the input 14 (mergeOutputs) is set to true, then + * this tensor is not produced. The shape is defined by the input 6 + * (timeMajor). If it is set to true, then the shape is set to + * [maxTime, batchSize, bwNumUnits], otherwise the shape is set to + * [batchSize, maxTime, bwNumUnits]. + * * 2: The forward hidden state output. + * A 2-D tensor of shape [batchSize, fwNumUnits] containing a hidden + * state from the last time step in the sequence. This output is + * optional and can be omitted. If this output is present then output + * 3 must be present as well. + * Available since HAL version 1.3. + * * 3: The backward hidden state output. + * A 2-D tensor of shape [batchSize, bwNumUnits] containing a hidden + * state from the last time step in the sequence. This output is + * optional and can be omitted. If this output is present then output + * 2 must be present as well. + * Available since HAL version 1.3. + */ + BIDIRECTIONAL_SEQUENCE_RNN = 43, + /** + * Greedily selects a subset of bounding boxes in descending order of score. + * + * This op applies NMS algorithm to each class. In each loop of execution, + * the box with maximum score gets selected and removed from the pending set. + * The scores of the rest of boxes are lowered according to the + * intersection-over-union (IOU) overlapping with the previously selected + * boxes and a specified NMS kernel method. Any boxes with score less + * than a threshold are removed from the pending set. + * + * Three NMS kernels are supported: + * * Hard: score_new = score_old * (1 if IoU < threshold else 0) + * * Linear: score_new = score_old * (1 if IoU < threshold else 1 - IoU) + * * Gaussian: score_new = score_old * exp(- IoU^2 / sigma) + * + * Axis-aligned bounding boxes are represented by its upper-left corner + * coordinate (x1,y1) and lower-right corner coordinate (x2,y2). A valid + * bounding box should satisfy x1 <= x2 and y1 <= y2. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Inputs: + * * 0: A 2-D Tensor of shape [num_rois, num_classes], specifying the score + * of each bounding box proposal. The boxes are grouped by batches in the + * first dimension. Zero num_rois is supported for this tensor. + * * 1: A 2-D Tensor specifying the bounding boxes of shape + * [num_rois, num_classes * 4], organized in the order [x1, y1, x2, y2]. + * The boxes are grouped by batches in the first dimension. The sequential + * order of the boxes corresponds with input0. For input0 of type + * {@link OperandType::TENSOR_QUANT8_ASYMM}, this tensor should be of + * {@link OperandType::TENSOR_QUANT16_ASYMM}, with zeroPoint of 0 and + * scale of 0.125. + * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * this tensor should be of {@link OperandType::TENSOR_QUANT16_ASYMM}, + * with zeroPoint of -128 and scale of 0.125. + * Zero num_rois is supported for this tensor. + * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape + * [num_rois], specifying the batch index of each box. Boxes with + * the same batch index are grouped together. + * * 3: An {@link OperandType::FLOAT32} scalar, score_threshold. Boxes + * with scores lower than the threshold are filtered before sending + * to the NMS algorithm. + * * 4: An {@link OperandType::INT32} scalar, specifying the maximum + * number of selected bounding boxes for each image. Set to a negative + * value for unlimited number of output bounding boxes. + * * 5: An {@link OperandType::INT32} scalar, specifying the NMS + * kernel method, options are 0:hard, 1:linear, 2:gaussian. + * * 6: An {@link OperandType::FLOAT32} scalar, specifying the IoU + * threshold in hard and linear NMS kernel. This field is ignored if + * gaussian kernel is selected. + * * 7: An {@link OperandType::FLOAT32} scalar, specifying the sigma in + * gaussian NMS kernel. This field is ignored if gaussian kernel is + * not selected. + * * 8: An {@link OperandType::FLOAT32} scalar, nms_score_threshold. + * Boxes with scores lower than the threshold are dropped during the + * score updating phase in soft NMS. + * + * Outputs: + * * 0: A 1-D Tensor of the same {@link OperandType} as input0, with shape + * [num_output_rois], specifying the score of each output box. The boxes + * are grouped by batches, but the sequential order in each batch is not + * guaranteed. For type of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * guaranteed. For type of {@link OperandType::TENSOR_QUANT8_ASYMM} + * or {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the scale and zero point must be the same as input0. + * * 1: A 2-D Tensor of the same {@link OperandType} as input1, with shape + * [num_output_rois, 4], specifying the coordinates of each + * output bounding box with the same format as input1. The sequential + * order of the boxes corresponds with output0. For type of + * {@link OperandType::TENSOR_QUANT16_ASYMM}, the scale must be + * 0.125 and the zero point must be 0. + * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape + * [num_output_rois], specifying the class of each output box. The + * sequential order of the boxes corresponds with output0. + * * 3: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape + * [num_output_rois], specifying the batch index of each box. Boxes + * with the same batch index are grouped together. + */ + BOX_WITH_NMS_LIMIT = 44, + /** + * Casts a tensor to a type. + * + * This operation ignores the scale and zeroPoint of quanized tensors, + * e.g. it treats a {@link OperandType::TENSOR_QUANT8_ASYMM} input + * as a tensor of uint8 values. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * Since HAL version 1.3, casting tensors of the following + * {@link OperandType} to the same {@link OperandType} is supported: + * * {@link OperandType::TENSOR_BOOL8} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT16_ASYMM} + * * {@link OperandType::TENSOR_QUANT16_SYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * * {@link OperandType::TENSOR_QUANT8_SYMM} + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: A tensor with the same shape as input0. + */ + CAST = 45, + /** + * Shuffle the channels of the input tensor. + * + * Given an input tensor and a integer value of num_groups, CHANNEL_SHUFFLE + * divide the channel dimension into num_groups groups, and reorganize the + * channels by grouping channels with the same index in each group. + * + * Along the channel dimension, the output is calculated using this formula: + * + * output_channel[k * num_groups + g] = input_channel[g * group_size + k] + * + * where group_size = num_channels / num_groups + * + * The number of channels must be divisible by num_groups. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be shuffled. + * * 1: An {@link OperandType::INT32} scalar, specifying the number of + * groups. + * * 2: An {@link OperandType::INT32} scalar, specifying the dimension + * channel shuffle would be performed on. Negative index is used to + * specify axis from the end (e.g. -1 for the last axis). Must be in + * the range [-n, n). + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} and same shape as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + CHANNEL_SHUFFLE = 46, + /** + * Apply postprocessing steps to bounding box detections. + * + * Bounding box detections are generated by applying transformation on a set + * of predefined anchors with the bounding box deltas from bounding box + * regression. A final step of hard NMS is applied to limit the number of + * returned boxes. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Inputs: + * * 0: A 3-D Tensor of shape [batches, num_anchors, num_classes], specifying + * the score of each anchor with each class. Class 0 for each + * [batches, num_anchors, 0] is background and will be ignored. + * * 1: A 3-D Tensor of shape [batches, num_anchors, length_box_encoding], with + * the first four values in length_box_encoding specifying the bounding + * box deltas. The box deltas are encoded in the order of [dy, dx, dh, dw], + * where dy and dx is the linear-scale relative correction factor for the + * center position of the bounding box with respect to the width and height, + * dh and dw is the log-scale relative correction factor for the width and + * height. All the entries in length_box_encoding beyond the first four + * values are ignored in this operation. + * * 2: A 2-D Tensor of shape [num_anchors, 4], specifying the shape of each + * predefined anchor, with format [ctr_y, ctr_x, h, w], where ctr_y and + * ctr_x are the center position of the box, and h and w are the height + * and the width. + * * 3: An {@link OperandType::FLOAT32} scalar, specifying the scaling + * factor for dy in bounding box deltas. + * * 4: An {@link OperandType::FLOAT32} scalar, specifying the scaling + * factor for dx in bounding box deltas. + * * 5: An {@link OperandType::FLOAT32} scalar, specifying the scaling + * factor for dh in bounding box deltas. + * * 6: An {@link OperandType::FLOAT32} scalar, specifying the scaling + * factor for dw in bounding box deltas. + * * 7: An {@link OperandType::BOOL} scalar, set to true to use regular + * multi-class NMS algorithm that do NMS separately for each class, + * set to false for a faster algorithm that only do one single NMS + * using the highest class score.. + * * 8: An {@link OperandType::INT32} scalar, max_num_detections, specifying + * the maximum number of boxes for the output. Boxes with the lowest + * scores are discarded to meet the limit. + * * 9: An {@link OperandType::INT32} scalar, only used when input7 is + * set to false, specifying the maximum number of classes per detection. + * * 10: An {@link OperandType::INT32} scalar, only used when input7 is + * set to true, specifying the maximum number of detections when + * applying NMS algorithm for each single class. + * * 11: A scalar, score_threshold. Boxes with scores lower than the + * threshold are filtered before sending to the NMS algorithm. The + * scalar must be of {@link OperandType::FLOAT16} if input0 is of + * {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. + * * 12: A scalar, specifying the IoU threshold for hard NMS. The scalar + * must be of {@link OperandType::FLOAT16} if input0 is of + * {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. + * * 13: An {@link OperandType::BOOL} scalar, set to true to include + * background class in the list of label map for the output, set + * to false to not include the background. When the background + * class is included, it has label 0 and the output classes start + * at 1 in the label map, otherwise, the output classes start at 0. + * + * Outputs: + * * 0: A 2-D tensor of the same {@link OperandType} as input0, with shape + * [batches, max_num_detections], specifying the score of each output + * detections. + * * 1: A 3-D tensor of shape [batches, max_num_detections, 4], specifying the + * coordinates of each output bounding box, with format + * [y1, x1, y2, x2]. + * * 2: A 2-D {@link OperandType::TENSOR_INT32} tensor, of shape + * [batches, max_num_detections], specifying the class label for each + * output detection. + * * 3: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape [batches], + * specifying the number of valid output detections for each batch. + */ + DETECTION_POSTPROCESSING = 47, + /** + * For input tensors x and y, computes x == y elementwise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * This operation supports broadcasting. + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType} and dimensions compatible + * with input0. + * + * Outputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + */ + EQUAL = 48, + /** + * Computes exponential of x element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + EXP = 49, + /** + * Inserts a dimension of 1 into a tensor's shape. + * + * Given a tensor input, this operation inserts a dimension of 1 at the + * given dimension index of input's shape. The dimension index starts at + * zero; if you specify a negative dimension index, it is counted backward + * from the end. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: An n-D tensor. + * * 1: An {@link OperandType::INT32} scalar specifying the dimension + * index to expand. Must be in the range [-(n + 1), (n + 1)). + * + * Outputs: + * * 0: An (n + 1)-D tensor with the same {@link OperandType} and data as + * input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + EXPAND_DIMS = 50, + /** + * Gathers values along an axis. + * + * Produces an output tensor with shape + * input0.dimension[:axis] + indices.dimension + input0.dimension[axis + 1:] + * where: + * # Vector indices (output is rank(input0)). + * output[a_0, ..., a_n, i, b_0, ..., b_n] = + * input0[a_0, ..., a_n, indices[i], b_0, ..., b_n] + * + * # Higher rank indices (output is rank(input0) + rank(indices) - 1). + * output[a_0, ..., a_n, i, ..., j, b_0, ... b_n] = + * input0[a_0, ..., a_n, indices[i, ..., j], b_0, ..., b_n] + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: An n-D tensor from which to gather values. + * * 1: An {@link OperandType::INT32} scalar specifying the axis. + * Negative index is used to specify axis from the end + * (e.g. -1 for the last axis). Must be in the range [-n, n). + * * 2: A k-D tensor {@link OperandType::TENSOR_INT32} of indices. + * The values must be in the bounds of the corresponding dimensions + * of input0. + * + * Outputs: + * * 0: An (n + k - 1)-D tensor with the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + GATHER = 51, + /** + * Generate aixs-aligned bounding box proposals. + * + * Bounding box proposals are generated by applying transformation on a set + * of predefined anchors with the bounding box deltas from bounding box + * regression. A final step of hard NMS is applied to limit the number of + * returned boxes. + * + * Axis-aligned bounding boxes are represented by its upper-left corner + * coordinate (x1,y1) and lower-right corner coordinate (x2,y2). A valid + * bounding box should satisfy x1 <= x2 and y1 <= y2. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Inputs: + * * 0: A 4-D Tensor specifying the score of each anchor at each + * location. With "NHWC" data layout, the tensor shape is + * [batches, height, width, num_anchors]. With "NCHW" data layout, + * the tensor shape is [batches, num_anchors, height, width]. + * * 1: A 4-D Tensor specifying the bounding box deltas. With "NHWC" data + * layout, the tensor shape is [batches, height, width, num_anchors * 4]. + * With "NCHW" data layout, the tensor shape is + * [batches, num_anchors * 4, height, width]. The box deltas are encoded + * in the order of [dx, dy, dw, dh], where dx and dy is the linear-scale + * relative correction factor for the center position of the bounding box + * with respect to the width and height, dw and dh is the log-scale + * relative correction factor for the width and height. The last + * dimensions is the channel dimension. + * * 2: A 2-D Tensor of shape [num_anchors, 4], specifying the shape of each + * predefined anchor, with format [x1, y1, x2, y2]. For input0 of type + * {@link OperandType::TENSOR_QUANT8_ASYMM} or + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, this tensor should be of + * {@link OperandType::TENSOR_QUANT16_SYMM}, with scale of 0.125. + * * 3: A 2-D Tensor of shape [batches, 2], specifying the size of + * each image in the batch, with format [image_height, image_width]. + * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM} or + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, this + * tensor should be of {@link OperandType::TENSOR_QUANT16_SYMM}, with + * scale of 0.125. + * * 4: An {@link OperandType::FLOAT32} scalar, specifying the ratio + * from the height of original image to the height of feature map. + * * 5: An {@link OperandType::FLOAT32} scalar, specifying the ratio + * from the width of original image to the width of feature map. + * * 6: An {@link OperandType::INT32} scalar, specifying the maximum + * number of boxes before going into the hard NMS algorithm. Boxes + * with the lowest scores are discarded to meet the limit. Set to + * a non-positive value for unlimited number. + * * 7: An {@link OperandType::INT32} scalar, specifying the maximum + * number of boxes returning from the hard NMS algorithm. Boxes + * with the lowest scores are discarded to meet the limit. Set to + * a non-positive value for unlimited number. + * * 8: An {@link OperandType::FLOAT32} scalar, specifying the IoU + * threshold for hard NMS. + * * 9: An {@link OperandType::FLOAT32} scalar, min_size. Boxes with + * height or width lower than the absolute threshold are filtered out. + * * 10: An {@link OperandType::BOOL} scalar, set to true to specify + * NCHW data layout for input0 and input1. Set to false for NHWC. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0, of shape + * [num_output_rois], specifying the score of each output box. + * The boxes are grouped by batches, but the sequential order in + * each batch is not guaranteed. For type of + * {@link OperandType::TENSOR_QUANT8_ASYMM} or + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, the scale and zero + * point must be the same as input0. + * * 1: A tensor of the same {@link OperandType} as input3, of shape + * [num_output_rois, 4], specifying the coordinates of each output + * bounding box for each class, with format [x1, y1, x2, y2]. + * The sequential order of the boxes corresponds with output0. + * For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the + * scale must be 0.125 and the zero point must be 0. + * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape + * [num_output_rois], specifying the batch index of each box. Boxes + * with the same batch index are grouped together. + */ + GENERATE_PROPOSALS = 52, + /** + * For input tensors x and y, computes x > y elementwise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * This operation supports broadcasting. + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType} and dimensions compatible + * with input0. + * + * Outputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + */ + GREATER = 53, + /** + * For input tensors x and y, computes x >= y elementwise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * This operation supports broadcasting. + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType} and dimensions compatible + * with input0. + * + * Outputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + */ + GREATER_EQUAL = 54, + /** + * Performs a grouped 2-D convolution operation. + * + * Given an input tensor of shape [batches, height, width, depth_in] and a + * filter tensor of shape [depth_out, filter_height, filter_width, depth_group] + * containing depth_out convolutional filters of depth depth_group, GROUPED_CONV + * applies a group of different filters to each input channel group, then + * concatenates the results together. + * + * Specifically, the input channels are divided into num_groups groups, each with + * depth depth_group, i.e. depth_in = num_groups * depth_group. The convolutional + * filters are also divided into num_groups groups, i.e. depth_out is divisible + * by num_groups. GROUPED_CONV applies each group of filters to the corresponding + * input channel group, and the result are concatenated together. + * + * The output dimensions are functions of the filter dimensions, stride, and + * padding. + * + * The values in the output tensor are computed as: + * + * output[b, i, j, g * channel_multiplier + q] = + * sum_{di, dj, dk} ( + * input[b, strides[1] * i + di, strides[2] * j + dj, + * g * depth_group + dk] * + * filter[g * channel_multiplier + q, di, dj, dk] + * ) + bias[channel] + * + * where channel_multiplier = depth_out / num_groups + * + * Supported tensor {@link OperandType} configurations: + * * 16 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias. + * + * * 32 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias. + * + * * Quantized: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * * Quantized signed (since HAL version 1.3): + * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * * Quantized with symmetric per channel quantization for the filter: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output. + * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. + * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, + * * * each value scaling is separate and equal to input.scale * filter.scales[channel]). + * + * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3): + * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output. + * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. + * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, + * * * each value scaling is separate and equal to input.scale * filter.scales[channel]). + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input, where depth_in = num_groups * depth_group. + * * 1: A 4-D tensor, of shape + * [depth_out, filter_height, filter_width, depth_group], specifying + * the filter, where depth_out must be divisible by num_groups. For + * tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} + * the channel dimension (channelDim at + * {@link SymmPerChannelQuantParams}) must be set to 0. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input + * tensor of type {@link OperandType::TENSOR_FLOAT32} or + * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same type. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. For filter tensor + * of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias + * should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of + * 0 and bias_scale of 0. The actual scale of each value 'i' is equal to + * bias_scale[i] = input_scale * filter_scale[i]. + * * 3: An {@link OperandType::INT32} scalar, specifying the padding on + * the left, in the ‘width’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the padding on + * the right, in the ‘width’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the padding on + * the top, in the ‘height’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the padding on + * the bottom, in the ‘height’ dimension. + * * 7: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 8: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 9: An {@link OperandType::INT32} scalar, specifying the number of + * groups. + * * 10: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 11: An {@link OperandType::BOOL} scalar, set to true to specify + * NCHW data layout for input0 and output0. Set to false for NHWC. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input, where depth_in = num_groups * depth_group. + * * 1: A 4-D tensor, of shape + * [depth_out, filter_height, filter_width, depth_group], specifying + * the filter, where depth_out must be divisible by num_groups. For + * tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} + * the channel dimension (SymmPerChannelQuantParams::channelDim) + * must be set to 0. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input + * tensor of type {@link OperandType::TENSOR_FLOAT32} or + * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same + * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same type. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. For filter tensor + * of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias + * should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of + * 0 and bias_scale of 0. The actual scale of each value 'i' is equal to + * bias_scale[i] = input_scale * filter_scale[i]. + * * 3: An {@link OperandType::INT32} scalar, specifying the implicit + * padding scheme, has to be one of the + * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. + * * 4: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the number of + * groups. + * * 7: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 8: An {@link OperandType::BOOL} scalar, set to true to specify + * NCHW data layout for input0 and output0. Set to false for NHWC. + * + * Outputs: + * * 0: The output 4-D tensor, of shape + * [batches, out_height, out_width, depth_out]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. + */ + GROUPED_CONV_2D = 55, + /** + * Localize the maximum keypoints from heatmaps. + * + * This operation approximates the accurate maximum keypoint scores and + * indices after bicubic upscaling by using Taylor expansion up to the + * quadratic term. + * + * The bounding box is represented by its upper-left corner coordinate + * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image. + * A valid bounding box should satisfy x1 <= x2 and y1 <= y2. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * + * Inputs: + * * 0: A 4-D Tensor of shape + * [num_boxes, heatmap_size, heatmap_size, num_keypoints], + * specifying the heatmaps, the height and width of heatmaps should + * be the same, and must be greater than or equal to 2. + * * 1: A 2-D Tensor of shape [num_boxes, 4], specifying the bounding boxes, + * each with format [x1, y1, x2, y2]. For input0 of type + * {@link OperandType::TENSOR_QUANT8_ASYMM}, this tensor should + * be of {@link OperandType::TENSOR_QUANT16_ASYMM}, with zeroPoint + * of 0 and scale of 0.125. + * For input0 of type + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, this tensor + * should be of {@link OperandType::TENSOR_QUANT16_ASYMM}, with + * zeroPoint of -128 and scale of 0.125. + * * 2: An {@link OperandType::BOOL} scalar, set to true to specify + * NCHW data layout for input0. Set to false for NHWC. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0, with shape + * [num_boxes, num_keypoints], specifying score of the keypoints. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} or + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint can be different from input0 scale and zeroPoint. + * * 1: A tensor of the same {@link OperandType} as input1, with shape + * [num_boxes, num_keypoints, 2], specifying the location of + * the keypoints, the second dimension is organized as + * [keypoint_x, keypoint_y]. + * For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the + * scale must be 0.125 and the zero point must be 0. + */ + HEATMAP_MAX_KEYPOINT = 56, + /** + * Applies instance normalization to the input tensor. + * + * The values in the output tensor are computed as: + * + * output[b, h, w, c] = + * (input[b, h, w, c] - mean[b, c]) * gamma / + * sqrt(var[b, c] + epsilon) + beta + * + * Where the mean and variance are computed across the spatial dimensions: + * + * mean[b, c] = + * sum_{h, w}(input[b, h, w, c]) / sum(1) + * + * var[b, c] = + * sum_{h, w}(pow(input[b, h, w, c] - mean[b, c], 2)) / sum(1) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be normalized. + * * 1: A scalar, specifying gamma, the scale applied to the normalized + * tensor. The scalar must be of {@link OperandType::FLOAT16} if + * input0 is of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. + * * 2: A scalar, specifying beta, the offset applied to the normalized + * tensor. The scalar must be of {@link OperandType::FLOAT16} if + * input0 is of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. + * * 3: A scalar, specifying epsilon, the small value added to variance to + * avoid dividing by zero. The scalar must be of {@link OperandType::FLOAT16} if + * input0 is of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. + * * 4: An {@link OperandType::BOOL} scalar, set to true to specify + * NCHW data layout for input0 and output0. Set to false for NHWC. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} and same shape as input0. + */ + INSTANCE_NORMALIZATION = 57, + /** + * For input tensors x and y, computes x < y elementwise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * This operation supports broadcasting. + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType} and dimensions compatible + * with input0. + * + * Outputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + */ + LESS = 58, + /** + * For input tensors x and y, computes x <= y elementwise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * This operation supports broadcasting. + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType} and dimensions compatible + * with input0. + * + * Outputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + */ + LESS_EQUAL = 59, + /** + * Computes natural logarithm of x element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + LOG = 60, + /** + * Returns the truth value of x AND y element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * + * Supported tensor rank: from 1 + * + * This operation supports broadcasting. + * + * Inputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + * * 1: A tensor of {@link OperandType::TENSOR_BOOL8} and dimensions + * compatible with input0. + * + * Outputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + */ + LOGICAL_AND = 61, + /** + * Computes the truth value of NOT x element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + LOGICAL_NOT = 62, + /** + * Returns the truth value of x OR y element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * + * Supported tensor rank: from 1 + * + * This operation supports broadcasting. + * + * Inputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + * * 1: A tensor of {@link OperandType::TENSOR_BOOL8} and dimensions + * compatible with input0. + * + * Outputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + */ + LOGICAL_OR = 63, + /** + * Computes the log softmax activations given logits. + * + * The output is calculated using this formula: + * + * output = logits * beta - log(reduce_sum(exp(logits * beta), axis)) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor specifying the input logits. + * * 1: A scalar, specifying the positive scaling factor for the exponent, + * beta. + * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the beta + * value must be of {@link OperandType::FLOAT16}. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta + * value must be of {@link OperandType::FLOAT32}. + * * 2: An {@link OperandType::INT32} scalar specifying the axis to + * reduce across. Negative index is used to specify axis from the + * end (e.g. -1 for the last axis). Must be in the range [-n, n). + * + * Outputs: + * * 0: The output tensor of the same {@link OperandType} and shape as + * input0. + */ + LOG_SOFTMAX = 64, + /** + * Returns the element-wise maximum of two tensors. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType} and compatible dimensions + * with input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scales and zeroPoint can be different from input0 scale and zeroPoint. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. + */ + MAXIMUM = 65, + /** + * Returns the element-wise minimum of two tensors. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType} and compatible dimensions + * with input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scales and zeroPoint can be different from input0 scale and zeroPoint. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. + */ + MINIMUM = 66, + /** + * Computes numerical negative value element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + NEG = 67, + /** + * For input tensors x and y, computes x != y elementwise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * This operation supports broadcasting. + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same {@link OperandType} and dimensions compatible + * with input0. + * + * Outputs: + * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. + */ + NOT_EQUAL = 68, + /** + * Pads a tensor with the given constant value according to the specified + * paddings. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be padded. + * * 1: A 2-D Tensor of {@link OperandType::TENSOR_INT32}, the paddings + * for each spatial dimension of the input tensor. The shape of the + * tensor must be {rank(input0), 2}. + * padding[i, 0] specifies the number of elements to be padded in the + * front of dimension i. + * padding[i, 1] specifies the number of elements to be padded after + * the end of dimension i. + * * 2: An scalar specifying the value to use for padding input0. + * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the + * pad value must be of {@link OperandType::FLOAT16}. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the + * pad value must be of {@link OperandType::FLOAT32}. + * For input tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the pad value must be of {@link OperandType::INT32}. The + * scale and zeroPoint are assumed to be the same as in input0. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. The + * output tensor has the same rank as input0, and each + * dimension of the output tensor has the same size as the + * corresponding dimension of the input tensor plus the size + * of the padding: + * output0.dimension[i] = + * padding[i, 0] + input0.dimension[i] + padding[i, 1] + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + PAD_V2 = 69, + /** + * Computes the power of one value to another. + * + * Given a tensor base and a tensor exponent, this operation computes + * base^exponent elementwise. + * + * This operations supports broadcasting. The size of the output is the + * maximum size along each dimension of the input operands. It starts with + * the trailing dimensions, and works its way forward. + * + * For example: + * base.dimension = {4, 1, 2} + * exponent.dimension = {5, 4, 3, 1} + * output.dimension = {5, 4, 3, 2} + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: A tensor specifying the base. + * * 1: A tensor specifying the exponent. + * + * Outputs: + * * 0: An output tensor. + */ + POW = 70, + /** + * Parametric Rectified Linear Unit. + * + * It follows: f(x) = alpha * x for x < 0, f(x) = x for x >= 0, where alpha + * is a learned array with the same {@link OperandType} and compatible + * dimensions as input x. + * + * Two dimensions are compatible when: + * 1. they are equal, or + * 2. one of them is 1 + * + * The size of the output is the maximum size along each dimension of the + * input operands. It starts with the trailing dimensions, and works its way + * forward. + * + * Example: + * input.dimension = {4, 1, 2} + * alpha.dimension = {5, 4, 3, 1} + * output.dimension = {5, 4, 3, 2} + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: A tensor, specifying the input. + * * 1: A tensor of the same {@link OperandType}, and compatible dimensions + * as input0, specifying the alpha. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scales and zeroPoint can be different from input0 scale and zeroPoint. + */ + PRELU = 71, + /** + * Quantizes the input tensor. + * + * The formula for {@link OperandType::TENSOR_QUANT8_ASYMM} output tensor is: + * + * output = max(0, min(255, round(input / scale) + zeroPoint) + * + * The formula for {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} output + * tensor is: + * + * output = max(-128, min(127, round(input / scale) + zeroPoint) + * + * Supported input tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported output tensor {@link OperandType}: + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: A tensor, may be zero-sized. + * + * Outputs: + * * 0: The output tensor of same shape as input0, but with + * {@link OperandType::TENSOR_QUANT8_ASYMM} or. + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}. + */ + QUANTIZE = 72, + /** + * A version of quantized LSTM, using 16 bit quantization for internal + * state. + * + * There is no projection layer, so cell state size is equal to the output + * size. + * + * Inputs: + * * 0: A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [numBatches, inputSize] specifying the input to the LSTM + * cell. Tensor is quantized with a fixed quantization range of + * [-1, 127/128] (scale = 1/128, zeroPoint = 128). + * * 1: The input-to-input weights. + * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [outputSize, inputSize] specifying input-to-input part of + * weights for fully-connected layer inside the LSTM cell. + * Quantization zero point and scale must be the same across all the + * weights. + * * 2: The input-to-forget weights. + * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [outputSize, inputSize] specifying input-to-forget part of + * weights for fully-connected layer inside the LSTM cell. + * Quantization zero point and scale must be the same across all the + * weights. + * * 3: The input-to-cell weights. + * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [outputSize, inputSize] specifying input-to-cell part of + * weights for fully-connected layer inside the LSTM cell. + * Quantization zero point and scale must be the same across all the + * weights. + * * 4: The input-to-output weights. + * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [outputSize, inputSize] specifying input-to-output part of + * weights for fully-connected layer inside the LSTM cell. + * Quantization zero point and scale must be the same across all the + * weights. + * * 5: The recurrent-to-input weights. + * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [outputSize, outputSize] specifying recurrent-to-input part + * of weights for fully-connected layer inside the LSTM cell. + * Quantization zero point and scale must be the same across all the + * weights. + * * 6: The recurrent-to-forget weights. + * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [outputSize, outputSize] specifying recurrent-to-forget + * part of weights for fully-connected layer inside the LSTM cell. + * Quantization zero point and scale must be the same across all the + * weights. + * * 7: The recurrent-to-cell weights. + * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [outputSize, outputSize] specifying recurrent-to-cell part + * of weights for fully-connected layer inside the LSTM cell. + * Quantization zero point and scale must be the same across all the + * weights. + * * 8: The recurrent-to-output weights. + * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [outputSize, outputSize] specifying recurrent-to-output + * part of weights for fully-connected layer inside the LSTM cell. + * Quantization zero point and scale must be the same across all the + * weights. + * * 9: The input gate bias. + * A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape + * [outputSize] specifying the bias for the fully-connected layer + * inside the LSTM cell. Bias is quantized with scale being a product + * of input and weights scales and zeroPoint equal to 0. + * * 10:The forget gate bias. + * A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape + * [outputSize] specifying the bias for the fully-connected layer + * inside the LSTM cell. Bias is quantized with scale being a product + * of input and weights scales and zeroPoint equal to 0. + * * 11:The cell bias. + * A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape + * [outputSize] specifying the bias for the fully-connected layer + * inside the LSTM cell. Bias is quantized with scale being a product + * of input and weights scales and zeroPoint equal to 0. + * * 12:The output gate bias. + * A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape + * [outputSize] specifying the bias for the fully-connected layer + * inside the LSTM cell. Bias is quantized with scale being a product + * of input and weights scales and zeroPoint equal to 0. + * * 13: A 2-D tensor of type {@link OperandType::TENSOR_QUANT16_SYMM} + * and shape [numBatches, outputSize] specifying the cell state from the + * previous time step of the LSTM cell. It is quantized using a + * quantization range of [-2^4, 2^4 * 32767/32768] (scale = 2^4 / + * 32768, zeroPoint = 0). + * * 14: A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [numBathes, outputSize] specifying the output of the LSTM + * cell from previous time-step. Tensor is quantized with a fixed + * quantization range of [-1, 127/128] (scale = 1/128, zeroPoint = + * 128). + * + * + * Outputs: + * * 0: A 2-D tensor of type {@link OperandType::TENSOR_QUANT16_SYMM} + * and shape [numBatches, outputSize] which contains a cell state from + * the current time step. Tensor is quantized using a quantization + * range of [-2^4, 2^4 * 32767/32768] (scale = 2^4 / 32768, zeroPoint = + * 0). + * * 1: A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM} + * and shape [numBathes, outputSize] which contains the output value. + * Tensor is quantized with a fixed quantization range of [-1, 127/128] + * (scale = 1/128, zeroPoint = 128). + */ + QUANTIZED_16BIT_LSTM = 73, + /** + * Draws samples from a multinomial distribution. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Inputs: + * * 0: A 2-D tensor with shape [batches, classes], specifying the + * unnormalized log-probabilities for all classes. + * * 1: A scalar {@link OperandType::INT32}, specifying the number of + * independent samples to draw for each row slice. + * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor with shape [2], + * specifying seeds used to initialize the random distribution. If both + * provided seeds are 0, both will be randomly generated. + * Outputs: + * * 0: A 2-D {@link OperandType::TENSOR_INT32} tensor with shape + * [batches, samples], containing the drawn samples. + */ + RANDOM_MULTINOMIAL = 74, + /** + * Reduces a tensor by computing the "logical and" of elements along given + * dimensions. + * + * If keep_dims is true, the reduced dimensions are + * retained with length 1. Otherwise, the rank of the tensor is reduced by + * 1 for each entry in dimensions. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor. + * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions + * to reduce. Dimension values must be in the range [-n, n). + * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true, + * retains reduced dimensions with length 1. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * If all dimensions are reduced and keep_dims is false, the output + * shape is [1]. + */ + REDUCE_ALL = 75, + /** + * Reduces a tensor by computing the "logical or" of elements along given + * dimensions. + * + * If keep_dims is true, the reduced dimensions are + * retained with length 1. Otherwise, the rank of the tensor is reduced by + * 1 for each entry in dimensions. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_BOOL8} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor. + * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions + * to reduce. Dimension values must be in the range [-n, n). + * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true, + * retains reduced dimensions with length 1. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * If all dimensions are reduced and keep_dims is false, the output + * shape is [1]. + */ + REDUCE_ANY = 76, + /** + * Reduces a tensor by computing the maximum of elements along given + * dimensions. + * + * If keep_dims is true, the reduced dimensions are + * retained with length 1. Otherwise, the rank of the tensor is reduced by + * 1 for each entry in dimensions. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor. + * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions + * to reduce. Dimension values must be in the range [-n, n). + * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true, + * retains reduced dimensions with length 1. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * If all dimensions are reduced and keep_dims is false, the output + * shape is [1]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + REDUCE_MAX = 77, + /** + * Reduces a tensor by computing the minimum of elements along given + * dimensions. + * + * If keep_dims is true, the reduced dimensions are + * retained with length 1. Otherwise, the rank of the tensor is reduced by + * 1 for each entry in dimensions. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor. + * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions + * to reduce. Dimension values must be in the range [-n, n). + * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true, + * retains reduced dimensions with length 1. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * If all dimensions are reduced and keep_dims is false, the output + * shape is [1]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + REDUCE_MIN = 78, + /** + * Reduces a tensor by multiplying elements along given dimensions. + * + * If keep_dims is true, the reduced dimensions are + * retained with length 1. Otherwise, the rank of the tensor is reduced by + * 1 for each entry in dimensions. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor. + * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions + * to reduce. Dimension values must be in the range [-n, n). + * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true, + * retains reduced dimensions with length 1. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * If all dimensions are reduced and keep_dims is false, the output + * shape is [1]. + */ + REDUCE_PROD = 79, + /** + * Reduces a tensor by summing elements along given dimensions. + * + * If keep_dims is true, the reduced dimensions are + * retained with length 1. Otherwise, the rank of the tensor is reduced by + * 1 for each entry in dimensions. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor. + * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions + * to reduce. Dimension values must be in the range [-n, n). + * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true, + * retains reduced dimensions with length 1. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. + * If all dimensions are reduced and keep_dims is false, the output + * shape is [1]. + */ + REDUCE_SUM = 80, + /** + * Select and scale the feature map of each region of interest to a unified + * output size by average pooling sampling points from bilinear interpolation. + * + * The region of interest is represented by its upper-left corner coordinate + * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image. + * A spatial scaling factor is applied to map into feature map coordinate. + * A valid region of interest should satisfy x1 <= x2 and y1 <= y2. + * + * No rounding is applied in this operation. The sampling points are unified + * distributed in the pooling bin and their values are calculated by bilinear + * interpolation. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * + * Inputs: + * * 0: A 4-D tensor, specifying the feature map. + * * 1: A 2-D Tensor of shape [num_rois, 4], specifying the locations of + * the regions of interest, each line with format [x1, y1, x2, y2]. + * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM}, + * this tensor should be of {@link OperandType::TENSOR_QUANT16_ASYMM}, + * with zeroPoint of 0 and scale of 0.125. Zero num_rois is + * supported for this tensor. + * * 2: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape + * [num_rois], specifying the batch index of each box. Boxes with + * the same batch index are grouped together. Zero num_rois is + * supported for this tensor. + * * 3: An {@link OperandType::INT32} scalar, specifying the output + * height of the output tensor. + * * 4: An {@link OperandType::INT32} scalar, specifying the output + * width of the output tensor. + * * 5: An {@link OperandType::FLOAT32} scalar, specifying the ratio + * from the height of original image to the height of feature map. + * * 6: An {@link OperandType::FLOAT32} scalar, specifying the ratio + * from the width of original image to the width of feature map. + * * 7: An {@link OperandType::INT32} scalar, specifying the number of + * sampling points in height dimension used to compute the output. + * Set to 0 for adaptive value of ceil(roi_height/out_height). + * * 8: An {@link OperandType::INT32} scalar, specifying the number of + * sampling points in width dimension used to compute the output. + * Set to 0 for adaptive value of ceil(roi_width/out_width). + * * 9: An {@link OperandType::BOOL} scalar, set to true to specify + * NCHW data layout for input0 and output0. Set to false for NHWC. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. The output + * shape is [num_rois, out_height, out_width, depth]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint can be different from the input0 scale and zeroPoint. + */ + ROI_ALIGN = 81, + /** + * Select and scale the feature map of each region of interest to a unified + * output size by max-pooling. + * + * The region of interest is represented by its upper-left corner coordinate + * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image. + * A spatial scaling factor is applied to map into feature map coordinate. + * A valid region of interest should satisfy x1 <= x2 and y1 <= y2. + * + * Rounding is applied in this operation to ensure integer boundary for + * regions of interest and pooling bins. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * + * Inputs: + * * 0: A 4-D tensor, specifying the feature map. + * * 1: A 2-D Tensor of shape [num_rois, 4], specifying the locations of + * the regions of interest, each line with format [x1, y1, x2, y2]. + * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * this tensor should be of {@link OperandType::TENSOR_QUANT16_ASYMM}, + * with zeroPoint of 0 and scale of 0.125. + * * 2: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape + * [num_rois], specifying the batch index of each box. Boxes with + * the same batch index are grouped together. + * * 3: An {@link OperandType::INT32} scalar, specifying the output + * height of the output tensor. + * * 4: An {@link OperandType::INT32} scalar, specifying the output + * width of the output tensor. + * * 5: An {@link OperandType::FLOAT32} scalar, specifying the ratio + * from the height of original image to the height of feature map. + * * 6: An {@link OperandType::FLOAT32} scalar, specifying the ratio + * from the width of original image to the width of feature map. + * * 7: An {@link OperandType::BOOL} scalar, set to true to specify + * NCHW data layout for input0 and output0. Set to false for NHWC. + * + * Outputs: + * * 0: A tensor of the same {@link OperandType} as input0. The output + * shape is [num_rois, out_height, out_width, depth]. + * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + ROI_POOLING = 82, + /** + * Computes reciprocal of square root of x element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + RSQRT = 83, + /** + * Using a tensor of booleans c and input tensors x and y select values + * elementwise from both input tensors: + * + * O[i] = C[i] ? x[i] : y[i]. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: A tensor of type {@link OperandType::TENSOR_BOOL8} acting as a + * mask that chooses, based on the value at each element, whether the + * corresponding element in the output should be taken from input1 (if + * true) or input2 (if false). + * * 1: An input tensor of the same shape as input0. + * * 2: An input tensor of the same shape and type as input1. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} + * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scales and zeroPoint can be different from input1 scale and zeroPoint. + * + * Outputs: + * * 0: A tensor of the same type and shape as input1 and input2. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. + */ + SELECT = 84, + /** + * Computes sin of x element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + SIN = 85, + /** + * Extracts a slice of specified size from the input tensor starting at a + * specified location. + * + * The starting location is specified as a 1-D tensor containing offsets + * for each dimension. The size is specified as a 1-D tensor containing + * either size of a slice along corresponding dimension or -1. In the latter + * case, all the remaining elements in dimension are included in the slice. + * + * A sum of begin offset and a size of a slice must not exceed size of a + * corresponding dimension. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: An n-D tensor to take slice from, may be zero-sized. + * * 1: A 1-D tensor of type {@link OperandType::TENSOR_INT32} specifying + * the beginning indices of the slice in each dimension. + * * 2: A 1-D tensor of type {@link OperandType::TENSOR_INT32} specifying + * the size of the slice in each dimension. + * + * Outputs: + * * 0: An n-D tensor of the same type as the input containing the slice. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * its scale and zeroPoint has to be same as the input0 scale and zeroPoint. + */ + SLICE = 86, + /** + * Splits a tensor along a given axis into num_splits subtensors. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: An n-D tensor to split. + * * 1: An {@link OperandType::INT32} scalar specifying the axis along + * which to split. + * * 2: An {@link OperandType::INT32} scalar indicating the number of + * splits along given axis. Must evenly divide axis size. + * + * Outputs: + * * 0 ~ (num_splits - 1): Resulting subtensors. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + SPLIT = 87, + /** + * Computes square root of x element-wise. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + SQRT = 88, + /** + * Constructs a tensor by tiling a given tensor. + * + * This operation creates a new tensor by replicating `input` `multiples` + * times. The output tensor's i-th dimension has `input.dims(i) * multiples[i]` + * elements, and the values of `input` are replicated `multiples[i]` times + * along the i-th dimension. + * For example, tiling `[a b c d]` by `[2]` produces `[a b c d a b c d]`. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: input, an n-D tensor specifying the input. + * * 1: multiples, a 1-D tensor of {@link OperandType::TENSOR_INT32}. + * The length of multiples must be n. + * + * Outputs: + * * 0: A tiled tensor of the same {@link OperandType} and rank as `input`. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + TILE = 89, + /** + * Finds values and indices of the k largest entries for the last dimension. + * + * Resulting values in each dimensions are sorted in descending order. If + * two values are equal, the one with larger index appears first. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: from 1 + * + * Inputs: + * * 0: input, an n-D tensor specifying the input. + * * 1: k, an {@link OperandType::INT32} scalar, specifying the number of + * top elements to look for along the last dimension. + * + * Outputs: + * * 0: An n-D tensor of the same type as the input, containing the k + * largest elements along each last dimensional slice. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + * * 1: An n-D tensor of type {@link OperandType::TENSOR_INT32} + * containing the indices of values within the last dimension of input. + */ + TOPK_V2 = 90, + /** + * Performs the transpose of 2-D convolution operation. + * + * This operation is sometimes called "deconvolution" after Deconvolutional + * Networks, but is actually the transpose (gradient) of + * {@link OperandType::CONV_2D} rather than an actual deconvolution. + * + * The output dimensions are functions of the filter dimensions, stride, and + * padding. + * + * Supported tensor {@link OperandType} configurations: + * * 16 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias. + * + * * 32 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias. + * + * * Quantized: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * * Quantized with symmetric per channel quantization for the filter: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output. + * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. + * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, + * * * each value scaling is separate and equal to input.scale * filter.scales[channel]). + * + * Available since HAL version 1.3: + * * Quantized signed (since HAL version 1.3): + * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3): + * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output. + * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter. + * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0, + * * * each value scaling is separate and equal to input.scale * filter.scales[channel]). + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input. + * * 1: A 4-D tensor, of shape + * [depth_out, filter_height, filter_width, depth_in], specifying the + * filter. For tensor of type + * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel + * dimension (SymmPerChannelQuantParams::channelDim) must be set to 0. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input + * tensor of type {@link OperandType::TENSOR_FLOAT32} or + * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the + * same type. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} + * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the bias should be of {@link OperandType::TENSOR_INT32}, + * with zeroPoint of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias must be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to + * bias_scale[i] = input_scale * filter_scale[i]. + * * 3: An {@link OperandType::INT32} scalar, specifying the padding on + * the left, in the ‘width’ dimension. + * * 4: An {@link OperandType::INT32} scalar, specifying the padding on + * the right, in the ‘width’ dimension. + * * 5: An {@link OperandType::INT32} scalar, specifying the padding on + * the top, in the ‘height’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the padding on + * the bottom, in the ‘height’ dimension. + * * 7: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 8: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 9: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 10: An {@link OperandType::BOOL} scalar, set to true to specify + * NCHW data layout for input0 and output0. Set to false for NHWC. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], + * specifying the input. + * * 1: A 4-D tensor, of shape + * [depth_out, filter_height, filter_width, depth_in], specifying the + * filter. For tensor of type + * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel + * dimension (SymmPerChannelQuantParams::channelDim) must be set to 0. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input + * tensor of type {@link OperandType::TENSOR_FLOAT32} or + * {@link OperandType::TENSOR_FLOAT16}, the bias should be of the + * same type. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} + * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + * the bias should be of {@link OperandType::TENSOR_INT32}, + * with zeroPoint of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias must be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to + * bias_scale[i] = input_scale * filter_scale[i]. + * * 3: An {@link OperandType::TENSOR_INT32} tensor, specifying the output + * tensor shape. + * * 4: An {@link OperandType::INT32} scalar, specifying the implicit + * padding scheme, has to be one of the + * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. + * * 5: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘width’ dimension. + * * 6: An {@link OperandType::INT32} scalar, specifying the stride when + * walking through input in the ‘height’ dimension. + * * 7: An {@link OperandType::INT32} scalar, and has to be one of the + * {@link FusedActivationFunc} values. Specifies the activation to + * invoke on the result. + * * 8: An {@link OperandType::BOOL} scalar, set to true to specify + * NCHW data layout for input0 and output0. Set to false for NHWC. + * + * Outputs: + * * 0: The output 4-D tensor, of shape + * [batches, out_height, out_width, depth_out]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. + */ + TRANSPOSE_CONV_2D = 91, + /** + * A recurrent neural network specified by an LSTM cell. + * + * Performs (fully) dynamic unrolling of input. + * + * This Op unrolls the input along the time dimension, and implements the + * following operation for each element in the sequence + * s = 1...sequence_length: + * outputs[s] = projection(state = activation(LSTMOp(inputs[s]))) + * + * Where LSTMOp is the LSTM op as in {@link OperandType::LSTM}, + * the "projection" is an optional projection layer from state and output + * and the “activation” is the function passed as the + * “fused_activation_function” argument (if not “NONE”). + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: 3, either time-major or batch-major. + * + * All input and output tensors must be of the same type. + * + * Inputs: + * * 0: The input (\f$x_t\f$). + * A 3-D tensor of shape: + * If time-major: [max_time, batch_size, input_size] + * If batch-major: [batch_size, max_time, input_size] + * where “max_time” is the number of timesteps (sequence length), + * “batch_size” corresponds to the batching dimension, and + * “input_size” is the size of the input. + * * 1: The input-to-input weights (\f$W_{xi}\f$). Optional. + * A 2-D tensor of shape [num_units, input_size], where “num_units” + * corresponds to the number of cell units. + * * 2: The input-to-forget weights (\f$W_{xf}\f$). + * A 2-D tensor of shape [num_units, input_size]. + * * 3: The input-to-cell weights (\f$W_{xc}\f$). + * A 2-D tensor of shape [num_units, input_size]. + * * 4: The input-to-output weights (\f$W_{xo}\f$). + * A 2-D tensor of shape [num_units, input_size]. + * * 5: The recurrent-to-input weights (\f$W_{hi}\f$). Optional. + * A 2-D tensor of shape [num_units, output_size], where “output_size” + * corresponds to either the number of cell units (i.e., “num_units”), + * or the second dimension of the “projection_weights”, if defined. + * * 6: The recurrent-to-forget weights (\f$W_{hf}\f$). + * A 2-D tensor of shape [num_units, output_size]. + * * 7: The recurrent-to-cell weights (\f$W_{hc}\f$). + * A 2-D tensor of shape [num_units, output_size]. + * * 8: The recurrent-to-output weights (\f$W_{ho}\f$). + * A 2-D tensor of shape [num_units, output_size]. + * * 9: The cell-to-input weights (\f$W_{ci}\f$). Optional. + * A 1-D tensor of shape [num_units]. + * * 10:The cell-to-forget weights (\f$W_{cf}\f$). Optional. + * A 1-D tensor of shape [num_units]. + * * 11:The cell-to-output weights (\f$W_{co}\f$). Optional. + * A 1-D tensor of shape [num_units]. + * * 12:The input gate bias (\f$b_i\f$). Optional. + * A 1-D tensor of shape [num_units]. + * * 13:The forget gate bias (\f$b_f\f$). + * A 1-D tensor of shape [num_units]. + * * 14:The cell bias (\f$b_c\f$). + * A 1-D tensor of shape [num_units]. + * * 15:The output gate bias (\f$b_o\f$). + * A 1-D tensor of shape [num_units]. + * * 16:The projection weights (\f$W_{proj}\f$). Optional. + * A 2-D tensor of shape [output_size, num_units]. + * * 17:The projection bias (\f$b_{proj}\f$). Optional. + * A 1-D tensor of shape [output_size]. + * * 18:The output state (in) (\f$h_{t-1}\f$). + * A 2-D tensor of shape [batch_size, output_size]. + * * 19:The cell state (in) (\f$C_{t-1}\f$). + * A 2-D tensor of shape [batch_size, num_units]. + * * 20:The activation function (\f$g\f$). + * A value indicating the activation function: + * <ul> + * <li>0: None; + * <li>1: Relu; + * <li>3: Relu6; + * <li>4: Tanh; + * <li>6: Sigmoid. + * </ul> + * * 21:The clipping threshold (\f$t_{cell}\f$) for the cell state, such + * that values are bound within [-cell_clip, cell_clip]. If set to 0.0 + * then clipping is disabled. + * * 22:The clipping threshold (\f$t_{proj}\f$) for the output from the + * projection layer, such that values are bound within + * [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled. + * * 23:Time-major if true, batch-major if false. + * * 24:The input layer normalization weights. Optional. + * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs + * to activation at input gate. + * * 25:The forget layer normalization weights. Optional. + * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs + * to activation at forget gate. + * * 26:The cell layer normalization weights. Optional. + * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs + * to activation at cell gate. + * * 27:The output layer normalization weights. Optional. + * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs + * to activation at output gate. + * + * Outputs: + * * 0: The output (\f$o_t\f$). + * A 3-D tensor of shape: + * If time-major: [max_time, batch_size, output_size] + * If batch-major: [batch_size, max_time, output_size] + * * 1: A tensor of shape [batch_size, output_size] containing a hidden + * state from the last time step in the sequence. This output is + * optional and can be omitted. If this output is present then + * output #2 must be present as well. + * Available since HAL version 1.3. + * * 2: A tensor of shape [batch_size, cell_size] containing a cell state + * from the last time step in the sequence. This output is optional + * and can be omitted. + * Available since HAL version 1.3. + */ + UNIDIRECTIONAL_SEQUENCE_LSTM = 92, + /** + * A recurrent neural network layer that applies a basic RNN cell to a + * sequence of inputs. + * + * This layer unrolls the input along the sequence dimension, and implements + * the following operation + * for each element in the sequence s = 1...sequence_length: + * outputs[s] = state = activation(inputs[s] * input_weights’ + state * + * recurrent_weights’ + bias) + * + * Where: + * * “input_weights” is a weight matrix that multiplies the inputs; + * * “recurrent_weights” is a weight matrix that multiplies the current + * “state” which itself is the output from the previous time step + * computation; + * * “bias” is a bias vector (added to each output vector in the batch); + * * “activation” is the function passed as the “fused_activation_function” + * argument (if not “NONE”). + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * The input tensors must all be the same type. + * + * Inputs: + * * 0: input. + * A 3-D tensor. The shape is defined by the input 6 (timeMajor). If + * it is set to 1, then the input has a shape [maxTime, batchSize, + * inputSize], otherwise the input has a shape [batchSize, maxTime, + * inputSize]. + * * 1: weights. + * A 2-D tensor of shape [numUnits, inputSize]. + * * 2: recurrent_weights. + * A 2-D tensor of shape [numUnits, numUnits]. + * * 3: bias. + * A 1-D tensor of shape [numUnits]. + * * 4: hidden state + * A 2-D tensor of shape [batchSize, numUnits]. Specifies a hidden + * state input for the first time step of the computation. + * * 5: fusedActivationFunction. + * A {@link FusedActivationFunc} value indicating the activation function. If + * “NONE” is specified then it results in a linear activation. + * * 6: timeMajor + * An {@link OperandType::INT32} scalar specifying the shape format + * of input and output tensors. Must be set to either 0 or 1. + * Outputs: + * * 0: output. + * A 3-D tensor. The shape is defined by the input 6 (timeMajor). If + * it is set to 1, then the output has a shape [maxTime, batchSize, + * numUnits], otherwise the output has a shape [batchSize, maxTime, + * numUnits]. + * * 1: A tensor of shape [batchSize, numUnits] containing hidden state + * from the last time step in the sequence. This output is optional + * and can be omitted. + * Available since HAL version 1.3. + */ + UNIDIRECTIONAL_SEQUENCE_RNN = 93, + /** + * Resizes images to given size using the nearest neighbor interpretation. + * + * Resized images must be distorted if their output aspect ratio is not the + * same as input aspect ratio. The corner pixels of output may not be the + * same as corner pixels of input. + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3) + * + * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. + * With the default data layout NHWC, the data is stored in the order of: + * [batch, height, width, channels]. Alternatively, the data layout could + * be NCHW, the data storage order of: [batch, channels, height, width]. + * + * Both resizing by shape and resizing by scale are supported. + * + * Inputs (resizing by shape): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. Zero batches is supported for this tensor. + * * 1: An {@link OperandType::INT32} scalar, specifying the output + * width of the output tensor. + * * 2: An {@link OperandType::INT32} scalar, specifying the output + * height of the output tensor. + * * 3: An {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * * 4: Align corners. An optional {@link OperandType::BOOL} + * scalar, default to false. If True, the centers of the 4 corner + * pixels of the input and output tensors are aligned, preserving the + * values at the corner pixels. + * Available since HAL version 1.3. + * * 5: Half pixel centers. An optional {@link OperandType::BOOL} + * scalar, default to false. If True, the pixel centers are assumed to + * be at (0.5, 0.5). This is the default behavior of image.resize in + * TF 2.0. If this parameter is True, then align_corners parameter + * must be False. + * Available since HAL version 1.3. + * + * Inputs (resizing by scale): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying + * the input. Zero batches is supported for this tensor. + * * 1: A scalar, specifying width_scale, the scaling factor of the width + * dimension from the input tensor to the output tensor. The output + * width is calculated as new_width = floor(width * width_scale). + * The scalar must be of {@link OperandType::FLOAT16} if input0 is + * of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} otherwise. + * * 2: A scalar, specifying height_scale, the scaling factor of the height + * dimension from the input tensor to the output tensor. The output + * height is calculated as new_height = floor(height * height_scale). + * The scalar must be of {@link OperandType::FLOAT16} if input0 is + * of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} otherwise. + * * 3: An {@link OperandType::BOOL} scalar, default to false. + * Set to true to specify NCHW data layout for input0 and output0. + * * 4: Align corners. An optional {@link OperandType::BOOL} + * scalar, default to false. If True, the centers of the 4 corner + * pixels of the input and output tensors are aligned, preserving the + * values at the corner pixels. + * Available since HAL version 1.3. + * * 5: Half pixel centers. An optional {@link OperandType::BOOL} + * scalar, default to false. If True, the pixel centers are assumed to + * be at (0.5, 0.5). This is the default behavior of image.resize in + * TF 2.0. If this parameter is True, then align_corners parameter + * must be False. + * Available since HAL version 1.3. + * + * Outputs: + * * 0: The output 4-D tensor, of shape + * [batches, new_height, new_width, depth]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and + * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor, + * the scale and zeroPoint must be the same as input0. + */ + RESIZE_NEAREST_NEIGHBOR = 94, + /** + * Quantized version of {@link OperationType::LSTM}. + * + * The input and the output use asymmetric quantized types, while the rest + * use symmetric ones. + * + * Inputs: + * * 0: The input to the LSTM cell. + * Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * Shape: [batchSize, inputSize] + * * 1: The input-to-input weights. Optional. + * Type: {@link OperandType::TENSOR_QUANT8_SYMM} + * Shape: [numUnits, inputSize] + * * 2: The input-to-forget weights. + * Type: {@link OperandType::TENSOR_QUANT8_SYMM} + * Shape: [numUnits, inputSize] + * * 3: The input-to-cell weights. + * Type: {@link OperandType::TENSOR_QUANT8_SYMM} + * Shape: [numUnits, inputSize] + * * 4: The input-to-output weights. + * Type: {@link OperandType::TENSOR_QUANT8_SYMM} + * Shape: [numUnits, inputSize] + * * 5: The recurrent-to-input weights. Optional. + * Type: {@link OperandType::TENSOR_QUANT8_SYMM} + * Shape: [numUnits, outputSize] + * * 6: The recurrent-to-forget weights. + * Type: {@link OperandType::TENSOR_QUANT8_SYMM} + * Shape: [numUnits, outputSize] + * * 7: The recurrent-to-cell weights. + * Type: {@link OperandType::TENSOR_QUANT8_SYMM} + * Shape: [numUnits, outputSize] + * * 8: The recurrent-to-output weights. + * Type: {@link OperandType::TENSOR_QUANT8_SYMM} + * Shape: [numUnits, outputSize] + * * 9: The cell-to-input weights (for peephole). Optional. + * Type: {@link OperandType::TENSOR_QUANT16_SYMM} + * Shape: [numUnits] + * * 10: The cell-to-forget weights (for peephole). Optional. + * Type: {@link OperandType::TENSOR_QUANT16_SYMM} + * Shape: [numUnits] + * * 11: The cell-to-output weights (for peephole). Optional. + * Type: {@link OperandType::TENSOR_QUANT16_SYMM} + * Shape: [numUnits] + * * 12: The input gate bias. Quantized with scale being the + * product of input and weights scales and zeroPoint equal to 0. + * Optional. + * Type: {@link OperandType::TENSOR_INT32} + * Shape: [numUnits] + * * 13: The forget gate bias. Quantized with scale being the + * product of input and weights scales and zeroPoint equal to 0. + * Type: {@link OperandType::TENSOR_INT32} + * Shape: [numUnits] + * * 14: The cell bias. Quantized with scale being the + * product of input and weights scales and zeroPoint equal to 0. + * Type: {@link OperandType::TENSOR_INT32} + * Shape: [numUnits] + * * 15: The output gate bias. Quantized with scale being the + * product of input and weights scales and zeroPoint equal to 0. + * Type: {@link OperandType::TENSOR_INT32} + * Shape: [numUnits] + * * 16: The projection weights. Optional. + * Type: {@link OperandType::TENSOR_QUANT8_SYMM} + * Shape: [outputSize, numUnits] + * * 17: The projection bias. Quantized with scale being the + * product of input and weights scales and zeroPoint equal to 0. + * Optional. + * Type: {@link OperandType::TENSOR_INT32} + * Shape: [outputSize] + * * 18: The output from the previous time step. + * Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * Shape: [batchSize, outputSize] + * * 19: The cell state from the previous time step. + * Type: {@link OperandType::TENSOR_QUANT16_SYMM} + * Shape: [batchSize, numUnits] + * * 20: The input layer normalization weights. Used to rescale + * normalized inputs to activation at input gate. Optional. + * Type: {@link OperandType::TENSOR_QUANT16_SYMM} + * Shape: [numUnits] + * * 21: The forget layer normalization weights. Used to + * rescale normalized inputs to activation at forget gate. Optional. + * Type: {@link OperandType::TENSOR_QUANT16_SYMM} + * Shape: [numUnits] + * * 22: The cell layer normalization weights. Used to rescale + * normalized inputs to activation at cell gate. Optional. + * Type: {@link OperandType::TENSOR_QUANT16_SYMM} + * Shape: [numUnits] + * * 23: The output layer normalization weights. Used to + * rescale normalized inputs to activation at output gate. Optional. + * Type: {@link OperandType::TENSOR_QUANT16_SYMM} + * Shape: [numUnits] + * * 24: The cell clip. If provided the cell state is clipped + * by this value prior to the cell output activation. Optional. + * Type: {@link OperandType::FLOAT32}. + * * 25: The projection clip. If provided and projection is enabled, + * this is used for clipping the projected values. Optional. + * Type: {@link OperandType::FLOAT32}. + * * 26: The scale of the intermediate result of matmul, + * i.e. input to layer normalization, at input gate. + * Type: {@link OperandType::FLOAT32}. + * * 27: The scale of the intermediate result of matmul, + * i.e. input to layer normalization, at forget gate. + * Type: {@link OperandType::FLOAT32}. + * * 28: The scale of the intermediate result of matmul, + * i.e. input to layer normalization, at cell gate. + * Type: {@link OperandType::FLOAT32}. + * * 29: The scale of the intermediate result of matmul, + * i.e. input to layer normalization, at output gate. + * Type: {@link OperandType::FLOAT32}. + * * 30: The zero point of the hidden state, i.e. input to + * projection. + * Type: {@link OperandType::INT32}. + * * 31: The scale of the hidden state, i.e. input to + * projection. + * Type: {@link OperandType::FLOAT32}. + * + * Outputs: + * * 0: The output state (out). + * Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * Shape: [batchSize, outputSize] + * * 1: The cell state (out). + * Type: {@link OperandType::TENSOR_QUANT16_SYMM} + * Shape: [batchSize, numUnits] + * * 2: The output. This is effectively the same as the current + * "output state (out)" value. + * Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * Shape: [batchSize, outputSize] + */ + QUANTIZED_LSTM = 95, + /** + * Executes one of the two referenced subgraphs as determined by a boolean + * value. + * + * The inputs and outputs of the two referenced subgraphs must agree with the + * signature of this operation. That is, if the operation has (3 + n) inputs + * and m outputs, both subgraphs must have n inputs and m outputs with the same + * types, ranks, dimensions, scales, + * zeroPoints, and extraParams as the corresponding operation + * inputs and outputs. + * All of the operands mentioned must have fully specified dimensions. + * + * Inputs: + * * 0: A value of type {@link OperandType::TENSOR_BOOL8} and shape [1] + * that determines which of the two referenced subgraphs to execute. + * The operand must have fully specified dimensions. + * * 1: A {@link OperandType::SUBGRAPH} reference to the subgraph to be + * executed if the condition is true. + * * 2: A {@link OperandType::SUBGRAPH} reference to the subgraph to be + * executed if the condition is false. + * * 3 ~ (n + 2): Inputs to be passed to the subgraph selected for execution. + * + * Outputs: + * * 0 ~ (m - 1): Outputs produced by the selected subgraph. + */ + IF = 96, + /** + * Executes the body subgraph until the condition subgraph outputs false. + * + * The inputs to this operation are the condition subgraph, the body subgraph, + * and operand values for the first iteration of the loop. The values are + * implicitly split into three groups of input-output, state-only, and + * input-only values, as described below. + * + * The outputs of this operation are the final values of input-output + * operands. + * + * Both the condition and body subgraph receive (m + k + n) inputs. + * * The first m (m >= 1) inputs are input-output operands. For the first + * iteration, these are initialized from the corresponding inputs of the + * WHILE operation. In subsequent iterations, their values come from the + * corresponding outputs of the body subgraph produced during the previous + * iteration. + * * The next k (k >= 0) inputs are state-only operands. They are similar to + * the input-output operands, except that their values are no longer + * available after the loop terminates. + * * The last n (n >= 0) inputs are input-only operands. Their values come + * from the corresponding inputs of the WHILE operation. + * + * The body subgraph produces (m + k) outputs. + * * The first m outputs are input-output operands. They become the outputs + * of the WHILE operation when a termination condition is reached. + * * The last k outputs are state-only operands. Their values are no longer + * available after the loop terminates. + * + * The numbers m, k, and n are inferred by the driver as follows: + * m = (WHILE operation output count) + * k = (body subgraph output count) - m + * n = (body subgraph input count) - m - k + * + * The pseudo-code below illustrates the flow of a WHILE operation with + * inputs condition, body, initial_input_output, initial_state, input_only + * (m = 1, k = 1, n = 1): + * + * input_output = initial_input_output + * state = initial_state + * while condition(input_output, state, input_only): + * input_output, state = body(input_output, state, input_only) + * return input_output + * + * Inputs: + * * 0: A {@link OperandType::SUBGRAPH} reference to the condition + * subgraph. The subgraph must have (m + k + n) inputs with + * the same types, ranks, dimensions, + * scales, zeroPoints, and extraParams as the + * corresponding inputs of the WHILE operation and exactly one output + * of {@link OperandType::TENSOR_BOOL8} and shape [1]. + * All of the operands mentioned must have fully specified dimensions. + * * 1: A {@link OperandType::SUBGRAPH} reference to the body subgraph. + * The subgraph must have (m + k + n) inputs and (m + k) outputs with + * the same types, ranks, dimensions, + * scales, zeroPoints, and extraParams as the + * corresponding inputs and outputs of the WHILE operation. + * All of the operands mentioned must have fully specified dimensions. + * * (m inputs): Initial values for input-output operands. + * * (k inputs): Initial values for state-only operands. + * * (n inputs): Values for input-only operands. + * + * Outputs: + * * 0 ~ (m - 1): Outputs produced by the loop. + */ + WHILE = 97, + /** + * Computes exponential linear activation on the input tensor element-wise. + * + * The output is calculated using the following formula: + * + * ELU(x) = max(0, x) + min(0, alpha * (exp(x) - 1)) + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor, specifying the input. May be zero-sized. + * * 1: A scalar, specifying the alpha parameter. + * For input tensor of {@link OperandType::TENSOR_FLOAT16}, + * the alpha value must be of {@link OperandType::FLOAT16}. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, + * the alpha value must be of {@link OperandType::FLOAT32}. + * + * Outputs: + * * 0: The output tensor of same shape and type as input0. + */ + ELU = 98, + /** + * Computes hard-swish activation on the input tensor element-wise. + * + * Hard swish activation is introduced in + * https://arxiv.org/pdf/1905.02244.pdf + * + * The output is calculated using the following formula: + * + * h-swish(x) = x * max(0, min(6, (x + 3))) / 6 + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A tensor, specifying the input. May be zero-sized. + * + * Outputs: + * * 0: The output tensor of same shape and type as input0. + * Scale and zero point of this tensor may be different from the input + * tensor's parameters. + */ + HARD_SWISH = 99, + /** + * Creates a tensor filled with a scalar value. + * + * Supported output tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: A 1-D tensor, specifying the desired output tensor shape. + * * 1: A scalar, specifying the value to fill the output tensors with. + * For output tensor of {@link OperandType::TENSOR_FLOAT16}, + * the scalar must be of {@link OperandType::FLOAT16}. + * For output tensor of {@link OperandType::TENSOR_FLOAT32}, + * the scalar must be of {@link OperandType::FLOAT32}. + * For output tensor of {@link OperandType::TENSOR_INT32}, + * the scalar must be of {@link OperandType::INT32}. + * + * Outputs: + * * 0: The output tensor. + */ + FILL = 100, + /** + * Returns the rank of a tensor. + * + * The rank of a tensor is the number of dimensions in it. Also known as + * "order", "degree", "ndims". + * + * Supported tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT16} + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * * {@link OperandType::TENSOR_QUANT16_SYMM} + * * {@link OperandType::TENSOR_BOOL8} + * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} + * * {@link OperandType::TENSOR_QUANT16_ASYMM} + * * {@link OperandType::TENSOR_QUANT8_SYMM} + * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} + * + * Supported tensor rank: from 1. + * + * Inputs: + * * 0: The input tensor. + * + * Outputs: + * * 0: A scalar of {@link OperandType::INT32}, specifying the rank + * of the input tensor. + */ + RANK = 101, +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OutputShape.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OutputShape.aidl new file mode 100644 index 0000000000..f90a613592 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OutputShape.aidl @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Describes the shape information of an output operand after execution. + */ +@VintfStability +parcelable OutputShape { + /** + * Dimensions of the operand. + */ + int[] dimensions; + /** + * Whether the provided buffer size is sufficient for the output. + */ + boolean isSufficient; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/PerformanceInfo.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/PerformanceInfo.aidl new file mode 100644 index 0000000000..6915c671d2 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/PerformanceInfo.aidl @@ -0,0 +1,36 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Performance information for the reference workload. + * + * Used by a driver to report its performance characteristics. + */ +@VintfStability +parcelable PerformanceInfo { + /** + * Ratio of the time taken by the driver to execute the workload compared to the time the CPU + * would take for the same workload. A lower number is better. + */ + float execTime; + /** + * Ratio of the energy used by the driver compared to what the CPU would use for doing the same + * workload. A lower number is better. + */ + float powerUsage; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Priority.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Priority.aidl new file mode 100644 index 0000000000..7dbf8e9883 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Priority.aidl @@ -0,0 +1,28 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Priority given to a prepared model for execution. + */ +@VintfStability +@Backing(type="int") +enum Priority { + LOW, + MEDIUM, + HIGH, +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Request.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Request.aidl new file mode 100644 index 0000000000..dc138bae13 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Request.aidl @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.RequestArgument; +import android.hardware.neuralnetworks.RequestMemoryPool; + +/** + * Inputs to be sent to and outputs to be retrieved from a prepared model. + * + * A Request serves two primary tasks: + * 1) Provides the input and output data to be used when executing the model. + * 2) Specifies any updates to the input operand metadata that were left unspecified at model + * preparation time. + * + * An output must not overlap with any other output, with an input, or with an operand of lifetime + * CONSTANT_POOL. + */ +@VintfStability +parcelable Request { + /** + * Input data and information to be used in the execution of a prepared model. + * + * The index of the input corresponds to the index in Model.main.inputIndexes. + * E.g., input[i] corresponds to Model.main.inputIndexes[i]. + */ + RequestArgument[] inputs; + /** + * Output data and information to be used in the execution of a prepared model. + * + * The index of the output corresponds to the index in Model.main.outputIndexes. + * E.g., output[i] corresponds to Model.main.outputIndexes[i]. + */ + RequestArgument[] outputs; + /** + * A collection of memory pools containing operand data for both the inputs and the outputs to a + * model. + */ + RequestMemoryPool[] pools; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestArgument.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestArgument.aidl new file mode 100644 index 0000000000..8dc9252381 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestArgument.aidl @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.DataLocation; + +/** + * Metadata information specifying the location of the input or output data and any updates to the + * input or output operand. + */ +@VintfStability +parcelable RequestArgument { + /** + * If true, the argument does not have a value. This can be used for operations that take + * optional arguments. If true, the fields of location are set to 0 and the dimensions vector is + * left empty. + */ + boolean hasNoValue; + /** + * The location within one of the memory pools passed in the Request. + */ + DataLocation location; + /** + * Updated dimension information. + * + * If dimensions.size() > 0, dimension information was provided along with the argument. This + * can be the case for models that accept inputs of varying size. This can't change the rank, + * just the value of the dimensions that were unspecified in the model. If dimensions.size() > + * 0, then all dimensions must be specified here; and any dimension that was specified in the + * model must have the same value here. + * + * If the dimensions in the model are not fully specified, then they must be fully specified + * here, unless hasNoValue is set to true. If the dimensions in the model are fully specified, + * then either dimensions.size() may be 0, or the dimensions in the model must be identical to + * the dimensions here. + */ + int[] dimensions; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestMemoryPool.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestMemoryPool.aidl new file mode 100644 index 0000000000..faca2fed94 --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestMemoryPool.aidl @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.Memory; + +/** + * A memory pool. + */ +@VintfStability +union RequestMemoryPool { + /** + * Specifies a client-managed shared memory pool. + */ + Memory pool; + /** + * Specifies a driver-managed buffer. It is the token returned from IDevice::allocate, and is + * specific to the IDevice object. + */ + int token; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Subgraph.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Subgraph.aidl new file mode 100644 index 0000000000..2e9c450f5d --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Subgraph.aidl @@ -0,0 +1,50 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +import android.hardware.neuralnetworks.Operand; +import android.hardware.neuralnetworks.Operation; + +/** + * An excerpt of the execution graph. + */ +@VintfStability +parcelable Subgraph { + /** + * All operands included in the subgraph. + */ + Operand[] operands; + /** + * All operations included in the subgraph. + * + * The operations are sorted into execution order. Every operand with lifetime SUBGRAPH_OUTPUT + * or TEMPORARY_VARIABLE must be written before it is read. + */ + Operation[] operations; + /** + * Input indexes of the subgraph. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + int[] inputIndexes; + /** + * Output indexes of the subgraph. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + int[] outputIndexes; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl new file mode 100644 index 0000000000..eb47df0e7b --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Parameters for TENSOR_QUANT8_SYMM_PER_CHANNEL operand. + */ +@VintfStability +parcelable SymmPerChannelQuantParams { + /** + * Array of scaling values for each channel. Each value must be greater than zero. + */ + float[] scales; + /** + * Index of the channel dimension + */ + int channelDim; +} diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Timing.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Timing.aidl new file mode 100644 index 0000000000..8130e08fce --- /dev/null +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Timing.aidl @@ -0,0 +1,36 @@ +/* + * 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. + */ + +package android.hardware.neuralnetworks; + +/** + * Timing information measured during execution. Each time is a duration from the beginning of some + * task to the end of that task, including time when that task is not active (for example, preempted + * by some other task, or waiting for some resource to become available). + * + * Times are measured in nanoseconds. When a time is not available, it must be reported as -1. + */ +@VintfStability +parcelable Timing { + /** + * Execution time on device (not driver, which runs on host processor). + */ + long timeOnDevice; + /** + * Execution time in driver (including time on device). + */ + long timeInDriver; +} diff --git a/neuralnetworks/aidl/utils/Android.bp b/neuralnetworks/aidl/utils/Android.bp new file mode 100644 index 0000000000..147d401201 --- /dev/null +++ b/neuralnetworks/aidl/utils/Android.bp @@ -0,0 +1,34 @@ +// +// Copyright (C) 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. +// + +cc_library_static { + name: "neuralnetworks_utils_hal_aidl", + defaults: ["neuralnetworks_utils_defaults"], + srcs: ["src/*"], + local_include_dirs: ["include/nnapi/hal/aidl/"], + export_include_dirs: ["include"], + static_libs: [ + "libarect", + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + ], + shared_libs: [ + "android.hardware.neuralnetworks-V1-ndk_platform", + "libbinder_ndk", + "libhidlbase", + "libnativewindow", + ], +} diff --git a/neuralnetworks/aidl/utils/OWNERS b/neuralnetworks/aidl/utils/OWNERS new file mode 100644 index 0000000000..e4feee3496 --- /dev/null +++ b/neuralnetworks/aidl/utils/OWNERS @@ -0,0 +1,11 @@ +# Neuralnetworks team +butlermichael@google.com +dgross@google.com +galarragas@google.com +jeanluc@google.com +levp@google.com +miaowang@google.com +pszczepaniak@google.com +slavash@google.com +vddang@google.com +xusongw@google.com diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h new file mode 100644 index 0000000000..1b2f69c245 --- /dev/null +++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 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. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_CONVERSIONS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_CONVERSIONS_H + +#include <aidl/android/hardware/neuralnetworks/BufferDesc.h> +#include <aidl/android/hardware/neuralnetworks/BufferRole.h> +#include <aidl/android/hardware/neuralnetworks/Capabilities.h> +#include <aidl/android/hardware/neuralnetworks/DataLocation.h> +#include <aidl/android/hardware/neuralnetworks/DeviceType.h> +#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h> +#include <aidl/android/hardware/neuralnetworks/ExecutionPreference.h> +#include <aidl/android/hardware/neuralnetworks/Extension.h> +#include <aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.h> +#include <aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.h> +#include <aidl/android/hardware/neuralnetworks/Memory.h> +#include <aidl/android/hardware/neuralnetworks/Model.h> +#include <aidl/android/hardware/neuralnetworks/Operand.h> +#include <aidl/android/hardware/neuralnetworks/OperandExtraParams.h> +#include <aidl/android/hardware/neuralnetworks/OperandLifeTime.h> +#include <aidl/android/hardware/neuralnetworks/OperandPerformance.h> +#include <aidl/android/hardware/neuralnetworks/OperandType.h> +#include <aidl/android/hardware/neuralnetworks/Operation.h> +#include <aidl/android/hardware/neuralnetworks/OperationType.h> +#include <aidl/android/hardware/neuralnetworks/OutputShape.h> +#include <aidl/android/hardware/neuralnetworks/PerformanceInfo.h> +#include <aidl/android/hardware/neuralnetworks/Priority.h> +#include <aidl/android/hardware/neuralnetworks/Request.h> +#include <aidl/android/hardware/neuralnetworks/RequestArgument.h> +#include <aidl/android/hardware/neuralnetworks/RequestMemoryPool.h> +#include <aidl/android/hardware/neuralnetworks/Subgraph.h> +#include <aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.h> +#include <aidl/android/hardware/neuralnetworks/Timing.h> + +#include <nnapi/Result.h> +#include <nnapi/Types.h> +#include <nnapi/hal/CommonUtils.h> + +#include <vector> + +namespace android::nn { + +GeneralResult<OperandType> unvalidatedConvert(const aidl_hal::OperandType& operandType); +GeneralResult<OperationType> unvalidatedConvert(const aidl_hal::OperationType& operationType); +GeneralResult<DeviceType> unvalidatedConvert(const aidl_hal::DeviceType& deviceType); +GeneralResult<Priority> unvalidatedConvert(const aidl_hal::Priority& priority); +GeneralResult<Capabilities> unvalidatedConvert(const aidl_hal::Capabilities& capabilities); +GeneralResult<Capabilities::OperandPerformance> unvalidatedConvert( + const aidl_hal::OperandPerformance& operandPerformance); +GeneralResult<Capabilities::PerformanceInfo> unvalidatedConvert( + const aidl_hal::PerformanceInfo& performanceInfo); +GeneralResult<DataLocation> unvalidatedConvert(const aidl_hal::DataLocation& location); +GeneralResult<Operand> unvalidatedConvert(const aidl_hal::Operand& operand); +GeneralResult<Operand::ExtraParams> unvalidatedConvert( + const std::optional<aidl_hal::OperandExtraParams>& optionalExtraParams); +GeneralResult<Operand::LifeTime> unvalidatedConvert( + const aidl_hal::OperandLifeTime& operandLifeTime); +GeneralResult<Operand::SymmPerChannelQuantParams> unvalidatedConvert( + const aidl_hal::SymmPerChannelQuantParams& symmPerChannelQuantParams); +GeneralResult<Operation> unvalidatedConvert(const aidl_hal::Operation& operation); +GeneralResult<Model> unvalidatedConvert(const aidl_hal::Model& model); +GeneralResult<Model::ExtensionNameAndPrefix> unvalidatedConvert( + const aidl_hal::ExtensionNameAndPrefix& extensionNameAndPrefix); +GeneralResult<Model::OperandValues> unvalidatedConvert(const std::vector<uint8_t>& operandValues); +GeneralResult<Model::Subgraph> unvalidatedConvert(const aidl_hal::Subgraph& subgraph); +GeneralResult<OutputShape> unvalidatedConvert(const aidl_hal::OutputShape& outputShape); +GeneralResult<MeasureTiming> unvalidatedConvert(bool measureTiming); +GeneralResult<SharedMemory> unvalidatedConvert(const aidl_hal::Memory& memory); +GeneralResult<Timing> unvalidatedConvert(const aidl_hal::Timing& timing); +GeneralResult<BufferDesc> unvalidatedConvert(const aidl_hal::BufferDesc& bufferDesc); +GeneralResult<BufferRole> unvalidatedConvert(const aidl_hal::BufferRole& bufferRole); +GeneralResult<Request> unvalidatedConvert(const aidl_hal::Request& request); +GeneralResult<Request::Argument> unvalidatedConvert( + const aidl_hal::RequestArgument& requestArgument); +GeneralResult<Request::MemoryPool> unvalidatedConvert( + const aidl_hal::RequestMemoryPool& memoryPool); +GeneralResult<ErrorStatus> unvalidatedConvert(const aidl_hal::ErrorStatus& errorStatus); +GeneralResult<ExecutionPreference> unvalidatedConvert( + const aidl_hal::ExecutionPreference& executionPreference); +GeneralResult<Extension> unvalidatedConvert(const aidl_hal::Extension& extension); +GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert( + const aidl_hal::ExtensionOperandTypeInformation& operandTypeInformation); +GeneralResult<SharedHandle> unvalidatedConvert( + const ::aidl::android::hardware::common::NativeHandle& handle); + +GeneralResult<ExecutionPreference> convert( + const aidl_hal::ExecutionPreference& executionPreference); +GeneralResult<SharedMemory> convert(const aidl_hal::Memory& memory); +GeneralResult<Model> convert(const aidl_hal::Model& model); +GeneralResult<Operand> convert(const aidl_hal::Operand& operand); +GeneralResult<OperandType> convert(const aidl_hal::OperandType& operandType); +GeneralResult<Priority> convert(const aidl_hal::Priority& priority); +GeneralResult<Request::MemoryPool> convert(const aidl_hal::RequestMemoryPool& memoryPool); +GeneralResult<Request> convert(const aidl_hal::Request& request); + +GeneralResult<std::vector<Operation>> convert(const std::vector<aidl_hal::Operation>& outputShapes); +GeneralResult<std::vector<SharedMemory>> convert(const std::vector<aidl_hal::Memory>& memories); + +GeneralResult<std::vector<uint32_t>> toUnsigned(const std::vector<int32_t>& vec); + +} // namespace android::nn + +namespace aidl::android::hardware::neuralnetworks::utils { + +namespace nn = ::android::nn; + +nn::GeneralResult<Memory> unvalidatedConvert(const nn::SharedMemory& memory); +nn::GeneralResult<OutputShape> unvalidatedConvert(const nn::OutputShape& outputShape); +nn::GeneralResult<ErrorStatus> unvalidatedConvert(const nn::ErrorStatus& errorStatus); + +nn::GeneralResult<Memory> convert(const nn::SharedMemory& memory); +nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus); +nn::GeneralResult<std::vector<OutputShape>> convert( + const std::vector<nn::OutputShape>& outputShapes); + +nn::GeneralResult<std::vector<int32_t>> toSigned(const std::vector<uint32_t>& vec); + +} // namespace aidl::android::hardware::neuralnetworks::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_CONVERSIONS_H diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h new file mode 100644 index 0000000000..79b511dc56 --- /dev/null +++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 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. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_H + +#include "nnapi/hal/aidl/Conversions.h" + +#include <android-base/logging.h> +#include <nnapi/Result.h> +#include <nnapi/Types.h> +#include <nnapi/Validation.h> + +namespace aidl::android::hardware::neuralnetworks::utils { + +constexpr auto kDefaultPriority = Priority::MEDIUM; +constexpr auto kVersion = nn::Version::ANDROID_S; + +template <typename Type> +nn::Result<void> validate(const Type& halObject) { + const auto maybeCanonical = nn::convert(halObject); + if (!maybeCanonical.has_value()) { + return nn::error() << maybeCanonical.error().message; + } + return {}; +} + +template <typename Type> +bool valid(const Type& halObject) { + const auto result = utils::validate(halObject); + if (!result.has_value()) { + LOG(ERROR) << result.error(); + } + return result.has_value(); +} + +nn::GeneralResult<Memory> clone(const Memory& memory); +nn::GeneralResult<Request> clone(const Request& request); +nn::GeneralResult<RequestMemoryPool> clone(const RequestMemoryPool& requestPool); +nn::GeneralResult<Model> clone(const Model& model); + +} // namespace aidl::android::hardware::neuralnetworks::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_H diff --git a/neuralnetworks/aidl/utils/src/Assertions.cpp b/neuralnetworks/aidl/utils/src/Assertions.cpp new file mode 100644 index 0000000000..0e88091cfb --- /dev/null +++ b/neuralnetworks/aidl/utils/src/Assertions.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (C) 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. + */ + +#include <aidl/android/hardware/neuralnetworks/DeviceType.h> +#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h> +#include <aidl/android/hardware/neuralnetworks/ExecutionPreference.h> +#include <aidl/android/hardware/neuralnetworks/FusedActivationFunc.h> +#include <aidl/android/hardware/neuralnetworks/IDevice.h> +#include <aidl/android/hardware/neuralnetworks/OperandLifeTime.h> +#include <aidl/android/hardware/neuralnetworks/OperandType.h> +#include <aidl/android/hardware/neuralnetworks/OperationType.h> +#include <aidl/android/hardware/neuralnetworks/Priority.h> + +#include <ControlFlow.h> +#include <nnapi/OperandTypes.h> +#include <nnapi/OperationTypes.h> +#include <nnapi/Types.h> +#include <type_traits> + +namespace { + +#define COMPARE_ENUMS_TYPES(lhsType, rhsType) \ + static_assert( \ + std::is_same_v< \ + std::underlying_type_t<::aidl::android::hardware::neuralnetworks::lhsType>, \ + std::underlying_type_t<::android::nn::rhsType>>, \ + "::aidl::android::hardware::neuralnetworks::" #lhsType \ + " does not have the same underlying type as ::android::nn::" #rhsType) + +COMPARE_ENUMS_TYPES(OperandType, OperandType); +COMPARE_ENUMS_TYPES(OperationType, OperationType); +COMPARE_ENUMS_TYPES(Priority, Priority); +COMPARE_ENUMS_TYPES(OperandLifeTime, Operand::LifeTime); +COMPARE_ENUMS_TYPES(ErrorStatus, ErrorStatus); + +#undef COMPARE_ENUMS_TYPES + +#define COMPARE_ENUMS_FULL(lhsSymbol, rhsSymbol, lhsType, rhsType) \ + static_assert( \ + static_cast< \ + std::underlying_type_t<::aidl::android::hardware::neuralnetworks::lhsType>>( \ + ::aidl::android::hardware::neuralnetworks::lhsType::lhsSymbol) == \ + static_cast<std::underlying_type_t<::android::nn::rhsType>>( \ + ::android::nn::rhsType::rhsSymbol), \ + "::aidl::android::hardware::neuralnetworks::" #lhsType "::" #lhsSymbol \ + " does not match ::android::nn::" #rhsType "::" #rhsSymbol) + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, OperandType, OperandType) + +COMPARE_ENUMS(FLOAT32); +COMPARE_ENUMS(INT32); +COMPARE_ENUMS(UINT32); +COMPARE_ENUMS(TENSOR_FLOAT32); +COMPARE_ENUMS(TENSOR_INT32); +COMPARE_ENUMS(TENSOR_QUANT8_ASYMM); +COMPARE_ENUMS(BOOL); +COMPARE_ENUMS(TENSOR_QUANT16_SYMM); +COMPARE_ENUMS(TENSOR_FLOAT16); +COMPARE_ENUMS(TENSOR_BOOL8); +COMPARE_ENUMS(FLOAT16); +COMPARE_ENUMS(TENSOR_QUANT8_SYMM_PER_CHANNEL); +COMPARE_ENUMS(TENSOR_QUANT16_ASYMM); +COMPARE_ENUMS(TENSOR_QUANT8_SYMM); +COMPARE_ENUMS(TENSOR_QUANT8_ASYMM_SIGNED); +COMPARE_ENUMS(SUBGRAPH); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, OperationType, OperationType) + +COMPARE_ENUMS(ADD); +COMPARE_ENUMS(AVERAGE_POOL_2D); +COMPARE_ENUMS(CONCATENATION); +COMPARE_ENUMS(CONV_2D); +COMPARE_ENUMS(DEPTHWISE_CONV_2D); +COMPARE_ENUMS(DEPTH_TO_SPACE); +COMPARE_ENUMS(DEQUANTIZE); +COMPARE_ENUMS(EMBEDDING_LOOKUP); +COMPARE_ENUMS(FLOOR); +COMPARE_ENUMS(FULLY_CONNECTED); +COMPARE_ENUMS(HASHTABLE_LOOKUP); +COMPARE_ENUMS(L2_NORMALIZATION); +COMPARE_ENUMS(L2_POOL_2D); +COMPARE_ENUMS(LOCAL_RESPONSE_NORMALIZATION); +COMPARE_ENUMS(LOGISTIC); +COMPARE_ENUMS(LSH_PROJECTION); +COMPARE_ENUMS(LSTM); +COMPARE_ENUMS(MAX_POOL_2D); +COMPARE_ENUMS(MUL); +COMPARE_ENUMS(RELU); +COMPARE_ENUMS(RELU1); +COMPARE_ENUMS(RELU6); +COMPARE_ENUMS(RESHAPE); +COMPARE_ENUMS(RESIZE_BILINEAR); +COMPARE_ENUMS(RNN); +COMPARE_ENUMS(SOFTMAX); +COMPARE_ENUMS(SPACE_TO_DEPTH); +COMPARE_ENUMS(SVDF); +COMPARE_ENUMS(TANH); +COMPARE_ENUMS(BATCH_TO_SPACE_ND); +COMPARE_ENUMS(DIV); +COMPARE_ENUMS(MEAN); +COMPARE_ENUMS(PAD); +COMPARE_ENUMS(SPACE_TO_BATCH_ND); +COMPARE_ENUMS(SQUEEZE); +COMPARE_ENUMS(STRIDED_SLICE); +COMPARE_ENUMS(SUB); +COMPARE_ENUMS(TRANSPOSE); +COMPARE_ENUMS(ABS); +COMPARE_ENUMS(ARGMAX); +COMPARE_ENUMS(ARGMIN); +COMPARE_ENUMS(AXIS_ALIGNED_BBOX_TRANSFORM); +COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_LSTM); +COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_RNN); +COMPARE_ENUMS(BOX_WITH_NMS_LIMIT); +COMPARE_ENUMS(CAST); +COMPARE_ENUMS(CHANNEL_SHUFFLE); +COMPARE_ENUMS(DETECTION_POSTPROCESSING); +COMPARE_ENUMS(EQUAL); +COMPARE_ENUMS(EXP); +COMPARE_ENUMS(EXPAND_DIMS); +COMPARE_ENUMS(GATHER); +COMPARE_ENUMS(GENERATE_PROPOSALS); +COMPARE_ENUMS(GREATER); +COMPARE_ENUMS(GREATER_EQUAL); +COMPARE_ENUMS(GROUPED_CONV_2D); +COMPARE_ENUMS(HEATMAP_MAX_KEYPOINT); +COMPARE_ENUMS(INSTANCE_NORMALIZATION); +COMPARE_ENUMS(LESS); +COMPARE_ENUMS(LESS_EQUAL); +COMPARE_ENUMS(LOG); +COMPARE_ENUMS(LOGICAL_AND); +COMPARE_ENUMS(LOGICAL_NOT); +COMPARE_ENUMS(LOGICAL_OR); +COMPARE_ENUMS(LOG_SOFTMAX); +COMPARE_ENUMS(MAXIMUM); +COMPARE_ENUMS(MINIMUM); +COMPARE_ENUMS(NEG); +COMPARE_ENUMS(NOT_EQUAL); +COMPARE_ENUMS(PAD_V2); +COMPARE_ENUMS(POW); +COMPARE_ENUMS(PRELU); +COMPARE_ENUMS(QUANTIZE); +COMPARE_ENUMS(QUANTIZED_16BIT_LSTM); +COMPARE_ENUMS(RANDOM_MULTINOMIAL); +COMPARE_ENUMS(REDUCE_ALL); +COMPARE_ENUMS(REDUCE_ANY); +COMPARE_ENUMS(REDUCE_MAX); +COMPARE_ENUMS(REDUCE_MIN); +COMPARE_ENUMS(REDUCE_PROD); +COMPARE_ENUMS(REDUCE_SUM); +COMPARE_ENUMS(ROI_ALIGN); +COMPARE_ENUMS(ROI_POOLING); +COMPARE_ENUMS(RSQRT); +COMPARE_ENUMS(SELECT); +COMPARE_ENUMS(SIN); +COMPARE_ENUMS(SLICE); +COMPARE_ENUMS(SPLIT); +COMPARE_ENUMS(SQRT); +COMPARE_ENUMS(TILE); +COMPARE_ENUMS(TOPK_V2); +COMPARE_ENUMS(TRANSPOSE_CONV_2D); +COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_LSTM); +COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_RNN); +COMPARE_ENUMS(RESIZE_NEAREST_NEIGHBOR); +COMPARE_ENUMS(QUANTIZED_LSTM); +COMPARE_ENUMS(IF); +COMPARE_ENUMS(WHILE); +COMPARE_ENUMS(ELU); +COMPARE_ENUMS(HARD_SWISH); +COMPARE_ENUMS(FILL); +COMPARE_ENUMS(RANK); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, Priority, Priority) + +COMPARE_ENUMS(LOW); +COMPARE_ENUMS(MEDIUM); +COMPARE_ENUMS(HIGH); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(lhsSymbol, rhsSymbol) \ + COMPARE_ENUMS_FULL(lhsSymbol, rhsSymbol, OperandLifeTime, Operand::LifeTime) + +COMPARE_ENUMS(TEMPORARY_VARIABLE, TEMPORARY_VARIABLE); +COMPARE_ENUMS(SUBGRAPH_INPUT, SUBGRAPH_INPUT); +COMPARE_ENUMS(SUBGRAPH_OUTPUT, SUBGRAPH_OUTPUT); +COMPARE_ENUMS(CONSTANT_COPY, CONSTANT_COPY); +COMPARE_ENUMS(CONSTANT_POOL, CONSTANT_REFERENCE); +COMPARE_ENUMS(NO_VALUE, NO_VALUE); +COMPARE_ENUMS(SUBGRAPH, SUBGRAPH); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, ErrorStatus, ErrorStatus) + +COMPARE_ENUMS(NONE); +COMPARE_ENUMS(DEVICE_UNAVAILABLE); +COMPARE_ENUMS(GENERAL_FAILURE); +COMPARE_ENUMS(OUTPUT_INSUFFICIENT_SIZE); +COMPARE_ENUMS(INVALID_ARGUMENT); +COMPARE_ENUMS(MISSED_DEADLINE_TRANSIENT); +COMPARE_ENUMS(MISSED_DEADLINE_PERSISTENT); +COMPARE_ENUMS(RESOURCE_EXHAUSTED_TRANSIENT); +COMPARE_ENUMS(RESOURCE_EXHAUSTED_PERSISTENT); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) \ + COMPARE_ENUMS_FULL(symbol, symbol, ExecutionPreference, ExecutionPreference) + +COMPARE_ENUMS(LOW_POWER); +COMPARE_ENUMS(FAST_SINGLE_ANSWER); +COMPARE_ENUMS(SUSTAINED_SPEED); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, DeviceType, DeviceType) + +COMPARE_ENUMS(OTHER); +COMPARE_ENUMS(CPU); +COMPARE_ENUMS(GPU); +COMPARE_ENUMS(ACCELERATOR); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) \ + COMPARE_ENUMS_FULL(symbol, symbol, FusedActivationFunc, FusedActivationFunc) + +COMPARE_ENUMS(NONE); +COMPARE_ENUMS(RELU); +COMPARE_ENUMS(RELU1); +COMPARE_ENUMS(RELU6); + +#undef COMPARE_ENUMS + +#undef COMPARE_ENUMS_FULL + +#define COMPARE_CONSTANTS(halSymbol, canonicalSymbol) \ + static_assert(::aidl::android::hardware::neuralnetworks::halSymbol == \ + ::android::nn::canonicalSymbol); + +COMPARE_CONSTANTS(IDevice::BYTE_SIZE_OF_CACHE_TOKEN, kByteSizeOfCacheToken); +COMPARE_CONSTANTS(IDevice::MAX_NUMBER_OF_CACHE_FILES, kMaxNumberOfCacheFiles); +COMPARE_CONSTANTS(IDevice::EXTENSION_TYPE_HIGH_BITS_PREFIX, kExtensionPrefixBits - 1); +COMPARE_CONSTANTS(IDevice::EXTENSION_TYPE_LOW_BITS_TYPE, kExtensionTypeBits); +COMPARE_CONSTANTS(IPreparedModel::DEFAULT_LOOP_TIMEOUT_DURATION_NS, + operation_while::kTimeoutNsDefault); +COMPARE_CONSTANTS(IPreparedModel::MAXIMUM_LOOP_TIMEOUT_DURATION_NS, + operation_while::kTimeoutNsMaximum); + +#undef COMPARE_CONSTANTS + +} // anonymous namespace diff --git a/neuralnetworks/aidl/utils/src/Conversions.cpp b/neuralnetworks/aidl/utils/src/Conversions.cpp new file mode 100644 index 0000000000..db3504bb74 --- /dev/null +++ b/neuralnetworks/aidl/utils/src/Conversions.cpp @@ -0,0 +1,731 @@ +/* + * Copyright (C) 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. + */ + +#include "Conversions.h" + +#include <aidl/android/hardware/common/NativeHandle.h> +#include <android-base/logging.h> +#include <android/hardware_buffer.h> +#include <cutils/native_handle.h> +#include <nnapi/OperandTypes.h> +#include <nnapi/OperationTypes.h> +#include <nnapi/Result.h> +#include <nnapi/SharedMemory.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/Validation.h> +#include <nnapi/hal/CommonUtils.h> +#include <nnapi/hal/HandleError.h> +#include <vndk/hardware_buffer.h> + +#include <algorithm> +#include <chrono> +#include <functional> +#include <iterator> +#include <limits> +#include <type_traits> +#include <utility> + +#define VERIFY_NON_NEGATIVE(value) \ + while (UNLIKELY(value < 0)) return NN_ERROR() + +namespace { + +template <typename Type> +constexpr std::underlying_type_t<Type> underlyingType(Type value) { + return static_cast<std::underlying_type_t<Type>>(value); +} + +constexpr auto kVersion = android::nn::Version::ANDROID_S; + +} // namespace + +namespace android::nn { +namespace { + +using ::aidl::android::hardware::common::NativeHandle; + +constexpr auto validOperandType(nn::OperandType operandType) { + switch (operandType) { + case nn::OperandType::FLOAT32: + case nn::OperandType::INT32: + case nn::OperandType::UINT32: + case nn::OperandType::TENSOR_FLOAT32: + case nn::OperandType::TENSOR_INT32: + case nn::OperandType::TENSOR_QUANT8_ASYMM: + case nn::OperandType::BOOL: + case nn::OperandType::TENSOR_QUANT16_SYMM: + case nn::OperandType::TENSOR_FLOAT16: + case nn::OperandType::TENSOR_BOOL8: + case nn::OperandType::FLOAT16: + case nn::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + case nn::OperandType::TENSOR_QUANT16_ASYMM: + case nn::OperandType::TENSOR_QUANT8_SYMM: + case nn::OperandType::TENSOR_QUANT8_ASYMM_SIGNED: + case nn::OperandType::SUBGRAPH: + return true; + case nn::OperandType::OEM: + case nn::OperandType::TENSOR_OEM_BYTE: + return false; + } + return nn::isExtension(operandType); +} + +template <typename Input> +using UnvalidatedConvertOutput = + std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; + +template <typename Type> +GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvertVec( + const std::vector<Type>& arguments) { + std::vector<UnvalidatedConvertOutput<Type>> canonical; + canonical.reserve(arguments.size()); + for (const auto& argument : arguments) { + canonical.push_back(NN_TRY(nn::unvalidatedConvert(argument))); + } + return canonical; +} + +template <typename Type> +GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( + const std::vector<Type>& arguments) { + return unvalidatedConvertVec(arguments); +} + +template <typename Type> +GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& halObject) { + auto canonical = NN_TRY(nn::unvalidatedConvert(halObject)); + const auto maybeVersion = validate(canonical); + if (!maybeVersion.has_value()) { + return error() << maybeVersion.error(); + } + const auto version = maybeVersion.value(); + if (version > kVersion) { + return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion; + } + return canonical; +} + +template <typename Type> +GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> validatedConvert( + const std::vector<Type>& arguments) { + std::vector<UnvalidatedConvertOutput<Type>> canonical; + canonical.reserve(arguments.size()); + for (const auto& argument : arguments) { + canonical.push_back(NN_TRY(validatedConvert(argument))); + } + return canonical; +} + +GeneralResult<Handle> unvalidatedConvertHelper(const NativeHandle& aidlNativeHandle) { + std::vector<base::unique_fd> fds; + fds.reserve(aidlNativeHandle.fds.size()); + for (const auto& fd : aidlNativeHandle.fds) { + const int dupFd = dup(fd.get()); + if (dupFd == -1) { + // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return + // here? + return NN_ERROR() << "Failed to dup the fd"; + } + fds.emplace_back(dupFd); + } + + return Handle{.fds = std::move(fds), .ints = aidlNativeHandle.ints}; +} + +struct NativeHandleDeleter { + void operator()(native_handle_t* handle) const { + if (handle) { + native_handle_close(handle); + native_handle_delete(handle); + } + } +}; + +using UniqueNativeHandle = std::unique_ptr<native_handle_t, NativeHandleDeleter>; + +static nn::GeneralResult<UniqueNativeHandle> nativeHandleFromAidlHandle( + const NativeHandle& handle) { + std::vector<base::unique_fd> fds; + fds.reserve(handle.fds.size()); + for (const auto& fd : handle.fds) { + const int dupFd = dup(fd.get()); + if (dupFd == -1) { + return NN_ERROR() << "Failed to dup the fd"; + } + fds.emplace_back(dupFd); + } + + 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"; + } + 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); +} + +} // anonymous namespace + +GeneralResult<OperandType> unvalidatedConvert(const aidl_hal::OperandType& operandType) { + VERIFY_NON_NEGATIVE(underlyingType(operandType)) << "Negative operand types are not allowed."; + return static_cast<OperandType>(operandType); +} + +GeneralResult<OperationType> unvalidatedConvert(const aidl_hal::OperationType& operationType) { + VERIFY_NON_NEGATIVE(underlyingType(operationType)) + << "Negative operation types are not allowed."; + return static_cast<OperationType>(operationType); +} + +GeneralResult<DeviceType> unvalidatedConvert(const aidl_hal::DeviceType& deviceType) { + return static_cast<DeviceType>(deviceType); +} + +GeneralResult<Priority> unvalidatedConvert(const aidl_hal::Priority& priority) { + return static_cast<Priority>(priority); +} + +GeneralResult<Capabilities> unvalidatedConvert(const aidl_hal::Capabilities& capabilities) { + const bool validOperandTypes = std::all_of( + capabilities.operandPerformance.begin(), capabilities.operandPerformance.end(), + [](const aidl_hal::OperandPerformance& operandPerformance) { + const auto maybeType = unvalidatedConvert(operandPerformance.type); + return !maybeType.has_value() ? false : validOperandType(maybeType.value()); + }); + if (!validOperandTypes) { + return NN_ERROR() << "Invalid OperandType when unvalidatedConverting OperandPerformance in " + "Capabilities"; + } + + auto operandPerformance = NN_TRY(unvalidatedConvert(capabilities.operandPerformance)); + auto table = NN_TRY(hal::utils::makeGeneralFailure( + Capabilities::OperandPerformanceTable::create(std::move(operandPerformance)), + nn::ErrorStatus::GENERAL_FAILURE)); + + return Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = NN_TRY( + unvalidatedConvert(capabilities.relaxedFloat32toFloat16PerformanceScalar)), + .relaxedFloat32toFloat16PerformanceTensor = NN_TRY( + unvalidatedConvert(capabilities.relaxedFloat32toFloat16PerformanceTensor)), + .operandPerformance = std::move(table), + .ifPerformance = NN_TRY(unvalidatedConvert(capabilities.ifPerformance)), + .whilePerformance = NN_TRY(unvalidatedConvert(capabilities.whilePerformance)), + }; +} + +GeneralResult<Capabilities::OperandPerformance> unvalidatedConvert( + const aidl_hal::OperandPerformance& operandPerformance) { + return Capabilities::OperandPerformance{ + .type = NN_TRY(unvalidatedConvert(operandPerformance.type)), + .info = NN_TRY(unvalidatedConvert(operandPerformance.info)), + }; +} + +GeneralResult<Capabilities::PerformanceInfo> unvalidatedConvert( + const aidl_hal::PerformanceInfo& performanceInfo) { + return Capabilities::PerformanceInfo{ + .execTime = performanceInfo.execTime, + .powerUsage = performanceInfo.powerUsage, + }; +} + +GeneralResult<DataLocation> unvalidatedConvert(const aidl_hal::DataLocation& location) { + VERIFY_NON_NEGATIVE(location.poolIndex) << "DataLocation: pool index must not be negative"; + VERIFY_NON_NEGATIVE(location.offset) << "DataLocation: offset must not be negative"; + VERIFY_NON_NEGATIVE(location.length) << "DataLocation: length must not be negative"; + if (location.offset > std::numeric_limits<uint32_t>::max()) { + return NN_ERROR() << "DataLocation: offset must be <= std::numeric_limits<uint32_t>::max()"; + } + if (location.length > std::numeric_limits<uint32_t>::max()) { + return NN_ERROR() << "DataLocation: length must be <= std::numeric_limits<uint32_t>::max()"; + } + return DataLocation{ + .poolIndex = static_cast<uint32_t>(location.poolIndex), + .offset = static_cast<uint32_t>(location.offset), + .length = static_cast<uint32_t>(location.length), + }; +} + +GeneralResult<Operation> unvalidatedConvert(const aidl_hal::Operation& operation) { + return Operation{ + .type = NN_TRY(unvalidatedConvert(operation.type)), + .inputs = NN_TRY(toUnsigned(operation.inputs)), + .outputs = NN_TRY(toUnsigned(operation.outputs)), + }; +} + +GeneralResult<Operand::LifeTime> unvalidatedConvert( + const aidl_hal::OperandLifeTime& operandLifeTime) { + return static_cast<Operand::LifeTime>(operandLifeTime); +} + +GeneralResult<Operand> unvalidatedConvert(const aidl_hal::Operand& operand) { + return Operand{ + .type = NN_TRY(unvalidatedConvert(operand.type)), + .dimensions = NN_TRY(toUnsigned(operand.dimensions)), + .scale = operand.scale, + .zeroPoint = operand.zeroPoint, + .lifetime = NN_TRY(unvalidatedConvert(operand.lifetime)), + .location = NN_TRY(unvalidatedConvert(operand.location)), + .extraParams = NN_TRY(unvalidatedConvert(operand.extraParams)), + }; +} + +GeneralResult<Operand::ExtraParams> unvalidatedConvert( + const std::optional<aidl_hal::OperandExtraParams>& optionalExtraParams) { + if (!optionalExtraParams.has_value()) { + return Operand::NoParams{}; + } + const auto& extraParams = optionalExtraParams.value(); + using Tag = aidl_hal::OperandExtraParams::Tag; + switch (extraParams.getTag()) { + case Tag::channelQuant: + return unvalidatedConvert(extraParams.get<Tag::channelQuant>()); + case Tag::extension: + return extraParams.get<Tag::extension>(); + } + return NN_ERROR() << "Unrecognized Operand::ExtraParams tag: " + << underlyingType(extraParams.getTag()); +} + +GeneralResult<Operand::SymmPerChannelQuantParams> unvalidatedConvert( + const aidl_hal::SymmPerChannelQuantParams& symmPerChannelQuantParams) { + VERIFY_NON_NEGATIVE(symmPerChannelQuantParams.channelDim) + << "Per-channel quantization channel dimension must not be negative."; + return Operand::SymmPerChannelQuantParams{ + .scales = symmPerChannelQuantParams.scales, + .channelDim = static_cast<uint32_t>(symmPerChannelQuantParams.channelDim), + }; +} + +GeneralResult<Model> unvalidatedConvert(const aidl_hal::Model& model) { + return Model{ + .main = NN_TRY(unvalidatedConvert(model.main)), + .referenced = NN_TRY(unvalidatedConvert(model.referenced)), + .operandValues = NN_TRY(unvalidatedConvert(model.operandValues)), + .pools = NN_TRY(unvalidatedConvert(model.pools)), + .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16, + .extensionNameToPrefix = NN_TRY(unvalidatedConvert(model.extensionNameToPrefix)), + }; +} + +GeneralResult<Model::Subgraph> unvalidatedConvert(const aidl_hal::Subgraph& subgraph) { + return Model::Subgraph{ + .operands = NN_TRY(unvalidatedConvert(subgraph.operands)), + .operations = NN_TRY(unvalidatedConvert(subgraph.operations)), + .inputIndexes = NN_TRY(toUnsigned(subgraph.inputIndexes)), + .outputIndexes = NN_TRY(toUnsigned(subgraph.outputIndexes)), + }; +} + +GeneralResult<Model::ExtensionNameAndPrefix> unvalidatedConvert( + const aidl_hal::ExtensionNameAndPrefix& extensionNameAndPrefix) { + return Model::ExtensionNameAndPrefix{ + .name = extensionNameAndPrefix.name, + .prefix = extensionNameAndPrefix.prefix, + }; +} + +GeneralResult<Extension> unvalidatedConvert(const aidl_hal::Extension& extension) { + return Extension{ + .name = extension.name, + .operandTypes = NN_TRY(unvalidatedConvert(extension.operandTypes)), + }; +} + +GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert( + const aidl_hal::ExtensionOperandTypeInformation& operandTypeInformation) { + VERIFY_NON_NEGATIVE(operandTypeInformation.byteSize) + << "Extension operand type byte size must not be negative"; + return Extension::OperandTypeInformation{ + .type = operandTypeInformation.type, + .isTensor = operandTypeInformation.isTensor, + .byteSize = static_cast<uint32_t>(operandTypeInformation.byteSize), + }; +} + +GeneralResult<OutputShape> unvalidatedConvert(const aidl_hal::OutputShape& outputShape) { + return OutputShape{ + .dimensions = NN_TRY(toUnsigned(outputShape.dimensions)), + .isSufficient = outputShape.isSufficient, + }; +} + +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<uint32_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<uint32_t>(memory.size), + .name = memory.name, + }); + } + + 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 UniqueNativeHandle handle = NN_TRY(nativeHandleFromAidlHandle(memory.handle)); + const native_handle_t* nativeHandle = handle.get(); + + // 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; + } + } + 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<uint32_t>(memory.size), + .name = memory.name, + }); +} + +GeneralResult<Model::OperandValues> unvalidatedConvert(const std::vector<uint8_t>& operandValues) { + return Model::OperandValues(operandValues.data(), operandValues.size()); +} + +GeneralResult<BufferDesc> unvalidatedConvert(const aidl_hal::BufferDesc& bufferDesc) { + return BufferDesc{.dimensions = NN_TRY(toUnsigned(bufferDesc.dimensions))}; +} + +GeneralResult<BufferRole> unvalidatedConvert(const aidl_hal::BufferRole& bufferRole) { + VERIFY_NON_NEGATIVE(bufferRole.modelIndex) << "BufferRole: modelIndex must not be negative"; + VERIFY_NON_NEGATIVE(bufferRole.ioIndex) << "BufferRole: ioIndex must not be negative"; + return BufferRole{ + .modelIndex = static_cast<uint32_t>(bufferRole.modelIndex), + .ioIndex = static_cast<uint32_t>(bufferRole.ioIndex), + .frequency = bufferRole.frequency, + }; +} + +GeneralResult<Request> unvalidatedConvert(const aidl_hal::Request& request) { + return Request{ + .inputs = NN_TRY(unvalidatedConvert(request.inputs)), + .outputs = NN_TRY(unvalidatedConvert(request.outputs)), + .pools = NN_TRY(unvalidatedConvert(request.pools)), + }; +} + +GeneralResult<Request::Argument> unvalidatedConvert(const aidl_hal::RequestArgument& argument) { + const auto lifetime = argument.hasNoValue ? Request::Argument::LifeTime::NO_VALUE + : Request::Argument::LifeTime::POOL; + return Request::Argument{ + .lifetime = lifetime, + .location = NN_TRY(unvalidatedConvert(argument.location)), + .dimensions = NN_TRY(toUnsigned(argument.dimensions)), + }; +} + +GeneralResult<Request::MemoryPool> unvalidatedConvert( + const aidl_hal::RequestMemoryPool& memoryPool) { + using Tag = aidl_hal::RequestMemoryPool::Tag; + switch (memoryPool.getTag()) { + case Tag::pool: + return unvalidatedConvert(memoryPool.get<Tag::pool>()); + case Tag::token: { + const auto token = memoryPool.get<Tag::token>(); + VERIFY_NON_NEGATIVE(token) << "Memory pool token must not be negative"; + return static_cast<Request::MemoryDomainToken>(token); + } + } + return NN_ERROR() << "Invalid Request::MemoryPool tag " << underlyingType(memoryPool.getTag()); +} + +GeneralResult<ErrorStatus> unvalidatedConvert(const aidl_hal::ErrorStatus& status) { + switch (status) { + case aidl_hal::ErrorStatus::NONE: + case aidl_hal::ErrorStatus::DEVICE_UNAVAILABLE: + case aidl_hal::ErrorStatus::GENERAL_FAILURE: + case aidl_hal::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE: + case aidl_hal::ErrorStatus::INVALID_ARGUMENT: + case aidl_hal::ErrorStatus::MISSED_DEADLINE_TRANSIENT: + case aidl_hal::ErrorStatus::MISSED_DEADLINE_PERSISTENT: + case aidl_hal::ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT: + case aidl_hal::ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT: + return static_cast<ErrorStatus>(status); + } + return NN_ERROR() << "Invalid ErrorStatus " << underlyingType(status); +} + +GeneralResult<ExecutionPreference> unvalidatedConvert( + const aidl_hal::ExecutionPreference& executionPreference) { + return static_cast<ExecutionPreference>(executionPreference); +} + +GeneralResult<SharedHandle> unvalidatedConvert(const NativeHandle& aidlNativeHandle) { + return std::make_shared<const Handle>(NN_TRY(unvalidatedConvertHelper(aidlNativeHandle))); +} + +GeneralResult<ExecutionPreference> convert( + const aidl_hal::ExecutionPreference& executionPreference) { + return validatedConvert(executionPreference); +} + +GeneralResult<SharedMemory> convert(const aidl_hal::Memory& operand) { + return validatedConvert(operand); +} + +GeneralResult<Model> convert(const aidl_hal::Model& model) { + return validatedConvert(model); +} + +GeneralResult<Operand> convert(const aidl_hal::Operand& operand) { + return unvalidatedConvert(operand); +} + +GeneralResult<OperandType> convert(const aidl_hal::OperandType& operandType) { + return unvalidatedConvert(operandType); +} + +GeneralResult<Priority> convert(const aidl_hal::Priority& priority) { + return validatedConvert(priority); +} + +GeneralResult<Request::MemoryPool> convert(const aidl_hal::RequestMemoryPool& memoryPool) { + return unvalidatedConvert(memoryPool); +} + +GeneralResult<Request> convert(const aidl_hal::Request& request) { + return validatedConvert(request); +} + +GeneralResult<std::vector<Operation>> convert(const std::vector<aidl_hal::Operation>& operations) { + return unvalidatedConvert(operations); +} + +GeneralResult<std::vector<SharedMemory>> convert(const std::vector<aidl_hal::Memory>& memories) { + return validatedConvert(memories); +} + +GeneralResult<std::vector<uint32_t>> toUnsigned(const std::vector<int32_t>& vec) { + if (!std::all_of(vec.begin(), vec.end(), [](int32_t v) { return v >= 0; })) { + return NN_ERROR() << "Negative value passed to conversion from signed to unsigned"; + } + return std::vector<uint32_t>(vec.begin(), vec.end()); +} + +} // namespace android::nn + +namespace aidl::android::hardware::neuralnetworks::utils { +namespace { + +template <typename Input> +using UnvalidatedConvertOutput = + std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; + +template <typename Type> +nn::GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvertVec( + const std::vector<Type>& arguments) { + std::vector<UnvalidatedConvertOutput<Type>> halObject(arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + halObject[i] = NN_TRY(unvalidatedConvert(arguments[i])); + } + return halObject; +} + +template <typename Type> +nn::GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& canonical) { + const auto maybeVersion = nn::validate(canonical); + if (!maybeVersion.has_value()) { + return nn::error() << maybeVersion.error(); + } + const auto version = maybeVersion.value(); + if (version > kVersion) { + return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion; + } + return utils::unvalidatedConvert(canonical); +} + +template <typename Type> +nn::GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> validatedConvert( + const std::vector<Type>& arguments) { + std::vector<UnvalidatedConvertOutput<Type>> halObject(arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + halObject[i] = NN_TRY(validatedConvert(arguments[i])); + } + return halObject; +} + +nn::GeneralResult<common::NativeHandle> unvalidatedConvert(const nn::Handle& handle) { + common::NativeHandle aidlNativeHandle; + aidlNativeHandle.fds.reserve(handle.fds.size()); + for (const auto& fd : handle.fds) { + const int dupFd = dup(fd.get()); + if (dupFd == -1) { + // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return + // here? + return NN_ERROR() << "Failed to dup the fd"; + } + aidlNativeHandle.fds.emplace_back(dupFd); + } + aidlNativeHandle.ints = handle.ints; + return aidlNativeHandle; +} + +static nn::GeneralResult<common::NativeHandle> aidlHandleFromNativeHandle( + const native_handle_t& handle) { + common::NativeHandle aidlNativeHandle; + + aidlNativeHandle.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(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd"; + } + aidlNativeHandle.fds.emplace_back(dupFd); + } + + aidlNativeHandle.ints = std::vector<int>(&handle.data[handle.numFds], + &handle.data[handle.numFds + handle.numInts]); + + return aidlNativeHandle; +} + +} // namespace + +nn::GeneralResult<common::NativeHandle> unvalidatedConvert(const nn::SharedHandle& sharedHandle) { + CHECK(sharedHandle != nullptr); + return unvalidatedConvert(*sharedHandle); +} + +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 (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, + }; +} + +nn::GeneralResult<ErrorStatus> unvalidatedConvert(const nn::ErrorStatus& errorStatus) { + switch (errorStatus) { + case nn::ErrorStatus::NONE: + case nn::ErrorStatus::DEVICE_UNAVAILABLE: + case nn::ErrorStatus::GENERAL_FAILURE: + case nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE: + case nn::ErrorStatus::INVALID_ARGUMENT: + case nn::ErrorStatus::MISSED_DEADLINE_TRANSIENT: + case nn::ErrorStatus::MISSED_DEADLINE_PERSISTENT: + case nn::ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT: + case nn::ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT: + return static_cast<ErrorStatus>(errorStatus); + default: + return ErrorStatus::GENERAL_FAILURE; + } +} + +nn::GeneralResult<OutputShape> unvalidatedConvert(const nn::OutputShape& outputShape) { + return OutputShape{.dimensions = NN_TRY(toSigned(outputShape.dimensions)), + .isSufficient = outputShape.isSufficient}; +} + +nn::GeneralResult<Memory> convert(const nn::SharedMemory& memory) { + return validatedConvert(memory); +} + +nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus) { + return validatedConvert(errorStatus); +} + +nn::GeneralResult<std::vector<OutputShape>> convert( + const std::vector<nn::OutputShape>& outputShapes) { + return validatedConvert(outputShapes); +} + +nn::GeneralResult<std::vector<int32_t>> toSigned(const std::vector<uint32_t>& vec) { + if (!std::all_of(vec.begin(), vec.end(), + [](uint32_t v) { return v <= std::numeric_limits<int32_t>::max(); })) { + return NN_ERROR() << "Vector contains a value that doesn't fit into int32_t."; + } + return std::vector<int32_t>(vec.begin(), vec.end()); +} + +} // namespace aidl::android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/aidl/utils/src/Utils.cpp b/neuralnetworks/aidl/utils/src/Utils.cpp new file mode 100644 index 0000000000..8d00e5926a --- /dev/null +++ b/neuralnetworks/aidl/utils/src/Utils.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 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. + */ + +#include "Utils.h" + +#include <nnapi/Result.h> + +namespace aidl::android::hardware::neuralnetworks::utils { +namespace { + +using ::android::nn::GeneralResult; + +template <typename Type> +nn::GeneralResult<std::vector<Type>> cloneVec(const std::vector<Type>& arguments) { + std::vector<Type> clonedObjects; + clonedObjects.reserve(arguments.size()); + for (const auto& argument : arguments) { + clonedObjects.push_back(NN_TRY(clone(argument))); + } + return clonedObjects; +} + +template <typename Type> +GeneralResult<std::vector<Type>> clone(const std::vector<Type>& arguments) { + return cloneVec(arguments); +} + +} // namespace + +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"; + } + nativeHandle.fds.emplace_back(newFd); + } + return Memory{ + .handle = std::move(nativeHandle), + .size = memory.size, + .name = memory.name, + }; +} + +GeneralResult<RequestMemoryPool> clone(const RequestMemoryPool& requestPool) { + using Tag = RequestMemoryPool::Tag; + switch (requestPool.getTag()) { + case Tag::pool: + return RequestMemoryPool::make<Tag::pool>(NN_TRY(clone(requestPool.get<Tag::pool>()))); + case Tag::token: + return RequestMemoryPool::make<Tag::token>(requestPool.get<Tag::token>()); + } + // Using explicit type conversion because std::variant inside the RequestMemoryPool confuses the + // compiler. + return (NN_ERROR() << "Unrecognized request pool tag: " << requestPool.getTag()) + . + operator GeneralResult<RequestMemoryPool>(); +} + +GeneralResult<Request> clone(const Request& request) { + return Request{ + .inputs = request.inputs, + .outputs = request.outputs, + .pools = NN_TRY(clone(request.pools)), + }; +} + +GeneralResult<Model> clone(const Model& model) { + return Model{ + .main = model.main, + .referenced = model.referenced, + .operandValues = model.operandValues, + .pools = NN_TRY(clone(model.pools)), + .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16, + .extensionNameToPrefix = model.extensionNameToPrefix, + }; +} + +} // namespace aidl::android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/aidl/vts/OWNERS b/neuralnetworks/aidl/vts/OWNERS new file mode 100644 index 0000000000..6719a5b3a2 --- /dev/null +++ b/neuralnetworks/aidl/vts/OWNERS @@ -0,0 +1,12 @@ +# Neuralnetworks team +butlermichael@google.com +dgross@google.com +jeanluc@google.com +levp@google.com +miaowang@google.com +mikie@google.com +mks@google.com +pszczepaniak@google.com +slavash@google.com +vddang@google.com +xusongw@google.com diff --git a/neuralnetworks/aidl/vts/functional/Android.bp b/neuralnetworks/aidl/vts/functional/Android.bp new file mode 100644 index 0000000000..aa7afbf6a7 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/Android.bp @@ -0,0 +1,68 @@ +// +// Copyright (C) 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. +// + +cc_test { + name: "VtsHalNeuralnetworksTargetTest", + defaults: [ + "neuralnetworks_vts_functional_defaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "BasicTests.cpp", + "Callbacks.cpp", + "CompilationCachingTests.cpp", + "GeneratedTestHarness.cpp", + "MemoryDomainTests.cpp", + "QualityOfServiceTests.cpp", + "TestAssertions.cpp", + "TestMain.cpp", + "Utils.cpp", + "ValidateModel.cpp", + "ValidateRequest.cpp", + "VtsHalNeuralnetworks.cpp", + ], + shared_libs: [ + "libbinder_ndk", + "libnativewindow", + "libvndksupport", + ], + static_libs: [ + "android.hardware.common-V2-ndk_platform", + "android.hardware.neuralnetworks-V1-ndk_platform", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libgmock", + "libhidlmemory", + "libneuralnetworks_generated_test_harness", + "libneuralnetworks_utils", + "libsync", + "neuralnetworks_utils_hal_aidl", + ], + whole_static_libs: [ + "neuralnetworks_generated_V1_0_example", + "neuralnetworks_generated_V1_1_example", + "neuralnetworks_generated_V1_2_example", + "neuralnetworks_generated_V1_3_example", + ], + header_libs: [ + "libbase_headers", + "libneuralnetworks_headers", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/neuralnetworks/aidl/vts/functional/AndroidTest.xml b/neuralnetworks/aidl/vts/functional/AndroidTest.xml new file mode 100644 index 0000000000..384d42078f --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/AndroidTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs VtsHalNeuralnetworksTargetTest."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="VtsHalNeuralnetworksTargetTest->/data/local/tmp/VtsHalNeuralnetworksTargetTest" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="VtsHalNeuralnetworksTargetTest" /> + <option name="native-test-timeout" value="20m" /> + </test> +</configuration> diff --git a/neuralnetworks/aidl/vts/functional/BasicTests.cpp b/neuralnetworks/aidl/vts/functional/BasicTests.cpp new file mode 100644 index 0000000000..b2f4507c22 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/BasicTests.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "neuralnetworks_aidl_hal_test" + +#include <aidl/android/hardware/neuralnetworks/Capabilities.h> +#include <aidl/android/hardware/neuralnetworks/IDevice.h> +#include <aidl/android/hardware/neuralnetworks/Operand.h> +#include <aidl/android/hardware/neuralnetworks/OperandType.h> +#include <aidl/android/hardware/neuralnetworks/Priority.h> +#include <android/binder_interface_utils.h> + +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +using implementation::PreparedModelCallback; + +// create device test +TEST_P(NeuralNetworksAidlTest, CreateDevice) {} + +// initialization +TEST_P(NeuralNetworksAidlTest, GetCapabilitiesTest) { + Capabilities capabilities; + const auto retStatus = kDevice->getCapabilities(&capabilities); + ASSERT_TRUE(retStatus.isOk()); + + auto isPositive = [](const PerformanceInfo& perf) { + return perf.execTime > 0.0f && perf.powerUsage > 0.0f; + }; + + EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceScalar)); + EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceTensor)); + const auto& opPerf = capabilities.operandPerformance; + EXPECT_TRUE( + std::all_of(opPerf.begin(), opPerf.end(), + [isPositive](const OperandPerformance& a) { return isPositive(a.info); })); + EXPECT_TRUE(std::is_sorted(opPerf.begin(), opPerf.end(), + [](const OperandPerformance& a, const OperandPerformance& b) { + return a.type < b.type; + })); + EXPECT_TRUE(std::all_of(opPerf.begin(), opPerf.end(), [](const OperandPerformance& a) { + return a.type != OperandType::SUBGRAPH; + })); + EXPECT_TRUE(isPositive(capabilities.ifPerformance)); + EXPECT_TRUE(isPositive(capabilities.whilePerformance)); +} + +// detect cycle +TEST_P(NeuralNetworksAidlTest, CycleTest) { + // opnd0 = TENSOR_FLOAT32 // model input + // opnd1 = TENSOR_FLOAT32 // model input + // opnd2 = INT32 // model input + // opnd3 = ADD(opnd0, opnd4, opnd2) + // opnd4 = ADD(opnd1, opnd3, opnd2) + // opnd5 = ADD(opnd4, opnd0, opnd2) // model output + // + // +-----+ + // | | + // v | + // 3 = ADD(0, 4, 2) | + // | | + // +----------+ | + // | | + // v | + // 4 = ADD(1, 3, 2) | + // | | + // +----------------+ + // | + // | + // +-------+ + // | + // v + // 5 = ADD(4, 0, 2) + + const std::vector<Operand> operands = { + { + // operands[0] + .type = OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::SUBGRAPH_INPUT, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }, + { + // operands[1] + .type = OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::SUBGRAPH_INPUT, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }, + { + // operands[2] + .type = OperandType::INT32, + .dimensions = {}, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::SUBGRAPH_INPUT, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }, + { + // operands[3] + .type = OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::TEMPORARY_VARIABLE, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }, + { + // operands[4] + .type = OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::TEMPORARY_VARIABLE, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }, + { + // operands[5] + .type = OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::SUBGRAPH_OUTPUT, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }, + }; + + const std::vector<Operation> operations = { + {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}}, + {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}}, + {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}}, + }; + + Subgraph subgraph = { + .operands = operands, + .operations = operations, + .inputIndexes = {0, 1, 2}, + .outputIndexes = {5}, + }; + const Model model = { + .main = std::move(subgraph), + .referenced = {}, + .operandValues = {}, + .pools = {}, + }; + + // ensure that getSupportedOperations() checks model validity + std::vector<bool> supportedOps; + const auto supportedOpsStatus = kDevice->getSupportedOperations(model, &supportedOps); + ASSERT_FALSE(supportedOpsStatus.isOk()); + ASSERT_EQ(supportedOpsStatus.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast<ErrorStatus>(supportedOpsStatus.getServiceSpecificError()), + ErrorStatus::INVALID_ARGUMENT); + + // ensure that prepareModel() checks model validity + auto preparedModelCallback = ndk::SharedRefBase::make<PreparedModelCallback>(); + auto prepareLaunchStatus = + kDevice->prepareModel(model, ExecutionPreference::FAST_SINGLE_ANSWER, kDefaultPriority, + kNoDeadline, {}, {}, kEmptyCacheToken, preparedModelCallback); + // Note that preparation can fail for reasons other than an + // invalid model (invalid model should result in + // INVALID_ARGUMENT) -- for example, perhaps not all + // operations are supported, or perhaps the device hit some + // kind of capacity limit. + ASSERT_FALSE(prepareLaunchStatus.isOk()); + EXPECT_EQ(prepareLaunchStatus.getExceptionCode(), EX_SERVICE_SPECIFIC); + EXPECT_NE(static_cast<ErrorStatus>(prepareLaunchStatus.getServiceSpecificError()), + ErrorStatus::NONE); + + EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE); + EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr); +} + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional diff --git a/neuralnetworks/aidl/vts/functional/Callbacks.cpp b/neuralnetworks/aidl/vts/functional/Callbacks.cpp new file mode 100644 index 0000000000..ca2bb48a3e --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/Callbacks.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "Callbacks" + +#include "Callbacks.h" + +#include <android-base/logging.h> +#include <android/binder_auto_utils.h> +#include <limits> + +namespace aidl::android::hardware::neuralnetworks::implementation { + +ndk::ScopedAStatus PreparedModelCallback::notify( + ErrorStatus errorStatus, const std::shared_ptr<IPreparedModel>& preparedModel) { + { + std::lock_guard<std::mutex> hold(mMutex); + // quick-return if object has already been notified + if (mNotified) { + return ndk::ScopedAStatus::ok(); + } + // store results and mark as notified + mErrorStatus = errorStatus; + mPreparedModel = preparedModel; + mNotified = true; + } + mCondition.notify_all(); + return ndk::ScopedAStatus::ok(); +} + +void PreparedModelCallback::wait() const { + std::unique_lock<std::mutex> lock(mMutex); + mCondition.wait(lock, [this] { return mNotified; }); +} + +ErrorStatus PreparedModelCallback::getStatus() const { + wait(); + return mErrorStatus; +} + +std::shared_ptr<IPreparedModel> PreparedModelCallback::getPreparedModel() const { + wait(); + return mPreparedModel; +} + +} // namespace aidl::android::hardware::neuralnetworks::implementation diff --git a/neuralnetworks/aidl/vts/functional/Callbacks.h b/neuralnetworks/aidl/vts/functional/Callbacks.h new file mode 100644 index 0000000000..0eb4d5f4a6 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/Callbacks.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 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. + */ + +#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_CALLBACKS_H +#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_CALLBACKS_H + +#include <android-base/thread_annotations.h> +#include <condition_variable> +#include <mutex> + +#include <aidl/android/hardware/neuralnetworks/BnPreparedModelCallback.h> +#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h> +#include <aidl/android/hardware/neuralnetworks/IPreparedModel.h> + +/* + * The Callback classes are used internally by the NeuralNetworks runtime to + * synchronize between different threads. An asynchronous task is launched + * paired with a callback object. When a client thread requires the output being + * generated by the asynchronous task, the client thread can wait for the result + * and be blocked until it has completed. Any wait may safely be called + * concurrently, even on the same callback object. When the asynchronous task + * has finished its workload, it must immediately call "notify". If the + * asynchronous task has failed to launch, the function that tried to launch the + * asynchronous task must immediately call "notify". This "notify" call + * awakens any client threads waiting on the callback object. + * + * These classes exist to enable synchronization across AIDL. When + * synchronization is only required in the same process, consider using + * std::future, std::mutex, std::condition_variable, or std::experimental::latch + * instead. + */ + +namespace aidl::android::hardware::neuralnetworks::implementation { + +/** + * The PreparedModelCallback class is used to receive the error status of + * preparing a model as well as the prepared model from a task executing + * asynchronously with respect to the runtime. If a calling thread calls wait + * or get* on a PreparedModelCallback object and the corresponding asynchronous + * task has not finished preparing the model, the calling thread will block + * until the asynchronous task has called notify. + * + * If the callback object is notified more than once, only the results of the + * first call to notify are used, and the results from subsequent calls are + * discarded. + * + * This callback object is passed as an argument to IDevice::prepareModel*. + */ +class PreparedModelCallback : public BnPreparedModelCallback { + public: + /** + * IPreparedModelCallback::notify marks the callback object with the return + * status of the asynchronous model preparation along with the prepared + * model, and allows all prior and future wait calls on the + * PreparedModelCallback object to proceed. + * + * IPreparedModelCallback::notify must be called on a given PreparedModelCallback object. + * + * If the callback object is notified more than once, only the results of + * the first call to notify are used, and the results from subsequent calls + * are discarded. + * + * @param status Error status returned from asynchronously preparing the + * model; will be: + * - NONE if the asynchronous preparation was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if the input model is invalid + * @param preparedModel Returned model that has been prepared for execution, + * nullptr if the model was unable to be prepared. + */ + ndk::ScopedAStatus notify(ErrorStatus status, + const std::shared_ptr<IPreparedModel>& preparedModel) override; + + /** + * PreparedModelCallback::wait blocks until notify has been called on the + * callback object. + */ + void wait() const; + + /** + * Retrieves the error status returned from the asynchronous task launched + * by IDevice::prepareModel*. If IDevice::prepareModel* has not finished + * asynchronously preparing the model, this call will block until the + * asynchronous task notifies the object. + * + * @return status Error status returned from asynchronously preparing the + * model; will be: + * - NONE if the asynchronous preparation was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if the input model is invalid + */ + ErrorStatus getStatus() const; + + /** + * Retrieves the model that has been prepared for execution from the + * asynchronous task launched by IDevice::prepareModel*. If + * IDevice::prepareModel* has not finished asynchronously preparing the + * model, this call will block until the asynchronous task notifies the + * object. + * + * @return preparedModel Returned model that has been prepared for + * execution, nullptr if the model was unable to be prepared. + */ + std::shared_ptr<IPreparedModel> getPreparedModel() const; + + private: + mutable std::mutex mMutex; + mutable std::condition_variable mCondition; + bool mNotified GUARDED_BY(mMutex) = false; + ErrorStatus mErrorStatus = ErrorStatus::GENERAL_FAILURE; + std::shared_ptr<IPreparedModel> mPreparedModel; +}; + +} // namespace aidl::android::hardware::neuralnetworks::implementation + +#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_CALLBACKS_H diff --git a/neuralnetworks/aidl/vts/functional/CompilationCachingTests.cpp b/neuralnetworks/aidl/vts/functional/CompilationCachingTests.cpp new file mode 100644 index 0000000000..e0b529f280 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/CompilationCachingTests.cpp @@ -0,0 +1,1177 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "neuralnetworks_aidl_hal_test" + +#include <android-base/logging.h> +#include <android/binder_auto_utils.h> +#include <android/binder_interface_utils.h> +#include <android/binder_status.h> +#include <fcntl.h> +#include <ftw.h> +#include <gtest/gtest.h> +#include <hidlmemory/mapping.h> +#include <unistd.h> + +#include <cstdio> +#include <cstdlib> +#include <iterator> +#include <random> +#include <thread> + +#include "Callbacks.h" +#include "GeneratedTestHarness.h" +#include "MemoryUtils.h" +#include "TestHarness.h" +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +// Forward declaration of the mobilenet generated test models in +// frameworks/ml/nn/runtime/test/generated/. +namespace generated_tests::mobilenet_224_gender_basic_fixed { +const test_helper::TestModel& get_test_model(); +} // namespace generated_tests::mobilenet_224_gender_basic_fixed + +namespace generated_tests::mobilenet_quantized { +const test_helper::TestModel& get_test_model(); +} // namespace generated_tests::mobilenet_quantized + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +using namespace test_helper; +using implementation::PreparedModelCallback; + +namespace float32_model { + +constexpr auto get_test_model = generated_tests::mobilenet_224_gender_basic_fixed::get_test_model; + +} // namespace float32_model + +namespace quant8_model { + +constexpr auto get_test_model = generated_tests::mobilenet_quantized::get_test_model; + +} // namespace quant8_model + +namespace { + +enum class AccessMode { READ_WRITE, READ_ONLY, WRITE_ONLY }; + +// Creates cache handles based on provided file groups. +// The outer vector corresponds to handles and the inner vector is for fds held by each handle. +void createCacheFds(const std::vector<std::string>& files, const std::vector<AccessMode>& mode, + std::vector<ndk::ScopedFileDescriptor>* fds) { + fds->clear(); + fds->reserve(files.size()); + for (uint32_t i = 0; i < files.size(); i++) { + const auto& file = files[i]; + int fd; + if (mode[i] == AccessMode::READ_ONLY) { + fd = open(file.c_str(), O_RDONLY); + } else if (mode[i] == AccessMode::WRITE_ONLY) { + fd = open(file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + } else if (mode[i] == AccessMode::READ_WRITE) { + fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + } else { + FAIL(); + } + ASSERT_GE(fd, 0); + fds->emplace_back(fd); + } +} + +void createCacheFds(const std::vector<std::string>& files, AccessMode mode, + std::vector<ndk::ScopedFileDescriptor>* fds) { + createCacheFds(files, std::vector<AccessMode>(files.size(), mode), fds); +} + +// Create a chain of broadcast operations. The second operand is always constant tensor [1]. +// For simplicity, activation scalar is shared. The second operand is not shared +// in the model to let driver maintain a non-trivial size of constant data and the corresponding +// data locations in cache. +// +// --------- activation -------- +// ↓ ↓ ↓ ↓ +// E.g. input -> ADD -> ADD -> ADD -> ... -> ADD -> output +// ↑ ↑ ↑ ↑ +// [1] [1] [1] [1] +// +// This function assumes the operation is either ADD or MUL. +template <typename CppType, TestOperandType operandType> +TestModel createLargeTestModelImpl(TestOperationType op, uint32_t len) { + EXPECT_TRUE(op == TestOperationType::ADD || op == TestOperationType::MUL); + + // Model operations and operands. + std::vector<TestOperation> operations(len); + std::vector<TestOperand> operands(len * 2 + 2); + + // The activation scalar, value = 0. + operands[0] = { + .type = TestOperandType::INT32, + .dimensions = {}, + .numberOfConsumers = len, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::CONSTANT_COPY, + .data = TestBuffer::createFromVector<int32_t>({0}), + }; + + // The buffer value of the constant second operand. The logical value is always 1.0f. + CppType bufferValue; + // The scale of the first and second operand. + float scale1, scale2; + if (operandType == TestOperandType::TENSOR_FLOAT32) { + bufferValue = 1.0f; + scale1 = 0.0f; + scale2 = 0.0f; + } else if (op == TestOperationType::ADD) { + bufferValue = 1; + scale1 = 1.0f; + scale2 = 1.0f; + } else { + // To satisfy the constraint on quant8 MUL: input0.scale * input1.scale < output.scale, + // set input1 to have scale = 0.5f and bufferValue = 2, i.e. 1.0f in floating point. + bufferValue = 2; + scale1 = 1.0f; + scale2 = 0.5f; + } + + for (uint32_t i = 0; i < len; i++) { + const uint32_t firstInputIndex = i * 2 + 1; + const uint32_t secondInputIndex = firstInputIndex + 1; + const uint32_t outputIndex = secondInputIndex + 1; + + // The first operation input. + operands[firstInputIndex] = { + .type = operandType, + .dimensions = {1}, + .numberOfConsumers = 1, + .scale = scale1, + .zeroPoint = 0, + .lifetime = (i == 0 ? TestOperandLifeTime::MODEL_INPUT + : TestOperandLifeTime::TEMPORARY_VARIABLE), + .data = (i == 0 ? TestBuffer::createFromVector<CppType>({1}) : TestBuffer()), + }; + + // The second operation input, value = 1. + operands[secondInputIndex] = { + .type = operandType, + .dimensions = {1}, + .numberOfConsumers = 1, + .scale = scale2, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::CONSTANT_COPY, + .data = TestBuffer::createFromVector<CppType>({bufferValue}), + }; + + // The operation. All operations share the same activation scalar. + // The output operand is created as an input in the next iteration of the loop, in the case + // of all but the last member of the chain; and after the loop as a model output, in the + // case of the last member of the chain. + operations[i] = { + .type = op, + .inputs = {firstInputIndex, secondInputIndex, /*activation scalar*/ 0}, + .outputs = {outputIndex}, + }; + } + + // For TestOperationType::ADD, output = 1 + 1 * len = len + 1 + // For TestOperationType::MUL, output = 1 * 1 ^ len = 1 + CppType outputResult = static_cast<CppType>(op == TestOperationType::ADD ? len + 1u : 1u); + + // The model output. + operands.back() = { + .type = operandType, + .dimensions = {1}, + .numberOfConsumers = 0, + .scale = scale1, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::MODEL_OUTPUT, + .data = TestBuffer::createFromVector<CppType>({outputResult}), + }; + + return { + .main = {.operands = std::move(operands), + .operations = std::move(operations), + .inputIndexes = {1}, + .outputIndexes = {len * 2 + 1}}, + .isRelaxed = false, + }; +} + +} // namespace + +// Tag for the compilation caching tests. +class CompilationCachingTestBase : public testing::Test { + protected: + CompilationCachingTestBase(std::shared_ptr<IDevice> device, OperandType type) + : kDevice(std::move(device)), kOperandType(type) {} + + void SetUp() override { + testing::Test::SetUp(); + ASSERT_NE(kDevice.get(), nullptr); + + // Create cache directory. The cache directory and a temporary cache file is always created + // to test the behavior of prepareModelFromCache, even when caching is not supported. + char cacheDirTemp[] = "/data/local/tmp/TestCompilationCachingXXXXXX"; + char* cacheDir = mkdtemp(cacheDirTemp); + ASSERT_NE(cacheDir, nullptr); + mCacheDir = cacheDir; + mCacheDir.push_back('/'); + + NumberOfCacheFiles numCacheFiles; + const auto ret = kDevice->getNumberOfCacheFilesNeeded(&numCacheFiles); + ASSERT_TRUE(ret.isOk()); + + mNumModelCache = numCacheFiles.numModelCache; + mNumDataCache = numCacheFiles.numDataCache; + ASSERT_GE(mNumModelCache, 0) << "Invalid numModelCache: " << mNumModelCache; + ASSERT_GE(mNumDataCache, 0) << "Invalid numDataCache: " << mNumDataCache; + mIsCachingSupported = mNumModelCache > 0 || mNumDataCache > 0; + + // Create empty cache files. + mTmpCache = mCacheDir + "tmp"; + for (uint32_t i = 0; i < mNumModelCache; i++) { + mModelCache.push_back({mCacheDir + "model" + std::to_string(i)}); + } + for (uint32_t i = 0; i < mNumDataCache; i++) { + mDataCache.push_back({mCacheDir + "data" + std::to_string(i)}); + } + // Placeholder handles, use AccessMode::WRITE_ONLY for createCacheFds to create files. + std::vector<ndk::ScopedFileDescriptor> modelHandle, dataHandle, tmpHandle; + createCacheFds(mModelCache, AccessMode::WRITE_ONLY, &modelHandle); + createCacheFds(mDataCache, AccessMode::WRITE_ONLY, &dataHandle); + createCacheFds({mTmpCache}, AccessMode::WRITE_ONLY, &tmpHandle); + + if (!mIsCachingSupported) { + LOG(INFO) << "NN VTS: Early termination of test because vendor service does not " + "support compilation caching."; + std::cout << "[ ] Early termination of test because vendor service does not " + "support compilation caching." + << std::endl; + } + } + + void TearDown() override { + // If the test passes, remove the tmp directory. Otherwise, keep it for debugging purposes. + if (!testing::Test::HasFailure()) { + // Recursively remove the cache directory specified by mCacheDir. + auto callback = [](const char* entry, const struct stat*, int, struct FTW*) { + return remove(entry); + }; + nftw(mCacheDir.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); + } + testing::Test::TearDown(); + } + + // Model and examples creators. According to kOperandType, the following methods will return + // either float32 model/examples or the quant8 variant. + TestModel createTestModel() { + if (kOperandType == OperandType::TENSOR_FLOAT32) { + return float32_model::get_test_model(); + } else { + return quant8_model::get_test_model(); + } + } + + TestModel createLargeTestModel(OperationType op, uint32_t len) { + if (kOperandType == OperandType::TENSOR_FLOAT32) { + return createLargeTestModelImpl<float, TestOperandType::TENSOR_FLOAT32>( + static_cast<TestOperationType>(op), len); + } else { + return createLargeTestModelImpl<uint8_t, TestOperandType::TENSOR_QUANT8_ASYMM>( + static_cast<TestOperationType>(op), len); + } + } + + // See if the service can handle the model. + bool isModelFullySupported(const Model& model) { + std::vector<bool> supportedOps; + const auto supportedCall = kDevice->getSupportedOperations(model, &supportedOps); + EXPECT_TRUE(supportedCall.isOk()); + EXPECT_EQ(supportedOps.size(), model.main.operations.size()); + if (!supportedCall.isOk() || supportedOps.size() != model.main.operations.size()) { + return false; + } + return std::all_of(supportedOps.begin(), supportedOps.end(), + [](bool valid) { return valid; }); + } + + void saveModelToCache(const Model& model, + const std::vector<ndk::ScopedFileDescriptor>& modelCache, + const std::vector<ndk::ScopedFileDescriptor>& dataCache, + std::shared_ptr<IPreparedModel>* preparedModel = nullptr) { + if (preparedModel != nullptr) *preparedModel = nullptr; + + // Launch prepare model. + std::shared_ptr<PreparedModelCallback> preparedModelCallback = + ndk::SharedRefBase::make<PreparedModelCallback>(); + std::vector<uint8_t> cacheToken(std::begin(mToken), std::end(mToken)); + const auto prepareLaunchStatus = kDevice->prepareModel( + model, ExecutionPreference::FAST_SINGLE_ANSWER, kDefaultPriority, kNoDeadline, + modelCache, dataCache, cacheToken, preparedModelCallback); + ASSERT_TRUE(prepareLaunchStatus.isOk()); + + // Retrieve prepared model. + preparedModelCallback->wait(); + ASSERT_EQ(preparedModelCallback->getStatus(), ErrorStatus::NONE); + if (preparedModel != nullptr) { + *preparedModel = preparedModelCallback->getPreparedModel(); + } + } + + bool checkEarlyTermination(ErrorStatus status) { + if (status == ErrorStatus::GENERAL_FAILURE) { + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot " + "save the prepared model that it does not support."; + std::cout << "[ ] Early termination of test because vendor service cannot " + "save the prepared model that it does not support." + << std::endl; + return true; + } + return false; + } + + bool checkEarlyTermination(const Model& model) { + if (!isModelFullySupported(model)) { + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot " + "prepare model that it does not support."; + std::cout << "[ ] Early termination of test because vendor service cannot " + "prepare model that it does not support." + << std::endl; + return true; + } + return false; + } + + void prepareModelFromCache(const std::vector<ndk::ScopedFileDescriptor>& modelCache, + const std::vector<ndk::ScopedFileDescriptor>& dataCache, + std::shared_ptr<IPreparedModel>* preparedModel, + ErrorStatus* status) { + // Launch prepare model from cache. + std::shared_ptr<PreparedModelCallback> preparedModelCallback = + ndk::SharedRefBase::make<PreparedModelCallback>(); + std::vector<uint8_t> cacheToken(std::begin(mToken), std::end(mToken)); + const auto prepareLaunchStatus = kDevice->prepareModelFromCache( + kNoDeadline, modelCache, dataCache, cacheToken, preparedModelCallback); + ASSERT_TRUE(prepareLaunchStatus.isOk() || + prepareLaunchStatus.getExceptionCode() == EX_SERVICE_SPECIFIC) + << "prepareLaunchStatus: " << prepareLaunchStatus.getDescription(); + if (!prepareLaunchStatus.isOk()) { + *preparedModel = nullptr; + *status = static_cast<ErrorStatus>(prepareLaunchStatus.getServiceSpecificError()); + return; + } + + // Retrieve prepared model. + preparedModelCallback->wait(); + *status = preparedModelCallback->getStatus(); + *preparedModel = preparedModelCallback->getPreparedModel(); + } + + // Absolute path to the temporary cache directory. + std::string mCacheDir; + + // Groups of file paths for model and data cache in the tmp cache directory, initialized with + // size = mNum{Model|Data}Cache. The outer vector corresponds to handles and the inner vector is + // for fds held by each handle. + std::vector<std::string> mModelCache; + std::vector<std::string> mDataCache; + + // A separate temporary file path in the tmp cache directory. + std::string mTmpCache; + + uint8_t mToken[static_cast<uint32_t>(IDevice::BYTE_SIZE_OF_CACHE_TOKEN)] = {}; + uint32_t mNumModelCache; + uint32_t mNumDataCache; + uint32_t mIsCachingSupported; + + const std::shared_ptr<IDevice> kDevice; + // The primary data type of the testModel. + const OperandType kOperandType; +}; + +using CompilationCachingTestParam = std::tuple<NamedDevice, OperandType>; + +// A parameterized fixture of CompilationCachingTestBase. Every test will run twice, with the first +// pass running with float32 models and the second pass running with quant8 models. +class CompilationCachingTest : public CompilationCachingTestBase, + public testing::WithParamInterface<CompilationCachingTestParam> { + protected: + CompilationCachingTest() + : CompilationCachingTestBase(getData(std::get<NamedDevice>(GetParam())), + std::get<OperandType>(GetParam())) {} +}; + +TEST_P(CompilationCachingTest, CacheSavingAndRetrieval) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + + // Save the compilation to cache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(model, modelCache, dataCache); + } + + // Retrieve preparedModel from cache. + { + preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (!mIsCachingSupported) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + return; + } else if (checkEarlyTermination(status)) { + ASSERT_EQ(preparedModel, nullptr); + return; + } else { + ASSERT_EQ(status, ErrorStatus::NONE); + ASSERT_NE(preparedModel, nullptr); + } + } + + // Execute and verify results. + EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL); +} + +TEST_P(CompilationCachingTest, CacheSavingAndRetrievalNonZeroOffset) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + + // Save the compilation to cache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + uint8_t placeholderBytes[] = {0, 0}; + // Write a placeholder integer to the cache. + // The driver should be able to handle non-empty cache and non-zero fd offset. + for (uint32_t i = 0; i < modelCache.size(); i++) { + ASSERT_EQ(write(modelCache[i].get(), &placeholderBytes, sizeof(placeholderBytes)), + sizeof(placeholderBytes)); + } + for (uint32_t i = 0; i < dataCache.size(); i++) { + ASSERT_EQ(write(dataCache[i].get(), &placeholderBytes, sizeof(placeholderBytes)), + sizeof(placeholderBytes)); + } + saveModelToCache(model, modelCache, dataCache); + } + + // Retrieve preparedModel from cache. + { + preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + uint8_t placeholderByte = 0; + // Advance the offset of each handle by one byte. + // The driver should be able to handle non-zero fd offset. + for (uint32_t i = 0; i < modelCache.size(); i++) { + ASSERT_GE(read(modelCache[i].get(), &placeholderByte, 1), 0); + } + for (uint32_t i = 0; i < dataCache.size(); i++) { + ASSERT_GE(read(dataCache[i].get(), &placeholderByte, 1), 0); + } + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (!mIsCachingSupported) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + return; + } else if (checkEarlyTermination(status)) { + ASSERT_EQ(preparedModel, nullptr); + return; + } else { + ASSERT_EQ(status, ErrorStatus::NONE); + ASSERT_NE(preparedModel, nullptr); + } + } + + // Execute and verify results. + EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL); +} + +TEST_P(CompilationCachingTest, SaveToCacheInvalidNumCache) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + + // Test with number of model cache files greater than mNumModelCache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + // Pass an additional cache file for model cache. + mModelCache.push_back({mTmpCache}); + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache.pop_back(); + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of model cache files smaller than mNumModelCache. + if (mModelCache.size() > 0) { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + // Pop out the last cache file. + auto tmp = mModelCache.back(); + mModelCache.pop_back(); + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache.push_back(tmp); + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of data cache files greater than mNumDataCache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + // Pass an additional cache file for data cache. + mDataCache.push_back({mTmpCache}); + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache.pop_back(); + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of data cache files smaller than mNumDataCache. + if (mDataCache.size() > 0) { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + // Pop out the last cache file. + auto tmp = mDataCache.back(); + mDataCache.pop_back(); + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache.push_back(tmp); + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } +} + +TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidNumCache) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + + // Save the compilation to cache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(model, modelCache, dataCache); + } + + // Test with number of model cache files greater than mNumModelCache. + { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + mModelCache.push_back({mTmpCache}); + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache.pop_back(); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of model cache files smaller than mNumModelCache. + if (mModelCache.size() > 0) { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + auto tmp = mModelCache.back(); + mModelCache.pop_back(); + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache.push_back(tmp); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of data cache files greater than mNumDataCache. + { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + mDataCache.push_back({mTmpCache}); + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache.pop_back(); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of data cache files smaller than mNumDataCache. + if (mDataCache.size() > 0) { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + auto tmp = mDataCache.back(); + mDataCache.pop_back(); + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache.push_back(tmp); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } +} + +TEST_P(CompilationCachingTest, SaveToCacheInvalidAccessMode) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE); + std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE); + + // Go through each handle in model cache, test with invalid access mode. + for (uint32_t i = 0; i < mNumModelCache; i++) { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + modelCacheMode[i] = AccessMode::READ_ONLY; + createCacheFds(mModelCache, modelCacheMode, &modelCache); + createCacheFds(mDataCache, dataCacheMode, &dataCache); + modelCacheMode[i] = AccessMode::READ_WRITE; + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in data cache, test with invalid access mode. + for (uint32_t i = 0; i < mNumDataCache; i++) { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + dataCacheMode[i] = AccessMode::READ_ONLY; + createCacheFds(mModelCache, modelCacheMode, &modelCache); + createCacheFds(mDataCache, dataCacheMode, &dataCache); + dataCacheMode[i] = AccessMode::READ_WRITE; + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } +} + +TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidAccessMode) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE); + std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE); + + // Save the compilation to cache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(model, modelCache, dataCache); + } + + // Go through each handle in model cache, test with invalid access mode. + for (uint32_t i = 0; i < mNumModelCache; i++) { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + modelCacheMode[i] = AccessMode::WRITE_ONLY; + createCacheFds(mModelCache, modelCacheMode, &modelCache); + createCacheFds(mDataCache, dataCacheMode, &dataCache); + modelCacheMode[i] = AccessMode::READ_WRITE; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in data cache, test with invalid access mode. + for (uint32_t i = 0; i < mNumDataCache; i++) { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + dataCacheMode[i] = AccessMode::WRITE_ONLY; + createCacheFds(mModelCache, modelCacheMode, &modelCache); + createCacheFds(mDataCache, dataCacheMode, &dataCache); + dataCacheMode[i] = AccessMode::READ_WRITE; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + } +} + +// Copy file contents between files. +// The vector sizes must match. +static void copyCacheFiles(const std::vector<std::string>& from, + const std::vector<std::string>& to) { + constexpr size_t kBufferSize = 1000000; + uint8_t buffer[kBufferSize]; + + ASSERT_EQ(from.size(), to.size()); + for (uint32_t i = 0; i < from.size(); i++) { + int fromFd = open(from[i].c_str(), O_RDONLY); + int toFd = open(to[i].c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + ASSERT_GE(fromFd, 0); + ASSERT_GE(toFd, 0); + + ssize_t readBytes; + while ((readBytes = read(fromFd, &buffer, kBufferSize)) > 0) { + ASSERT_EQ(write(toFd, &buffer, readBytes), readBytes); + } + ASSERT_GE(readBytes, 0); + + close(fromFd); + close(toFd); + } +} + +// Number of operations in the large test model. +constexpr uint32_t kLargeModelSize = 100; +constexpr uint32_t kNumIterationsTOCTOU = 100; + +TEST_P(CompilationCachingTest, SaveToCache_TOCTOU) { + if (!mIsCachingSupported) return; + + // Create test models and check if fully supported by the service. + const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize); + const Model modelMul = createModel(testModelMul); + if (checkEarlyTermination(modelMul)) return; + const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize); + const Model modelAdd = createModel(testModelAdd); + if (checkEarlyTermination(modelAdd)) return; + + // Save the modelMul compilation to cache. + auto modelCacheMul = mModelCache; + for (auto& cache : modelCacheMul) { + cache.append("_mul"); + } + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(modelCacheMul, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelMul, modelCache, dataCache); + } + + // Use a different token for modelAdd. + mToken[0]++; + + // This test is probabilistic, so we run it multiple times. + for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) { + // Save the modelAdd compilation to cache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + + // Spawn a thread to copy the cache content concurrently while saving to cache. + std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache)); + saveModelToCache(modelAdd, modelCache, dataCache); + thread.join(); + } + + // Retrieve preparedModel from cache. + { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + + // The preparation may fail or succeed, but must not crash. If the preparation succeeds, + // the prepared model must be executed with the correct result and not crash. + if (status != ErrorStatus::NONE) { + ASSERT_EQ(preparedModel, nullptr); + } else { + ASSERT_NE(preparedModel, nullptr); + EvaluatePreparedModel(kDevice, preparedModel, testModelAdd, + /*testKind=*/TestKind::GENERAL); + } + } + } +} + +TEST_P(CompilationCachingTest, PrepareFromCache_TOCTOU) { + if (!mIsCachingSupported) return; + + // Create test models and check if fully supported by the service. + const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize); + const Model modelMul = createModel(testModelMul); + if (checkEarlyTermination(modelMul)) return; + const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize); + const Model modelAdd = createModel(testModelAdd); + if (checkEarlyTermination(modelAdd)) return; + + // Save the modelMul compilation to cache. + auto modelCacheMul = mModelCache; + for (auto& cache : modelCacheMul) { + cache.append("_mul"); + } + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(modelCacheMul, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelMul, modelCache, dataCache); + } + + // Use a different token for modelAdd. + mToken[0]++; + + // This test is probabilistic, so we run it multiple times. + for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) { + // Save the modelAdd compilation to cache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelAdd, modelCache, dataCache); + } + + // Retrieve preparedModel from cache. + { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + + // Spawn a thread to copy the cache content concurrently while preparing from cache. + std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache)); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + thread.join(); + + // The preparation may fail or succeed, but must not crash. If the preparation succeeds, + // the prepared model must be executed with the correct result and not crash. + if (status != ErrorStatus::NONE) { + ASSERT_EQ(preparedModel, nullptr); + } else { + ASSERT_NE(preparedModel, nullptr); + EvaluatePreparedModel(kDevice, preparedModel, testModelAdd, + /*testKind=*/TestKind::GENERAL); + } + } + } +} + +TEST_P(CompilationCachingTest, ReplaceSecuritySensitiveCache) { + if (!mIsCachingSupported) return; + + // Create test models and check if fully supported by the service. + const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize); + const Model modelMul = createModel(testModelMul); + if (checkEarlyTermination(modelMul)) return; + const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize); + const Model modelAdd = createModel(testModelAdd); + if (checkEarlyTermination(modelAdd)) return; + + // Save the modelMul compilation to cache. + auto modelCacheMul = mModelCache; + for (auto& cache : modelCacheMul) { + cache.append("_mul"); + } + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(modelCacheMul, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelMul, modelCache, dataCache); + } + + // Use a different token for modelAdd. + mToken[0]++; + + // Save the modelAdd compilation to cache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelAdd, modelCache, dataCache); + } + + // Replace the model cache of modelAdd with modelMul. + copyCacheFiles(modelCacheMul, mModelCache); + + // Retrieve the preparedModel from cache, expect failure. + { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + } +} + +// TODO(b/179270601): restore kNamedDeviceChoices. +static const auto kOperandTypeChoices = + testing::Values(OperandType::TENSOR_FLOAT32, OperandType::TENSOR_QUANT8_ASYMM); + +std::string printCompilationCachingTest( + const testing::TestParamInfo<CompilationCachingTestParam>& info) { + const auto& [namedDevice, operandType] = info.param; + const std::string type = (operandType == OperandType::TENSOR_FLOAT32 ? "float32" : "quant8"); + return gtestCompliantName(getName(namedDevice) + "_" + type); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CompilationCachingTest); +INSTANTIATE_TEST_SUITE_P(TestCompilationCaching, CompilationCachingTest, + testing::Combine(testing::ValuesIn(getNamedDevices()), + kOperandTypeChoices), + printCompilationCachingTest); + +using CompilationCachingSecurityTestParam = std::tuple<NamedDevice, OperandType, uint32_t>; + +class CompilationCachingSecurityTest + : public CompilationCachingTestBase, + public testing::WithParamInterface<CompilationCachingSecurityTestParam> { + protected: + CompilationCachingSecurityTest() + : CompilationCachingTestBase(getData(std::get<NamedDevice>(GetParam())), + std::get<OperandType>(GetParam())) {} + + void SetUp() { + CompilationCachingTestBase::SetUp(); + generator.seed(kSeed); + } + + // Get a random integer within a closed range [lower, upper]. + template <typename T> + T getRandomInt(T lower, T upper) { + std::uniform_int_distribution<T> dis(lower, upper); + return dis(generator); + } + + // Randomly flip one single bit of the cache entry. + void flipOneBitOfCache(const std::string& filename, bool* skip) { + FILE* pFile = fopen(filename.c_str(), "r+"); + ASSERT_EQ(fseek(pFile, 0, SEEK_END), 0); + long int fileSize = ftell(pFile); + if (fileSize == 0) { + fclose(pFile); + *skip = true; + return; + } + ASSERT_EQ(fseek(pFile, getRandomInt(0l, fileSize - 1), SEEK_SET), 0); + int readByte = fgetc(pFile); + ASSERT_NE(readByte, EOF); + ASSERT_EQ(fseek(pFile, -1, SEEK_CUR), 0); + ASSERT_NE(fputc(static_cast<uint8_t>(readByte) ^ (1U << getRandomInt(0, 7)), pFile), EOF); + fclose(pFile); + *skip = false; + } + + // Randomly append bytes to the cache entry. + void appendBytesToCache(const std::string& filename, bool* skip) { + FILE* pFile = fopen(filename.c_str(), "a"); + uint32_t appendLength = getRandomInt(1, 256); + for (uint32_t i = 0; i < appendLength; i++) { + ASSERT_NE(fputc(getRandomInt<uint8_t>(0, 255), pFile), EOF); + } + fclose(pFile); + *skip = false; + } + + enum class ExpectedResult { GENERAL_FAILURE, NOT_CRASH }; + + // Test if the driver behaves as expected when given corrupted cache or token. + // The modifier will be invoked after save to cache but before prepare from cache. + // The modifier accepts one pointer argument "skip" as the returning value, indicating + // whether the test should be skipped or not. + void testCorruptedCache(ExpectedResult expected, std::function<void(bool*)> modifier) { + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + + // Save the compilation to cache. + { + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(model, modelCache, dataCache); + } + + bool skip = false; + modifier(&skip); + if (skip) return; + + // Retrieve preparedModel from cache. + { + std::shared_ptr<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache; + createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + + switch (expected) { + case ExpectedResult::GENERAL_FAILURE: + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + break; + case ExpectedResult::NOT_CRASH: + ASSERT_EQ(preparedModel == nullptr, status != ErrorStatus::NONE); + break; + default: + FAIL(); + } + } + } + + const uint32_t kSeed = std::get<uint32_t>(GetParam()); + std::mt19937 generator; +}; + +TEST_P(CompilationCachingSecurityTest, CorruptedModelCache) { + if (!mIsCachingSupported) return; + for (uint32_t i = 0; i < mNumModelCache; i++) { + testCorruptedCache(ExpectedResult::GENERAL_FAILURE, + [this, i](bool* skip) { flipOneBitOfCache(mModelCache[i], skip); }); + } +} + +TEST_P(CompilationCachingSecurityTest, WrongLengthModelCache) { + if (!mIsCachingSupported) return; + for (uint32_t i = 0; i < mNumModelCache; i++) { + testCorruptedCache(ExpectedResult::GENERAL_FAILURE, + [this, i](bool* skip) { appendBytesToCache(mModelCache[i], skip); }); + } +} + +TEST_P(CompilationCachingSecurityTest, CorruptedDataCache) { + if (!mIsCachingSupported) return; + for (uint32_t i = 0; i < mNumDataCache; i++) { + testCorruptedCache(ExpectedResult::NOT_CRASH, + [this, i](bool* skip) { flipOneBitOfCache(mDataCache[i], skip); }); + } +} + +TEST_P(CompilationCachingSecurityTest, WrongLengthDataCache) { + if (!mIsCachingSupported) return; + for (uint32_t i = 0; i < mNumDataCache; i++) { + testCorruptedCache(ExpectedResult::NOT_CRASH, + [this, i](bool* skip) { appendBytesToCache(mDataCache[i], skip); }); + } +} + +TEST_P(CompilationCachingSecurityTest, WrongToken) { + if (!mIsCachingSupported) return; + testCorruptedCache(ExpectedResult::GENERAL_FAILURE, [this](bool* skip) { + // Randomly flip one single bit in mToken. + uint32_t ind = + getRandomInt(0u, static_cast<uint32_t>(IDevice::BYTE_SIZE_OF_CACHE_TOKEN) - 1); + mToken[ind] ^= (1U << getRandomInt(0, 7)); + *skip = false; + }); +} + +std::string printCompilationCachingSecurityTest( + const testing::TestParamInfo<CompilationCachingSecurityTestParam>& info) { + const auto& [namedDevice, operandType, seed] = info.param; + const std::string type = (operandType == OperandType::TENSOR_FLOAT32 ? "float32" : "quant8"); + return gtestCompliantName(getName(namedDevice) + "_" + type + "_" + std::to_string(seed)); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CompilationCachingSecurityTest); +INSTANTIATE_TEST_SUITE_P(TestCompilationCaching, CompilationCachingSecurityTest, + testing::Combine(testing::ValuesIn(getNamedDevices()), kOperandTypeChoices, + testing::Range(0U, 10U)), + printCompilationCachingSecurityTest); + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional diff --git a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp new file mode 100644 index 0000000000..4beb828253 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp @@ -0,0 +1,925 @@ +/* + * Copyright (C) 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. + */ + +#include "GeneratedTestHarness.h" + +#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h> +#include <android-base/logging.h> +#include <android/binder_auto_utils.h> +#include <android/sync.h> +#include <gtest/gtest.h> + +#include <algorithm> +#include <chrono> +#include <iostream> +#include <iterator> +#include <numeric> +#include <vector> + +#include <MemoryUtils.h> +#include <android/binder_status.h> +#include <nnapi/Result.h> +#include <nnapi/SharedMemory.h> +#include <nnapi/Types.h> +#include <nnapi/hal/aidl/Conversions.h> +#include <nnapi/hal/aidl/Utils.h> + +#include "Callbacks.h" +#include "TestHarness.h" +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +namespace nn = ::android::nn; +using namespace test_helper; +using implementation::PreparedModelCallback; + +namespace { + +enum class OutputType { FULLY_SPECIFIED, UNSPECIFIED, INSUFFICIENT, MISSED_DEADLINE }; + +struct TestConfig { + Executor executor; + bool measureTiming; + OutputType outputType; + MemoryType memoryType; + // `reportSkipping` indicates if a test should print an info message in case + // it is skipped. The field is set to true by default and is set to false in + // quantization coupling tests to suppress skipping a test + bool reportSkipping; + TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType) + : executor(executor), + measureTiming(measureTiming), + outputType(outputType), + memoryType(memoryType), + reportSkipping(true) {} + TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType, + bool reportSkipping) + : executor(executor), + measureTiming(measureTiming), + outputType(outputType), + memoryType(memoryType), + reportSkipping(reportSkipping) {} +}; + +enum class IOType { INPUT, OUTPUT }; + +class DeviceMemoryAllocator { + public: + DeviceMemoryAllocator(const std::shared_ptr<IDevice>& device, + const std::shared_ptr<IPreparedModel>& preparedModel, + const TestModel& testModel) + : kDevice(device), kPreparedModel(preparedModel), kTestModel(testModel) {} + + // Allocate device memory for a target input/output operand. + // Return {IBuffer object, token} if successful. + // Return {nullptr, 0} if device memory is not supported. + template <IOType ioType> + std::pair<std::shared_ptr<IBuffer>, int32_t> allocate(uint32_t index) { + std::pair<std::shared_ptr<IBuffer>, int32_t> buffer; + allocateInternal<ioType>(index, &buffer); + return buffer; + } + + private: + template <IOType ioType> + void allocateInternal(int32_t index, std::pair<std::shared_ptr<IBuffer>, int32_t>* result) { + ASSERT_NE(result, nullptr); + + // Prepare arguments. + BufferRole role = {.modelIndex = 0, .ioIndex = index, .frequency = 1.0f}; + std::vector<BufferRole> inputRoles, outputRoles; + if constexpr (ioType == IOType::INPUT) { + inputRoles = {role}; + } else { + outputRoles = {role}; + } + + // Allocate device memory. + DeviceBuffer buffer; + IPreparedModelParcel parcel; + parcel.preparedModel = kPreparedModel; + const auto ret = kDevice->allocate({}, {parcel}, inputRoles, outputRoles, &buffer); + + // Check allocation results. + if (ret.isOk()) { + ASSERT_NE(buffer.buffer, nullptr); + ASSERT_GT(buffer.token, 0); + } else { + ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast<ErrorStatus>(ret.getServiceSpecificError()), + ErrorStatus::GENERAL_FAILURE); + buffer.buffer = nullptr; + buffer.token = 0; + } + + // Initialize input data from TestBuffer. + if constexpr (ioType == IOType::INPUT) { + if (buffer.buffer != nullptr) { + // TestBuffer -> Shared memory. + const auto& testBuffer = + kTestModel.main.operands[kTestModel.main.inputIndexes[index]].data; + ASSERT_GT(testBuffer.size(), 0); + const auto sharedMemory = nn::createSharedMemory(testBuffer.size()).value(); + const auto memory = utils::convert(sharedMemory).value(); + const auto mapping = nn::map(sharedMemory).value(); + uint8_t* inputPtr = static_cast<uint8_t*>(std::get<void*>(mapping.pointer)); + ASSERT_NE(inputPtr, nullptr); + const uint8_t* begin = testBuffer.get<uint8_t>(); + const uint8_t* end = begin + testBuffer.size(); + std::copy(begin, end, inputPtr); + + // Shared memory -> IBuffer. + auto ret = buffer.buffer->copyFrom(memory, {}); + ASSERT_TRUE(ret.isOk()); + } + } + *result = {std::move(buffer.buffer), buffer.token}; + } + + const std::shared_ptr<IDevice> kDevice; + const std::shared_ptr<IPreparedModel> kPreparedModel; + const TestModel& kTestModel; +}; + +Subgraph createSubgraph(const TestSubgraph& testSubgraph, uint32_t* constCopySize, + std::vector<const TestBuffer*>* constCopies, uint32_t* constRefSize, + std::vector<const TestBuffer*>* constReferences) { + CHECK(constCopySize != nullptr); + CHECK(constCopies != nullptr); + CHECK(constRefSize != nullptr); + CHECK(constReferences != nullptr); + + // Operands. + std::vector<Operand> operands(testSubgraph.operands.size()); + for (uint32_t i = 0; i < testSubgraph.operands.size(); i++) { + const auto& op = testSubgraph.operands[i]; + + DataLocation loc = {}; + if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) { + loc = { + .poolIndex = 0, + .offset = *constCopySize, + .length = static_cast<int64_t>(op.data.size()), + }; + constCopies->push_back(&op.data); + *constCopySize += op.data.alignedSize(); + } else if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) { + loc = { + .poolIndex = 0, + .offset = *constRefSize, + .length = static_cast<int64_t>(op.data.size()), + }; + constReferences->push_back(&op.data); + *constRefSize += op.data.alignedSize(); + } else if (op.lifetime == TestOperandLifeTime::SUBGRAPH) { + loc = { + .poolIndex = 0, + .offset = *op.data.get<uint32_t>(), + .length = 0, + }; + } + + std::optional<OperandExtraParams> extraParams; + if (op.type == TestOperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) { + using Tag = OperandExtraParams::Tag; + extraParams = OperandExtraParams::make<Tag::channelQuant>(SymmPerChannelQuantParams{ + .scales = op.channelQuant.scales, + .channelDim = static_cast<int32_t>(op.channelQuant.channelDim)}); + } + + operands[i] = {.type = static_cast<OperandType>(op.type), + .dimensions = utils::toSigned(op.dimensions).value(), + .scale = op.scale, + .zeroPoint = op.zeroPoint, + .lifetime = static_cast<OperandLifeTime>(op.lifetime), + .location = loc, + .extraParams = std::move(extraParams)}; + } + + // Operations. + std::vector<Operation> operations(testSubgraph.operations.size()); + std::transform(testSubgraph.operations.begin(), testSubgraph.operations.end(), + operations.begin(), [](const TestOperation& op) -> Operation { + return {.type = static_cast<OperationType>(op.type), + .inputs = utils::toSigned(op.inputs).value(), + .outputs = utils::toSigned(op.outputs).value()}; + }); + + return {.operands = std::move(operands), + .operations = std::move(operations), + .inputIndexes = utils::toSigned(testSubgraph.inputIndexes).value(), + .outputIndexes = utils::toSigned(testSubgraph.outputIndexes).value()}; +} + +void copyTestBuffers(const std::vector<const TestBuffer*>& buffers, uint8_t* output) { + uint32_t offset = 0; + for (const TestBuffer* buffer : buffers) { + const uint8_t* begin = buffer->get<uint8_t>(); + const uint8_t* end = begin + buffer->size(); + std::copy(begin, end, output + offset); + offset += buffer->alignedSize(); + } +} + +} // namespace + +void waitForSyncFence(int syncFd) { + constexpr int kInfiniteTimeout = -1; + ASSERT_GT(syncFd, 0); + int r = sync_wait(syncFd, kInfiniteTimeout); + ASSERT_GE(r, 0); +} + +Model createModel(const TestModel& testModel) { + uint32_t constCopySize = 0; + uint32_t constRefSize = 0; + std::vector<const TestBuffer*> constCopies; + std::vector<const TestBuffer*> constReferences; + + Subgraph mainSubgraph = createSubgraph(testModel.main, &constCopySize, &constCopies, + &constRefSize, &constReferences); + std::vector<Subgraph> refSubgraphs(testModel.referenced.size()); + std::transform(testModel.referenced.begin(), testModel.referenced.end(), refSubgraphs.begin(), + [&constCopySize, &constCopies, &constRefSize, + &constReferences](const TestSubgraph& testSubgraph) { + return createSubgraph(testSubgraph, &constCopySize, &constCopies, + &constRefSize, &constReferences); + }); + + // Constant copies. + std::vector<uint8_t> operandValues(constCopySize); + copyTestBuffers(constCopies, operandValues.data()); + + // Shared memory. + std::vector<nn::SharedMemory> pools = {}; + if (constRefSize > 0) { + const auto pool = nn::createSharedMemory(constRefSize).value(); + pools.push_back(pool); + + // load data + const auto mappedMemory = nn::map(pool).value(); + uint8_t* mappedPtr = static_cast<uint8_t*>(std::get<void*>(mappedMemory.pointer)); + CHECK(mappedPtr != nullptr); + + copyTestBuffers(constReferences, mappedPtr); + } + + std::vector<Memory> aidlPools; + aidlPools.reserve(pools.size()); + for (auto& pool : pools) { + auto aidlPool = utils::convert(pool).value(); + aidlPools.push_back(std::move(aidlPool)); + } + + return {.main = std::move(mainSubgraph), + .referenced = std::move(refSubgraphs), + .operandValues = std::move(operandValues), + .pools = std::move(aidlPools), + .relaxComputationFloat32toFloat16 = testModel.isRelaxed}; +} + +static bool isOutputSizeGreaterThanOne(const TestModel& testModel, uint32_t index) { + const auto byteSize = testModel.main.operands[testModel.main.outputIndexes[index]].data.size(); + return byteSize > 1u; +} + +static void makeOutputInsufficientSize(uint32_t outputIndex, Request* request) { + auto& length = request->outputs[outputIndex].location.length; + ASSERT_GT(length, 1u); + length -= 1u; +} + +static void makeOutputDimensionsUnspecified(Model* model) { + for (auto i : model->main.outputIndexes) { + auto& dims = model->main.operands[i].dimensions; + std::fill(dims.begin(), dims.end(), 0); + } +} + +// Manages the lifetime of memory resources used in an execution. +class ExecutionContext { + public: + ExecutionContext(std::shared_ptr<IDevice> device, std::shared_ptr<IPreparedModel> preparedModel) + : kDevice(std::move(device)), kPreparedModel(std::move(preparedModel)) {} + + std::optional<Request> createRequest(const TestModel& testModel, MemoryType memoryType); + std::vector<TestBuffer> getOutputBuffers(const TestModel& testModel, + const Request& request) const; + + private: + // Get a TestBuffer with data copied from an IBuffer object. + void getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size, + TestBuffer* testBuffer) const; + + static constexpr uint32_t kInputPoolIndex = 0; + static constexpr uint32_t kOutputPoolIndex = 1; + static constexpr uint32_t kDeviceMemoryBeginIndex = 2; + + const std::shared_ptr<IDevice> kDevice; + const std::shared_ptr<IPreparedModel> kPreparedModel; + std::unique_ptr<TestMemoryBase> mInputMemory, mOutputMemory; + std::vector<std::shared_ptr<IBuffer>> mBuffers; +}; + +std::optional<Request> ExecutionContext::createRequest(const TestModel& testModel, + MemoryType memoryType) { + // Memory pools are organized as: + // - 0: Input shared memory pool + // - 1: Output shared memory pool + // - [2, 2+i): Input device memories + // - [2+i, 2+i+o): Output device memories + DeviceMemoryAllocator allocator(kDevice, kPreparedModel, testModel); + std::vector<int32_t> tokens; + mBuffers.clear(); + + // Model inputs. + std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size()); + size_t inputSize = 0; + for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) { + const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]]; + if (op.data.size() == 0) { + // Omitted input. + inputs[i] = {.hasNoValue = true}; + continue; + } else if (memoryType == MemoryType::DEVICE) { + SCOPED_TRACE("Input index = " + std::to_string(i)); + auto [buffer, token] = allocator.allocate<IOType::INPUT>(i); + if (buffer != nullptr) { + DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() + + kDeviceMemoryBeginIndex)}; + mBuffers.push_back(std::move(buffer)); + tokens.push_back(token); + inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}}; + continue; + } + } + + // Reserve shared memory for input. + DataLocation loc = {.poolIndex = kInputPoolIndex, + .offset = static_cast<int64_t>(inputSize), + .length = static_cast<int64_t>(op.data.size())}; + inputSize += op.data.alignedSize(); + inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}}; + } + + // Model outputs. + std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size()); + size_t outputSize = 0; + for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) { + const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]]; + if (memoryType == MemoryType::DEVICE) { + SCOPED_TRACE("Output index = " + std::to_string(i)); + auto [buffer, token] = allocator.allocate<IOType::OUTPUT>(i); + if (buffer != nullptr) { + DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() + + kDeviceMemoryBeginIndex)}; + mBuffers.push_back(std::move(buffer)); + tokens.push_back(token); + outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}}; + continue; + } + } + + // In the case of zero-sized output, we should at least provide a one-byte buffer. + // This is because zero-sized tensors are only supported internally to the driver, or + // reported in output shapes. It is illegal for the client to pre-specify a zero-sized + // tensor as model output. Otherwise, we will have two semantic conflicts: + // - "Zero dimension" conflicts with "unspecified dimension". + // - "Omitted operand buffer" conflicts with "zero-sized operand buffer". + size_t bufferSize = std::max<size_t>(op.data.size(), 1); + + // Reserve shared memory for output. + DataLocation loc = {.poolIndex = kOutputPoolIndex, + .offset = static_cast<int64_t>(outputSize), + .length = static_cast<int64_t>(bufferSize)}; + outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize(); + outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}}; + } + + if (memoryType == MemoryType::DEVICE && mBuffers.empty()) { + return std::nullopt; + } + + // Memory pools. + if (memoryType == MemoryType::BLOB_AHWB) { + mInputMemory = TestBlobAHWB::create(std::max<size_t>(inputSize, 1)); + mOutputMemory = TestBlobAHWB::create(std::max<size_t>(outputSize, 1)); + } else { + mInputMemory = TestAshmem::create(std::max<size_t>(inputSize, 1)); + mOutputMemory = TestAshmem::create(std::max<size_t>(outputSize, 1)); + } + CHECK_NE(mInputMemory, nullptr); + CHECK_NE(mOutputMemory, nullptr); + std::vector<RequestMemoryPool> pools; + pools.reserve(kDeviceMemoryBeginIndex + mBuffers.size()); + + auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory()); + CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message; + auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory()); + CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message; + + pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>( + std::move(copiedInputMemory).value())); + pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>( + std::move(copiedOutputMemory).value())); + for (const auto& token : tokens) { + pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::token>(token)); + } + + // Copy input data to the input shared memory pool. + uint8_t* inputPtr = mInputMemory->getPointer(); + for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) { + if (!inputs[i].hasNoValue && inputs[i].location.poolIndex == kInputPoolIndex) { + const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]]; + const uint8_t* begin = op.data.get<uint8_t>(); + const uint8_t* end = begin + op.data.size(); + std::copy(begin, end, inputPtr + inputs[i].location.offset); + } + } + return Request{ + .inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)}; +} + +std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const TestModel& testModel, + const Request& request) const { + // Copy out output results. + uint8_t* outputPtr = mOutputMemory->getPointer(); + std::vector<TestBuffer> outputBuffers; + for (uint32_t i = 0; i < request.outputs.size(); i++) { + const auto& outputLoc = request.outputs[i].location; + if (outputLoc.poolIndex == kOutputPoolIndex) { + outputBuffers.emplace_back(outputLoc.length, outputPtr + outputLoc.offset); + } else { + const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]]; + if (op.data.size() == 0) { + outputBuffers.emplace_back(0, nullptr); + } else { + SCOPED_TRACE("Output index = " + std::to_string(i)); + const uint32_t bufferIndex = outputLoc.poolIndex - kDeviceMemoryBeginIndex; + TestBuffer buffer; + getBuffer(mBuffers[bufferIndex], op.data.size(), &buffer); + outputBuffers.push_back(std::move(buffer)); + } + } + } + return outputBuffers; +} + +// Get a TestBuffer with data copied from an IBuffer object. +void ExecutionContext::getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size, + TestBuffer* testBuffer) const { + // IBuffer -> Shared memory. + auto sharedMemory = nn::createSharedMemory(size).value(); + auto aidlMemory = utils::convert(sharedMemory).value(); + const auto ret = buffer->copyTo(aidlMemory); + ASSERT_TRUE(ret.isOk()); + + // Shared memory -> TestBuffer. + const auto outputMemory = nn::map(sharedMemory).value(); + const uint8_t* outputPtr = std::visit( + [](auto* ptr) { return static_cast<const uint8_t*>(ptr); }, outputMemory.pointer); + ASSERT_NE(outputPtr, nullptr); + ASSERT_NE(testBuffer, nullptr); + *testBuffer = TestBuffer(size, outputPtr); +} + +static bool hasZeroSizedOutput(const TestModel& testModel) { + return std::any_of(testModel.main.outputIndexes.begin(), testModel.main.outputIndexes.end(), + [&testModel](uint32_t index) { + return testModel.main.operands[index].data.size() == 0; + }); +} + +void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device, + const std::shared_ptr<IPreparedModel>& preparedModel, + const TestModel& testModel, const TestConfig& testConfig, + bool* skipped = nullptr) { + if (skipped != nullptr) { + *skipped = false; + } + // If output0 does not have size larger than one byte, we can not test with insufficient buffer. + if (testConfig.outputType == OutputType::INSUFFICIENT && + !isOutputSizeGreaterThanOne(testModel, 0)) { + return; + } + + ExecutionContext context(device, preparedModel); + auto maybeRequest = context.createRequest(testModel, testConfig.memoryType); + // Skip if testing memory domain but no device memory has been allocated. + if (!maybeRequest.has_value()) { + return; + } + + Request request = std::move(maybeRequest).value(); + + constexpr uint32_t kInsufficientOutputIndex = 0; + if (testConfig.outputType == OutputType::INSUFFICIENT) { + makeOutputInsufficientSize(kInsufficientOutputIndex, &request); + } + + int64_t loopTimeoutDuration = kOmittedTimeoutDuration; + // OutputType::MISSED_DEADLINE is only used by + // TestKind::INTINITE_LOOP_TIMEOUT tests to verify that an infinite loop is + // aborted after a timeout. + if (testConfig.outputType == OutputType::MISSED_DEADLINE) { + // Override the default loop timeout duration with a small value to + // speed up test execution. + constexpr int64_t kMillisecond = 1'000'000; + loopTimeoutDuration = 1 * kMillisecond; + } + + ErrorStatus executionStatus; + std::vector<OutputShape> outputShapes; + Timing timing = kNoTiming; + switch (testConfig.executor) { + case Executor::SYNC: { + SCOPED_TRACE("synchronous"); + + ExecutionResult executionResult; + // execute + const auto ret = preparedModel->executeSynchronously(request, testConfig.measureTiming, + kNoDeadline, loopTimeoutDuration, + &executionResult); + ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC) + << ret.getDescription(); + if (ret.isOk()) { + executionStatus = executionResult.outputSufficientSize + ? ErrorStatus::NONE + : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE; + outputShapes = std::move(executionResult.outputShapes); + timing = executionResult.timing; + } else { + executionStatus = static_cast<ErrorStatus>(ret.getServiceSpecificError()); + } + break; + } + case Executor::FENCED: { + SCOPED_TRACE("fenced"); + ErrorStatus result = ErrorStatus::NONE; + ndk::ScopedFileDescriptor syncFenceFd; + std::shared_ptr<IFencedExecutionCallback> fencedCallback; + auto ret = preparedModel->executeFenced(request, {}, testConfig.measureTiming, + kNoDeadline, loopTimeoutDuration, kNoDuration, + &syncFenceFd, &fencedCallback); + ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC) + << ret.getDescription(); + if (!ret.isOk()) { + result = static_cast<ErrorStatus>(ret.getServiceSpecificError()); + executionStatus = result; + } else if (syncFenceFd.get() != -1) { + std::vector<ndk::ScopedFileDescriptor> waitFor; + auto dupFd = dup(syncFenceFd.get()); + ASSERT_NE(dupFd, -1); + waitFor.emplace_back(dupFd); + // If a sync fence is returned, try start another run waiting for the sync fence. + ret = preparedModel->executeFenced(request, waitFor, testConfig.measureTiming, + kNoDeadline, loopTimeoutDuration, kNoDuration, + &syncFenceFd, &fencedCallback); + ASSERT_TRUE(ret.isOk()); + waitForSyncFence(syncFenceFd.get()); + } + if (result == ErrorStatus::NONE) { + ASSERT_NE(fencedCallback, nullptr); + Timing timingFenced; + auto ret = + fencedCallback->getExecutionInfo(&timing, &timingFenced, &executionStatus); + ASSERT_TRUE(ret.isOk()); + } + break; + } + default: { + FAIL() << "Unsupported execution mode for AIDL interface."; + } + } + + if (testConfig.outputType != OutputType::FULLY_SPECIFIED && + executionStatus == ErrorStatus::GENERAL_FAILURE) { + if (skipped != nullptr) { + *skipped = true; + } + if (!testConfig.reportSkipping) { + return; + } + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot " + "execute model that it does not support."; + std::cout << "[ ] Early termination of test because vendor service cannot " + "execute model that it does not support." + << std::endl; + GTEST_SKIP(); + } + if (!testConfig.measureTiming) { + EXPECT_EQ(timing, kNoTiming); + } else { + if (timing.timeOnDevice != -1 && timing.timeInDriver != -1) { + EXPECT_LE(timing.timeOnDevice, timing.timeInDriver); + } + } + + switch (testConfig.outputType) { + case OutputType::FULLY_SPECIFIED: + if (testConfig.executor == Executor::FENCED && hasZeroSizedOutput(testModel)) { + // Executor::FENCED does not support zero-sized output. + ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus); + return; + } + // If the model output operands are fully specified, outputShapes must be either + // either empty, or have the same number of elements as the number of outputs. + ASSERT_EQ(ErrorStatus::NONE, executionStatus); + ASSERT_TRUE(outputShapes.size() == 0 || + outputShapes.size() == testModel.main.outputIndexes.size()); + break; + case OutputType::UNSPECIFIED: + if (testConfig.executor == Executor::FENCED) { + // For Executor::FENCED, the output shape must be fully specified. + ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus); + return; + } + // If the model output operands are not fully specified, outputShapes must have + // the same number of elements as the number of outputs. + ASSERT_EQ(ErrorStatus::NONE, executionStatus); + ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size()); + break; + case OutputType::INSUFFICIENT: + if (testConfig.executor == Executor::FENCED) { + // For Executor::FENCED, the output shape must be fully specified. + ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus); + return; + } + ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus); + ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size()); + // Check that all returned output dimensions are at least as fully specified as the + // union of the information about the corresponding operand in the model and in the + // request. In this test, all model outputs have known rank with all dimensions + // unspecified, and no dimensional information is provided in the request. + for (uint32_t i = 0; i < outputShapes.size(); i++) { + ASSERT_EQ(outputShapes[i].isSufficient, i != kInsufficientOutputIndex); + const auto& actual = outputShapes[i].dimensions; + const auto& golden = + testModel.main.operands[testModel.main.outputIndexes[i]].dimensions; + ASSERT_EQ(actual.size(), golden.size()); + for (uint32_t j = 0; j < actual.size(); j++) { + if (actual[j] == 0) continue; + EXPECT_EQ(actual[j], golden[j]) << "index: " << j; + } + } + return; + case OutputType::MISSED_DEADLINE: + ASSERT_TRUE(executionStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT || + executionStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT) + << "executionStatus = " << executionStatus; + return; + } + + // Go through all outputs, check returned output shapes. + for (uint32_t i = 0; i < outputShapes.size(); i++) { + EXPECT_TRUE(outputShapes[i].isSufficient); + const auto& expect = testModel.main.operands[testModel.main.outputIndexes[i]].dimensions; + const auto unsignedActual = nn::toUnsigned(outputShapes[i].dimensions); + ASSERT_TRUE(unsignedActual.has_value()); + const std::vector<uint32_t>& actual = unsignedActual.value(); + EXPECT_EQ(expect, actual); + } + + // Retrieve execution results. + const std::vector<TestBuffer> outputs = context.getOutputBuffers(testModel, request); + + // We want "close-enough" results. + checkResults(testModel, outputs); +} + +void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device, + const std::shared_ptr<IPreparedModel>& preparedModel, + const TestModel& testModel, TestKind testKind) { + std::vector<OutputType> outputTypesList; + std::vector<bool> measureTimingList; + std::vector<Executor> executorList; + std::vector<MemoryType> memoryTypeList; + + switch (testKind) { + case TestKind::GENERAL: { + outputTypesList = {OutputType::FULLY_SPECIFIED}; + measureTimingList = {false, true}; + executorList = {Executor::SYNC}; + memoryTypeList = {MemoryType::ASHMEM}; + } break; + case TestKind::DYNAMIC_SHAPE: { + outputTypesList = {OutputType::UNSPECIFIED, OutputType::INSUFFICIENT}; + measureTimingList = {false, true}; + executorList = {Executor::SYNC, Executor::FENCED}; + memoryTypeList = {MemoryType::ASHMEM}; + } break; + case TestKind::MEMORY_DOMAIN: { + outputTypesList = {OutputType::FULLY_SPECIFIED}; + measureTimingList = {false}; + executorList = {Executor::SYNC, Executor::FENCED}; + memoryTypeList = {MemoryType::BLOB_AHWB, MemoryType::DEVICE}; + } break; + case TestKind::FENCED_COMPUTE: { + outputTypesList = {OutputType::FULLY_SPECIFIED}; + measureTimingList = {false, true}; + executorList = {Executor::FENCED}; + memoryTypeList = {MemoryType::ASHMEM}; + } break; + case TestKind::QUANTIZATION_COUPLING: { + LOG(FATAL) << "Wrong TestKind for EvaluatePreparedModel"; + return; + } break; + case TestKind::INTINITE_LOOP_TIMEOUT: { + outputTypesList = {OutputType::MISSED_DEADLINE}; + measureTimingList = {false, true}; + executorList = {Executor::SYNC, Executor::FENCED}; + memoryTypeList = {MemoryType::ASHMEM}; + } break; + } + + for (const OutputType outputType : outputTypesList) { + for (const bool measureTiming : measureTimingList) { + for (const Executor executor : executorList) { + for (const MemoryType memoryType : memoryTypeList) { + const TestConfig testConfig(executor, measureTiming, outputType, memoryType); + EvaluatePreparedModel(device, preparedModel, testModel, testConfig); + } + } + } + } +} + +void EvaluatePreparedCoupledModels(const std::shared_ptr<IDevice>& device, + const std::shared_ptr<IPreparedModel>& preparedModel, + const TestModel& testModel, + const std::shared_ptr<IPreparedModel>& preparedCoupledModel, + const TestModel& coupledModel) { + const std::vector<OutputType> outputTypesList = {OutputType::FULLY_SPECIFIED}; + const std::vector<bool> measureTimingList = {false, true}; + const std::vector<Executor> executorList = {Executor::SYNC, Executor::FENCED}; + + for (const OutputType outputType : outputTypesList) { + for (const bool measureTiming : measureTimingList) { + for (const Executor executor : executorList) { + const TestConfig testConfig(executor, measureTiming, outputType, MemoryType::ASHMEM, + /*reportSkipping=*/false); + bool baseSkipped = false; + EvaluatePreparedModel(device, preparedModel, testModel, testConfig, &baseSkipped); + bool coupledSkipped = false; + EvaluatePreparedModel(device, preparedCoupledModel, coupledModel, testConfig, + &coupledSkipped); + ASSERT_EQ(baseSkipped, coupledSkipped); + if (baseSkipped) { + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot " + "execute model that it does not support."; + std::cout << "[ ] Early termination of test because vendor service " + "cannot " + "execute model that it does not support." + << std::endl; + GTEST_SKIP(); + } + } + } + } +} + +void Execute(const std::shared_ptr<IDevice>& device, const TestModel& testModel, + TestKind testKind) { + Model model = createModel(testModel); + if (testKind == TestKind::DYNAMIC_SHAPE) { + makeOutputDimensionsUnspecified(&model); + } + + std::shared_ptr<IPreparedModel> preparedModel; + switch (testKind) { + case TestKind::GENERAL: + case TestKind::DYNAMIC_SHAPE: + case TestKind::MEMORY_DOMAIN: + case TestKind::FENCED_COMPUTE: + case TestKind::INTINITE_LOOP_TIMEOUT: { + createPreparedModel(device, model, &preparedModel); + if (preparedModel == nullptr) return; + EvaluatePreparedModel(device, preparedModel, testModel, testKind); + } break; + case TestKind::QUANTIZATION_COUPLING: { + ASSERT_TRUE(testModel.hasQuant8CoupledOperands()); + createPreparedModel(device, model, &preparedModel, + /*reportSkipping*/ false); + TestModel signedQuantizedModel = convertQuant8AsymmOperandsToSigned(testModel); + std::shared_ptr<IPreparedModel> preparedCoupledModel; + createPreparedModel(device, createModel(signedQuantizedModel), &preparedCoupledModel, + /*reportSkipping*/ false); + // If we couldn't prepare a model with unsigned quantization, we must + // fail to prepare a model with signed quantization as well. + if (preparedModel == nullptr) { + ASSERT_EQ(preparedCoupledModel, nullptr); + // If we failed to prepare both of the models, we can safely skip + // the test. + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot " + "prepare model that it does not support."; + std::cout + << "[ ] Early termination of test because vendor service cannot " + "prepare model that it does not support." + << std::endl; + GTEST_SKIP(); + } + ASSERT_NE(preparedCoupledModel, nullptr); + EvaluatePreparedCoupledModels(device, preparedModel, testModel, preparedCoupledModel, + signedQuantizedModel); + } break; + } +} + +void GeneratedTestBase::SetUp() { + testing::TestWithParam<GeneratedTestParam>::SetUp(); + ASSERT_NE(kDevice, nullptr); +} + +std::vector<NamedModel> getNamedModels(const FilterFn& filter) { + return TestModelManager::get().getTestModels(filter); +} + +std::vector<NamedModel> getNamedModels(const FilterNameFn& filter) { + return TestModelManager::get().getTestModels(filter); +} + +std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info) { + const auto& [namedDevice, namedModel] = info.param; + return gtestCompliantName(getName(namedDevice) + "_" + getName(namedModel)); +} + +// Tag for the generated tests +class GeneratedTest : public GeneratedTestBase {}; + +// Tag for the dynamic output shape tests +class DynamicOutputShapeTest : public GeneratedTest {}; + +// Tag for the memory domain tests +class MemoryDomainTest : public GeneratedTest {}; + +// Tag for the fenced compute tests +class FencedComputeTest : public GeneratedTest {}; + +// Tag for the dynamic output shape tests +class QuantizationCouplingTest : public GeneratedTest {}; + +// Tag for the loop timeout tests +class InfiniteLoopTimeoutTest : public GeneratedTest {}; + +TEST_P(GeneratedTest, Test) { + Execute(kDevice, kTestModel, TestKind::GENERAL); +} + +TEST_P(DynamicOutputShapeTest, Test) { + Execute(kDevice, kTestModel, TestKind::DYNAMIC_SHAPE); +} + +TEST_P(MemoryDomainTest, Test) { + Execute(kDevice, kTestModel, TestKind::MEMORY_DOMAIN); +} + +TEST_P(FencedComputeTest, Test) { + Execute(kDevice, kTestModel, TestKind::FENCED_COMPUTE); +} + +TEST_P(QuantizationCouplingTest, Test) { + Execute(kDevice, kTestModel, TestKind::QUANTIZATION_COUPLING); +} + +TEST_P(InfiniteLoopTimeoutTest, Test) { + Execute(kDevice, kTestModel, TestKind::INTINITE_LOOP_TIMEOUT); +} + +INSTANTIATE_GENERATED_TEST(GeneratedTest, + [](const TestModel& testModel) { return !testModel.expectFailure; }); + +INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest, [](const TestModel& testModel) { + return !testModel.expectFailure && !testModel.hasScalarOutputs(); +}); + +INSTANTIATE_GENERATED_TEST(MemoryDomainTest, + [](const TestModel& testModel) { return !testModel.expectFailure; }); + +INSTANTIATE_GENERATED_TEST(FencedComputeTest, + [](const TestModel& testModel) { return !testModel.expectFailure; }); + +INSTANTIATE_GENERATED_TEST(QuantizationCouplingTest, [](const TestModel& testModel) { + return !testModel.expectFailure && testModel.hasQuant8CoupledOperands() && + testModel.main.operations.size() == 1; +}); + +INSTANTIATE_GENERATED_TEST(InfiniteLoopTimeoutTest, [](const TestModel& testModel) { + return testModel.isInfiniteLoopTimeoutTest(); +}); + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional diff --git a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h new file mode 100644 index 0000000000..ad40f06874 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 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. + */ + +#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_GENERATED_TEST_HARNESS_H +#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_GENERATED_TEST_HARNESS_H + +#include <functional> +#include <vector> + +#include <TestHarness.h> +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +using NamedModel = Named<const test_helper::TestModel*>; +using GeneratedTestParam = std::tuple<NamedDevice, NamedModel>; + +class GeneratedTestBase : public testing::TestWithParam<GeneratedTestParam> { + protected: + void SetUp() override; + const std::shared_ptr<IDevice> kDevice = getData(std::get<NamedDevice>(GetParam())); + const test_helper::TestModel& kTestModel = *getData(std::get<NamedModel>(GetParam())); +}; + +using FilterFn = std::function<bool(const test_helper::TestModel&)>; +std::vector<NamedModel> getNamedModels(const FilterFn& filter); + +using FilterNameFn = std::function<bool(const std::string&)>; +std::vector<NamedModel> getNamedModels(const FilterNameFn& filter); + +std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info); + +#define INSTANTIATE_GENERATED_TEST(TestSuite, filter) \ + GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TestSuite); \ + INSTANTIATE_TEST_SUITE_P(TestGenerated, TestSuite, \ + testing::Combine(testing::ValuesIn(getNamedDevices()), \ + testing::ValuesIn(getNamedModels(filter))), \ + printGeneratedTest) + +// Tag for the validation tests, instantiated in VtsHalNeuralnetworks.cpp. +// TODO: Clean up the hierarchy for ValidationTest. +class ValidationTest : public GeneratedTestBase {}; + +Model createModel(const test_helper::TestModel& testModel); + +void PrepareModel(const std::shared_ptr<IDevice>& device, const Model& model, + std::shared_ptr<IPreparedModel>* preparedModel); + +enum class TestKind { + // Runs a test model and compares the results to a golden data + GENERAL, + // Same as GENERAL but sets dimensions for the output tensors to zeros + DYNAMIC_SHAPE, + // Same as GENERAL but use device memories for inputs and outputs + MEMORY_DOMAIN, + // Same as GENERAL but use executeFenced for exeuction + FENCED_COMPUTE, + // Tests if quantized model with TENSOR_QUANT8_ASYMM produces the same result + // (OK/SKIPPED/FAILED) as the model with all such tensors converted to + // TENSOR_QUANT8_ASYMM_SIGNED. + QUANTIZATION_COUPLING, + // Runs a test model and verifies that MISSED_DEADLINE_* is returned. + INTINITE_LOOP_TIMEOUT +}; + +void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device, + const std::shared_ptr<IPreparedModel>& preparedModel, + const test_helper::TestModel& testModel, TestKind testKind); + +void waitForSyncFence(int syncFd); + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional + +#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_GENERATED_TEST_HARNESS_H diff --git a/neuralnetworks/aidl/vts/functional/LogTestCaseToLogcat.h b/neuralnetworks/aidl/vts/functional/LogTestCaseToLogcat.h new file mode 100644 index 0000000000..c9fd432a43 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/LogTestCaseToLogcat.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 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. + */ + +#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_LOG_TEST_CASE_TO_LOGCAT_H +#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_LOG_TEST_CASE_TO_LOGCAT_H + +#include <android-base/logging.h> +#include <gtest/gtest.h> + +namespace aidl::android::hardware::neuralnetworks { + +class LogTestCaseToLogcat : public ::testing::EmptyTestEventListener { + public: + void OnTestStart(const ::testing::TestInfo& test_info) override { + LOG(INFO) << "[Test Case] " << test_info.test_suite_name() << "." << test_info.name() + << " BEGIN"; + } + + void OnTestEnd(const ::testing::TestInfo& test_info) override { + LOG(INFO) << "[Test Case] " << test_info.test_suite_name() << "." << test_info.name() + << " END"; + } +}; + +} // namespace aidl::android::hardware::neuralnetworks + +#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_LOG_TEST_CASE_TO_LOGCAT_H diff --git a/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp new file mode 100644 index 0000000000..a37a0caa29 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp @@ -0,0 +1,1176 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "neuralnetworks_aidl_hal_test" + +#include <android-base/logging.h> +#include <android/binder_auto_utils.h> +#include <android/binder_interface_utils.h> +#include <android/binder_status.h> +#include <gtest/gtest.h> + +#include <LegacyUtils.h> +#include <TestHarness.h> +#include <Utils.h> +#include <nnapi/SharedMemory.h> +#include <nnapi/hal/aidl/Conversions.h> +#include <nnapi/hal/aidl/Utils.h> + +#include "AidlHalInterfaces.h" +#include "Callbacks.h" +#include "GeneratedTestHarness.h" +#include "MemoryUtils.h" +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +using namespace test_helper; +using implementation::PreparedModelCallback; + +namespace { + +// An AIDL driver is likely to support at least one of the following operand types. +const std::vector<TestOperandType> kTestOperandTypeChoicesVector = { + TestOperandType::TENSOR_FLOAT32, + TestOperandType::TENSOR_FLOAT16, + TestOperandType::TENSOR_QUANT8_ASYMM, + TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED, +}; +const auto kTestOperandTypeChoices = testing::ValuesIn(kTestOperandTypeChoicesVector); +// TODO(b/179270601): restore kNamedDeviceChoices + +bool isInChoices(TestOperandType type) { + return std::count(kTestOperandTypeChoicesVector.begin(), kTestOperandTypeChoicesVector.end(), + type) > 0; +} + +bool isFloat(TestOperandType type) { + CHECK(isInChoices(type)); + return type == TestOperandType::TENSOR_FLOAT32 || type == TestOperandType::TENSOR_FLOAT16; +} + +// Create placeholder buffers for model constants as well as inputs and outputs. +// We only care about the size here because we will not check accuracy in validation tests. +void createDummyData(TestModel* testModel) { + for (auto& operand : testModel->main.operands) { + if (operand.data != nullptr) continue; + switch (operand.lifetime) { + case TestOperandLifeTime::SUBGRAPH_INPUT: + case TestOperandLifeTime::SUBGRAPH_OUTPUT: + case TestOperandLifeTime::CONSTANT_COPY: + case TestOperandLifeTime::CONSTANT_REFERENCE: { + const uint32_t size = nn::nonExtensionOperandSizeOfData( + static_cast<nn::OperandType>(operand.type), operand.dimensions); + operand.data = TestBuffer(size); + } break; + default: + break; + } + } +} + +TestOperand createInt32Scalar(int32_t value) { + return { + .type = TestOperandType::INT32, + .dimensions = {}, + .numberOfConsumers = 1, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::CONSTANT_COPY, + .data = TestBuffer::createFromVector<int32_t>({value}), + }; +} + +// Construct a test model with multiple CONV_2D operations with the given operand as inputs. +// The dimensions of the filters are chosen to ensure outputs has the same dimensions as inputs. +// We choose CONV_2D operation because it is commonly supported by most drivers. +TestModel createConvModel(const TestOperand& operand, uint32_t numOperations) { + CHECK(isInChoices(operand.type)); + + TestOperand weight = {.type = operand.type, + .dimensions = {operand.dimensions[3], 3, 3, operand.dimensions[3]}, + .numberOfConsumers = 1, + .scale = isFloat(operand.type) ? 0.0f : 1.0f, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::CONSTANT_COPY}; + + TestOperand bias = { + .type = isFloat(operand.type) ? operand.type : TestOperandType::TENSOR_INT32, + .dimensions = {operand.dimensions[3]}, + .numberOfConsumers = 1, + .scale = operand.scale * weight.scale, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::CONSTANT_COPY}; + + TestOperand output = operand; + output.numberOfConsumers = 0; + output.lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT; + + const std::vector<TestOperand> operands = { + operand, + std::move(weight), + std::move(bias), + createInt32Scalar(1), // same padding + createInt32Scalar(1), // width stride + createInt32Scalar(1), // height stride + createInt32Scalar(0), // activation = NONE + std::move(output), + }; + + TestModel model; + for (uint32_t i = 0; i < numOperations; i++) { + model.main.operands.insert(model.main.operands.end(), operands.begin(), operands.end()); + const uint32_t inputIndex = operands.size() * i; + const uint32_t outputIndex = inputIndex + operands.size() - 1; + std::vector<uint32_t> inputs(operands.size() - 1); + std::iota(inputs.begin(), inputs.end(), inputIndex); + model.main.operations.push_back({.type = TestOperationType::CONV_2D, + .inputs = std::move(inputs), + .outputs = {outputIndex}}); + model.main.inputIndexes.push_back(inputIndex); + model.main.outputIndexes.push_back(outputIndex); + } + createDummyData(&model); + return model; +} + +// Construct a test model with a single ADD operation with the given operand as input0 and input1. +// This is to cover additional cases that the CONV_2D model does not support, e.g. arbitrary input +// operand rank, scalar input operand. We choose ADD operation because it is commonly supported by +// most drivers. +TestModel createSingleAddModel(const TestOperand& operand) { + CHECK(isInChoices(operand.type)); + + TestOperand act = { + .type = TestOperandType::INT32, + .dimensions = {}, + .numberOfConsumers = 1, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT, + }; + + TestOperand output = operand; + output.numberOfConsumers = 0; + output.lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT; + + TestModel model = { + .main = + { + .operands = + { + operand, + operand, + std::move(act), + output, + }, + .operations = {{.type = TestOperationType::ADD, + .inputs = {0, 1, 2}, + .outputs = {3}}}, + .inputIndexes = {0, 1, 2}, + .outputIndexes = {3}, + }, + }; + createDummyData(&model); + return model; +} + +// A placeholder invalid IPreparedModel class for MemoryDomainAllocateTest.InvalidPreparedModel +class InvalidPreparedModel : public BnPreparedModel { + public: + ndk::ScopedAStatus executeSynchronously(const Request&, bool, int64_t, int64_t, + ExecutionResult*) override { + return ndk::ScopedAStatus::fromServiceSpecificError( + static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE)); + } + ndk::ScopedAStatus executeFenced(const Request&, const std::vector<ndk::ScopedFileDescriptor>&, + bool, int64_t, int64_t, int64_t, ndk::ScopedFileDescriptor*, + std::shared_ptr<IFencedExecutionCallback>*) override { + return ndk::ScopedAStatus::fromServiceSpecificError( + static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE)); + } +}; + +template <typename... Args> +std::vector<RequestMemoryPool> createRequestMemoryPools(const Args&... pools) { + std::vector<RequestMemoryPool> memoryPools; + memoryPools.reserve(sizeof...(Args)); + // This fold operator calls push_back on each of the function arguments. + (memoryPools.push_back(utils::clone(pools).value()), ...); + return memoryPools; +}; + +} // namespace + +class MemoryDomainTestBase : public testing::Test { + protected: + MemoryDomainTestBase(std::shared_ptr<IDevice> device, TestOperandType type) + : kDevice(std::move(device)), + kTestOperandType(type), + kTestOperand(kTestOperandMap.at(type)), + kTestOperandDataSize(nn::nonExtensionOperandSizeOfData(static_cast<nn::OperandType>(type), + kTestOperand.dimensions)) {} + + void SetUp() override { + testing::Test::SetUp(); + ASSERT_NE(kDevice, nullptr); + } + + std::shared_ptr<IPreparedModel> createConvPreparedModel(const TestOperand& testOperand, + uint32_t numOperations = 1) { + const TestModel testModel = createConvModel(testOperand, numOperations); + const Model model = createModel(testModel); + std::shared_ptr<IPreparedModel> preparedModel; + createPreparedModel(kDevice, model, &preparedModel, /*reportSkipping=*/false); + return preparedModel; + } + + std::shared_ptr<IPreparedModel> createAddPreparedModel(const TestOperand& testOperand) { + const TestModel testModel = createSingleAddModel(testOperand); + const Model model = createModel(testModel); + std::shared_ptr<IPreparedModel> preparedModel; + createPreparedModel(kDevice, model, &preparedModel, /*reportSkipping=*/false); + return preparedModel; + } + + static const std::map<TestOperandType, TestOperand> kTestOperandMap; + + const std::shared_ptr<IDevice> kDevice; + const TestOperandType kTestOperandType; + const TestOperand& kTestOperand; + const uint32_t kTestOperandDataSize; +}; + +const std::map<TestOperandType, TestOperand> MemoryDomainTestBase::kTestOperandMap = { + {TestOperandType::TENSOR_FLOAT32, + { + .type = TestOperandType::TENSOR_FLOAT32, + .dimensions = {1, 32, 32, 8}, + .numberOfConsumers = 1, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT, + }}, + {TestOperandType::TENSOR_FLOAT16, + { + .type = TestOperandType::TENSOR_FLOAT16, + .dimensions = {1, 32, 32, 8}, + .numberOfConsumers = 1, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT, + }}, + {TestOperandType::TENSOR_QUANT8_ASYMM, + { + .type = TestOperandType::TENSOR_QUANT8_ASYMM, + .dimensions = {1, 32, 32, 8}, + .numberOfConsumers = 1, + .scale = 0.5f, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT, + }}, + {TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED, + { + .type = TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED, + .dimensions = {1, 32, 32, 8}, + .numberOfConsumers = 1, + .scale = 0.5f, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT, + }}, +}; + +using MemoryDomainAllocateTestParam = std::tuple<NamedDevice, TestOperandType>; +class MemoryDomainAllocateTest : public MemoryDomainTestBase, + public testing::WithParamInterface<MemoryDomainAllocateTestParam> { + protected: + MemoryDomainAllocateTest() + : MemoryDomainTestBase(getData(std::get<NamedDevice>(GetParam())), + std::get<TestOperandType>(GetParam())) {} + + struct AllocateTestArgs { + std::vector<int32_t> dimensions; + std::vector<std::shared_ptr<IPreparedModel>> preparedModels; + std::vector<BufferRole> inputRoles; + std::vector<BufferRole> outputRoles; + }; + + // Validation test for IDevice::allocate. The driver is expected to fail with INVALID_ARGUMENT, + // or GENERAL_FAILURE if memory domain is not supported. + void validateAllocate(AllocateTestArgs args) { + std::vector<IPreparedModelParcel> preparedModelParcels; + preparedModelParcels.reserve(args.preparedModels.size()); + for (const auto& model : args.preparedModels) { + preparedModelParcels.push_back({.preparedModel = model}); + } + DeviceBuffer buffer; + const auto ret = + kDevice->allocate({.dimensions = std::move(args.dimensions)}, preparedModelParcels, + args.inputRoles, args.outputRoles, &buffer); + + ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_TRUE(static_cast<ErrorStatus>(ret.getServiceSpecificError()) == + ErrorStatus::INVALID_ARGUMENT || + static_cast<ErrorStatus>(ret.getServiceSpecificError()) == + ErrorStatus::GENERAL_FAILURE); + } + + void testConflictOperands(const std::shared_ptr<IPreparedModel>& model1, + const std::shared_ptr<IPreparedModel>& model2) { + validateAllocate({ + .preparedModels = {model1, model2}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}, + {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}}, + }); + validateAllocate({ + .preparedModels = {model1, model2}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + .outputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}}, + }); + validateAllocate({ + .preparedModels = {model1, model2}, + .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}, + {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}}, + }); + } +}; + +TEST_P(MemoryDomainAllocateTest, EmptyRole) { + // Test with empty prepared models and roles. + validateAllocate({}); + + auto preparedModel = createConvPreparedModel(kTestOperand); + if (preparedModel == nullptr) return; + + // Test again with non-empty prepared models but empty roles. + validateAllocate({ + .preparedModels = {preparedModel}, + }); +} + +TEST_P(MemoryDomainAllocateTest, NullptrPreparedModel) { + // Test with nullptr prepared model as input role. + validateAllocate({ + .preparedModels = {nullptr}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); + + // Test with nullptr prepared model as output role. + validateAllocate({ + .preparedModels = {nullptr}, + .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); +} + +TEST_P(MemoryDomainAllocateTest, InvalidPreparedModel) { + std::shared_ptr<InvalidPreparedModel> invalidPreparedModel = + ndk::SharedRefBase::make<InvalidPreparedModel>(); + + // Test with invalid prepared model as input role. + validateAllocate({ + .preparedModels = {invalidPreparedModel}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); + + // Test with invalid prepared model as output role. + validateAllocate({ + .preparedModels = {invalidPreparedModel}, + .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); +} + +TEST_P(MemoryDomainAllocateTest, InvalidModelIndex) { + auto preparedModel = createConvPreparedModel(kTestOperand); + if (preparedModel == nullptr) return; + + // This should fail, because the model index is out of bound. + validateAllocate({ + .preparedModels = {preparedModel}, + .inputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}}, + }); + + // This should fail, because the model index is out of bound. + validateAllocate({ + .preparedModels = {preparedModel}, + .outputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}}, + }); +} + +TEST_P(MemoryDomainAllocateTest, InvalidIOIndex) { + auto preparedModel = createConvPreparedModel(kTestOperand); + if (preparedModel == nullptr) return; + + // This should fail, because the model only has one input. + validateAllocate({ + .preparedModels = {preparedModel}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 1, .frequency = 1.0f}}, + }); + + // This should fail, because the model only has one output. + validateAllocate({ + .preparedModels = {preparedModel}, + .outputRoles = {{.modelIndex = 0, .ioIndex = 1, .frequency = 1.0f}}, + }); +} + +TEST_P(MemoryDomainAllocateTest, InvalidFrequency) { + auto preparedModel = createConvPreparedModel(kTestOperand); + if (preparedModel == nullptr) return; + + for (float invalidFreq : {10.0f, 0.0f, -0.5f}) { + // Test with invalid frequency for input roles. + validateAllocate({ + .preparedModels = {preparedModel}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = invalidFreq}}, + }); + // Test with invalid frequency for output roles. + validateAllocate({ + .preparedModels = {preparedModel}, + .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = invalidFreq}}, + }); + } +} + +TEST_P(MemoryDomainAllocateTest, SameRoleSpecifiedTwice) { + auto preparedModel = createConvPreparedModel(kTestOperand); + if (preparedModel == nullptr) return; + + // Same role with same model index. + validateAllocate({ + .preparedModels = {preparedModel}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}, + {.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); + validateAllocate({ + .preparedModels = {preparedModel}, + .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}, + {.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); + + // Different model indexes, but logically referring to the same role. + validateAllocate({ + .preparedModels = {preparedModel, preparedModel}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}, + {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}}, + }); + validateAllocate({ + .preparedModels = {preparedModel, preparedModel}, + .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}, + {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}}, + }); +} + +TEST_P(MemoryDomainAllocateTest, ConflictOperandType) { + const std::map<TestOperandType, TestOperandType> conflictTypeMap = { + {TestOperandType::TENSOR_FLOAT32, TestOperandType::TENSOR_FLOAT16}, + {TestOperandType::TENSOR_FLOAT16, TestOperandType::TENSOR_FLOAT32}, + {TestOperandType::TENSOR_QUANT8_ASYMM, TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED}, + {TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED, TestOperandType::TENSOR_QUANT8_ASYMM}, + }; + + TestOperand conflictTestOperand = kTestOperand; + const auto it = conflictTypeMap.find(kTestOperandType); + ASSERT_FALSE(it == conflictTypeMap.end()); + conflictTestOperand.type = it->second; + + auto preparedModel = createConvPreparedModel(kTestOperand); + auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand); + if (preparedModel == nullptr || conflictPreparedModel == nullptr) return; + testConflictOperands(preparedModel, conflictPreparedModel); +} + +TEST_P(MemoryDomainAllocateTest, ConflictScale) { + if (isFloat(kTestOperandType)) return; + + TestOperand conflictTestOperand = kTestOperand; + ASSERT_NE(conflictTestOperand.scale, 1.0f); + conflictTestOperand.scale = 1.0f; + + auto preparedModel = createConvPreparedModel(kTestOperand); + auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand); + if (preparedModel == nullptr || conflictPreparedModel == nullptr) return; + testConflictOperands(preparedModel, conflictPreparedModel); +} + +TEST_P(MemoryDomainAllocateTest, ConflictZeroPoint) { + if (isFloat(kTestOperandType)) return; + + TestOperand conflictTestOperand = kTestOperand; + ASSERT_NE(conflictTestOperand.zeroPoint, 10); + conflictTestOperand.zeroPoint = 10; + + auto preparedModel = createConvPreparedModel(kTestOperand); + auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand); + if (preparedModel == nullptr || conflictPreparedModel == nullptr) return; + testConflictOperands(preparedModel, conflictPreparedModel); +} + +TEST_P(MemoryDomainAllocateTest, ConflictRankBetweenRoles) { + TestOperand conflictTestOperand = kTestOperand; + conflictTestOperand.dimensions.pop_back(); + + auto preparedModel = createAddPreparedModel(kTestOperand); + auto conflictPreparedModel = createAddPreparedModel(conflictTestOperand); + if (preparedModel == nullptr || conflictPreparedModel == nullptr) return; + testConflictOperands(preparedModel, conflictPreparedModel); +} + +TEST_P(MemoryDomainAllocateTest, ConflictDimensionsBetweenRoles) { + TestOperand conflictTestOperand = kTestOperand; + conflictTestOperand.dimensions[0] = 4; + + auto preparedModel = createConvPreparedModel(kTestOperand); + auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand); + if (preparedModel == nullptr || conflictPreparedModel == nullptr) return; + testConflictOperands(preparedModel, conflictPreparedModel); +} + +TEST_P(MemoryDomainAllocateTest, ConflictRankBetweenRoleAndDesc) { + auto preparedModel = createConvPreparedModel(kTestOperand); + if (preparedModel == nullptr) return; + + auto badDimensions = utils::toSigned(kTestOperand.dimensions).value(); + badDimensions.pop_back(); + + validateAllocate({ + .dimensions = badDimensions, + .preparedModels = {preparedModel}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); + validateAllocate({ + .dimensions = badDimensions, + .preparedModels = {preparedModel}, + .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); +} + +TEST_P(MemoryDomainAllocateTest, ConflictDimensionsBetweenRoleAndDesc) { + auto preparedModel = createConvPreparedModel(kTestOperand); + if (preparedModel == nullptr) return; + + auto badDimensions = utils::toSigned(kTestOperand.dimensions).value(); + badDimensions[0] = 4; + + validateAllocate({ + .dimensions = badDimensions, + .preparedModels = {preparedModel}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); + validateAllocate({ + .dimensions = badDimensions, + .preparedModels = {preparedModel}, + .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}}, + }); +} + +TEST_P(MemoryDomainAllocateTest, ConflictRankWithScalarRole) { + auto preparedModel = createAddPreparedModel(kTestOperand); + if (preparedModel == nullptr) return; + + // This should fail, because the target operand is a scalar but a non-empty dimension is + // specified. + validateAllocate({ + .dimensions = {1}, + .preparedModels = {preparedModel}, + .inputRoles = {{.modelIndex = 0, .ioIndex = 2, .frequency = 1.0f}}, + }); +} + +std::string printMemoryDomainAllocateTest( + const testing::TestParamInfo<MemoryDomainAllocateTestParam>& info) { + const auto& [namedDevice, operandType] = info.param; + const std::string type = toString(static_cast<OperandType>(operandType)); + return gtestCompliantName(getName(namedDevice) + "_" + type); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainAllocateTest); +INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainAllocateTest, + testing::Combine(testing::ValuesIn(getNamedDevices()), + kTestOperandTypeChoices), + printMemoryDomainAllocateTest); + +class MemoryDomainCopyTestBase : public MemoryDomainTestBase { + protected: + MemoryDomainCopyTestBase(std::shared_ptr<IDevice> device, TestOperandType type) + : MemoryDomainTestBase(std::move(device), type) {} + + // Allocates device memory for roles of a single prepared model. + // Returns {IBuffer, token} if success; returns {nullptr, 0} if not supported. + DeviceBuffer allocateBuffer(const std::shared_ptr<IPreparedModel>& preparedModel, + const std::vector<int32_t>& inputIndexes, + const std::vector<int32_t>& outputIndexes, + const std::vector<int32_t>& dimensions) { + if (preparedModel == nullptr) { + return {.buffer = nullptr, .token = 0}; + } + + std::vector<BufferRole> inputRoles(inputIndexes.size()), outputRoles(outputIndexes.size()); + auto trans = [](int32_t ind) -> BufferRole { + return {.modelIndex = 0, .ioIndex = ind, .frequency = 1.0f}; + }; + std::transform(inputIndexes.begin(), inputIndexes.end(), inputRoles.begin(), trans); + std::transform(outputIndexes.begin(), outputIndexes.end(), outputRoles.begin(), trans); + + IPreparedModelParcel parcel; + parcel.preparedModel = preparedModel; + + DeviceBuffer buffer; + + const auto ret = kDevice->allocate({.dimensions = dimensions}, {parcel}, inputRoles, + outputRoles, &buffer); + + if (!ret.isOk()) { + EXPECT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC); + EXPECT_EQ(static_cast<ErrorStatus>(ret.getServiceSpecificError()), + ErrorStatus::GENERAL_FAILURE); + return DeviceBuffer{ + .buffer = nullptr, + .token = 0, + }; + } + + EXPECT_NE(buffer.buffer, nullptr); + EXPECT_GT(buffer.token, 0); + + return buffer; + } + + DeviceBuffer allocateBuffer(const std::shared_ptr<IPreparedModel>& preparedModel, + const std::vector<int32_t>& inputIndexes, + const std::vector<int32_t>& outputIndexes) { + return allocateBuffer(preparedModel, inputIndexes, outputIndexes, {}); + } + + Memory allocateSharedMemory(uint32_t size) { + const auto sharedMemory = nn::createSharedMemory(size).value(); + auto memory = utils::convert(sharedMemory).value(); + EXPECT_EQ(memory.size, size); + return memory; + } + + void testCopyFrom(const std::shared_ptr<IBuffer>& buffer, const Memory& memory, + const std::vector<int32_t>& dimensions, ErrorStatus expectedStatus) { + const auto ret = buffer->copyFrom(memory, dimensions); + if (expectedStatus == ErrorStatus::NONE) { + ASSERT_TRUE(ret.isOk()); + } else { + ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(expectedStatus, static_cast<ErrorStatus>(ret.getServiceSpecificError())); + } + } + + void testCopyTo(const std::shared_ptr<IBuffer>& buffer, const Memory& memory, + ErrorStatus expectedStatus) { + const auto ret = buffer->copyTo(memory); + if (expectedStatus == ErrorStatus::NONE) { + ASSERT_TRUE(ret.isOk()); + } else { + ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(expectedStatus, static_cast<ErrorStatus>(ret.getServiceSpecificError())); + } + } + + void initializeDeviceMemory(const std::shared_ptr<IBuffer>& buffer) { + Memory memory = allocateSharedMemory(kTestOperandDataSize); + ASSERT_EQ(memory.size, kTestOperandDataSize); + testCopyFrom(buffer, memory, utils::toSigned(kTestOperand.dimensions).value(), + ErrorStatus::NONE); + } +}; + +using MemoryDomainCopyTestParam = std::tuple<NamedDevice, TestOperandType>; +class MemoryDomainCopyTest : public MemoryDomainCopyTestBase, + public testing::WithParamInterface<MemoryDomainCopyTestParam> { + protected: + MemoryDomainCopyTest() + : MemoryDomainCopyTestBase(getData(std::get<NamedDevice>(GetParam())), + std::get<TestOperandType>(GetParam())) {} +}; + +TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidMemorySize) { + auto preparedModel = createConvPreparedModel(kTestOperand); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}); + if (buffer == nullptr) return; + + uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2; + Memory badMemory1 = allocateSharedMemory(badMemorySize1); + Memory badMemory2 = allocateSharedMemory(badMemorySize2); + testCopyFrom(buffer, badMemory1, {}, ErrorStatus::INVALID_ARGUMENT); + testCopyFrom(buffer, badMemory2, {}, ErrorStatus::INVALID_ARGUMENT); +} + +TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidMemorySize_DynamicShape) { + TestOperand testOperand = kTestOperand; + testOperand.dimensions[0] = 0; + auto preparedModel = createConvPreparedModel(testOperand); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}); + if (buffer == nullptr) return; + + uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2; + Memory badMemory1 = allocateSharedMemory(badMemorySize1); + Memory badMemory2 = allocateSharedMemory(badMemorySize2); + Memory goodMemory = allocateSharedMemory(kTestOperandDataSize); + + const auto goodDimensions = utils::toSigned(kTestOperand.dimensions).value(); + auto badDimensions = goodDimensions; + badDimensions[0] = 2; + + testCopyFrom(buffer, badMemory1, goodDimensions, ErrorStatus::INVALID_ARGUMENT); + testCopyFrom(buffer, badMemory2, goodDimensions, ErrorStatus::INVALID_ARGUMENT); + testCopyFrom(buffer, goodMemory, goodDimensions, ErrorStatus::NONE); + testCopyFrom(buffer, goodMemory, badDimensions, ErrorStatus::INVALID_ARGUMENT); +} + +TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidDimensions) { + auto preparedModel = createConvPreparedModel(kTestOperand); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}); + if (buffer == nullptr) return; + + Memory memory = allocateSharedMemory(kTestOperandDataSize); + + const auto goodDimensions = utils::toSigned(kTestOperand.dimensions).value(); + std::vector<int32_t> badDimensions = goodDimensions; + badDimensions.pop_back(); + testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT); + + badDimensions = goodDimensions; + badDimensions[0] = 2; + testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT); + + badDimensions = goodDimensions; + badDimensions[0] = 0; + testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT); + + testCopyFrom(buffer, memory, {}, ErrorStatus::NONE); + testCopyFrom(buffer, memory, goodDimensions, ErrorStatus::NONE); +} + +TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidDimensions_DynamicShape) { + TestOperand testOperand = kTestOperand; + testOperand.dimensions[0] = 0; + auto preparedModel = createConvPreparedModel(testOperand); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}); + if (buffer == nullptr) return; + + Memory memory = allocateSharedMemory(kTestOperandDataSize); + + const auto goodDimensions = utils::toSigned(kTestOperand.dimensions).value(); + std::vector<int32_t> badDimensions = goodDimensions; + badDimensions.pop_back(); + testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT); + + badDimensions = goodDimensions; + badDimensions[0] = 2; + badDimensions[3] = 4; + testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT); + + badDimensions = goodDimensions; + badDimensions[0] = 1; + badDimensions[3] = 0; + testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT); + + testCopyFrom(buffer, memory, {}, ErrorStatus::INVALID_ARGUMENT); + testCopyFrom(buffer, memory, goodDimensions, ErrorStatus::NONE); +} + +TEST_P(MemoryDomainCopyTest, CopyTo_UninitializedMemory) { + auto preparedModel = createConvPreparedModel(kTestOperand); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}); + if (buffer == nullptr) return; + + Memory memory = allocateSharedMemory(kTestOperandDataSize); + testCopyTo(buffer, memory, ErrorStatus::GENERAL_FAILURE); +} + +TEST_P(MemoryDomainCopyTest, CopyTo_InvalidMemorySize) { + auto preparedModel = createConvPreparedModel(kTestOperand); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}); + if (buffer == nullptr) return; + + uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2; + Memory badMemory1 = allocateSharedMemory(badMemorySize1); + Memory badMemory2 = allocateSharedMemory(badMemorySize2); + Memory goodMemory = allocateSharedMemory(kTestOperandDataSize); + + initializeDeviceMemory(buffer); + testCopyTo(buffer, badMemory1, ErrorStatus::INVALID_ARGUMENT); + testCopyTo(buffer, badMemory2, ErrorStatus::INVALID_ARGUMENT); + testCopyTo(buffer, goodMemory, ErrorStatus::NONE); +} + +TEST_P(MemoryDomainCopyTest, CopyTo_InvalidMemorySize_DynamicShape) { + TestOperand testOperand = kTestOperand; + testOperand.dimensions[0] = 0; + auto preparedModel = createConvPreparedModel(testOperand); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}); + if (buffer == nullptr) return; + + uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2; + Memory badMemory1 = allocateSharedMemory(badMemorySize1); + Memory badMemory2 = allocateSharedMemory(badMemorySize2); + Memory goodMemory = allocateSharedMemory(kTestOperandDataSize); + + initializeDeviceMemory(buffer); + testCopyTo(buffer, badMemory1, ErrorStatus::INVALID_ARGUMENT); + testCopyTo(buffer, badMemory2, ErrorStatus::INVALID_ARGUMENT); + testCopyTo(buffer, goodMemory, ErrorStatus::NONE); +} + +std::string printMemoryDomainCopyTest( + const testing::TestParamInfo<MemoryDomainCopyTestParam>& info) { + const auto& [namedDevice, operandType] = info.param; + const std::string type = toString(static_cast<OperandType>(operandType)); + return gtestCompliantName(getName(namedDevice) + "_" + type); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainCopyTest); +INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainCopyTest, + testing::Combine(testing::ValuesIn(getNamedDevices()), + kTestOperandTypeChoices), + printMemoryDomainCopyTest); + +using MemoryDomainExecutionTestParam = std::tuple<NamedDevice, TestOperandType, Executor>; +class MemoryDomainExecutionTest + : public MemoryDomainCopyTestBase, + public testing::WithParamInterface<MemoryDomainExecutionTestParam> { + protected: + MemoryDomainExecutionTest() + : MemoryDomainCopyTestBase(getData(std::get<NamedDevice>(GetParam())), + std::get<TestOperandType>(GetParam())) {} + + RequestMemoryPool createSharedMemoryPool(uint32_t size) { + return RequestMemoryPool(allocateSharedMemory(size)); + } + + RequestMemoryPool createDeviceMemoryPool(uint32_t token) { + return RequestMemoryPool(static_cast<int32_t>(token)); + } + + void testExecution(const std::shared_ptr<IPreparedModel>& preparedModel, const Request& request, + ErrorStatus expectedStatus) { + switch (kExecutor) { + case Executor::SYNC: + EXPECT_EQ(executeSync(preparedModel, request), expectedStatus); + break; + case Executor::FENCED: + EXPECT_EQ(executeFenced(preparedModel, request), expectedStatus); + break; + default: + ASSERT_TRUE(false); + } + } + + ErrorStatus executeSync(const std::shared_ptr<IPreparedModel>& preparedModel, + const Request& request) { + ExecutionResult executionResult; + const auto ret = preparedModel->executeSynchronously( + request, false, kNoDeadline, kOmittedTimeoutDuration, &executionResult); + + if (!ret.isOk()) { + EXPECT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC); + return static_cast<ErrorStatus>(ret.getServiceSpecificError()); + } + const ErrorStatus executionStatus = executionResult.outputSufficientSize + ? ErrorStatus::NONE + : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE; + EXPECT_EQ(executionResult.timing, kNoTiming); + return executionStatus; + } + + ErrorStatus executeFenced(const std::shared_ptr<IPreparedModel>& preparedModel, + const Request& request) { + ndk::ScopedFileDescriptor syncFence; + std::shared_ptr<IFencedExecutionCallback> fencedCallback; + const auto ret = preparedModel->executeFenced(request, {}, false, kNoDeadline, + kOmittedTimeoutDuration, kNoDuration, + &syncFence, &fencedCallback); + if (!ret.isOk()) { + EXPECT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC); + return static_cast<ErrorStatus>(ret.getServiceSpecificError()); + } + if (syncFence.get() != -1) { + waitForSyncFence(syncFence.get()); + } + EXPECT_NE(fencedCallback, nullptr); + + ErrorStatus executionStatus = ErrorStatus::GENERAL_FAILURE; + Timing time = kNoTiming; + Timing timeFenced = kNoTiming; + const auto retExecutionInfo = + fencedCallback->getExecutionInfo(&time, &timeFenced, &executionStatus); + EXPECT_TRUE(retExecutionInfo.isOk()); + EXPECT_EQ(time, kNoTiming); + return executionStatus; + } + + const Executor kExecutor = std::get<Executor>(GetParam()); +}; + +TEST_P(MemoryDomainExecutionTest, InvalidToken) { + auto preparedModel = createConvPreparedModel(kTestOperand); + if (preparedModel == nullptr) return; + + RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool badDeviceMemory1 = createDeviceMemoryPool(0); // Invalid token. + RequestMemoryPool badDeviceMemory2 = createDeviceMemoryPool(100); // Unknown token. + RequestArgument sharedMemoryArg = { + .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}}; + RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}}; + + testExecution(preparedModel, + {.inputs = {deviceMemoryArg}, + .outputs = {sharedMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory1)}, + ErrorStatus::INVALID_ARGUMENT); + testExecution(preparedModel, + {.inputs = {deviceMemoryArg}, + .outputs = {sharedMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory2)}, + ErrorStatus::INVALID_ARGUMENT); + testExecution(preparedModel, + {.inputs = {sharedMemoryArg}, + .outputs = {deviceMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory1)}, + ErrorStatus::INVALID_ARGUMENT); + testExecution(preparedModel, + {.inputs = {sharedMemoryArg}, + .outputs = {deviceMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory2)}, + ErrorStatus::INVALID_ARGUMENT); +} + +TEST_P(MemoryDomainExecutionTest, InvalidPreparedModel) { + auto preparedModel = createConvPreparedModel(kTestOperand); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}); + if (buffer == nullptr) return; + auto badPreparedModel = createConvPreparedModel(kTestOperand); + if (badPreparedModel == nullptr) return; + + RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool deviceMemory = createDeviceMemoryPool(token); + RequestArgument sharedMemoryArg = { + .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}}; + RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}}; + + // This should fail, because the buffer is not allocated for badPreparedModel. + initializeDeviceMemory(buffer); + testExecution(badPreparedModel, + {.inputs = {deviceMemoryArg}, + .outputs = {sharedMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); + testExecution(badPreparedModel, + {.inputs = {sharedMemoryArg}, + .outputs = {deviceMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); +} + +TEST_P(MemoryDomainExecutionTest, InvalidIOIndex) { + auto preparedModel = createConvPreparedModel(kTestOperand, 2); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {}); + if (buffer == nullptr) return; + + RequestMemoryPool sharedMemory1 = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool sharedMemory2 = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool sharedMemory3 = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool deviceMemory = createDeviceMemoryPool(token); + RequestArgument sharedMemoryArg1 = { + .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}}; + RequestArgument sharedMemoryArg2 = { + .location = {.poolIndex = 1, .offset = 0, .length = kTestOperandDataSize}}; + RequestArgument sharedMemoryArg3 = { + .location = {.poolIndex = 2, .offset = 0, .length = kTestOperandDataSize}}; + RequestArgument deviceMemoryArg = {.location = {.poolIndex = 3}}; + + // This should fail, because the device memory is not allocated for input 1. + initializeDeviceMemory(buffer); + testExecution(preparedModel, + {.inputs = {sharedMemoryArg1, deviceMemoryArg}, + .outputs = {sharedMemoryArg2, sharedMemoryArg3}, + .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, sharedMemory3, + deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); + + // This should fail, because the device memory is not allocated for output 1. + testExecution(preparedModel, + {.inputs = {sharedMemoryArg1, sharedMemoryArg2}, + .outputs = {sharedMemoryArg3, deviceMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, sharedMemory3, + deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); +} + +TEST_P(MemoryDomainExecutionTest, InvalidIOType) { + auto preparedModel = createConvPreparedModel(kTestOperand); + auto [inputBuffer, inputToken] = allocateBuffer(preparedModel, {0}, {}); + auto [outputBuffer, outputToken] = allocateBuffer(preparedModel, {}, {0}); + if (inputBuffer == nullptr || outputBuffer == nullptr) return; + + RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool deviceMemory = createDeviceMemoryPool(inputToken); + RequestArgument sharedMemoryArg = { + .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}}; + RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}}; + + // This should fail, because the device memory is allocated for input but used as output. + testExecution(preparedModel, + {.inputs = {sharedMemoryArg}, + .outputs = {deviceMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); + + // This should fail, because the device memory is allocated for output but used as input. + deviceMemory.set<RequestMemoryPool::Tag::token>(outputToken); + initializeDeviceMemory(outputBuffer); + testExecution(preparedModel, + {.inputs = {deviceMemoryArg}, + .outputs = {sharedMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); +} + +TEST_P(MemoryDomainExecutionTest, UninitializedMemory) { + auto preparedModel = createConvPreparedModel(kTestOperand); + auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}); + if (buffer == nullptr) return; + + RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool deviceMemory = createDeviceMemoryPool(token); + RequestArgument sharedMemoryArg = { + .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}}; + RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}}; + + // This should fail, because the device memory is not initialized. + testExecution(preparedModel, + {.inputs = {deviceMemoryArg}, + .outputs = {sharedMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::GENERAL_FAILURE); + + // This should initialize the device memory. + testExecution(preparedModel, + {.inputs = {sharedMemoryArg}, + .outputs = {deviceMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::NONE); + + // Test again with initialized device memory. + testExecution(preparedModel, + {.inputs = {deviceMemoryArg}, + .outputs = {sharedMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::NONE); +} + +TEST_P(MemoryDomainExecutionTest, SameRequestMultipleRoles) { + auto preparedModel = createConvPreparedModel(kTestOperand, 2); + auto [buffer, token] = allocateBuffer(preparedModel, {0, 1}, {0, 1}); + if (buffer == nullptr) return; + + RequestMemoryPool sharedMemory1 = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool sharedMemory2 = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool deviceMemory = createDeviceMemoryPool(token); + RequestArgument sharedMemoryArg1 = { + .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}}; + RequestArgument sharedMemoryArg2 = { + .location = {.poolIndex = 1, .offset = 0, .length = kTestOperandDataSize}}; + RequestArgument deviceMemoryArg = {.location = {.poolIndex = 2}}; + + // This should fail, because the same device memory cannot be used for both input and output. + initializeDeviceMemory(buffer); + testExecution(preparedModel, + {.inputs = {deviceMemoryArg, sharedMemoryArg1}, + .outputs = {deviceMemoryArg, sharedMemoryArg2}, + .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); + + // This should fail, because the same device memory cannot be used for multiple outputs. + testExecution(preparedModel, + {.inputs = {sharedMemoryArg1, sharedMemoryArg2}, + .outputs = {deviceMemoryArg, deviceMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); + + // The same device memory can be used for multiple inputs. + initializeDeviceMemory(buffer); + testExecution(preparedModel, + {.inputs = {deviceMemoryArg, deviceMemoryArg}, + .outputs = {sharedMemoryArg1, sharedMemoryArg2}, + .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, deviceMemory)}, + ErrorStatus::NONE); +} + +TEST_P(MemoryDomainExecutionTest, InvalidDimensions) { + // FENCED execution does not support dynamic shape. + if (kExecutor == Executor::FENCED) return; + + TestOperand testOperand = kTestOperand; + testOperand.dimensions[0] = 0; + auto preparedModel = createConvPreparedModel(testOperand); + auto deviceBuffer = allocateBuffer(preparedModel, {0}, {0}, + utils::toSigned(kTestOperand.dimensions).value()); + if (deviceBuffer.buffer == nullptr) return; + + RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize); + RequestMemoryPool deviceMemory = createDeviceMemoryPool(deviceBuffer.token); + auto badDimensions = utils::toSigned(kTestOperand.dimensions).value(); + badDimensions[0] = 2; + RequestArgument sharedMemoryArg = { + .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}, + .dimensions = badDimensions}; + RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}}; + RequestArgument deviceMemoryArgWithBadDimensions = {.location = {.poolIndex = 1}, + .dimensions = badDimensions}; + + initializeDeviceMemory(deviceBuffer.buffer); + testExecution(preparedModel, + {.inputs = {deviceMemoryArgWithBadDimensions}, + .outputs = {sharedMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); + + testExecution(preparedModel, + {.inputs = {sharedMemoryArg}, + .outputs = {deviceMemoryArgWithBadDimensions}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::INVALID_ARGUMENT); + + testExecution(preparedModel, + {.inputs = {sharedMemoryArg}, + .outputs = {deviceMemoryArg}, + .pools = createRequestMemoryPools(sharedMemory, deviceMemory)}, + ErrorStatus::GENERAL_FAILURE); +} + +const auto kExecutorChoices = testing::Values(Executor::SYNC, Executor::FENCED); + +std::string printMemoryDomainExecutionTest( + const testing::TestParamInfo<MemoryDomainExecutionTestParam>& info) { + const auto& [namedDevice, operandType, executor] = info.param; + const std::string type = toString(static_cast<OperandType>(operandType)); + const std::string executorStr = toString(executor); + return gtestCompliantName(getName(namedDevice) + "_" + type + "_" + executorStr); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainExecutionTest); +INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainExecutionTest, + testing::Combine(testing::ValuesIn(getNamedDevices()), + kTestOperandTypeChoices, kExecutorChoices), + printMemoryDomainExecutionTest); + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional diff --git a/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp new file mode 100644 index 0000000000..58db98f374 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (C) 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. + */ + +#include <android/binder_enums.h> +#include <android/binder_interface_utils.h> +#include <android/binder_status.h> + +#include <nnapi/hal/aidl/Conversions.h> + +#include "Callbacks.h" +#include "GeneratedTestHarness.h" +#include "Utils.h" + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +using implementation::PreparedModelCallback; +using test_helper::TestBuffer; +using test_helper::TestModel; + +enum class DeadlineBoundType { NOW, UNLIMITED, SHORT }; +constexpr std::array<DeadlineBoundType, 3> deadlineBounds = { + DeadlineBoundType::NOW, DeadlineBoundType::UNLIMITED, DeadlineBoundType::SHORT}; +std::string toString(DeadlineBoundType type) { + switch (type) { + case DeadlineBoundType::NOW: + return "NOW"; + case DeadlineBoundType::UNLIMITED: + return "UNLIMITED"; + case DeadlineBoundType::SHORT: + return "SHORT"; + } + LOG(FATAL) << "Unrecognized DeadlineBoundType: " << static_cast<int>(type); + return {}; +} + +constexpr auto kShortDuration = std::chrono::milliseconds{5}; + +using Results = std::tuple<ErrorStatus, std::vector<OutputShape>, Timing>; +using MaybeResults = std::optional<Results>; + +static int64_t makeDeadline(DeadlineBoundType deadlineBoundType) { + const auto getNanosecondsSinceEpoch = [](const auto& time) -> int64_t { + const auto timeSinceEpoch = time.time_since_epoch(); + return std::chrono::duration_cast<std::chrono::nanoseconds>(timeSinceEpoch).count(); + }; + + std::chrono::steady_clock::time_point timePoint; + switch (deadlineBoundType) { + case DeadlineBoundType::NOW: + timePoint = std::chrono::steady_clock::now(); + break; + case DeadlineBoundType::UNLIMITED: + timePoint = std::chrono::steady_clock::time_point::max(); + break; + case DeadlineBoundType::SHORT: + timePoint = std::chrono::steady_clock::now() + kShortDuration; + break; + } + + return getNanosecondsSinceEpoch(timePoint); +} + +void runPrepareModelTest(const std::shared_ptr<IDevice>& device, const Model& model, + Priority priority, std::optional<DeadlineBoundType> deadlineBound) { + int64_t deadline = kNoDeadline; + if (deadlineBound.has_value()) { + deadline = makeDeadline(deadlineBound.value()); + } + + // see if service can handle model + std::vector<bool> supportedOps; + const auto supportedCallStatus = device->getSupportedOperations(model, &supportedOps); + ASSERT_TRUE(supportedCallStatus.isOk()); + ASSERT_NE(0ul, supportedOps.size()); + const bool fullySupportsModel = + std::all_of(supportedOps.begin(), supportedOps.end(), [](bool valid) { return valid; }); + + // launch prepare model + const std::shared_ptr<PreparedModelCallback> preparedModelCallback = + ndk::SharedRefBase::make<PreparedModelCallback>(); + const auto prepareLaunchStatus = + device->prepareModel(model, ExecutionPreference::FAST_SINGLE_ANSWER, priority, deadline, + {}, {}, kEmptyCacheToken, preparedModelCallback); + ASSERT_TRUE(prepareLaunchStatus.isOk()) + << "prepareLaunchStatus: " << prepareLaunchStatus.getDescription(); + + // retrieve prepared model + preparedModelCallback->wait(); + const ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus(); + const std::shared_ptr<IPreparedModel> preparedModel = preparedModelCallback->getPreparedModel(); + + // The getSupportedOperations call returns a list of operations that are guaranteed not to fail + // if prepareModel is called, and 'fullySupportsModel' is true i.f.f. the entire model is + // guaranteed. If a driver has any doubt that it can prepare an operation, it must return false. + // So here, if a driver isn't sure if it can support an operation, but reports that it + // successfully prepared the model, the test can continue. + if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) { + ASSERT_EQ(nullptr, preparedModel.get()); + return; + } + + // verify return status + if (!deadlineBound.has_value()) { + EXPECT_EQ(ErrorStatus::NONE, prepareReturnStatus); + } else { + switch (deadlineBound.value()) { + case DeadlineBoundType::NOW: + case DeadlineBoundType::SHORT: + // Either the driver successfully completed the task or it + // aborted and returned MISSED_DEADLINE_*. + EXPECT_TRUE(prepareReturnStatus == ErrorStatus::NONE || + prepareReturnStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT || + prepareReturnStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT); + break; + case DeadlineBoundType::UNLIMITED: + // If an unlimited deadline is supplied, we expect the execution to + // proceed normally. In this case, check it normally by breaking out + // of the switch statement. + EXPECT_EQ(ErrorStatus::NONE, prepareReturnStatus); + break; + } + } + ASSERT_EQ(prepareReturnStatus == ErrorStatus::NONE, preparedModel.get() != nullptr); +} + +void runPrepareModelTests(const std::shared_ptr<IDevice>& device, const Model& model) { + // test priority + for (auto priority : ndk::enum_range<Priority>{}) { + SCOPED_TRACE("priority: " + toString(priority)); + if (priority == kDefaultPriority) continue; + runPrepareModelTest(device, model, priority, {}); + } + + // test deadline + for (auto deadlineBound : deadlineBounds) { + SCOPED_TRACE("deadlineBound: " + toString(deadlineBound)); + runPrepareModelTest(device, model, kDefaultPriority, deadlineBound); + } +} + +static MaybeResults executeSynchronously(const std::shared_ptr<IPreparedModel>& preparedModel, + const Request& request, int64_t deadline) { + SCOPED_TRACE("synchronous"); + const bool measure = false; + + // run execution + ExecutionResult executionResult; + const auto ret = preparedModel->executeSynchronously(request, measure, deadline, + kOmittedTimeoutDuration, &executionResult); + EXPECT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC) + << ret.getDescription(); + if (!ret.isOk()) { + if (ret.getExceptionCode() != EX_SERVICE_SPECIFIC) { + return std::nullopt; + } + return MaybeResults( + {static_cast<ErrorStatus>(ret.getServiceSpecificError()), {}, kNoTiming}); + } + + // return results + return MaybeResults({executionResult.outputSufficientSize + ? ErrorStatus::NONE + : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, + std::move(executionResult.outputShapes), executionResult.timing}); +} + +void runExecutionTest(const std::shared_ptr<IPreparedModel>& preparedModel, + const TestModel& testModel, const Request& request, + const ExecutionContext& context, DeadlineBoundType deadlineBound) { + const auto deadline = makeDeadline(deadlineBound); + + // Perform execution and unpack results. + const auto results = executeSynchronously(preparedModel, request, deadline); + if (!results.has_value()) return; + const auto& [status, outputShapes, timing] = results.value(); + + // Verify no timing information was returned + EXPECT_EQ(timing, kNoTiming); + + // Validate deadline information if applicable. + switch (deadlineBound) { + case DeadlineBoundType::NOW: + case DeadlineBoundType::SHORT: + // Either the driver successfully completed the task or it + // aborted and returned MISSED_DEADLINE_*. + ASSERT_TRUE(status == ErrorStatus::NONE || + status == ErrorStatus::MISSED_DEADLINE_TRANSIENT || + status == ErrorStatus::MISSED_DEADLINE_PERSISTENT); + break; + case DeadlineBoundType::UNLIMITED: + // If an unlimited deadline is supplied, we expect the execution to + // proceed normally. In this case, check it normally by breaking out + // of the switch statement. + ASSERT_EQ(ErrorStatus::NONE, status); + break; + } + + // If the model output operands are fully specified, outputShapes must be either + // either empty, or have the same number of elements as the number of outputs. + ASSERT_TRUE(outputShapes.size() == 0 || + outputShapes.size() == testModel.main.outputIndexes.size()); + + // Go through all outputs, check returned output shapes. + for (uint32_t i = 0; i < outputShapes.size(); i++) { + EXPECT_TRUE(outputShapes[i].isSufficient); + const auto expect = + utils::toSigned(testModel.main.operands[testModel.main.outputIndexes[i]].dimensions) + .value(); + const std::vector<int32_t>& actual = outputShapes[i].dimensions; + EXPECT_EQ(expect, actual); + } + + // Retrieve execution results. + const std::vector<TestBuffer> outputs = context.getOutputBuffers(request); + + // We want "close-enough" results. + if (status == ErrorStatus::NONE) { + checkResults(testModel, outputs); + } +} + +void runExecutionTests(const std::shared_ptr<IPreparedModel>& preparedModel, + const TestModel& testModel, const Request& request, + const ExecutionContext& context) { + for (auto deadlineBound : deadlineBounds) { + runExecutionTest(preparedModel, testModel, request, context, deadlineBound); + } +} + +void runTests(const std::shared_ptr<IDevice>& device, const TestModel& testModel) { + // setup + const Model model = createModel(testModel); + + // run prepare model tests + runPrepareModelTests(device, model); + + // prepare model + std::shared_ptr<IPreparedModel> preparedModel; + createPreparedModel(device, model, &preparedModel); + if (preparedModel == nullptr) return; + + // run execution tests + ExecutionContext context; + const Request request = context.createRequest(testModel); + runExecutionTests(preparedModel, testModel, request, context); +} + +class DeadlineTest : public GeneratedTestBase {}; + +TEST_P(DeadlineTest, Test) { + runTests(kDevice, kTestModel); +} + +INSTANTIATE_GENERATED_TEST(DeadlineTest, + [](const TestModel& testModel) { return !testModel.expectFailure; }); + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional diff --git a/neuralnetworks/aidl/vts/functional/TestAssertions.cpp b/neuralnetworks/aidl/vts/functional/TestAssertions.cpp new file mode 100644 index 0000000000..a9e945608c --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/TestAssertions.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 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. + */ + +#include <aidl/android/hardware/neuralnetworks/IPreparedModel.h> +#include <aidl/android/hardware/neuralnetworks/OperandType.h> +#include <aidl/android/hardware/neuralnetworks/OperationType.h> + +#include <ControlFlow.h> +#include <TestHarness.h> + +namespace aidl::android::hardware::neuralnetworks { + +namespace nn = ::android::nn; + +static_assert(static_cast<uint64_t>(IPreparedModel::DEFAULT_LOOP_TIMEOUT_DURATION_NS) == + nn::operation_while::kTimeoutNsDefault); +static_assert(static_cast<uint64_t>(IPreparedModel::MAXIMUM_LOOP_TIMEOUT_DURATION_NS) == + nn::operation_while::kTimeoutNsMaximum); + +// Make sure that the HIDL enums are compatible with the values defined in +// frameworks/ml/nn/tools/test_generator/test_harness/include/TestHarness.h. +using namespace test_helper; +#define CHECK_TEST_ENUM(EnumType, enumValue) \ + static_assert(static_cast<EnumType>(Test##EnumType::enumValue) == EnumType::enumValue) + +CHECK_TEST_ENUM(OperandType, FLOAT32); +CHECK_TEST_ENUM(OperandType, INT32); +CHECK_TEST_ENUM(OperandType, UINT32); +CHECK_TEST_ENUM(OperandType, TENSOR_FLOAT32); +CHECK_TEST_ENUM(OperandType, TENSOR_INT32); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_ASYMM); +CHECK_TEST_ENUM(OperandType, BOOL); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT16_SYMM); +CHECK_TEST_ENUM(OperandType, TENSOR_FLOAT16); +CHECK_TEST_ENUM(OperandType, TENSOR_BOOL8); +CHECK_TEST_ENUM(OperandType, FLOAT16); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_SYMM_PER_CHANNEL); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT16_ASYMM); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_SYMM); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_ASYMM_SIGNED); + +CHECK_TEST_ENUM(OperationType, ADD); +CHECK_TEST_ENUM(OperationType, AVERAGE_POOL_2D); +CHECK_TEST_ENUM(OperationType, CONCATENATION); +CHECK_TEST_ENUM(OperationType, CONV_2D); +CHECK_TEST_ENUM(OperationType, DEPTHWISE_CONV_2D); +CHECK_TEST_ENUM(OperationType, DEPTH_TO_SPACE); +CHECK_TEST_ENUM(OperationType, DEQUANTIZE); +CHECK_TEST_ENUM(OperationType, EMBEDDING_LOOKUP); +CHECK_TEST_ENUM(OperationType, FLOOR); +CHECK_TEST_ENUM(OperationType, FULLY_CONNECTED); +CHECK_TEST_ENUM(OperationType, HASHTABLE_LOOKUP); +CHECK_TEST_ENUM(OperationType, L2_NORMALIZATION); +CHECK_TEST_ENUM(OperationType, L2_POOL_2D); +CHECK_TEST_ENUM(OperationType, LOCAL_RESPONSE_NORMALIZATION); +CHECK_TEST_ENUM(OperationType, LOGISTIC); +CHECK_TEST_ENUM(OperationType, LSH_PROJECTION); +CHECK_TEST_ENUM(OperationType, LSTM); +CHECK_TEST_ENUM(OperationType, MAX_POOL_2D); +CHECK_TEST_ENUM(OperationType, MUL); +CHECK_TEST_ENUM(OperationType, RELU); +CHECK_TEST_ENUM(OperationType, RELU1); +CHECK_TEST_ENUM(OperationType, RELU6); +CHECK_TEST_ENUM(OperationType, RESHAPE); +CHECK_TEST_ENUM(OperationType, RESIZE_BILINEAR); +CHECK_TEST_ENUM(OperationType, RNN); +CHECK_TEST_ENUM(OperationType, SOFTMAX); +CHECK_TEST_ENUM(OperationType, SPACE_TO_DEPTH); +CHECK_TEST_ENUM(OperationType, SVDF); +CHECK_TEST_ENUM(OperationType, TANH); +CHECK_TEST_ENUM(OperationType, BATCH_TO_SPACE_ND); +CHECK_TEST_ENUM(OperationType, DIV); +CHECK_TEST_ENUM(OperationType, MEAN); +CHECK_TEST_ENUM(OperationType, PAD); +CHECK_TEST_ENUM(OperationType, SPACE_TO_BATCH_ND); +CHECK_TEST_ENUM(OperationType, SQUEEZE); +CHECK_TEST_ENUM(OperationType, STRIDED_SLICE); +CHECK_TEST_ENUM(OperationType, SUB); +CHECK_TEST_ENUM(OperationType, TRANSPOSE); +CHECK_TEST_ENUM(OperationType, ABS); +CHECK_TEST_ENUM(OperationType, ARGMAX); +CHECK_TEST_ENUM(OperationType, ARGMIN); +CHECK_TEST_ENUM(OperationType, AXIS_ALIGNED_BBOX_TRANSFORM); +CHECK_TEST_ENUM(OperationType, BIDIRECTIONAL_SEQUENCE_LSTM); +CHECK_TEST_ENUM(OperationType, BIDIRECTIONAL_SEQUENCE_RNN); +CHECK_TEST_ENUM(OperationType, BOX_WITH_NMS_LIMIT); +CHECK_TEST_ENUM(OperationType, CAST); +CHECK_TEST_ENUM(OperationType, CHANNEL_SHUFFLE); +CHECK_TEST_ENUM(OperationType, DETECTION_POSTPROCESSING); +CHECK_TEST_ENUM(OperationType, EQUAL); +CHECK_TEST_ENUM(OperationType, EXP); +CHECK_TEST_ENUM(OperationType, EXPAND_DIMS); +CHECK_TEST_ENUM(OperationType, GATHER); +CHECK_TEST_ENUM(OperationType, GENERATE_PROPOSALS); +CHECK_TEST_ENUM(OperationType, GREATER); +CHECK_TEST_ENUM(OperationType, GREATER_EQUAL); +CHECK_TEST_ENUM(OperationType, GROUPED_CONV_2D); +CHECK_TEST_ENUM(OperationType, HEATMAP_MAX_KEYPOINT); +CHECK_TEST_ENUM(OperationType, INSTANCE_NORMALIZATION); +CHECK_TEST_ENUM(OperationType, LESS); +CHECK_TEST_ENUM(OperationType, LESS_EQUAL); +CHECK_TEST_ENUM(OperationType, LOG); +CHECK_TEST_ENUM(OperationType, LOGICAL_AND); +CHECK_TEST_ENUM(OperationType, LOGICAL_NOT); +CHECK_TEST_ENUM(OperationType, LOGICAL_OR); +CHECK_TEST_ENUM(OperationType, LOG_SOFTMAX); +CHECK_TEST_ENUM(OperationType, MAXIMUM); +CHECK_TEST_ENUM(OperationType, MINIMUM); +CHECK_TEST_ENUM(OperationType, NEG); +CHECK_TEST_ENUM(OperationType, NOT_EQUAL); +CHECK_TEST_ENUM(OperationType, PAD_V2); +CHECK_TEST_ENUM(OperationType, POW); +CHECK_TEST_ENUM(OperationType, PRELU); +CHECK_TEST_ENUM(OperationType, QUANTIZE); +CHECK_TEST_ENUM(OperationType, QUANTIZED_16BIT_LSTM); +CHECK_TEST_ENUM(OperationType, RANDOM_MULTINOMIAL); +CHECK_TEST_ENUM(OperationType, REDUCE_ALL); +CHECK_TEST_ENUM(OperationType, REDUCE_ANY); +CHECK_TEST_ENUM(OperationType, REDUCE_MAX); +CHECK_TEST_ENUM(OperationType, REDUCE_MIN); +CHECK_TEST_ENUM(OperationType, REDUCE_PROD); +CHECK_TEST_ENUM(OperationType, REDUCE_SUM); +CHECK_TEST_ENUM(OperationType, ROI_ALIGN); +CHECK_TEST_ENUM(OperationType, ROI_POOLING); +CHECK_TEST_ENUM(OperationType, RSQRT); +CHECK_TEST_ENUM(OperationType, SELECT); +CHECK_TEST_ENUM(OperationType, SIN); +CHECK_TEST_ENUM(OperationType, SLICE); +CHECK_TEST_ENUM(OperationType, SPLIT); +CHECK_TEST_ENUM(OperationType, SQRT); +CHECK_TEST_ENUM(OperationType, TILE); +CHECK_TEST_ENUM(OperationType, TOPK_V2); +CHECK_TEST_ENUM(OperationType, TRANSPOSE_CONV_2D); +CHECK_TEST_ENUM(OperationType, UNIDIRECTIONAL_SEQUENCE_LSTM); +CHECK_TEST_ENUM(OperationType, UNIDIRECTIONAL_SEQUENCE_RNN); +CHECK_TEST_ENUM(OperationType, RESIZE_NEAREST_NEIGHBOR); + +#undef CHECK_TEST_ENUM + +} // namespace aidl::android::hardware::neuralnetworks diff --git a/neuralnetworks/aidl/vts/functional/TestMain.cpp b/neuralnetworks/aidl/vts/functional/TestMain.cpp new file mode 100644 index 0000000000..1d58608fa3 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/TestMain.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 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. + */ + +#include <android/binder_process.h> +#include <gtest/gtest.h> +#include "LogTestCaseToLogcat.h" + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + testing::UnitTest::GetInstance()->listeners().Append( + new aidl::android::hardware::neuralnetworks::LogTestCaseToLogcat()); + ABinderProcess_startThreadPool(); + return RUN_ALL_TESTS(); +} diff --git a/neuralnetworks/aidl/vts/functional/Utils.cpp b/neuralnetworks/aidl/vts/functional/Utils.cpp new file mode 100644 index 0000000000..3c7f5f797d --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/Utils.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (C) 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. + */ + +#include "Utils.h" + +#include <aidl/android/hardware/neuralnetworks/IPreparedModelParcel.h> +#include <aidl/android/hardware/neuralnetworks/Operand.h> +#include <aidl/android/hardware/neuralnetworks/OperandType.h> +#include <android-base/logging.h> +#include <android/binder_status.h> +#include <android/hardware_buffer.h> + +#include <iostream> +#include <limits> +#include <numeric> + +#include <MemoryUtils.h> +#include <nnapi/SharedMemory.h> +#include <nnapi/hal/aidl/Conversions.h> +#include <nnapi/hal/aidl/Utils.h> + +namespace aidl::android::hardware::neuralnetworks { + +using test_helper::TestBuffer; +using test_helper::TestModel; + +uint32_t sizeOfData(OperandType type) { + switch (type) { + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::TENSOR_FLOAT32: + case OperandType::TENSOR_INT32: + return 4; + case OperandType::TENSOR_QUANT16_SYMM: + case OperandType::TENSOR_FLOAT16: + case OperandType::FLOAT16: + case OperandType::TENSOR_QUANT16_ASYMM: + return 2; + case OperandType::TENSOR_QUANT8_ASYMM: + case OperandType::BOOL: + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + case OperandType::TENSOR_QUANT8_SYMM: + case OperandType::TENSOR_QUANT8_ASYMM_SIGNED: + return 1; + case OperandType::SUBGRAPH: + return 0; + default: + CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type); + return 0; + } +} + +static bool isTensor(OperandType type) { + switch (type) { + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::FLOAT16: + case OperandType::BOOL: + case OperandType::SUBGRAPH: + return false; + case OperandType::TENSOR_FLOAT32: + case OperandType::TENSOR_INT32: + case OperandType::TENSOR_QUANT16_SYMM: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_QUANT16_ASYMM: + case OperandType::TENSOR_QUANT8_ASYMM: + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + case OperandType::TENSOR_QUANT8_SYMM: + case OperandType::TENSOR_QUANT8_ASYMM_SIGNED: + return true; + default: + CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type); + return false; + } +} + +uint32_t sizeOfData(const Operand& operand) { + const uint32_t dataSize = sizeOfData(operand.type); + if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0; + return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize, + std::multiplies<>{}); +} + +std::unique_ptr<TestAshmem> TestAshmem::create(uint32_t size) { + auto ashmem = std::make_unique<TestAshmem>(size); + return ashmem->mIsValid ? std::move(ashmem) : nullptr; +} + +void TestAshmem::initialize(uint32_t size) { + mIsValid = false; + ASSERT_GT(size, 0); + const auto sharedMemory = nn::createSharedMemory(size).value(); + mMappedMemory = nn::map(sharedMemory).value(); + mPtr = static_cast<uint8_t*>(std::get<void*>(mMappedMemory.pointer)); + CHECK_NE(mPtr, nullptr); + mAidlMemory = utils::convert(sharedMemory).value(); + mIsValid = true; +} + +std::unique_ptr<TestBlobAHWB> TestBlobAHWB::create(uint32_t size) { + auto ahwb = std::make_unique<TestBlobAHWB>(size); + return ahwb->mIsValid ? std::move(ahwb) : nullptr; +} + +void TestBlobAHWB::initialize(uint32_t size) { + mIsValid = false; + ASSERT_GT(size, 0); + const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN; + const AHardwareBuffer_Desc desc = { + .width = size, + .height = 1, + .layers = 1, + .format = AHARDWAREBUFFER_FORMAT_BLOB, + .usage = usage, + .stride = size, + }; + + ASSERT_EQ(AHardwareBuffer_allocate(&desc, &mAhwb), 0); + ASSERT_NE(mAhwb, nullptr); + + const auto sharedMemory = + nn::createSharedMemoryFromAHWB(mAhwb, /*takeOwnership=*/false).value(); + mMapping = nn::map(sharedMemory).value(); + mPtr = static_cast<uint8_t*>(std::get<void*>(mMapping.pointer)); + CHECK_NE(mPtr, nullptr); + mAidlMemory = utils::convert(sharedMemory).value(); + + mIsValid = true; +} + +TestBlobAHWB::~TestBlobAHWB() { + if (mAhwb) { + AHardwareBuffer_unlock(mAhwb, nullptr); + AHardwareBuffer_release(mAhwb); + } +} + +std::string gtestCompliantName(std::string name) { + // gtest test names must only contain alphanumeric characters + std::replace_if( + name.begin(), name.end(), [](char c) { return !std::isalnum(c); }, '_'); + return name; +} + +::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus) { + return os << toString(errorStatus); +} + +Request ExecutionContext::createRequest(const TestModel& testModel, MemoryType memoryType) { + CHECK(memoryType == MemoryType::ASHMEM || memoryType == MemoryType::BLOB_AHWB); + + // Model inputs. + std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size()); + size_t inputSize = 0; + for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) { + const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]]; + if (op.data.size() == 0) { + // Omitted input. + inputs[i] = {.hasNoValue = true}; + } else { + DataLocation loc = {.poolIndex = kInputPoolIndex, + .offset = static_cast<int64_t>(inputSize), + .length = static_cast<int64_t>(op.data.size())}; + inputSize += op.data.alignedSize(); + inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}}; + } + } + + // Model outputs. + std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size()); + size_t outputSize = 0; + for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) { + const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]]; + + // In the case of zero-sized output, we should at least provide a one-byte buffer. + // This is because zero-sized tensors are only supported internally to the driver, or + // reported in output shapes. It is illegal for the client to pre-specify a zero-sized + // tensor as model output. Otherwise, we will have two semantic conflicts: + // - "Zero dimension" conflicts with "unspecified dimension". + // - "Omitted operand buffer" conflicts with "zero-sized operand buffer". + size_t bufferSize = std::max<size_t>(op.data.size(), 1); + + DataLocation loc = {.poolIndex = kOutputPoolIndex, + .offset = static_cast<int64_t>(outputSize), + .length = static_cast<int64_t>(bufferSize)}; + outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize(); + outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}}; + } + + // Allocate memory pools. + if (memoryType == MemoryType::ASHMEM) { + mInputMemory = TestAshmem::create(inputSize); + mOutputMemory = TestAshmem::create(outputSize); + } else { + mInputMemory = TestBlobAHWB::create(inputSize); + mOutputMemory = TestBlobAHWB::create(outputSize); + } + CHECK_NE(mInputMemory, nullptr); + CHECK_NE(mOutputMemory, nullptr); + + auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory()); + CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message; + auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory()); + CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message; + + std::vector<RequestMemoryPool> pools; + pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>( + std::move(copiedInputMemory).value())); + pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>( + std::move(copiedOutputMemory).value())); + + // Copy input data to the memory pool. + uint8_t* inputPtr = mInputMemory->getPointer(); + for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) { + const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]]; + if (op.data.size() > 0) { + const uint8_t* begin = op.data.get<uint8_t>(); + const uint8_t* end = begin + op.data.size(); + std::copy(begin, end, inputPtr + inputs[i].location.offset); + } + } + + return {.inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)}; +} + +std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const Request& request) const { + // Copy out output results. + uint8_t* outputPtr = mOutputMemory->getPointer(); + std::vector<TestBuffer> outputBuffers; + for (const auto& output : request.outputs) { + outputBuffers.emplace_back(output.location.length, outputPtr + output.location.offset); + } + return outputBuffers; +} + +} // namespace aidl::android::hardware::neuralnetworks diff --git a/neuralnetworks/aidl/vts/functional/Utils.h b/neuralnetworks/aidl/vts/functional/Utils.h new file mode 100644 index 0000000000..266301ca97 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/Utils.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 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. + */ + +#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_UTILS_H +#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_UTILS_H + +#include <android-base/logging.h> +#include <android/hardware_buffer.h> +#include <gtest/gtest.h> + +#include <algorithm> +#include <iosfwd> +#include <string> +#include <utility> +#include <vector> + +#include <aidl/android/hardware/neuralnetworks/IDevice.h> +#include <aidl/android/hardware/neuralnetworks/Memory.h> +#include <aidl/android/hardware/neuralnetworks/Operand.h> +#include <aidl/android/hardware/neuralnetworks/OperandType.h> +#include <aidl/android/hardware/neuralnetworks/Priority.h> +#include <aidl/android/hardware/neuralnetworks/Request.h> + +#include <TestHarness.h> +#include <nnapi/SharedMemory.h> + +namespace aidl::android::hardware::neuralnetworks { + +namespace nn = ::android::nn; + +inline constexpr Priority kDefaultPriority = Priority::MEDIUM; + +inline constexpr Timing kNoTiming = {.timeOnDevice = -1, .timeInDriver = -1}; +inline constexpr int64_t kNoDeadline = -1; +inline constexpr int64_t kOmittedTimeoutDuration = -1; +inline constexpr int64_t kNoDuration = -1; +inline const std::vector<uint8_t> kEmptyCacheToken(IDevice::BYTE_SIZE_OF_CACHE_TOKEN); + +// Returns the amount of space needed to store a value of the specified type. +// +// Aborts if the specified type is an extension type or OEM type. +uint32_t sizeOfData(OperandType type); + +// Returns the amount of space needed to store a value of the dimensions and +// type of this operand. For a non-extension, non-OEM tensor with unspecified +// rank or at least one unspecified dimension, returns zero. +// +// Aborts if the specified type is an extension type or OEM type. +uint32_t sizeOfData(const Operand& operand); + +// Convenience class to manage the lifetime of memory resources. +class TestMemoryBase { + DISALLOW_COPY_AND_ASSIGN(TestMemoryBase); + + public: + TestMemoryBase() = default; + virtual ~TestMemoryBase() = default; + uint8_t* getPointer() const { return mPtr; } + const Memory* getAidlMemory() const { return &mAidlMemory; } + + protected: + uint8_t* mPtr = nullptr; + Memory mAidlMemory; + bool mIsValid = false; +}; + +class TestAshmem : public TestMemoryBase { + public: + static std::unique_ptr<TestAshmem> create(uint32_t size); + + // Prefer TestAshmem::create. + // The constructor calls initialize, which constructs the memory resources. This is a workaround + // that gtest macros cannot be used directly in a constructor. + TestAshmem(uint32_t size) { initialize(size); } + + private: + void initialize(uint32_t size); + nn::Mapping mMappedMemory; +}; + +class TestBlobAHWB : public TestMemoryBase { + public: + static std::unique_ptr<TestBlobAHWB> create(uint32_t size); + + // Prefer TestBlobAHWB::create. + // The constructor calls initialize, which constructs the memory resources. This is a + // workaround that gtest macros cannot be used directly in a constructor. + TestBlobAHWB(uint32_t size) { initialize(size); } + ~TestBlobAHWB(); + + private: + void initialize(uint32_t size); + AHardwareBuffer* mAhwb = nullptr; + nn::Mapping mMapping; +}; + +enum class MemoryType { ASHMEM, BLOB_AHWB, DEVICE }; + +// Manages the lifetime of memory resources used in an execution. +class ExecutionContext { + DISALLOW_COPY_AND_ASSIGN(ExecutionContext); + + public: + static constexpr uint32_t kInputPoolIndex = 0; + static constexpr uint32_t kOutputPoolIndex = 1; + + ExecutionContext() = default; + + // Create HIDL Request from the TestModel struct. + Request createRequest(const test_helper::TestModel& testModel, + MemoryType memoryType = MemoryType::ASHMEM); + + // After execution, copy out output results from the output memory pool. + std::vector<test_helper::TestBuffer> getOutputBuffers(const Request& request) const; + + private: + std::unique_ptr<TestMemoryBase> mInputMemory, mOutputMemory; +}; + +template <typename Type> +using Named = std::pair<std::string, Type>; + +template <typename Type> +const std::string& getName(const Named<Type>& namedData) { + return namedData.first; +} + +template <typename Type> +const Type& getData(const Named<Type>& namedData) { + return namedData.second; +} + +std::string gtestCompliantName(std::string name); + +// pretty-print values for error messages +::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus); + +} // namespace aidl::android::hardware::neuralnetworks + +#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_UTILS_H diff --git a/neuralnetworks/aidl/vts/functional/ValidateModel.cpp b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp new file mode 100644 index 0000000000..b84d981abd --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp @@ -0,0 +1,1338 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "neuralnetworks_aidl_hal_test" + +#include <aidl/android/hardware/common/NativeHandle.h> +#include <android/binder_auto_utils.h> +#include <android/binder_enums.h> +#include <android/binder_interface_utils.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/hal/aidl/Conversions.h> +#include <nnapi/hal/aidl/Utils.h> + +#include <optional> +#include <type_traits> +#include <utility> + +#include "Callbacks.h" +#include "GeneratedTestHarness.h" +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +using common::NativeHandle; +using implementation::PreparedModelCallback; + +using PrepareModelMutation = std::function<void(Model*, ExecutionPreference*, Priority*)>; + +///////////////////////// UTILITY FUNCTIONS ///////////////////////// + +static void validateGetSupportedOperations(const std::shared_ptr<IDevice>& device, + const std::string& message, const Model& model) { + SCOPED_TRACE(message + " [getSupportedOperations]"); + + std::vector<bool> supported; + const auto retStatus = device->getSupportedOperations(model, &supported); + + ASSERT_FALSE(retStatus.isOk()); + ASSERT_EQ(retStatus.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast<ErrorStatus>(retStatus.getServiceSpecificError()), + ErrorStatus::INVALID_ARGUMENT); +} + +static void validatePrepareModel(const std::shared_ptr<IDevice>& device, const std::string& message, + const Model& model, ExecutionPreference preference, + Priority priority) { + SCOPED_TRACE(message + " [prepareModel]"); + + std::shared_ptr<PreparedModelCallback> preparedModelCallback = + ndk::SharedRefBase::make<PreparedModelCallback>(); + const auto prepareLaunchStatus = + device->prepareModel(model, preference, priority, kNoDeadline, {}, {}, kEmptyCacheToken, + preparedModelCallback); + ASSERT_FALSE(prepareLaunchStatus.isOk()); + ASSERT_EQ(prepareLaunchStatus.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast<ErrorStatus>(prepareLaunchStatus.getServiceSpecificError()), + ErrorStatus::INVALID_ARGUMENT); + + preparedModelCallback->wait(); + ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus(); + ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, prepareReturnStatus); + std::shared_ptr<IPreparedModel> preparedModel = preparedModelCallback->getPreparedModel(); + ASSERT_EQ(nullptr, preparedModel.get()); +} + +static bool validExecutionPreference(ExecutionPreference preference) { + return preference == ExecutionPreference::LOW_POWER || + preference == ExecutionPreference::FAST_SINGLE_ANSWER || + preference == ExecutionPreference::SUSTAINED_SPEED; +} + +static bool validExecutionPriority(Priority priority) { + return priority == Priority::LOW || priority == Priority::MEDIUM || priority == Priority::HIGH; +} + +// Primary validation function. This function will take a valid model, apply a +// mutation to invalidate the model, the execution preference, or the priority, +// then pass these to supportedOperations and/or prepareModel if that method is +// called with an invalid argument. +static void validate(const std::shared_ptr<IDevice>& device, const std::string& message, + const Model& originalModel, const PrepareModelMutation& mutate) { + Model model = utils::clone(originalModel).value(); + ExecutionPreference preference = ExecutionPreference::FAST_SINGLE_ANSWER; + Priority priority = kDefaultPriority; + mutate(&model, &preference, &priority); + + if (validExecutionPreference(preference) && validExecutionPriority(priority)) { + validateGetSupportedOperations(device, message, model); + } + + validatePrepareModel(device, message, model, preference, priority); +} + +static uint32_t addOperand(Model* model) { + model->main.operands.push_back({ + .type = OperandType::INT32, + .dimensions = {}, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::SUBGRAPH_INPUT, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }); + return model->main.operands.size() - 1; +} + +static uint32_t addOperand(Model* model, OperandLifeTime lifetime) { + uint32_t index = addOperand(model); + model->main.operands[index].lifetime = lifetime; + return index; +} + +// If we introduce a CONSTANT_COPY for an operand of size operandSize, +// how much will this increase the size of the model? This assumes +// that we can (re)use all of model.operandValues for the operand +// value. +static size_t constantCopyExtraSize(const Model& model, size_t operandSize) { + const size_t operandValuesSize = model.operandValues.size(); + return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0; +} + +// Highly specialized utility routine for converting an operand to +// CONSTANT_COPY lifetime. +// +// Expects that: +// - operand has a known size +// - operand->lifetime has already been set to CONSTANT_COPY +// - operand->location has been zeroed out +// +// Does the following: +// - initializes operand->location to point to the beginning of model->operandValues +// - resizes model->operandValues (if necessary) to be large enough for the operand +// value, padding it with zeroes on the end +// +// Potential problem: +// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the +// operand with unspecified (but deterministic) data. This means that the model may be invalidated +// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the +// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid +// value). For now, this should be fine because it just means we're not testing what we think we're +// testing in certain cases; but we can handwave this and assume we're probabilistically likely to +// exercise the validation code over the span of the entire test set and operand space. +// +// Aborts if the specified operand type is an extension type or OEM type. +static void becomeConstantCopy(Model* model, Operand* operand) { + // sizeOfData will abort if the specified type is an extension type or OEM type. + const size_t sizeOfOperand = sizeOfData(*operand); + EXPECT_NE(sizeOfOperand, size_t(0)); + operand->location.poolIndex = 0; + operand->location.offset = 0; + operand->location.length = sizeOfOperand; + if (model->operandValues.size() < sizeOfOperand) { + model->operandValues.resize(sizeOfOperand); + } +} + +// The sizeForBinder() functions estimate the size of the +// representation of a value when sent to binder. It's probably a bit +// of an under-estimate, because we don't know the size of the +// metadata in the binder format (e.g., representation of the size of +// a vector); but at least it adds up "big" things like vector +// contents. However, it doesn't treat inter-field or end-of-struct +// padding in a methodical way -- there's no attempt to be consistent +// in whether or not padding in the native (C++) representation +// contributes to the estimated size for the binder representation; +// and there's no attempt to understand what padding (if any) is +// needed in the binder representation. +// +// This assumes that non-metadata uses a fixed length encoding (e.g., +// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than +// using an encoding whose length is related to the magnitude of the +// encoded value). + +template <typename Type> +static size_t sizeForBinder(const Type& val) { + static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>, + "expected a trivially copyable type"); + return sizeof(val); +} + +template <typename Type> +static size_t sizeForBinder(const std::vector<Type>& vec) { + return std::accumulate(vec.begin(), vec.end(), 0, + [](size_t acc, const Type& x) { return acc + sizeForBinder(x); }); +} + +template <> +size_t sizeForBinder(const SymmPerChannelQuantParams& symmPerChannelQuantParams) { + size_t size = 0; + + size += sizeForBinder(symmPerChannelQuantParams.scales); + size += sizeForBinder(symmPerChannelQuantParams.channelDim); + + return size; +} + +template <> +size_t sizeForBinder(const std::optional<OperandExtraParams>& optionalExtraParams) { + if (!optionalExtraParams.has_value()) { + return 0; + } + const auto& extraParams = optionalExtraParams.value(); + using Tag = OperandExtraParams::Tag; + switch (extraParams.getTag()) { + case Tag::channelQuant: + return sizeForBinder(extraParams.get<Tag::channelQuant>()); + case Tag::extension: + return sizeForBinder(extraParams.get<Tag::extension>()); + } + LOG(FATAL) << "Unrecognized extraParams tag: " << static_cast<int>(extraParams.getTag()); + return 0; +} + +template <> +size_t sizeForBinder(const Operand& operand) { + size_t size = 0; + + size += sizeForBinder(operand.type); + size += sizeForBinder(operand.dimensions); + size += sizeForBinder(operand.scale); + size += sizeForBinder(operand.zeroPoint); + size += sizeForBinder(operand.lifetime); + size += sizeForBinder(operand.location); + size += sizeForBinder(operand.extraParams); + + return size; +} + +template <> +size_t sizeForBinder(const Operation& operation) { + size_t size = 0; + + size += sizeForBinder(operation.type); + size += sizeForBinder(operation.inputs); + size += sizeForBinder(operation.outputs); + + return size; +} + +template <> +size_t sizeForBinder(const std::string& name) { + return name.size(); +} + +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); + + return size; +} + +template <> +size_t sizeForBinder(const Subgraph& subgraph) { + size_t size = 0; + + size += sizeForBinder(subgraph.operands); + size += sizeForBinder(subgraph.operations); + size += sizeForBinder(subgraph.inputIndexes); + size += sizeForBinder(subgraph.outputIndexes); + + return size; +} + +template <> +size_t sizeForBinder(const ExtensionNameAndPrefix& extensionNameToPrefix) { + size_t size = 0; + + size += sizeForBinder(extensionNameToPrefix.name); + size += sizeForBinder(extensionNameToPrefix.prefix); + + return size; +} + +template <> +size_t sizeForBinder(const Model& model) { + size_t size = 0; + + size += sizeForBinder(model.main); + size += sizeForBinder(model.referenced); + size += sizeForBinder(model.operandValues); + size += sizeForBinder(model.pools); + size += sizeForBinder(model.relaxComputationFloat32toFloat16); + size += sizeForBinder(model.extensionNameToPrefix); + + return size; +} + +// https://developer.android.com/reference/android/os/TransactionTooLargeException.html +// +// "The Binder transaction buffer has a limited fixed size, +// currently 1Mb, which is shared by all transactions in progress +// for the process." +// +// Will our representation fit under this limit? There are two complications: +// - Our representation size is just approximate (see sizeForBinder()). +// - This object may not be the only occupant of the Binder transaction buffer. +// So we'll be very conservative: We want the representation size to be no +// larger than half the transaction buffer size. +// +// If our representation grows large enough that it still fits within +// the transaction buffer but combined with other transactions may +// exceed the buffer size, then we may see intermittent HAL transport +// errors. +static bool exceedsBinderSizeLimit(size_t representationSize) { + // Instead of using this fixed buffer size, we might instead be able to use + // ProcessState::self()->getMmapSize(). However, this has a potential + // problem: The binder/mmap size of the current process does not necessarily + // indicate the binder/mmap size of the service (i.e., the other process). + // The only way it would be a good indication is if both the current process + // and the service use the default size. + static const size_t kHalfBufferSize = 1024 * 1024 / 2; + + return representationSize > kHalfBufferSize; +} + +///////////////////////// VALIDATE EXECUTION ORDER //////////////////////////// + +static void mutateExecutionOrderTest(const std::shared_ptr<IDevice>& device, const Model& model, + const std::vector<uint32_t>& numberOfConsumers) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + const Operation& operationObj = model.main.operations[operation]; + for (uint32_t input : operationObj.inputs) { + if (model.main.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE || + model.main.operands[input].lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) { + // This operation reads an operand written by some + // other operation. Move this operation to the + // beginning of the sequence, ensuring that it reads + // the operand before that operand is written, thereby + // violating execution order rules. + const std::string message = "mutateExecutionOrderTest: operation " + + std::to_string(operation) + " is a reader"; + validate(device, message, model, + [operation](Model* model, ExecutionPreference*, Priority*) { + auto& operations = model->main.operations; + std::rotate(operations.begin(), operations.begin() + operation, + operations.begin() + operation + 1); + }); + break; // only need to do this once per operation + } + } + for (uint32_t output : operationObj.outputs) { + if (numberOfConsumers[output] > 0) { + // This operation writes an operand read by some other + // operation. Move this operation to the end of the + // sequence, ensuring that it writes the operand after + // that operand is read, thereby violating execution + // order rules. + const std::string message = "mutateExecutionOrderTest: operation " + + std::to_string(operation) + " is a writer"; + validate(device, message, model, + [operation](Model* model, ExecutionPreference*, Priority*) { + auto& operations = model->main.operations; + std::rotate(operations.begin() + operation, + operations.begin() + operation + 1, operations.end()); + }); + break; // only need to do this once per operation + } + } + } +} + +///////////////////////// VALIDATE MODEL OPERAND TYPE ///////////////////////// + +static const int32_t invalidOperandTypes[] = { + -1, + static_cast<int32_t>(*(ndk::enum_range<OperandType>().end() - 1)) + 1, +}; + +static void mutateOperandTypeTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.main.operands.size(); ++operand) { + for (int32_t invalidOperandType : invalidOperandTypes) { + const std::string message = "mutateOperandTypeTest: operand " + + std::to_string(operand) + " set to value " + + std::to_string(invalidOperandType); + validate(device, message, model, + [operand, invalidOperandType](Model* model, ExecutionPreference*, Priority*) { + model->main.operands[operand].type = + static_cast<OperandType>(invalidOperandType); + }); + } + } +} + +///////////////////////// VALIDATE OPERAND RANK ///////////////////////// + +static uint32_t getInvalidRank(OperandType type) { + switch (type) { + case OperandType::FLOAT16: + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::BOOL: + return 1; + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_FLOAT32: + case OperandType::TENSOR_INT32: + case OperandType::TENSOR_QUANT8_ASYMM: + case OperandType::TENSOR_QUANT8_SYMM: + case OperandType::TENSOR_QUANT16_ASYMM: + case OperandType::TENSOR_QUANT16_SYMM: + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + return 0; + default: + return 0; + } +} + +static void mutateOperandRankTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.main.operands.size(); ++operand) { + const uint32_t invalidRank = getInvalidRank(model.main.operands[operand].type); + if (invalidRank == 0) { + continue; + } + const std::string message = "mutateOperandRankTest: operand " + std::to_string(operand) + + " has rank of " + std::to_string(invalidRank); + validate(device, message, model, + [operand, invalidRank](Model* model, ExecutionPreference*, Priority*) { + model->main.operands[operand].dimensions = + std::vector<int32_t>(invalidRank, 0); + }); + } +} + +///////////////////////// VALIDATE OPERAND SCALE ///////////////////////// + +static float getInvalidScale(OperandType type) { + switch (type) { + case OperandType::FLOAT16: + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::BOOL: + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_FLOAT32: + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + case OperandType::SUBGRAPH: + return 1.0f; + case OperandType::TENSOR_INT32: + return -1.0f; + case OperandType::TENSOR_QUANT8_SYMM: + case OperandType::TENSOR_QUANT8_ASYMM: + case OperandType::TENSOR_QUANT16_ASYMM: + case OperandType::TENSOR_QUANT16_SYMM: + return 0.0f; + default: + return 0.0f; + } +} + +static void mutateOperandScaleTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.main.operands.size(); ++operand) { + const float invalidScale = getInvalidScale(model.main.operands[operand].type); + const std::string message = "mutateOperandScaleTest: operand " + std::to_string(operand) + + " has scale of " + std::to_string(invalidScale); + validate(device, message, model, + [operand, invalidScale](Model* model, ExecutionPreference*, Priority*) { + model->main.operands[operand].scale = invalidScale; + }); + } +} + +///////////////////////// VALIDATE OPERAND ZERO POINT ///////////////////////// + +static std::vector<int32_t> getInvalidZeroPoints(OperandType type) { + switch (type) { + case OperandType::FLOAT16: + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::BOOL: + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_FLOAT32: + case OperandType::TENSOR_INT32: + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + case OperandType::SUBGRAPH: + return {1}; + case OperandType::TENSOR_QUANT8_ASYMM: + return {-1, 256}; + case OperandType::TENSOR_QUANT8_SYMM: + return {-129, -1, 1, 128}; + case OperandType::TENSOR_QUANT16_ASYMM: + return {-1, 65536}; + case OperandType::TENSOR_QUANT16_SYMM: + return {-32769, -1, 1, 32768}; + default: + return {}; + } +} + +static void mutateOperandZeroPointTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.main.operands.size(); ++operand) { + const std::vector<int32_t> invalidZeroPoints = + getInvalidZeroPoints(model.main.operands[operand].type); + for (int32_t invalidZeroPoint : invalidZeroPoints) { + const std::string message = "mutateOperandZeroPointTest: operand " + + std::to_string(operand) + " has zero point of " + + std::to_string(invalidZeroPoint); + validate(device, message, model, + [operand, invalidZeroPoint](Model* model, ExecutionPreference*, Priority*) { + model->main.operands[operand].zeroPoint = invalidZeroPoint; + }); + } + } +} + +///////////////////////// VALIDATE OPERAND LIFETIME ///////////////////////////////////////////// + +static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize, + const Operand& operand) { + // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime + // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime + + // Ways to get an invalid lifetime: + // - change whether a lifetime means an operand should have a writer + std::vector<OperandLifeTime> ret; + switch (operand.lifetime) { + case OperandLifeTime::SUBGRAPH_OUTPUT: + case OperandLifeTime::TEMPORARY_VARIABLE: + ret = { + OperandLifeTime::SUBGRAPH_INPUT, + OperandLifeTime::CONSTANT_COPY, + }; + break; + case OperandLifeTime::CONSTANT_COPY: + case OperandLifeTime::CONSTANT_POOL: + case OperandLifeTime::SUBGRAPH_INPUT: + ret = { + OperandLifeTime::TEMPORARY_VARIABLE, + OperandLifeTime::SUBGRAPH_OUTPUT, + }; + break; + case OperandLifeTime::NO_VALUE: + // Not enough information to know whether + // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid -- + // is this operand written (then CONSTANT_COPY would be + // invalid) or not (then TEMPORARY_VARIABLE would be + // invalid)? + break; + case OperandLifeTime::SUBGRAPH: + break; + default: + ADD_FAILURE(); + break; + } + + const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown + if (!operandSize || + exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) { + // Unknown size or too-large size + ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end()); + } + + return ret; +} + +static void mutateOperandLifeTimeTest(const std::shared_ptr<IDevice>& device, const Model& model) { + const size_t modelSize = sizeForBinder(model); + for (size_t operand = 0; operand < model.main.operands.size(); ++operand) { + const std::vector<OperandLifeTime> invalidLifeTimes = + getInvalidLifeTimes(model, modelSize, model.main.operands[operand]); + for (OperandLifeTime invalidLifeTime : invalidLifeTimes) { + const std::string message = "mutateOperandLifetimeTest: operand " + + std::to_string(operand) + " has lifetime " + + toString(invalidLifeTime) + " instead of lifetime " + + toString(model.main.operands[operand].lifetime); + validate(device, message, model, + [operand, invalidLifeTime](Model* model, ExecutionPreference*, Priority*) { + static const DataLocation kZeroDataLocation = {}; + Operand& operandObj = model->main.operands[operand]; + switch (operandObj.lifetime) { + case OperandLifeTime::SUBGRAPH_INPUT: { + auto& inputs = model->main.inputIndexes; + inputs.erase(std::remove(inputs.begin(), inputs.end(), operand), + inputs.end()); + break; + } + case OperandLifeTime::SUBGRAPH_OUTPUT: { + auto& outputs = model->main.outputIndexes; + outputs.erase(std::remove(outputs.begin(), outputs.end(), operand), + outputs.end()); + break; + } + default: + break; + } + operandObj.lifetime = invalidLifeTime; + operandObj.location = kZeroDataLocation; + switch (invalidLifeTime) { + case OperandLifeTime::CONSTANT_COPY: { + becomeConstantCopy(model, &operandObj); + break; + } + case OperandLifeTime::SUBGRAPH_INPUT: + model->main.inputIndexes.push_back(operand); + break; + case OperandLifeTime::SUBGRAPH_OUTPUT: + model->main.outputIndexes.push_back(operand); + break; + default: + break; + } + }); + } + } +} + +///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT ////////////////////////////////////// + +static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize, + const Operand& operand) { + // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes): + // - change whether a lifetime means an operand is a model input, a model output, or neither + // - preserve whether or not a lifetime means an operand should have a writer + switch (operand.lifetime) { + case OperandLifeTime::CONSTANT_COPY: + case OperandLifeTime::CONSTANT_POOL: + return OperandLifeTime::SUBGRAPH_INPUT; + case OperandLifeTime::SUBGRAPH_INPUT: { + const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown + if (!operandSize || + exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) { + // Unknown size or too-large size + break; + } + return OperandLifeTime::CONSTANT_COPY; + } + case OperandLifeTime::SUBGRAPH_OUTPUT: + return OperandLifeTime::TEMPORARY_VARIABLE; + case OperandLifeTime::TEMPORARY_VARIABLE: + return OperandLifeTime::SUBGRAPH_OUTPUT; + case OperandLifeTime::NO_VALUE: + // Not enough information to know whether + // TEMPORARY_VARIABLE or CONSTANT_COPY would be an + // appropriate choice -- is this operand written (then + // TEMPORARY_VARIABLE would be appropriate) or not (then + // CONSTANT_COPY would be appropriate)? + break; + case OperandLifeTime::SUBGRAPH: + break; + default: + ADD_FAILURE(); + break; + } + + return std::nullopt; +} + +static void mutateOperandInputOutputTest(const std::shared_ptr<IDevice>& device, + const Model& model) { + const size_t modelSize = sizeForBinder(model); + for (size_t operand = 0; operand < model.main.operands.size(); ++operand) { + const std::optional<OperandLifeTime> changedLifeTime = + getInputOutputLifeTime(model, modelSize, model.main.operands[operand]); + if (changedLifeTime) { + const std::string message = "mutateOperandInputOutputTest: operand " + + std::to_string(operand) + " has lifetime " + + toString(*changedLifeTime) + " instead of lifetime " + + toString(model.main.operands[operand].lifetime); + validate(device, message, model, + [operand, changedLifeTime](Model* model, ExecutionPreference*, Priority*) { + static const DataLocation kZeroDataLocation = {}; + Operand& operandObj = model->main.operands[operand]; + operandObj.lifetime = *changedLifeTime; + operandObj.location = kZeroDataLocation; + if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) { + becomeConstantCopy(model, &operandObj); + } + }); + } + } +} + +///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS //////////////////////////////////// + +static void mutateOperandAddWriterTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + for (size_t badOutputNum = 0; + badOutputNum < model.main.operations[operation].outputs.size(); ++badOutputNum) { + const uint32_t outputOperandIndex = + model.main.operations[operation].outputs[badOutputNum]; + const std::string message = "mutateOperandAddWriterTest: operation " + + std::to_string(operation) + " writes to " + + std::to_string(outputOperandIndex); + // We'll insert a copy of the operation, all of whose + // OTHER output operands are newly-created -- i.e., + // there'll only be a duplicate write of ONE of that + // operation's output operands. + validate(device, message, model, + [operation, badOutputNum](Model* model, ExecutionPreference*, Priority*) { + Operation newOperation = model->main.operations[operation]; + for (size_t outputNum = 0; outputNum < newOperation.outputs.size(); + ++outputNum) { + if (outputNum == badOutputNum) continue; + + Operand operandValue = + model->main.operands[newOperation.outputs[outputNum]]; + if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) { + operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE; + } else { + ASSERT_EQ(operandValue.lifetime, + OperandLifeTime::TEMPORARY_VARIABLE); + } + newOperation.outputs[outputNum] = model->main.operands.size(); + model->main.operands.push_back(operandValue); + } + // Where do we insert the extra writer (a new + // operation)? It has to be later than all the + // writers of its inputs. The easiest thing to do + // is to insert it at the end of the operation + // sequence. + model->main.operations.push_back(newOperation); + }); + } + } +} + +///////////////////////// VALIDATE EXTRA ??? ///////////////////////// + +// TODO: Operand::location + +///////////////////////// VALIDATE OPERATION OPERAND TYPE ///////////////////////// + +static void mutateOperand(Operand* operand, OperandType type) { + Operand newOperand = *operand; + newOperand.type = type; + switch (type) { + case OperandType::FLOAT16: + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::BOOL: + newOperand.dimensions = {}; + newOperand.scale = 0.0f; + newOperand.zeroPoint = 0; + break; + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_FLOAT32: + newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions + : std::vector<int32_t>({1}); + newOperand.scale = 0.0f; + newOperand.zeroPoint = 0; + break; + case OperandType::TENSOR_INT32: + newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions + : std::vector<int32_t>({1}); + newOperand.zeroPoint = 0; + break; + case OperandType::TENSOR_QUANT8_ASYMM: + case OperandType::TENSOR_QUANT8_SYMM: + case OperandType::TENSOR_QUANT16_ASYMM: + case OperandType::TENSOR_QUANT16_SYMM: + newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions + : std::vector<int32_t>({1}); + newOperand.scale = operand->scale != 0.0f ? operand->scale : 1.0f; + break; + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: { + newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions + : std::vector<int32_t>({1}); + newOperand.scale = 0.0f; + newOperand.zeroPoint = 0; + + SymmPerChannelQuantParams channelQuant; + channelQuant.channelDim = 0; + channelQuant.scales = std::vector<float>( + operand->dimensions.size() > 0 ? static_cast<size_t>(operand->dimensions[0]) + : 0); + for (size_t i = 0; i < channelQuant.scales.size(); ++i) { + channelQuant.scales[i] = 1.0f; + } + newOperand.extraParams->set<OperandExtraParams::Tag::channelQuant>( + std::move(channelQuant)); + } break; + default: + break; + } + *operand = newOperand; +} + +static bool mutateOperationOperandTypeSkip(size_t operand, OperandType type, const Model& model) { + if (type == model.main.operands[operand].type) { + return true; + } + for (const Operation& operation : model.main.operations) { + // Skip mutateOperationOperandTypeTest for the following operations. + // - LSH_PROJECTION's second argument is allowed to have any type. + // - ARGMIN and ARGMAX's first argument can be any of + // TENSOR_(FLOAT16|FLOAT32|INT32|QUANT8_ASYMM). + // - CAST's argument can be any of TENSOR_(FLOAT16|FLOAT32|INT32|QUANT8_ASYMM). + // - RANDOM_MULTINOMIAL's argument can be either TENSOR_FLOAT16 or TENSOR_FLOAT32. + // - DEQUANTIZE input can be any of + // TENSOR_(QUANT8_ASYMM|QUANT8_ASYMM_SIGNED|QUANT8_SYMM|QUANT8_SYMM_PER_CHANNEL), + // output can be of either TENSOR_FLOAT16 or TENSOR_FLOAT32. + // - QUANTIZE input can be either TENSOR_FLOAT16 or TENSOR_FLOAT32 + // - CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL + // - DEPTHWISE_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL + // - GROUPED_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL + // - TRANSPOSE_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL + // - AXIS_ALIGNED_BBOX_TRANSFORM bounding boxes (arg 1) can be of + // TENSOR_QUANT8_ASYMM or TENSOR_QUANT8_ASYMM_SIGNED. + // - RANK's input can have any TENSOR_* type. + switch (operation.type) { + case OperationType::LSH_PROJECTION: { + if (operand == operation.inputs[1]) { + return true; + } + } break; + case OperationType::CAST: + case OperationType::ARGMAX: + case OperationType::ARGMIN: { + if (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32 || + type == OperandType::TENSOR_INT32 || type == OperandType::TENSOR_QUANT8_ASYMM || + type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED) { + return true; + } + } break; + case OperationType::QUANTIZE: { + if (operand == operation.inputs[0] && + (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) { + return true; + } + if (operand == operation.outputs[0] && + (type == OperandType::TENSOR_QUANT8_ASYMM || + type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)) { + return true; + } + } break; + case OperationType::RANDOM_MULTINOMIAL: { + if (operand == operation.inputs[0] && + (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) { + return true; + } + } break; + case OperationType::DEQUANTIZE: { + if (operand == operation.inputs[0] && + (type == OperandType::TENSOR_QUANT8_ASYMM || + type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED || + type == OperandType::TENSOR_QUANT8_SYMM || + type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL)) { + return true; + } + if (operand == operation.outputs[0] && + (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) { + return true; + } + } break; + case OperationType::TRANSPOSE_CONV_2D: + case OperationType::GROUPED_CONV_2D: + case OperationType::DEPTHWISE_CONV_2D: + case OperationType::CONV_2D: { + if (operand == operation.inputs[1] && + (type == OperandType::TENSOR_QUANT8_ASYMM || + type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL)) { + return true; + } + } break; + case OperationType::AXIS_ALIGNED_BBOX_TRANSFORM: { + if (operand == operation.inputs[1] && + (type == OperandType::TENSOR_QUANT8_ASYMM || + type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)) { + return true; + } + } break; + case OperationType::RANK: { + if (operand == operation.inputs[0] && + (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32 || + type == OperandType::TENSOR_INT32 || + type == OperandType::TENSOR_QUANT8_ASYMM || + type == OperandType::TENSOR_QUANT16_SYMM || + type == OperandType::TENSOR_BOOL8 || + type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL || + type == OperandType::TENSOR_QUANT16_ASYMM || + type == OperandType::TENSOR_QUANT8_SYMM || + type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)) { + return true; + } + } break; + default: + break; + } + } + return false; +} + +static void mutateOperationOperandTypeTest(const std::shared_ptr<IDevice>& device, + const Model& model) { + for (size_t operand = 0; operand < model.main.operands.size(); ++operand) { + for (OperandType invalidOperandType : ndk::enum_range<OperandType>()) { + if (mutateOperationOperandTypeSkip(operand, invalidOperandType, model)) { + continue; + } + const std::string message = "mutateOperationOperandTypeTest: operand " + + std::to_string(operand) + " set to type " + + toString(invalidOperandType); + validate(device, message, model, + [operand, invalidOperandType](Model* model, ExecutionPreference*, Priority*) { + mutateOperand(&model->main.operands[operand], invalidOperandType); + }); + } + } +} + +///////////////////////// VALIDATE MODEL OPERATION TYPE ///////////////////////// + +static const int32_t invalidOperationTypes[] = { + -1, + static_cast<int32_t>(*(ndk::enum_range<OperationType>().end() - 1)) + 1, +}; + +static void mutateOperationTypeTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + for (int32_t invalidOperationType : invalidOperationTypes) { + const std::string message = "mutateOperationTypeTest: operation " + + std::to_string(operation) + " set to value " + + std::to_string(invalidOperationType); + validate(device, message, model, + [operation, invalidOperationType](Model* model, ExecutionPreference*, + Priority*) { + model->main.operations[operation].type = + static_cast<OperationType>(invalidOperationType); + }); + } + } +} + +///////////////////////// VALIDATE MODEL OPERATION INPUT OPERAND INDEX ///////////////////////// + +static void mutateOperationInputOperandIndexTest(const std::shared_ptr<IDevice>& device, + const Model& model) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + const uint32_t invalidOperand = model.main.operands.size(); + for (size_t input = 0; input < model.main.operations[operation].inputs.size(); ++input) { + const std::string message = "mutateOperationInputOperandIndexTest: operation " + + std::to_string(operation) + " input " + + std::to_string(input); + validate(device, message, model, + [operation, input, invalidOperand](Model* model, ExecutionPreference*, + Priority*) { + model->main.operations[operation].inputs[input] = invalidOperand; + }); + } + } +} + +///////////////////////// VALIDATE MODEL OPERATION OUTPUT OPERAND INDEX ///////////////////////// + +static void mutateOperationOutputOperandIndexTest(const std::shared_ptr<IDevice>& device, + const Model& model) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + const uint32_t invalidOperand = model.main.operands.size(); + for (size_t output = 0; output < model.main.operations[operation].outputs.size(); + ++output) { + const std::string message = "mutateOperationOutputOperandIndexTest: operation " + + std::to_string(operation) + " output " + + std::to_string(output); + validate(device, message, model, + [operation, output, invalidOperand](Model* model, ExecutionPreference*, + Priority*) { + model->main.operations[operation].outputs[output] = invalidOperand; + }); + } + } +} + +///////////////////////// VALIDATE MODEL OPERANDS WRITTEN /////////////////////////////////////// + +static void mutateOperationRemoveWriteTest(const std::shared_ptr<IDevice>& device, + const Model& model, + const std::vector<uint32_t>& numberOfConsumers) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + for (size_t outputNum = 0; outputNum < model.main.operations[operation].outputs.size(); + ++outputNum) { + const uint32_t outputOperandIndex = model.main.operations[operation].outputs[outputNum]; + if (numberOfConsumers[outputOperandIndex] > 0) { + const std::string message = "mutateOperationRemoveWriteTest: operation " + + std::to_string(operation) + " writes to " + + std::to_string(outputOperandIndex); + validate(device, message, model, + [operation, outputNum](Model* model, ExecutionPreference*, Priority*) { + int32_t& outputOperandIndex = + model->main.operations[operation].outputs[outputNum]; + Operand operandValue = model->main.operands[outputOperandIndex]; + if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) { + operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE; + } else { + ASSERT_EQ(operandValue.lifetime, + OperandLifeTime::TEMPORARY_VARIABLE); + } + outputOperandIndex = model->main.operands.size(); + model->main.operands.push_back(operandValue); + }); + } + } + } +} + +///////////////////////// REMOVE OPERAND FROM EVERYTHING ///////////////////////// + +static void removeValueAndDecrementGreaterValues(std::vector<int32_t>* vec, uint32_t value) { + if (vec) { + // remove elements matching "value" + vec->erase(std::remove(vec->begin(), vec->end(), value), vec->end()); + + // decrement elements exceeding "value" + std::transform(vec->begin(), vec->end(), vec->begin(), + [value](uint32_t v) { return v > value ? v-- : v; }); + } +} + +static void removeOperand(Model* model, uint32_t index) { + model->main.operands.erase(model->main.operands.begin() + index); + for (Operation& operation : model->main.operations) { + removeValueAndDecrementGreaterValues(&operation.inputs, index); + removeValueAndDecrementGreaterValues(&operation.outputs, index); + } + removeValueAndDecrementGreaterValues(&model->main.inputIndexes, index); + removeValueAndDecrementGreaterValues(&model->main.outputIndexes, index); +} + +static bool removeOperandSkip(size_t operandIndex, const Model& model, + const std::vector<uint32_t>& numberOfConsumers) { + if (numberOfConsumers[operandIndex] == 0) { + // Removing an unused operand has no effect. + return true; + } + for (const Operation& operation : model.main.operations) { + // Skip removeOperandTest for the following operations. + // - SPLIT's outputs are not checked during prepareModel. + if (operation.type == OperationType::SPLIT) { + for (const size_t index : operation.outputs) { + if (index == operandIndex) { + return true; + } + } + } + // BIDIRECTIONAL_SEQUENCE_LSTM and BIDIRECTIONAL_SEQUENCE_RNN can have + // either one, two, three or four outputs depending on their + // mergeOutputs parameter and if state outputs are provided. + // UNIDIRECTIONAL_SEQUENCE_LSTM and UNIDIRECTIONAL_SEQUENCE_RNN can have + // either one or three outputs depending on whether state outputs are + // provided. + if (operation.type == OperationType::UNIDIRECTIONAL_SEQUENCE_LSTM || + operation.type == OperationType::UNIDIRECTIONAL_SEQUENCE_RNN || + operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_LSTM || + operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_RNN) { + for (const size_t index : operation.outputs) { + if (index == operandIndex) { + return true; + } + } + } + } + return false; +} + +static void removeOperandTest(const std::shared_ptr<IDevice>& device, const Model& model, + const std::vector<uint32_t>& numberOfConsumers) { + for (size_t operand = 0; operand < model.main.operands.size(); ++operand) { + if (removeOperandSkip(operand, model, numberOfConsumers)) { + continue; + } + const std::string message = "removeOperandTest: operand " + std::to_string(operand); + validate(device, message, model, [operand](Model* model, ExecutionPreference*, Priority*) { + removeOperand(model, operand); + }); + } +} + +///////////////////////// REMOVE OPERATION ///////////////////////// + +static void removeOperation(Model* model, uint32_t index) { + auto& operations = model->main.operations; + operations.erase(operations.begin() + index); +} + +static void removeOperationTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + const std::string message = "removeOperationTest: operation " + std::to_string(operation); + validate(device, message, model, + [operation](Model* model, ExecutionPreference*, Priority*) { + removeOperation(model, operation); + }); + } +} + +///////////////////////// REMOVE OPERATION INPUT ///////////////////////// + +static bool removeOperationInputSkip(const Operation& op, size_t input) { + // Skip removeOperationInputTest for the following operations. + // - CONCATENATION has at least 2 inputs, with the last element being INT32. + // - CONV_2D, DEPTHWISE_CONV_2D, MAX_POOL_2D, AVERAGE_POOL_2D, L2_POOL_2D, RESIZE_BILINEAR, + // SPACE_TO_DEPTH, SPACE_TO_DEPTH, SPACE_TO_BATCH_ND, BATCH_TO_SPACE_ND can have an optional + // layout parameter. + // RESIZE_BILINEAR and RESIZE_NEAREST_NEIGHBOR can have optional + // align_corners and half_pixel_centers parameters. + // - L2_NORMALIZATION, LOCAL_RESPONSE_NORMALIZATION, SOFTMAX can have an optional axis + // parameter. + switch (op.type) { + case OperationType::CONCATENATION: { + if (op.inputs.size() > 2 && input != op.inputs.size() - 1) { + return true; + } + } break; + case OperationType::DEPTHWISE_CONV_2D: { + if ((op.inputs.size() == 12 && input == 11) || (op.inputs.size() == 9 && input == 8)) { + return true; + } + } break; + case OperationType::CONV_2D: + case OperationType::AVERAGE_POOL_2D: + case OperationType::MAX_POOL_2D: + case OperationType::L2_POOL_2D: { + if ((op.inputs.size() == 11 && input == 10) || (op.inputs.size() == 8 && input == 7)) { + return true; + } + } break; + case OperationType::RESIZE_BILINEAR: { + if (op.inputs.size() >= 4 && input >= 3) { + return true; + } + } break; + case OperationType::RESIZE_NEAREST_NEIGHBOR: { + if (op.inputs.size() >= 5 && input >= 3) { + return true; + } + } break; + case OperationType::SPACE_TO_DEPTH: + case OperationType::DEPTH_TO_SPACE: + case OperationType::BATCH_TO_SPACE_ND: { + if (op.inputs.size() == 3 && input == 2) { + return true; + } + } break; + case OperationType::SPACE_TO_BATCH_ND: { + if (op.inputs.size() == 4 && input == 3) { + return true; + } + } break; + case OperationType::L2_NORMALIZATION: { + if (op.inputs.size() == 2 && input == 1) { + return true; + } + } break; + case OperationType::LOCAL_RESPONSE_NORMALIZATION: { + if (op.inputs.size() == 6 && input == 5) { + return true; + } + } break; + case OperationType::SOFTMAX: { + if (op.inputs.size() == 3 && input == 2) { + return true; + } + } break; + default: + break; + } + return false; +} + +static void removeOperationInputTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + for (size_t input = 0; input < model.main.operations[operation].inputs.size(); ++input) { + const Operation& op = model.main.operations[operation]; + if (removeOperationInputSkip(op, input)) { + continue; + } + const std::string message = "removeOperationInputTest: operation " + + std::to_string(operation) + ", input " + + std::to_string(input); + validate(device, message, model, + [operation, input](Model* model, ExecutionPreference*, Priority*) { + auto& inputs = model->main.operations[operation].inputs; + inputs.erase(inputs.begin() + input); + }); + } + } +} + +///////////////////////// REMOVE OPERATION OUTPUT ///////////////////////// + +static void removeOperationOutputTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + for (size_t output = 0; output < model.main.operations[operation].outputs.size(); + ++output) { + const std::string message = "removeOperationOutputTest: operation " + + std::to_string(operation) + ", output " + + std::to_string(output); + validate(device, message, model, + [operation, output](Model* model, ExecutionPreference*, Priority*) { + auto& outputs = model->main.operations[operation].outputs; + outputs.erase(outputs.begin() + output); + }); + } + } +} + +///////////////////////// MODEL VALIDATION ///////////////////////// + +// TODO: remove model input +// TODO: remove model output +// TODO: add unused operation + +///////////////////////// ADD OPERATION INPUT ///////////////////////// + +static bool addOperationInputSkip(const Operation& op) { + // Skip addOperationInputTest for the following operations. + // - L2_NORMALIZATION, LOCAL_RESPONSE_NORMALIZATION, SOFTMAX can have an optional INT32 axis + // parameter. + if ((op.type == OperationType::L2_NORMALIZATION && op.inputs.size() == 1) || + (op.type == OperationType::LOCAL_RESPONSE_NORMALIZATION && op.inputs.size() == 5) || + (op.type == OperationType::SOFTMAX && op.inputs.size() == 2) || + (op.type == OperationType::RESIZE_BILINEAR && op.inputs.size() < 6) || + (op.type == OperationType::RESIZE_NEAREST_NEIGHBOR && op.inputs.size() < 6)) { + return true; + } + return false; +} + +static void addOperationInputTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + if (addOperationInputSkip(model.main.operations[operation])) { + continue; + } + const std::string message = "addOperationInputTest: operation " + std::to_string(operation); + validate(device, message, model, + [operation](Model* model, ExecutionPreference*, Priority*) { + uint32_t index = addOperand(model, OperandLifeTime::SUBGRAPH_INPUT); + model->main.operations[operation].inputs.push_back(index); + model->main.inputIndexes.push_back(index); + }); + } +} + +///////////////////////// ADD OPERATION OUTPUT ///////////////////////// + +static void addOperationOutputTest(const std::shared_ptr<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.main.operations.size(); ++operation) { + const std::string message = + "addOperationOutputTest: operation " + std::to_string(operation); + validate(device, message, model, + [operation](Model* model, ExecutionPreference*, Priority*) { + uint32_t index = addOperand(model, OperandLifeTime::SUBGRAPH_OUTPUT); + model->main.operations[operation].outputs.push_back(index); + model->main.outputIndexes.push_back(index); + }); + } +} + +///////////////////////// VALIDATE EXECUTION PREFERENCE ///////////////////////// + +static const int32_t invalidExecutionPreferences[] = { + static_cast<int32_t>(ExecutionPreference::LOW_POWER) - 1, // lower bound + static_cast<int32_t>(ExecutionPreference::SUSTAINED_SPEED) + 1, // upper bound +}; + +static void mutateExecutionPreferenceTest(const std::shared_ptr<IDevice>& device, + const Model& model) { + for (int32_t invalidPreference : invalidExecutionPreferences) { + const std::string message = + "mutateExecutionPreferenceTest: preference " + std::to_string(invalidPreference); + validate(device, message, model, + [invalidPreference](Model*, ExecutionPreference* preference, Priority*) { + *preference = static_cast<ExecutionPreference>(invalidPreference); + }); + } +} + +///////////////////////// VALIDATE PRIORITY ///////////////////////// + +static const int32_t invalidPriorities[] = { + static_cast<int32_t>(Priority::LOW) - 1, // lower bound + static_cast<int32_t>(Priority::HIGH) + 1, // upper bound +}; + +static void mutateExecutionPriorityTest(const std::shared_ptr<IDevice>& device, + const Model& model) { + for (int32_t invalidPriority : invalidPriorities) { + const std::string message = + "mutatePriorityTest: priority " + std::to_string(invalidPriority); + validate(device, message, model, + [invalidPriority](Model*, ExecutionPreference*, Priority* priority) { + *priority = static_cast<Priority>(invalidPriority); + }); + } +} + +////////////////////////// ENTRY POINT ////////////////////////////// + +void validateModel(const std::shared_ptr<IDevice>& device, const Model& model) { + const auto numberOfConsumers = nn::countNumberOfConsumers( + model.main.operands.size(), nn::convert(model.main.operations).value()); + mutateExecutionOrderTest(device, model, numberOfConsumers); + mutateOperandTypeTest(device, model); + mutateOperandRankTest(device, model); + mutateOperandScaleTest(device, model); + mutateOperandZeroPointTest(device, model); + mutateOperandLifeTimeTest(device, model); + mutateOperandInputOutputTest(device, model); + mutateOperandAddWriterTest(device, model); + mutateOperationOperandTypeTest(device, model); + mutateOperationTypeTest(device, model); + mutateOperationInputOperandIndexTest(device, model); + mutateOperationOutputOperandIndexTest(device, model); + mutateOperationRemoveWriteTest(device, model, numberOfConsumers); + removeOperandTest(device, model, numberOfConsumers); + removeOperationTest(device, model); + removeOperationInputTest(device, model); + removeOperationOutputTest(device, model); + addOperationInputTest(device, model); + addOperationOutputTest(device, model); + mutateExecutionPreferenceTest(device, model); + mutateExecutionPriorityTest(device, model); +} + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional diff --git a/neuralnetworks/aidl/vts/functional/ValidateRequest.cpp b/neuralnetworks/aidl/vts/functional/ValidateRequest.cpp new file mode 100644 index 0000000000..db8f429f13 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/ValidateRequest.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "neuralnetworks_aidl_hal_test" + +#include <android/binder_auto_utils.h> + +#include <chrono> + +#include <TestHarness.h> +#include <nnapi/hal/aidl/Utils.h> + +#include "Callbacks.h" +#include "GeneratedTestHarness.h" +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +using ExecutionMutation = std::function<void(Request*)>; + +///////////////////////// UTILITY FUNCTIONS ///////////////////////// + +// Primary validation function. This function will take a valid request, apply a +// mutation to it to invalidate the request, then pass it to interface calls +// that use the request. +static void validate(const std::shared_ptr<IPreparedModel>& preparedModel, + const std::string& message, const Request& originalRequest, + const ExecutionMutation& mutate) { + Request request = utils::clone(originalRequest).value(); + mutate(&request); + + // We'd like to test both with timing requested and without timing + // requested. Rather than running each test both ways, we'll decide whether + // to request timing by hashing the message. We do not use std::hash because + // it is not guaranteed stable across executions. + char hash = 0; + for (auto c : message) { + hash ^= c; + }; + bool measure = (hash & 1); + + // synchronous + { + SCOPED_TRACE(message + " [executeSynchronously]"); + ExecutionResult executionResult; + const auto executeStatus = preparedModel->executeSynchronously( + request, measure, kNoDeadline, kOmittedTimeoutDuration, &executionResult); + ASSERT_FALSE(executeStatus.isOk()); + ASSERT_EQ(executeStatus.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast<ErrorStatus>(executeStatus.getServiceSpecificError()), + ErrorStatus::INVALID_ARGUMENT); + } + + // fenced + { + SCOPED_TRACE(message + " [executeFenced]"); + ndk::ScopedFileDescriptor syncFence; + std::shared_ptr<IFencedExecutionCallback> callback; + const auto executeStatus = preparedModel->executeFenced(request, {}, false, kNoDeadline, + kOmittedTimeoutDuration, + kNoDuration, &syncFence, &callback); + ASSERT_FALSE(executeStatus.isOk()); + ASSERT_EQ(executeStatus.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast<ErrorStatus>(executeStatus.getServiceSpecificError()), + ErrorStatus::INVALID_ARGUMENT); + } +} + +///////////////////////// REMOVE INPUT //////////////////////////////////// + +static void removeInputTest(const std::shared_ptr<IPreparedModel>& preparedModel, + const Request& request) { + for (size_t input = 0; input < request.inputs.size(); ++input) { + const std::string message = "removeInput: removed input " + std::to_string(input); + validate(preparedModel, message, request, [input](Request* request) { + request->inputs.erase(request->inputs.begin() + input); + }); + } +} + +///////////////////////// REMOVE OUTPUT //////////////////////////////////// + +static void removeOutputTest(const std::shared_ptr<IPreparedModel>& preparedModel, + const Request& request) { + for (size_t output = 0; output < request.outputs.size(); ++output) { + const std::string message = "removeOutput: removed Output " + std::to_string(output); + validate(preparedModel, message, request, [output](Request* request) { + request->outputs.erase(request->outputs.begin() + output); + }); + } +} + +///////////////////////////// ENTRY POINT ////////////////////////////////// + +void validateRequest(const std::shared_ptr<IPreparedModel>& preparedModel, const Request& request) { + removeInputTest(preparedModel, request); + removeOutputTest(preparedModel, request); +} + +void validateRequestFailure(const std::shared_ptr<IPreparedModel>& preparedModel, + const Request& request) { + SCOPED_TRACE("Expecting request to fail [executeSynchronously]"); + ExecutionResult executionResult; + const auto executeStatus = preparedModel->executeSynchronously( + request, false, kNoDeadline, kOmittedTimeoutDuration, &executionResult); + + ASSERT_FALSE(executeStatus.isOk()); + ASSERT_EQ(executeStatus.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_NE(static_cast<ErrorStatus>(executeStatus.getServiceSpecificError()), ErrorStatus::NONE); +} + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional diff --git a/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.cpp b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.cpp new file mode 100644 index 0000000000..2d91b8edd9 --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "neuralnetworks_aidl_hal_test" +#include "VtsHalNeuralnetworks.h" + +#include <android-base/logging.h> +#include <android/binder_auto_utils.h> +#include <android/binder_interface_utils.h> +#include <android/binder_manager.h> +#include <android/binder_status.h> +#include <gtest/gtest.h> +#include <memory> +#include <string> +#include <utility> + +#include <TestHarness.h> +#include <aidl/Vintf.h> +#include <nnapi/hal/aidl/Conversions.h> + +#include "Callbacks.h" +#include "GeneratedTestHarness.h" +#include "Utils.h" + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +using implementation::PreparedModelCallback; + +// internal helper function +void createPreparedModel(const std::shared_ptr<IDevice>& device, const Model& model, + std::shared_ptr<IPreparedModel>* preparedModel, bool reportSkipping) { + ASSERT_NE(nullptr, preparedModel); + *preparedModel = nullptr; + + // see if service can handle model + std::vector<bool> supportedOperations; + const auto supportedCallStatus = device->getSupportedOperations(model, &supportedOperations); + ASSERT_TRUE(supportedCallStatus.isOk()); + ASSERT_NE(0ul, supportedOperations.size()); + const bool fullySupportsModel = std::all_of( + supportedOperations.begin(), supportedOperations.end(), [](bool v) { return v; }); + + // launch prepare model + const std::shared_ptr<PreparedModelCallback> preparedModelCallback = + ndk::SharedRefBase::make<PreparedModelCallback>(); + const auto prepareLaunchStatus = + device->prepareModel(model, ExecutionPreference::FAST_SINGLE_ANSWER, kDefaultPriority, + kNoDeadline, {}, {}, kEmptyCacheToken, preparedModelCallback); + ASSERT_TRUE(prepareLaunchStatus.isOk()) << prepareLaunchStatus.getDescription(); + + // retrieve prepared model + preparedModelCallback->wait(); + const ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus(); + *preparedModel = preparedModelCallback->getPreparedModel(); + + // The getSupportedOperations call returns a list of operations that are guaranteed not to fail + // if prepareModel is called, and 'fullySupportsModel' is true i.f.f. the entire model is + // guaranteed. If a driver has any doubt that it can prepare an operation, it must return false. + // So here, if a driver isn't sure if it can support an operation, but reports that it + // successfully prepared the model, the test can continue. + if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) { + ASSERT_EQ(nullptr, preparedModel->get()); + if (!reportSkipping) { + return; + } + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot prepare " + "model that it does not support."; + std::cout << "[ ] Early termination of test because vendor service cannot " + "prepare model that it does not support." + << std::endl; + GTEST_SKIP(); + } + + ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus); + ASSERT_NE(nullptr, preparedModel->get()); +} + +void NeuralNetworksAidlTest::SetUp() { + testing::TestWithParam<NeuralNetworksAidlTestParam>::SetUp(); + ASSERT_NE(kDevice, nullptr); +} + +static NamedDevice makeNamedDevice(const std::string& name) { + ndk::SpAIBinder binder(AServiceManager_getService(name.c_str())); + return {name, IDevice::fromBinder(binder)}; +} + +static std::vector<NamedDevice> getNamedDevicesImpl() { + // Retrieves the name of all service instances that implement IDevice, + // including any Lazy HAL instances. + const std::vector<std::string> names = ::android::getAidlHalInstanceNames(IDevice::descriptor); + + // Get a handle to each device and pair it with its name. + std::vector<NamedDevice> namedDevices; + namedDevices.reserve(names.size()); + std::transform(names.begin(), names.end(), std::back_inserter(namedDevices), makeNamedDevice); + return namedDevices; +} + +const std::vector<NamedDevice>& getNamedDevices() { + const static std::vector<NamedDevice> devices = getNamedDevicesImpl(); + return devices; +} + +std::string printNeuralNetworksAidlTest( + const testing::TestParamInfo<NeuralNetworksAidlTestParam>& info) { + return gtestCompliantName(getName(info.param)); +} + +INSTANTIATE_DEVICE_TEST(NeuralNetworksAidlTest); + +// Forward declaration from ValidateModel.cpp +void validateModel(const std::shared_ptr<IDevice>& device, const Model& model); +// Forward declaration from ValidateRequest.cpp +void validateRequest(const std::shared_ptr<IPreparedModel>& preparedModel, const Request& request); +// Forward declaration from ValidateRequest.cpp +void validateRequestFailure(const std::shared_ptr<IPreparedModel>& preparedModel, + const Request& request); + +void validateEverything(const std::shared_ptr<IDevice>& device, const Model& model, + const Request& request) { + validateModel(device, model); + + // Create IPreparedModel. + std::shared_ptr<IPreparedModel> preparedModel; + createPreparedModel(device, model, &preparedModel); + if (preparedModel == nullptr) return; + + validateRequest(preparedModel, request); + // HIDL also had test that expected executeFenced to fail on received null fd (-1). This is not + // allowed in AIDL and will result in EX_TRANSACTION_FAILED. +} + +void validateFailure(const std::shared_ptr<IDevice>& device, const Model& model, + const Request& request) { + // TODO: Should this always succeed? + // What if the invalid input is part of the model (i.e., a parameter). + validateModel(device, model); + + // Create IPreparedModel. + std::shared_ptr<IPreparedModel> preparedModel; + createPreparedModel(device, model, &preparedModel); + if (preparedModel == nullptr) return; + + validateRequestFailure(preparedModel, request); +} + +TEST_P(ValidationTest, Test) { + const Model model = createModel(kTestModel); + ExecutionContext context; + const Request request = context.createRequest(kTestModel); + if (kTestModel.expectFailure) { + validateFailure(kDevice, model, request); + } else { + validateEverything(kDevice, model, request); + } +} + +INSTANTIATE_GENERATED_TEST(ValidationTest, [](const std::string& testName) { + // Skip validation for the "inputs_as_internal" and "all_tensors_as_inputs" + // generated tests. + return testName.find("inputs_as_internal") == std::string::npos && + testName.find("all_tensors_as_inputs") == std::string::npos; +}); + +std::string toString(Executor executor) { + switch (executor) { + case Executor::ASYNC: + return "ASYNC"; + case Executor::SYNC: + return "SYNC"; + case Executor::BURST: + return "BURST"; + case Executor::FENCED: + return "FENCED"; + default: + CHECK(false); + } +} + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional diff --git a/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.h b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.h new file mode 100644 index 0000000000..9b81ee116e --- /dev/null +++ b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 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. + */ + +#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_VTS_HAL_NEURALNETWORKS_H +#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_VTS_HAL_NEURALNETWORKS_H + +#include <gtest/gtest.h> +#include <vector> + +#include <aidl/android/hardware/neuralnetworks/IDevice.h> + +#include "Callbacks.h" +#include "Utils.h" + +namespace aidl::android::hardware::neuralnetworks::vts::functional { + +using NamedDevice = Named<std::shared_ptr<IDevice>>; +using NeuralNetworksAidlTestParam = NamedDevice; + +class NeuralNetworksAidlTest : public testing::TestWithParam<NeuralNetworksAidlTestParam> { + protected: + void SetUp() override; + const std::shared_ptr<IDevice> kDevice = getData(GetParam()); +}; + +const std::vector<NamedDevice>& getNamedDevices(); + +std::string printNeuralNetworksAidlTest( + const testing::TestParamInfo<NeuralNetworksAidlTestParam>& info); + +#define INSTANTIATE_DEVICE_TEST(TestSuite) \ + GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TestSuite); \ + INSTANTIATE_TEST_SUITE_P(PerInstance, TestSuite, testing::ValuesIn(getNamedDevices()), \ + printNeuralNetworksAidlTest) + +// Create an IPreparedModel object. If the model cannot be prepared, +// "preparedModel" will be nullptr instead. +void createPreparedModel(const std::shared_ptr<IDevice>& device, const Model& model, + std::shared_ptr<IPreparedModel>* preparedModel, + bool reportSkipping = true); + +enum class Executor { ASYNC, SYNC, BURST, FENCED }; + +std::string toString(Executor executor); + +} // namespace aidl::android::hardware::neuralnetworks::vts::functional + +#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_VTS_HAL_NEURALNETWORKS_H diff --git a/neuralnetworks/utils/common/Android.bp b/neuralnetworks/utils/common/Android.bp index 6c491ae7ae..50295f1aad 100644 --- a/neuralnetworks/utils/common/Android.bp +++ b/neuralnetworks/utils/common/Android.bp @@ -22,10 +22,12 @@ cc_library_static { export_include_dirs: ["include"], cflags: ["-Wthread-safety"], static_libs: [ + "libarect", "neuralnetworks_types", ], shared_libs: [ "libhidlbase", + "libnativewindow", ], } diff --git a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h index b3989e5878..547f203d6d 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h @@ -24,15 +24,21 @@ #include <functional> #include <vector> -// Shorthand +// Shorthands namespace android::hardware::neuralnetworks { namespace hal = ::android::hardware::neuralnetworks; } // namespace android::hardware::neuralnetworks -// Shorthand +// Shorthands +namespace aidl::android::hardware::neuralnetworks { +namespace aidl_hal = ::aidl::android::hardware::neuralnetworks; +} // namespace aidl::android::hardware::neuralnetworks + +// Shorthands namespace android::nn { namespace hal = ::android::hardware::neuralnetworks; -} +namespace aidl_hal = ::aidl::android::hardware::neuralnetworks; +} // namespace android::nn namespace android::hardware::neuralnetworks::utils { @@ -68,10 +74,12 @@ nn::GeneralResult<void> unflushDataFromSharedToPointer( std::vector<uint32_t> countNumberOfConsumers(size_t numberOfOperands, const std::vector<nn::Operation>& operations); -nn::GeneralResult<nn::Memory> createSharedMemoryFromHidlMemory(const hidl_memory& memory); +nn::GeneralResult<hidl_memory> createHidlMemoryFromSharedMemory(const nn::SharedMemory& memory); +nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory); + +nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::Handle& handle); +nn::GeneralResult<nn::Handle> sharedHandleFromNativeHandle(const native_handle_t* handle); -nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::SharedHandle& handle); -nn::GeneralResult<nn::SharedHandle> sharedHandleFromNativeHandle(const native_handle_t* handle); nn::GeneralResult<hidl_vec<hidl_handle>> convertSyncFences( const std::vector<nn::SyncFence>& fences); diff --git a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h b/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h index 95a20a8f80..209b66304c 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H + #include <android/hidl/base/1.0/IBase.h> #include <hidl/HidlSupport.h> #include <nnapi/Result.h> @@ -50,7 +53,8 @@ nn::GeneralResult<Type> handleTransportError(const hardware::Return<Type>& ret) }) template <typename Type> -nn::GeneralResult<Type> makeGeneralFailure(nn::Result<Type> result, nn::ErrorStatus status) { +nn::GeneralResult<Type> makeGeneralFailure( + nn::Result<Type> result, nn::ErrorStatus status = nn::ErrorStatus::GENERAL_FAILURE) { if (!result.has_value()) { return nn::error(status) << std::move(result).error(); } @@ -75,7 +79,8 @@ nn::ExecutionResult<Type> makeExecutionFailure(nn::GeneralResult<Type> result) { } template <typename Type> -nn::ExecutionResult<Type> makeExecutionFailure(nn::Result<Type> result, nn::ErrorStatus status) { +nn::ExecutionResult<Type> makeExecutionFailure( + nn::Result<Type> result, nn::ErrorStatus status = nn::ErrorStatus::GENERAL_FAILURE) { return makeExecutionFailure(makeGeneralFailure(result, status)); } @@ -86,4 +91,6 @@ nn::ExecutionResult<Type> makeExecutionFailure(nn::Result<Type> result, nn::Erro } else \ return NN_ERROR(canonical) -} // namespace android::hardware::neuralnetworks::utils
\ No newline at end of file +} // namespace android::hardware::neuralnetworks::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h index 8c04b8887b..0e98c2eea2 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h @@ -31,9 +31,9 @@ class InvalidBuffer final : public nn::IBuffer { public: nn::Request::MemoryDomainToken getToken() const override; - nn::GeneralResult<void> copyTo(const nn::Memory& dst) const override; + nn::GeneralResult<void> copyTo(const nn::SharedMemory& dst) const override; - nn::GeneralResult<void> copyFrom(const nn::Memory& src, + nn::GeneralResult<void> copyFrom(const nn::SharedMemory& src, const nn::Dimensions& dimensions) const override; }; diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h index 83e60b6a25..996858cd55 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h @@ -29,7 +29,7 @@ namespace android::hardware::neuralnetworks::utils { class InvalidBurst final : public nn::IBurst { public: - OptionalCacheHold cacheMemory(const nn::Memory& memory) const override; + OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override; nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( const nn::Request& request, nn::MeasureTiming measure) const override; diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h index d2c2469403..c8ca6f2d36 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h @@ -46,9 +46,9 @@ class ResilientBuffer final : public nn::IBuffer { nn::Request::MemoryDomainToken getToken() const override; - nn::GeneralResult<void> copyTo(const nn::Memory& dst) const override; + nn::GeneralResult<void> copyTo(const nn::SharedMemory& dst) const override; - nn::GeneralResult<void> copyFrom(const nn::Memory& src, + nn::GeneralResult<void> copyFrom(const nn::SharedMemory& src, const nn::Dimensions& dimensions) const override; private: diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h index 0df287f2f8..3b87330872 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h @@ -44,7 +44,7 @@ class ResilientBurst final : public nn::IBurst, nn::SharedBurst getBurst() const; nn::GeneralResult<nn::SharedBurst> recover(const nn::IBurst* failingBurst) const; - OptionalCacheHold cacheMemory(const nn::Memory& memory) const override; + OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override; nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( const nn::Request& request, nn::MeasureTiming measure) const override; diff --git a/neuralnetworks/utils/common/src/CommonUtils.cpp b/neuralnetworks/utils/common/src/CommonUtils.cpp index c04c8dfa8b..7a5035f6fc 100644 --- a/neuralnetworks/utils/common/src/CommonUtils.cpp +++ b/neuralnetworks/utils/common/src/CommonUtils.cpp @@ -20,11 +20,14 @@ #include <android-base/logging.h> #include <android-base/unique_fd.h> +#include <android/hardware_buffer.h> +#include <hidl/HidlSupport.h> #include <nnapi/Result.h> #include <nnapi/SharedMemory.h> #include <nnapi/TypeUtils.h> #include <nnapi/Types.h> #include <nnapi/Validation.h> +#include <vndk/hardware_buffer.h> #include <algorithm> #include <any> @@ -203,13 +206,13 @@ nn::GeneralResult<std::reference_wrapper<const nn::Request>> flushDataFromPointe nn::GeneralResult<void> unflushDataFromSharedToPointer( const nn::Request& request, const std::optional<nn::Request>& maybeRequestInShared) { if (!maybeRequestInShared.has_value() || maybeRequestInShared->pools.empty() || - !std::holds_alternative<nn::Memory>(maybeRequestInShared->pools.back())) { + !std::holds_alternative<nn::SharedMemory>(maybeRequestInShared->pools.back())) { return {}; } const auto& requestInShared = *maybeRequestInShared; // Map the memory. - const auto& outputMemory = std::get<nn::Memory>(requestInShared.pools.back()); + const auto& outputMemory = std::get<nn::SharedMemory>(requestInShared.pools.back()); const auto [pointer, size, context] = NN_TRY(map(outputMemory)); const uint8_t* constantPointer = std::visit([](const auto& o) { return static_cast<const uint8_t*>(o); }, pointer); @@ -248,44 +251,128 @@ std::vector<uint32_t> countNumberOfConsumers(size_t numberOfOperands, return nn::countNumberOfConsumers(numberOfOperands, operations); } -nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::SharedHandle& handle) { - if (handle == nullptr) { - return {}; +nn::GeneralResult<hidl_memory> createHidlMemoryFromSharedMemory(const nn::SharedMemory& memory) { + 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); +} + +static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) { + return (value + multiple - 1) / multiple * multiple; +} + +nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory) { + CHECK_LE(memory.size(), std::numeric_limits<uint32_t>::max()); + + if (memory.name() != "hardware_buffer_blob") { + return std::make_shared<const nn::Memory>(nn::Memory{ + .handle = NN_TRY(sharedHandleFromNativeHandle(memory.handle())), + .size = static_cast<uint32_t>(memory.size()), + .name = memory.name(), + }); + } + + const auto size = 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. + + // 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, memory.handle(), + AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE, + &hardwareBuffer); + if (status == NO_ERROR) { + break; + } + } + if (status != NO_ERROR) { + return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) + << "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(), + }); +} + +nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::Handle& handle) { std::vector<base::unique_fd> fds; - fds.reserve(handle->fds.size()); - for (const auto& fd : handle->fds) { - int dupFd = dup(fd); + fds.reserve(handle.fds.size()); + for (const auto& fd : handle.fds) { + const int dupFd = dup(fd); if (dupFd == -1) { return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd"; } fds.emplace_back(dupFd); } - native_handle_t* nativeHandle = native_handle_create(handle->fds.size(), handle->ints.size()); + 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(nn::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]); + std::copy(handle.ints.begin(), handle.ints.end(), &nativeHandle->data[nativeHandle->numFds]); hidl_handle hidlHandle; hidlHandle.setTo(nativeHandle, /*shouldOwn=*/true); return hidlHandle; } -nn::GeneralResult<nn::SharedHandle> sharedHandleFromNativeHandle(const native_handle_t* handle) { +nn::GeneralResult<nn::Handle> sharedHandleFromNativeHandle(const native_handle_t* handle) { if (handle == nullptr) { - return nullptr; + return NN_ERROR() << "sharedHandleFromNativeHandle failed because handle is nullptr"; } std::vector<base::unique_fd> fds; fds.reserve(handle->numFds); for (int i = 0; i < handle->numFds; ++i) { - int dupFd = dup(handle->data[i]); + const int dupFd = dup(handle->data[i]); if (dupFd == -1) { return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd"; } @@ -295,18 +382,18 @@ nn::GeneralResult<nn::SharedHandle> sharedHandleFromNativeHandle(const native_ha std::vector<int> ints(&handle->data[handle->numFds], &handle->data[handle->numFds + handle->numInts]); - return std::make_shared<const nn::Handle>(nn::Handle{ - .fds = std::move(fds), - .ints = std::move(ints), - }); + return nn::Handle{.fds = std::move(fds), .ints = std::move(ints)}; } nn::GeneralResult<hidl_vec<hidl_handle>> convertSyncFences( const std::vector<nn::SyncFence>& syncFences) { hidl_vec<hidl_handle> handles(syncFences.size()); for (size_t i = 0; i < syncFences.size(); ++i) { - handles[i] = - NN_TRY(hal::utils::hidlHandleFromSharedHandle(syncFences[i].getSharedHandle())); + const auto& handle = syncFences[i].getSharedHandle(); + if (handle == nullptr) { + return NN_ERROR() << "convertSyncFences failed because sync fence is empty"; + } + handles[i] = NN_TRY(hidlHandleFromSharedHandle(*handle)); } return handles; } diff --git a/neuralnetworks/utils/common/src/InvalidBuffer.cpp b/neuralnetworks/utils/common/src/InvalidBuffer.cpp index c6f75d7137..e73001dee5 100644 --- a/neuralnetworks/utils/common/src/InvalidBuffer.cpp +++ b/neuralnetworks/utils/common/src/InvalidBuffer.cpp @@ -30,11 +30,11 @@ nn::Request::MemoryDomainToken InvalidBuffer::getToken() const { return nn::Request::MemoryDomainToken{}; } -nn::GeneralResult<void> InvalidBuffer::copyTo(const nn::Memory& /*dst*/) const { +nn::GeneralResult<void> InvalidBuffer::copyTo(const nn::SharedMemory& /*dst*/) const { return NN_ERROR() << "InvalidBuffer"; } -nn::GeneralResult<void> InvalidBuffer::copyFrom(const nn::Memory& /*src*/, +nn::GeneralResult<void> InvalidBuffer::copyFrom(const nn::SharedMemory& /*src*/, const nn::Dimensions& /*dimensions*/) const { return NN_ERROR() << "InvalidBuffer"; } diff --git a/neuralnetworks/utils/common/src/InvalidBurst.cpp b/neuralnetworks/utils/common/src/InvalidBurst.cpp index 4ca6603eb7..81ca18d259 100644 --- a/neuralnetworks/utils/common/src/InvalidBurst.cpp +++ b/neuralnetworks/utils/common/src/InvalidBurst.cpp @@ -26,7 +26,8 @@ namespace android::hardware::neuralnetworks::utils { -InvalidBurst::OptionalCacheHold InvalidBurst::cacheMemory(const nn::Memory& /*memory*/) const { +InvalidBurst::OptionalCacheHold InvalidBurst::cacheMemory( + const nn::SharedMemory& /*memory*/) const { return nullptr; } diff --git a/neuralnetworks/utils/common/src/ResilientBuffer.cpp b/neuralnetworks/utils/common/src/ResilientBuffer.cpp index 47abbe268f..1904375fd4 100644 --- a/neuralnetworks/utils/common/src/ResilientBuffer.cpp +++ b/neuralnetworks/utils/common/src/ResilientBuffer.cpp @@ -99,12 +99,12 @@ nn::Request::MemoryDomainToken ResilientBuffer::getToken() const { return getBuffer()->getToken(); } -nn::GeneralResult<void> ResilientBuffer::copyTo(const nn::Memory& dst) const { +nn::GeneralResult<void> ResilientBuffer::copyTo(const nn::SharedMemory& dst) const { const auto fn = [&dst](const nn::IBuffer& buffer) { return buffer.copyTo(dst); }; return protect(*this, fn); } -nn::GeneralResult<void> ResilientBuffer::copyFrom(const nn::Memory& src, +nn::GeneralResult<void> ResilientBuffer::copyFrom(const nn::SharedMemory& src, const nn::Dimensions& dimensions) const { const auto fn = [&src, &dimensions](const nn::IBuffer& buffer) { return buffer.copyFrom(src, dimensions); diff --git a/neuralnetworks/utils/common/src/ResilientBurst.cpp b/neuralnetworks/utils/common/src/ResilientBurst.cpp index 0d3cb33a98..5ca868bc9a 100644 --- a/neuralnetworks/utils/common/src/ResilientBurst.cpp +++ b/neuralnetworks/utils/common/src/ResilientBurst.cpp @@ -94,7 +94,8 @@ nn::GeneralResult<nn::SharedBurst> ResilientBurst::recover(const nn::IBurst* fai return mBurst; } -ResilientBurst::OptionalCacheHold ResilientBurst::cacheMemory(const nn::Memory& memory) const { +ResilientBurst::OptionalCacheHold ResilientBurst::cacheMemory( + const nn::SharedMemory& memory) const { return getBurst()->cacheMemory(memory); } diff --git a/neuralnetworks/utils/common/test/MockBuffer.h b/neuralnetworks/utils/common/test/MockBuffer.h index c5405fb837..59d57007ae 100644 --- a/neuralnetworks/utils/common/test/MockBuffer.h +++ b/neuralnetworks/utils/common/test/MockBuffer.h @@ -27,9 +27,9 @@ namespace android::nn { class MockBuffer final : public IBuffer { public: MOCK_METHOD(Request::MemoryDomainToken, getToken, (), (const, override)); - MOCK_METHOD(GeneralResult<void>, copyTo, (const Memory& dst), (const, override)); - MOCK_METHOD(GeneralResult<void>, copyFrom, (const Memory& src, const Dimensions& dimensions), - (const, override)); + MOCK_METHOD(GeneralResult<void>, copyTo, (const SharedMemory& dst), (const, override)); + MOCK_METHOD(GeneralResult<void>, copyFrom, + (const SharedMemory& src, const Dimensions& dimensions), (const, override)); }; } // namespace android::nn diff --git a/neuralnetworks/utils/common/test/ResilientBufferTest.cpp b/neuralnetworks/utils/common/test/ResilientBufferTest.cpp index deb9b7cf21..7afd0203bb 100644 --- a/neuralnetworks/utils/common/test/ResilientBufferTest.cpp +++ b/neuralnetworks/utils/common/test/ResilientBufferTest.cpp @@ -15,9 +15,11 @@ */ #include <gmock/gmock.h> +#include <nnapi/SharedMemory.h> #include <nnapi/TypeUtils.h> #include <nnapi/Types.h> #include <nnapi/hal/ResilientBuffer.h> +#include <memory> #include <tuple> #include <utility> #include "MockBuffer.h" @@ -113,7 +115,8 @@ TEST(ResilientBufferTest, copyTo) { EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(Return(kNoError)); // run test - const auto result = buffer->copyTo({}); + const nn::SharedMemory memory = std::make_shared<const nn::Memory>(); + const auto result = buffer->copyTo(memory); // verify result ASSERT_TRUE(result.has_value()) @@ -126,7 +129,8 @@ TEST(ResilientBufferTest, copyToError) { EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnGeneralFailure); // run test - const auto result = buffer->copyTo({}); + const nn::SharedMemory memory = std::make_shared<const nn::Memory>(); + const auto result = buffer->copyTo(memory); // verify result ASSERT_FALSE(result.has_value()); @@ -140,7 +144,8 @@ TEST(ResilientBufferTest, copyToDeadObjectFailedRecovery) { EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure); // run test - const auto result = buffer->copyTo({}); + const nn::SharedMemory memory = std::make_shared<const nn::Memory>(); + const auto result = buffer->copyTo(memory); // verify result ASSERT_FALSE(result.has_value()); @@ -156,7 +161,8 @@ TEST(ResilientBufferTest, copyToDeadObjectSuccessfulRecovery) { EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer)); // run test - const auto result = buffer->copyTo({}); + const nn::SharedMemory memory = std::make_shared<const nn::Memory>(); + const auto result = buffer->copyTo(memory); // verify result ASSERT_TRUE(result.has_value()) @@ -169,7 +175,8 @@ TEST(ResilientBufferTest, copyFrom) { EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(Return(kNoError)); // run test - const auto result = buffer->copyFrom({}, {}); + const nn::SharedMemory memory = std::make_shared<const nn::Memory>(); + const auto result = buffer->copyFrom(memory, {}); // verify result ASSERT_TRUE(result.has_value()) @@ -182,7 +189,8 @@ TEST(ResilientBufferTest, copyFromError) { EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnGeneralFailure); // run test - const auto result = buffer->copyFrom({}, {}); + const nn::SharedMemory memory = std::make_shared<const nn::Memory>(); + const auto result = buffer->copyFrom(memory, {}); // verify result ASSERT_FALSE(result.has_value()); @@ -196,7 +204,8 @@ TEST(ResilientBufferTest, copyFromDeadObjectFailedRecovery) { EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure); // run test - const auto result = buffer->copyFrom({}, {}); + const nn::SharedMemory memory = std::make_shared<const nn::Memory>(); + const auto result = buffer->copyFrom(memory, {}); // verify result ASSERT_FALSE(result.has_value()); @@ -212,7 +221,8 @@ TEST(ResilientBufferTest, copyFromDeadObjectSuccessfulRecovery) { EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer)); // run test - const auto result = buffer->copyFrom({}, {}); + const nn::SharedMemory memory = std::make_shared<const nn::Memory>(); + const auto result = buffer->copyFrom(memory, {}); // verify result ASSERT_TRUE(result.has_value()) diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl index c8d7645ba2..cd9239e190 100644 --- a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl +++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl @@ -35,6 +35,6 @@ package android.hardware.power.stats; parcelable EnergyConsumer { int id; int ordinal; - android.hardware.power.stats.EnergyConsumerType type; + android.hardware.power.stats.EnergyConsumerType type = android.hardware.power.stats.EnergyConsumerType.OTHER; @utf8InCpp String name; } diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl index 7b05d2f426..ce3e1f59bf 100644 --- a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl +++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl @@ -34,6 +34,10 @@ package android.hardware.power.stats; @VintfStability enum EnergyConsumerType { OTHER = 0, - CPU_CLUSTER = 1, - DISPLAY = 2, + BLUETOOTH = 1, + CPU_CLUSTER = 2, + DISPLAY = 3, + GNSS = 4, + MOBILE_RADIO = 5, + WIFI = 6, } diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl index 2ff1279584..ec616f215e 100644 --- a/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl +++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl @@ -32,10 +32,10 @@ parcelable EnergyConsumer { int ordinal; /* Type of this EnergyConsumer */ - EnergyConsumerType type; + EnergyConsumerType type = EnergyConsumerType.OTHER; /** * Unique name of this EnergyConsumer. Vendor/device specific. Opaque to framework */ @utf8InCpp String name; -}
\ No newline at end of file +} diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl index 7fd2348d70..d871ced214 100644 --- a/power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl +++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl @@ -20,6 +20,10 @@ package android.hardware.power.stats; @VintfStability enum EnergyConsumerType { OTHER, + BLUETOOTH, CPU_CLUSTER, DISPLAY, -}
\ No newline at end of file + GNSS, + MOBILE_RADIO, + WIFI, +} diff --git a/radio/1.6/IRadio.hal b/radio/1.6/IRadio.hal index b756ce1261..714be47571 100644 --- a/radio/1.6/IRadio.hal +++ b/radio/1.6/IRadio.hal @@ -344,6 +344,9 @@ interface IRadio extends @1.5::IRadio { * setPreferredNetworkType, setPreferredNetworkTypesBitmap will not be called anymore * except for IRadio v1.5 or older devices. * + * In case of an emergency call, the modem is authorized to bypass this + * restriction. + * * @param serial Serial number of request. * @param networkTypeBitmap a 32-bit bearer bitmap of RadioAccessFamily * @@ -462,7 +465,7 @@ interface IRadio extends @1.5::IRadio { * cell information isn't known then the appropriate unknown value will be returned. * This does not cause or change the rate of unsolicited cellInfoList(). * - * This is identitcal to getCellInfoList in V1.0, but it requests updated version of CellInfo. + * This is identical to getCellInfoList in V1.0, but it requests updated version of CellInfo. * * @param serial Serial number of request. * @@ -518,4 +521,20 @@ interface IRadio extends @1.5::IRadio { * Response function is IRadioResponse.getSlicingConfigResponse() */ oneway getSlicingConfig(int32_t serial); + + /** + * Provide Carrier specific information to the modem that must be used to + * encrypt the IMSI and IMPI. Sent by the framework during boot, carrier + * switch and everytime the framework receives a new certificate. + * + * @param serial Serial number of request. + * @param imsiEncryptionInfo ImsiEncryptionInfo as defined in types.hal. + * + * Response callback is + * IRadioResponse.setCarrierInfoForImsiEncryptionResponse() + * + * Note this API is the same as the 1.1 version except using the 1.6 ImsiEncryptionInfo + * as the input param. + */ + oneway setCarrierInfoForImsiEncryption_1_6(int32_t serial, @1.6::ImsiEncryptionInfo imsiEncryptionInfo); }; diff --git a/radio/1.6/IRadioResponse.hal b/radio/1.6/IRadioResponse.hal index 6ad5cf262a..56ce809dfe 100644 --- a/radio/1.6/IRadioResponse.hal +++ b/radio/1.6/IRadioResponse.hal @@ -19,6 +19,7 @@ package android.hardware.radio@1.6; import @1.0::SendSmsResult; import @1.4::RadioAccessFamily; import @1.5::IRadioResponse; +import @1.5::RadioAccessSpecifier; import @1.6::Call; import @1.6::CellInfo; import @1.6::RegStateResult; @@ -344,6 +345,7 @@ interface IRadioResponse extends @1.5::IRadioResponse { /** * @param info Response info struct containing response type, serial no. and error + * @param specifiers List of RadioAccessSpecifiers that are scanned. * * Valid errors returned: * RadioError:NONE @@ -351,7 +353,8 @@ interface IRadioResponse extends @1.5::IRadioResponse { * RadioError:INTERNAL_ERR * RadioError:INVALID_ARGUMENTS */ - oneway getSystemSelectionChannelsResponse(RadioResponseInfo info); + oneway getSystemSelectionChannelsResponse( + RadioResponseInfo info, vec<RadioAccessSpecifier> specifiers); /** * This is identical to getCellInfoListResponse_1_5 but uses an updated version of CellInfo. diff --git a/radio/1.6/types.hal b/radio/1.6/types.hal index 6c23650dde..5d363c95a0 100644 --- a/radio/1.6/types.hal +++ b/radio/1.6/types.hal @@ -27,6 +27,7 @@ import @1.1::EutranBands; import @1.1::GeranBands; import @1.1::ScanStatus; import @1.1::UtranBands; +import @1.1::ImsiEncryptionInfo; import @1.2::Call; import @1.2::CellInfoCdma; import @1.2::CellConnectionStatus; @@ -1115,3 +1116,20 @@ enum SscMode : int32_t { MODE_2 = 2, MODE_3 = 3, }; + +/** + * Public key type from carrier certificate. + */ +enum PublicKeyType : int32_t { + EPDG = 1, // Key type to be used for ePDG + WLAN = 2, // Key type to be used for WLAN +}; + +/** + * Carrier specific Information sent by the carrier, + * which will be used to encrypt the IMSI and IMPI. + */ +struct ImsiEncryptionInfo { + @1.1::ImsiEncryptionInfo base; + PublicKeyType keyType; // Public key type +}; diff --git a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp index 07b8ccb035..fb50990a28 100644 --- a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp +++ b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp @@ -676,3 +676,29 @@ TEST_P(RadioHidlTest_v1_6, getCurrentCalls_1_6) { EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial); EXPECT_EQ(::android::hardware::radio::V1_6::RadioError::NONE, radioRsp_v1_6->rspInfo.error); } + +/* + * Test IRadio.setCarrierInfoForImsiEncryption_1_6() for the response returned. + */ +TEST_P(RadioHidlTest_v1_6, setCarrierInfoForImsiEncryption_1_6) { + serial = GetRandomSerialNumber(); + ::android::hardware::radio::V1_6::ImsiEncryptionInfo imsiInfo; + imsiInfo.base.mcc = "310"; + imsiInfo.base.mnc = "004"; + imsiInfo.base.carrierKey = (std::vector<uint8_t>){1, 2, 3, 4, 5, 6}; + imsiInfo.base.keyIdentifier = "Test"; + imsiInfo.base.expirationTime = 20180101; + imsiInfo.keyType = PublicKeyType::EPDG; + + radio_v1_6->setCarrierInfoForImsiEncryption_1_6(serial, imsiInfo); + EXPECT_EQ(std::cv_status::no_timeout, wait()); + EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo.type); + EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial); + + if (cardStatus.base.base.base.cardState == CardState::ABSENT) { + ASSERT_TRUE(CheckAnyOfErrors( + radioRsp_v1_6->rspInfo.error, + {::android::hardware::radio::V1_6::RadioError::NONE, + ::android::hardware::radio::V1_6::RadioError::REQUEST_NOT_SUPPORTED})); + } +} diff --git a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h index f32e31296d..f610f2af76 100644 --- a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h +++ b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h @@ -805,7 +805,8 @@ class RadioResponse_v1_6 : public ::android::hardware::radio::V1_6::IRadioRespon const ::android::hardware::radio::V1_6::RadioResponseInfo& info); Return<void> getSystemSelectionChannelsResponse( - const ::android::hardware::radio::V1_6::RadioResponseInfo& info); + const ::android::hardware::radio::V1_6::RadioResponseInfo& info, + const hidl_vec<::android::hardware::radio::V1_5::RadioAccessSpecifier>& specifier); Return<void> getSignalStrengthResponse_1_6( const ::android::hardware::radio::V1_6::RadioResponseInfo& info, diff --git a/radio/1.6/vts/functional/radio_response.cpp b/radio/1.6/vts/functional/radio_response.cpp index fad3f12e2b..027e9acd74 100644 --- a/radio/1.6/vts/functional/radio_response.cpp +++ b/radio/1.6/vts/functional/radio_response.cpp @@ -1190,7 +1190,8 @@ Return<void> RadioResponse_v1_6::getCellInfoListResponse_1_6( } Return<void> RadioResponse_v1_6::getSystemSelectionChannelsResponse( - const ::android::hardware::radio::V1_6::RadioResponseInfo& info) { + const ::android::hardware::radio::V1_6::RadioResponseInfo& info, + const hidl_vec<::android::hardware::radio::V1_5::RadioAccessSpecifier>& /*specifier*/) { rspInfo = info; parent_v1_6.notify(info.serial); return Void(); diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/AttestationKey.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/AttestationKey.aidl new file mode 100644 index 0000000000..893b016e44 --- /dev/null +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/AttestationKey.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 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.security.keymint; +@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability +parcelable AttestationKey { + byte[] keyBlob; + android.hardware.security.keymint.KeyParameter[] attestKeyParams; + byte[] issuerSubjectName; +} diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl index 594844a736..3faba48abd 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl @@ -111,6 +111,10 @@ enum ErrorCode { STORAGE_KEY_UNSUPPORTED = -77, INCOMPATIBLE_MGF_DIGEST = -78, UNSUPPORTED_MGF_DIGEST = -79, + MISSING_NOT_BEFORE = -80, + MISSING_NOT_AFTER = -81, + MISSING_ISSUER_SUBJECT = -82, + INVALID_ISSUER_SUBJECT = -83, UNIMPLEMENTED = -100, VERSION_MISMATCH = -101, UNKNOWN_ERROR = -1000, diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl index 132135bf06..d3c691086b 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl @@ -35,13 +35,15 @@ package android.hardware.security.keymint; interface IKeyMintDevice { android.hardware.security.keymint.KeyMintHardwareInfo getHardwareInfo(); void addRngEntropy(in byte[] data); - android.hardware.security.keymint.KeyCreationResult generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams); - android.hardware.security.keymint.KeyCreationResult importKey(in android.hardware.security.keymint.KeyParameter[] keyParams, in android.hardware.security.keymint.KeyFormat keyFormat, in byte[] keyData); + android.hardware.security.keymint.KeyCreationResult generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams, in @nullable android.hardware.security.keymint.AttestationKey attestationKey); + android.hardware.security.keymint.KeyCreationResult importKey(in android.hardware.security.keymint.KeyParameter[] keyParams, in android.hardware.security.keymint.KeyFormat keyFormat, in byte[] keyData, in @nullable android.hardware.security.keymint.AttestationKey attestationKey); android.hardware.security.keymint.KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, in byte[] wrappingKeyBlob, in byte[] maskingKey, in android.hardware.security.keymint.KeyParameter[] unwrappingParams, in long passwordSid, in long biometricSid); - byte[] upgradeKey(in byte[] inKeyBlobToUpgrade, in android.hardware.security.keymint.KeyParameter[] inUpgradeParams); - void deleteKey(in byte[] inKeyBlob); + byte[] upgradeKey(in byte[] keyBlobToUpgrade, in android.hardware.security.keymint.KeyParameter[] upgradeParams); + void deleteKey(in byte[] keyBlob); void deleteAllKeys(); void destroyAttestationIds(); - android.hardware.security.keymint.BeginResult begin(in android.hardware.security.keymint.KeyPurpose inPurpose, in byte[] inKeyBlob, in android.hardware.security.keymint.KeyParameter[] inParams, in android.hardware.security.keymint.HardwareAuthToken inAuthToken); + android.hardware.security.keymint.BeginResult begin(in android.hardware.security.keymint.KeyPurpose purpose, in byte[] keyBlob, in android.hardware.security.keymint.KeyParameter[] params, in android.hardware.security.keymint.HardwareAuthToken authToken); + void deviceLocked(in boolean passwordOnly, in @nullable android.hardware.security.secureclock.TimeStampToken timestampToken); + void earlyBootEnded(); const int AUTH_TOKEN_MAC_LENGTH = 32; } diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl new file mode 100644 index 0000000000..a864c3cca9 --- /dev/null +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl @@ -0,0 +1,43 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.security.keymint; +@VintfStability +interface IRemotelyProvisionedComponent { + byte[] generateEcdsaP256KeyPair(in boolean testMode, out android.hardware.security.keymint.MacedPublicKey macedPublicKey); + void generateCertificateRequest(in boolean testMode, in android.hardware.security.keymint.MacedPublicKey[] keysToSign, in byte[] endpointEncryptionCertChain, in byte[] challenge, out byte[] keysToSignMac, out android.hardware.security.keymint.ProtectedData protectedData); + const int STATUS_FAILED = 1; + const int STATUS_INVALID_MAC = 2; + const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3; + const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4; + const int STATUS_INVALID_EEK = 5; +} diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl index 93966ea2b0..d06312a290 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl @@ -37,4 +37,5 @@ parcelable KeyMintHardwareInfo { android.hardware.security.keymint.SecurityLevel securityLevel; @utf8InCpp String keyMintName; @utf8InCpp String keyMintAuthorName; + boolean timestampTokenRequired; } diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl index c1e92af3a7..61bb7e40e9 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl @@ -39,4 +39,5 @@ enum KeyPurpose { VERIFY = 3, WRAP_KEY = 5, AGREE_KEY = 6, + ATTEST_KEY = 7, } diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl new file mode 100644 index 0000000000..b4caeed7e3 --- /dev/null +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl @@ -0,0 +1,37 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// 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.security.keymint; +@VintfStability +parcelable MacedPublicKey { + byte[] macedKey; +} diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl new file mode 100644 index 0000000000..46f602f808 --- /dev/null +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 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.security.keymint; +@VintfStability +parcelable ProtectedData { + byte[] protectedData; +} diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl index b924a13266..03982e3621 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl @@ -94,4 +94,8 @@ enum Tag { MAC_LENGTH = 805307371, RESET_SINCE_ID_ROTATION = 1879049196, CONFIRMATION_TOKEN = -1879047187, + CERTIFICATE_SERIAL = -2147482642, + CERTIFICATE_SUBJECT = -1879047185, + CERTIFICATE_NOT_BEFORE = 1610613744, + CERTIFICATE_NOT_AFTER = 1610613745, } diff --git a/security/keymint/aidl/android/hardware/security/keymint/AttestationKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/AttestationKey.aidl new file mode 100644 index 0000000000..8167cebacd --- /dev/null +++ b/security/keymint/aidl/android/hardware/security/keymint/AttestationKey.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 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.security.keymint; + +import android.hardware.security.keymint.KeyParameter; + +/** + * Contains a key blob with Tag::ATTEST_KEY that can be used to sign an attestation certificate, + * and the DER-encoded X.501 Subject Name that will be placed in the Issuer field of the attestation + * certificate. + */ +@VintfStability +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable AttestationKey { + byte[] keyBlob; + KeyParameter[] attestKeyParams; + byte[] issuerSubjectName; +} diff --git a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl index b20601d4a1..57651303bf 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl @@ -42,7 +42,7 @@ enum ErrorCode { INVALID_AUTHORIZATION_TIMEOUT = -16, UNSUPPORTED_KEY_FORMAT = -17, INCOMPATIBLE_KEY_FORMAT = -18, - UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19, /** For PKCS8 & PKCS12 */ + UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19, /** For PKCS8 & PKCS12 */ UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20, /** For PKCS8 & PKCS12 */ INVALID_INPUT_LENGTH = -21, KEY_EXPORT_OPTIONS_INVALID = -22, @@ -101,6 +101,10 @@ enum ErrorCode { STORAGE_KEY_UNSUPPORTED = -77, INCOMPATIBLE_MGF_DIGEST = -78, UNSUPPORTED_MGF_DIGEST = -79, + MISSING_NOT_BEFORE = -80, + MISSING_NOT_AFTER = -81, + MISSING_ISSUER_SUBJECT = -82, + INVALID_ISSUER_SUBJECT = -83, UNIMPLEMENTED = -100, VERSION_MISMATCH = -101, diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl index 0120a30dd5..13e98af8bd 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl @@ -16,16 +16,18 @@ package android.hardware.security.keymint; +import android.hardware.security.keymint.AttestationKey; import android.hardware.security.keymint.BeginResult; import android.hardware.security.keymint.ByteArray; import android.hardware.security.keymint.HardwareAuthToken; import android.hardware.security.keymint.IKeyMintOperation; import android.hardware.security.keymint.KeyCreationResult; import android.hardware.security.keymint.KeyFormat; -import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.KeyMintHardwareInfo; +import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.KeyPurpose; import android.hardware.security.keymint.SecurityLevel; +import android.hardware.security.secureclock.TimeStampToken; /** * KeyMint device definition. @@ -314,9 +316,18 @@ interface IKeyMintDevice { * provided in params. See above for detailed specifications of which tags are required * for which types of keys. * + * @param attestationKey, if provided, specifies the key that must be used to sign the + * attestation certificate. If `keyParams` does not contain a Tag::ATTESTATION_CHALLENGE + * but `attestationKey` is non-null, the IKeyMintDevice must return + * ErrorCode::INVALID_ARGUMENT. If the provided AttestationKey does not contain a key + * blob containing an asymmetric key with KeyPurpose::ATTEST_KEY, the IKeyMintDevice must + * return ErrorCode::INVALID_PURPOSE. If the provided AttestationKey has an empty issuer + * subject name, the IKeyMintDevice must return ErrorCode::INVALID_ARGUMENT. + * * @return The result of key creation. See KeyCreationResult.aidl. */ - KeyCreationResult generateKey(in KeyParameter[] keyParams); + KeyCreationResult generateKey( + in KeyParameter[] keyParams, in @nullable AttestationKey attestationKey); /** * Imports key material into an IKeyMintDevice. Key definition parameters and return values @@ -344,10 +355,18 @@ interface IKeyMintDevice { * * @param inKeyData The key material to import, in the format specified in keyFormat. * + * @param attestationKey, if provided, specifies the key that must be used to sign the + * attestation certificate. If `keyParams` does not contain a Tag::ATTESTATION_CHALLENGE + * but `attestationKey` is non-null, the IKeyMintDevice must return + * ErrorCode::INVALID_ARGUMENT. If the provided AttestationKey does not contain a key + * blob containing an asymmetric key with KeyPurpose::ATTEST_KEY, the IKeyMintDevice must + * return ErrorCode::INVALID_PURPOSE. If the provided AttestationKey has an empty issuer + * subject name, the IKeyMintDevice must return ErrorCode::INVALID_ARGUMENT. + * * @return The result of key creation. See KeyCreationResult.aidl. */ KeyCreationResult importKey(in KeyParameter[] keyParams, in KeyFormat keyFormat, - in byte[] keyData); + in byte[] keyData, in @nullable AttestationKey attestationKey); /** * Securely imports a key, or key pair, returning a key blob and a description of the imported @@ -429,12 +448,9 @@ interface IKeyMintDevice { * * @return The result of key creation. See KeyCreationResult.aidl. */ - KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, - in byte[] wrappingKeyBlob, - in byte[] maskingKey, - in KeyParameter[] unwrappingParams, - in long passwordSid, - in long biometricSid); + KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, in byte[] wrappingKeyBlob, + in byte[] maskingKey, in KeyParameter[] unwrappingParams, in long passwordSid, + in long biometricSid); /** * Upgrades an old key blob. Keys can become "old" in two ways: IKeyMintDevice can be @@ -469,7 +485,7 @@ interface IKeyMintDevice { * @return A new key blob that references the same key as keyBlobToUpgrade, but is in the new * format, or has the new version data. */ - byte[] upgradeKey(in byte[] inKeyBlobToUpgrade, in KeyParameter[] inUpgradeParams); + byte[] upgradeKey(in byte[] keyBlobToUpgrade, in KeyParameter[] upgradeParams); /** * Deletes the key, or key pair, associated with the key blob. Calling this function on @@ -479,7 +495,7 @@ interface IKeyMintDevice { * * @param inKeyBlob The opaque descriptor returned by generateKey() or importKey(); */ - void deleteKey(in byte[] inKeyBlob); + void deleteKey(in byte[] keyBlob); /** * Deletes all keys in the hardware keystore. Used when keystore is reset completely. After @@ -705,8 +721,44 @@ interface IKeyMintDevice { * from operations that generate an IV or nonce, and IKeyMintOperation object pointer * which is used to perform update(), finish() or abort() operations. */ - BeginResult begin(in KeyPurpose inPurpose, - in byte[] inKeyBlob, - in KeyParameter[] inParams, - in HardwareAuthToken inAuthToken); + BeginResult begin(in KeyPurpose purpose, in byte[] keyBlob, in KeyParameter[] params, + in HardwareAuthToken authToken); + + /** + * Called by client to notify the IKeyMintDevice that the device is now locked, and keys with + * the UNLOCKED_DEVICE_REQUIRED tag should no longer be usable. When this function is called, + * the IKeyMintDevice should note the current timestamp, and attempts to use + * UNLOCKED_DEVICE_REQUIRED keys must be rejected with Error::DEVICE_LOCKED until an + * authentication token with a later timestamp is presented. If the `passwordOnly' argument is + * set to true the sufficiently-recent authentication token must indicate that the user + * authenticated with a password, not a biometric. + * + * Note that the IKeyMintDevice UNLOCKED_DEVICE_REQUIRED semantics are slightly different from + * the UNLOCKED_DEVICE_REQUIRED semantics enforced by keystore. Keystore handles device locking + * on a per-user basis. Because auth tokens do not contain an Android user ID, it's not + * possible to replicate the keystore enformcement logic in IKeyMintDevice. So from the + * IKeyMintDevice perspective, any user unlock unlocks all UNLOCKED_DEVICE_REQUIRED keys. + * Keystore will continue enforcing the per-user device locking. + * + * @param passwordOnly specifies whether the device must be unlocked with a password, rather + * than a biometric, before UNLOCKED_DEVICE_REQUIRED keys can be used. + * + * @param timestampToken is used by StrongBox implementations of IKeyMintDevice. It + * provides the StrongBox IKeyMintDevice with a fresh, MACed timestamp which it can use as the + * device-lock time, for future comparison against auth tokens when operations using + * UNLOCKED_DEVICE_REQUIRED keys are attempted. Unless the auth token timestamp is newer than + * the timestamp in the timestampToken, the device is still considered to be locked. + * Crucially, if a StrongBox IKeyMintDevice receives a deviceLocked() call with a timestampToken + * timestamp that is less than the timestamp in the last deviceLocked() call, it must ignore the + * new timestamp. TEE IKeyMintDevice implementations will receive an empty timestampToken (zero + * values and empty vectors) and should use their own clock as the device-lock time. + */ + void deviceLocked(in boolean passwordOnly, in @nullable TimeStampToken timestampToken); + + /** + * Called by client to notify the IKeyMintDevice that the device has left the early boot + * state, and that keys with the EARLY_BOOT_ONLY tag may no longer be used. All attempts to use + * an EARLY_BOOT_ONLY key after this method is called must fail with Error::INVALID_KEY_BLOB. + */ + void earlyBootEnded(); } diff --git a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl new file mode 100644 index 0000000000..1b09e9dfb7 --- /dev/null +++ b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl @@ -0,0 +1,262 @@ +/* + * 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. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.MacedPublicKey; +import android.hardware.security.keymint.ProtectedData; + +/** + * An IRemotelyProvisionedComponent is a secure-side component for which certificates can be + * remotely provisioned. It provides an interface for generating asymmetric key pairs and then + * creating a CertificateRequest that contains the generated public keys, plus other information to + * authenticate the request origin. The CertificateRequest can be sent to a server, which can + * validate the request and create certificates. + * + * This interface does not provide any way to use the generated and certified key pairs. It's + * intended to be implemented by a HAL service that does other things with keys (e.g. Keymint). + * + * The root of trust for secure provisioning is something called the "Boot Certificate Chain", or + * BCC. The BCC is a chain of public key certificates, represented as COSE_Sign1 objects containing + * COSE_Key representations of the public keys. The "root" of the BCC is a self-signed certificate + * for a device-unique public key, denoted DK_pub. All public keys in the BCC are device-unique. The + * public key from each certificate in the chain is used to sign the next certificate in the + * chain. The final, "leaf" certificate contains a public key, denoted KM_pub, whose corresponding + * private key, denoted KM_priv, is available for use by the IRemotelyProvisionedComponent. + * + * BCC Design + * ========== + * + * The BCC is designed to mirror the boot stages of a device, and to prove the content and integrity + * of each firmware image. In a proper BCC, each boot stage hashes its own private key with the code + * and any relevant configuration parameters of the next stage to produce a key pair for the next + * stage. Each stage also uses its own private key to sign the public key of the next stage, + * including in the certificate the hash of the next firmware stage, then loads the next stage, + * passing the private key and certificate to it in a manner that does not leak the private key to + * later boot stages. The BCC root key pair is generated by immutable code (e.g. ROM), from a + * device-unique secret. After the device-unique secret is used, it must be made unavailable to any + * later boot stage. + * + * In this way, booting the device incrementally builds a certificate chain that (a) identifies and + * validates the integrity of every stage and (b) contains a set of public keys that correspond to + * private keys, one known to each stage. Any stage can compute the secrets of all later stages + * (given the necessary input), but no stage can compute the secret of any preceding stage. Updating + * the firmware or configuration of any stage changes the key pair of that stage, and of all + * subsequent stages, and no attacker who compromised the previous version of the updated firmware + * can know or predict the post-update key pairs. + * + * The first BCC certificate is special because its contained public key, DK_pub, will never change, + * making it a permanent, device-unique identifier. Although the remaining keys in the BCC are also + * device-unique, they are not necessarily permanent, since they can change when the device software + * is updated. + * + * When the provisioning server receives a message signed by KM_priv and containing a BCC that + * chains from DK_pub to KM_pub, it can be certain that (barring vulnerabilities in some boot + * stage), the CertificateRequest came from the device associated with DK_pub, running the specific + * software identified by the certificates in the BCC. If the server has some mechanism for knowing + * which the DK_pub values of "valid" devices, it can determine whether signing certificates is + * appropriate. + * + * Degenerate BCCs + * =============== + * + * While a proper BCC, as described above, reflects the complete boot sequence from boot ROM to the + * secure area image of the IRemotelyProvisionedComponent, it's also possible to use a "degenerate" + * BCC which consists only of a single, self-signed certificate containing the public key of a + * hardware-bound key pair. This is an appopriate solution for devices which haven't implemented + * everything necessary to produce a proper BCC, but can derive a unique key pair in the secure + * area. In this degenerate case, DK_pub is the same as KM_pub. + * + * BCC Privacy + * =========== + * + * Because the BCC constitutes an unspoofable, device-unique identifier, special care is taken to + * prevent its availability to entities who may wish to track devices. Two precautions are taken: + * + * 1. The BCC is never exported from the IRemotelyProvisionedComponent except in encrypted + * form. The portion of the CertificateRequest that contains the BCC is encrypted using an + * Endpoint Encryption Key (EEK). The EEK is provided in the form of a certificate chain whose + * root must be pre-provisioned into the secure area (hardcoding the roots into the secure area + * firmware image is a recommended approach). Multiple roots may be provisioned. If the provided + * EEK does not chain back to this already-known root, the IRemotelyProvisionedComponent must + * reject it. + * + * 2. Precaution 1 above ensures that only an entity with a valid EEK private key can decrypt the + * BCC. To make it feasible to build a provisioning server which cannot use the BCC to track + * devices, the CertificateRequest is structured so that the server can be partitioned into two + * components. The "decrypter" decrypts the BCC, verifies DK_pub and the device's right to + * receive provisioned certificates, but does not see the public keys to be signed or the + * resulting certificates. The "certifier" gets informed of the results of the decrypter's + * validation and sees the public keys to be signed and resulting certificates, but does not see + * the BCC. + * + * Test Mode + * ========= + * + * The IRemotelyProvisionedComponent supports a test mode, allowing the generation of test key pairs + * and test CertificateRequests. Test keys/requests are annotated as such, and the BCC used for test + * CertificateRequests must contain freshly-generated keys, not the real BCC key pairs. + */ +@VintfStability +interface IRemotelyProvisionedComponent { + const int STATUS_FAILED = 1; + const int STATUS_INVALID_MAC = 2; + const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3; + const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4; + const int STATUS_INVALID_EEK = 5; + + /** + * generateKeyPair generates a new ECDSA P-256 key pair that can be certified. Note that this + * method only generates ECDSA P-256 key pairs, but the interface can be extended to add methods + * for generating keys for other algorithms, if necessary. + * + * @param in boolean testMode indicates whether the generated key is for testing only. Test keys + * are marked (see the definition of PublicKey in the MacedPublicKey structure) to + * prevent them from being confused with production keys. + * + * @param out MacedPublicKey macedPublicKey contains the public key of the generated key pair, + * MACed so that generateCertificateRequest can easily verify, without the + * privateKeyHandle, that the contained public key is for remote certification. + * + * @return data representing a handle to the private key. The format is implementation-defined, + * but note that specific services may define a required format. + */ + byte[] generateEcdsaP256KeyPair(in boolean testMode, out MacedPublicKey macedPublicKey); + + /** + * generateCertificateRequest creates a certificate request to be sent to the provisioning + * server. + * + * @param in boolean testMode indicates whether the generated certificate request is for testing + * only. + * + * @param in MacedPublicKey[] keysToSign contains the set of keys to certify. The + * IRemotelyProvisionedComponent must validate the MACs on each key. If any entry in the + * array lacks a valid MAC, the method must return STATUS_INVALID_MAC. + * + * If testMode is true, the keysToCertify array must contain only keys flagged as test + * keys. Otherwise, the method must return STATUS_PRODUCTION_KEY_IN_TEST_REQUEST. + * + * If testMode is false, the keysToCertify array must not contain any keys flagged as + * test keys. Otherwise, the method must return STATUS_TEST_KEY_IN_PRODUCTION_REQUEST. + * + * @param in endpointEncryptionKey contains an X25519 public key which will be used to encrypt + * the BCC. For flexibility, this is represented as a certificate chain, represented as a + * CBOR array of COSE_Sign1 objects, ordered from root to leaf. The leaf contains the + * X25519 encryption key, each other element is an Ed25519 key signing the next in the + * chain. The root is self-signed. + * + * EekChain = [ + SignedSignatureKey, SignedEek ] + * + * SignedSignatureKey = [ // COSE_Sign1 + * protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * unprotected: bstr .size 0 + * payload: bstr .cbor SignatureKey, + * signature: bstr PureEd25519(.cbor SignatureKeySignatureInput) + * ] + * + * SignatureKey = { // COSE_Key + * 1 : 1, // Key type : Octet Key Pair + * 3 : -8, // Algorithm : EdDSA + * -1 : 6, // Curve : Ed25519 + * -2 : bstr // Ed25519 public key + * } + * + * SignatureKeySignatureInput = [ + * context: "Signature1", + * body_protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * external_aad: bstr .size 0, + * payload: bstr .cbor SignatureKey + * ] + * + * SignedEek = [ // COSE_Sign1 + * protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * unprotected: bstr .size 0 + * payload: bstr .cbor Eek, + * signature: bstr PureEd25519(.cbor EekSignatureInput) + * ] + * + * Eek = { // COSE_Key + * 1 : 1, // Key type : Octet Key Pair + * 2 : bstr // KID : EEK ID + * 3 : -25, // Algorithm : ECDH-ES + HKDF-256 + * -1 : 4, // Curve : X25519 + * -2 : bstr // Ed25519 public key + * } + * + * EekSignatureInput = [ + * context: "Signature1", + * body_protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * external_aad: bstr .size 0, + * payload: bstr .cbor Eek + * ] + * + * If the contents of endpointEncryptionKey do not match the SignedEek structure above, + * the method must return STATUS_INVALID_EEK. + * + * If testMode is true, the method must ignore the length and content of the signatures + * in the chain, which implies that it must not attempt to validate the signature. + * + * If testMode is false, the method must validate the chain signatures, and must verify + * that the public key in the root certifictate is in its pre-configured set of + * authorized EEK root keys. If the public key is not in the database, or if signature + * verification fails, the method must return STATUS_INVALID_EEK. + * + * @param in challenge contains a byte string from the provisioning server that must be signed + * by the secure area. See the description of the 'signature' output parameter for + * details. + * + * @param out keysToSignMac contains the MAC of KeysToSign in the CertificateRequest + * structure. Specifically, it contains: + * + * HMAC-256(EK_mac, .cbor KeysToMacStructure) + * + * Where EK_mac is an ephemeral MAC key, found in ProtectedData (see below). The MACed + * data is the "tag" field of a COSE_Mac0 structure like: + * + * MacedKeys = [ // COSE_Mac0 + * protected : bstr .cbor { + * 1 : 5, // Algorithm : HMAC-256 + * }, + * unprotected : bstr .size 0, + * // Payload is PublicKeys from keysToSign argument, in provided order. + * payload: bstr .cbor [ * PublicKey ], + * tag: bstr + * ] + * + * KeysToMacStructure = [ + * context : "MAC0", + * protected : bstr .cbor { 1 : 5 }, // Algorithm : HMAC-256 + * external_aad : bstr .size 0, + * // Payload is PublicKeys from keysToSign argument, in provided order. + * payload : bstr .cbor [ * PublicKey ] + * ] + * + * @param out ProtectedData contains the encrypted BCC and the ephemeral MAC key used to + * authenticate the keysToSign (see keysToSignMac output argument). + */ + void generateCertificateRequest(in boolean testMode, in MacedPublicKey[] keysToSign, + in byte[] endpointEncryptionCertChain, in byte[] challenge, out byte[] keysToSignMac, + out ProtectedData protectedData); +} diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl index 1a107ba728..2fcaf4cf3b 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl @@ -45,4 +45,11 @@ parcelable KeyMintHardwareInfo { * same author. */ @utf8InCpp String keyMintAuthorName; + + /* The timestampTokenRequired is a boolean flag, which when true reflects that IKeyMintDevice + * instance will expect a valid TimeStampToken with various operations. This will typically + * required by the StrongBox implementations that generally don't have secure clock hardware to + * generate timestamp tokens. + */ + boolean timestampTokenRequired; } diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl index 68c1740791..978a02723c 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl @@ -16,12 +16,11 @@ package android.hardware.security.keymint; - /** * Possible purposes of a key (or pair). */ @VintfStability -@Backing(type = "int") +@Backing(type="int") enum KeyPurpose { /* Usable with RSA, EC and AES keys. */ ENCRYPT = 0, @@ -42,5 +41,7 @@ enum KeyPurpose { /* Key Agreement, usable with EC keys. */ AGREE_KEY = 6, - /* TODO(seleneh) add ATTEST_KEY and their corresponding codes and tests later*/ + /* Usable as an attestation signing key. Keys with this purpose must not have any other + * purpose. */ + ATTEST_KEY = 7, } diff --git a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl new file mode 100644 index 0000000000..da85a5048f --- /dev/null +++ b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl @@ -0,0 +1,57 @@ +/* + * 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. + */ + +package android.hardware.security.keymint; + +/** + * MacedPublicKey contains a CBOR-encoded public key, MACed by an IRemotelyProvisionedComponent, to + * prove that the key pair was generated by that component. + */ +@VintfStability +parcelable MacedPublicKey { + /** + * key is a COSE_Mac0 structure containing the new public key. It's MACed by a key available + * only to the secure environment, as proof that the public key was generated by that + * environment. In CDDL, assuming the contained key is an Ed25519 public key: + * + * MacedPublicKey = [ // COSE_Mac0 + * protected: bstr .cbor { 1 : 5}, // Algorithm : HMAC-256 + * unprotected: bstr .size 0, + * payload : bstr .cbor PublicKey, + * tag : bstr HMAC-256(K_mac, MAC_structure) + * ] + * + * PublicKey = { // COSE_Key + * 1 : 1, // Key type : octet key pair + * 3 : -8 // Algorithm : EdDSA + * -1 : 6, // Curve : Ed25519 + * -2 : bstr // X coordinate, little-endian + * ? -70000 : nil // Presence indicates this is a test key. If set, K_mac is + * // all zeros. + * }, + * + * MAC_structure = [ + * context : "MAC0", + * protected : bstr .cbor { 1 : 5 }, + * external_aad : bstr .size 0, + * payload : bstr .cbor PublicKey + * ] + * + * if a non-Ed25519 public key were contained, the contents of the PublicKey map would change a + * little; see RFC 8152 for details. + */ + byte[] macedKey; +} diff --git a/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl new file mode 100644 index 0000000000..1ec3bf0718 --- /dev/null +++ b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl @@ -0,0 +1,169 @@ +/* + * Copyright (C) 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.security.keymint; + +/** + * ProtectedData contains the encrypted BCC and the ephemeral MAC key used to + * authenticate the keysToSign (see keysToSignMac output argument). + */ +@VintfStability +parcelable ProtectedData { + /** + * ProtectedData is a COSE_Encrypt structure, specified by the following CDDL + * + * ProtectedData = [ // COSE_Encrypt + * protected: bstr .cbor { + * 1 : 3 // Algorithm : AES-GCM 256 + * }, + * unprotected: { + * 5 : bstr .size 12 // IV + * }, + * ciphertext: bstr, // AES-GCM-128(K, .cbor ProtectedDataPayload) + * recipients : [ + * [ // COSE_Recipient + * protected : bstr .cbor { + * 1 : -25 // Algorithm : ECDH-ES + HKDF-256 + * }, + * unprotected : { + * -1 : { // COSE_Key + * 1 : 1, // Key type : Octet Key Pair + * -1 : 4, // Curve : X25519 + * -2 : bstr // Sender X25519 public key + * } + * 4 : bstr, // KID : EEK ID + * }, + * ciphertext : nil + * ] + * ] + * ] + * + * K = HKDF-256(ECDH(EEK_pub, Ephemeral_priv), Context) + * + * Context = [ // COSE_KDF_Context + * AlgorithmID : 3 // AES-GCM 256 + * PartyUInfo : [ + * identity : bstr "client" + * nonce : bstr .size 0, + * other : bstr // Ephemeral pubkey + * ], + * PartyVInfo : [ + * identity : bstr "server", + * nonce : bstr .size 0, + * other : bstr // EEK pubkey + * ], + * SuppPubInfo : [ + * 128, // Output key length + * protected : bstr .size 0 + * ] + * ] + * + * ProtectedDataPayload [ + * SignedMac, + * Bcc, + * ] + * + * SignedMac = [ // COSE_Sign1 + * bstr .cbor { // Protected params + * 1 : -8, // Algorithm : EdDSA + * }, + * bstr .size 0, // Unprotected params + * bstr .size 32, // MAC key + * bstr PureEd25519(DK_priv, .cbor SignedMac_structure) + * ] + * + * SignedMac_structure = [ + * "Signature1", + * bstr .cbor { // Protected params + * 1 : -8, // Algorithm : EdDSA + * }, + * bstr .cbor SignedMacAad + * bstr .size 32 // MAC key + * ] + * + * SignedMacAad = [ + * challenge : bstr, + * DeviceInfo + * ] + * + * Bcc = [ + * PubKey, // DK_pub + * + BccEntry, // Root -> leaf (KM_pub) + * ] + * + * BccPayload = { // CWT + * 1 : tstr, // Issuer + * 2 : tstr, // Subject + * // See the Open Profile for DICE for details on these fields. + * ? -4670545 : bstr, // Code Hash + * ? -4670546 : bstr, // Code Descriptor + * ? -4670547 : bstr, // Configuration Hash + * ? -4670548 : bstr .cbor { // Configuration Descriptor + * ? -70002 : tstr, // Component name + * ? -70003 : int, // Firmware version + * ? -70004 : null, // Resettable + * }, + * ? -4670549 : bstr, // Authority Hash + * ? -4670550 : bstr, // Authority Descriptor + * ? -4670551 : bstr, // Mode + * -4670552 : bstr .cbor PubKey // Subject Public Key + * -4670553 : bstr // Key Usage + * } + * + * BccEntry = [ // COSE_Sign1 + * protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * unprotected: bstr .size 0, + * payload: bstr .cbor BccPayload, + * // First entry in the chain is signed by DK_pub, the others are each signed by their + * // immediate predecessor. See RFC 8032 for signature representation. + * signature: bstr .cbor PureEd25519(SigningKey, bstr .cbor BccEntryInput) + * ] + * + * PubKey = { // COSE_Key + * 1 : 1, // Key type : octet key pair + * 3 : -8, // Algorithm : EdDSA + * 4 : 2, // Ops: Verify + * -1 : 6, // Curve : Ed25519 + * -2 : bstr // X coordinate, little-endian + * } + * + * BccEntryInput = [ + * context: "Signature1", + * protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * external_aad: bstr .size 0, + * payload: bstr .cbor BccPayload + * ] + * + * DeviceInfo = { + * ? "brand" : tstr, + * ? "manufacturer" : tstr, + * ? "product" : tstr, + * ? "model" : tstr, + * ? "board" : tstr, + * ? "vb_state" : "green" / "yellow" / "orange", + * ? "bootloader_state" : "locked" / "unlocked", + * ? "os_version" : tstr, + * ? "system_patch_level" : uint, // YYYYMMDD + * ? "boot_patch_level" : uint, // YYYYMMDD + * ? "vendor_patch_level" : uint, // YYYYMMDD + * } + */ + byte[] protectedData; +} diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl index f52e32b96a..4f58cbeb76 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl @@ -933,4 +933,35 @@ enum Tag { * Must never appear in KeyCharacteristics. */ CONFIRMATION_TOKEN = (9 << 28) /* TagType:BYTES */ | 1005, + + /** + * Tag::CERTIFICATE_SERIAL specifies the serial number to be assigned to the + * attestation certificate to be generated for the given key. This parameter should only + * be passed to keyMint in the attestation parameters during generateKey() and importKey(). + */ + CERTIFICATE_SERIAL = (8 << 28) /* TagType:BIGNUM */ | 1006, + + /** + * Tag::CERTIFICATE_SUBJECT the certificate subject. The value is a DER encoded X509 NAME. + * This value is used when generating a self signed certificates. This tag may be specified + * during generateKey and importKey. If not provided the subject name shall default to + * <TODO default subject here>. + */ + CERTIFICATE_SUBJECT = (9 << 28) /* TagType:BYTES */ | 1007, + + /** + * Tag::CERTIFICATE_NOT_BEFORE the beginning of the validity of the certificate in UNIX epoch + * time in seconds. This value is used when generating attestation or self signed certificates. + * ErrorCode::MISSING_NOT_BEFORE must be returned if this tag is not provided if this tag is + * not provided to generateKey or importKey. + */ + CERTIFICATE_NOT_BEFORE = (6 << 28) /* TagType:DATE */ | 1008, + + /** + * Tag::CERTIFICATE_NOT_AFTER the end of the validity of the certificate in UNIX epoch + * time in seconds. This value is used when generating attestation or self signed certificates. + * ErrorCode::MISSING_NOT_AFTER must be returned if this tag is not provided to generateKey + * or importKey. + */ + CERTIFICATE_NOT_AFTER = (6 << 28) /* TagType:DATE */ | 1009, } diff --git a/security/keymint/aidl/default/Android.bp b/security/keymint/aidl/default/Android.bp index b2758adcb1..e160548931 100644 --- a/security/keymint/aidl/default/Android.bp +++ b/security/keymint/aidl/default/Android.bp @@ -2,7 +2,11 @@ cc_binary { name: "android.hardware.security.keymint-service", relative_install_path: "hw", init_rc: ["android.hardware.security.keymint-service.rc"], - vintf_fragments: ["android.hardware.security.keymint-service.xml"], + vintf_fragments: [ + "android.hardware.security.keymint-service.xml", + "android.hardware.security.sharedsecret-service.xml", + "android.hardware.security.secureclock-service.xml", + ], vendor: true, cflags: [ "-Wall", @@ -10,17 +14,45 @@ cc_binary { ], shared_libs: [ "android.hardware.security.keymint-V1-ndk_platform", + "android.hardware.security.sharedsecret-V1-ndk_platform", + "android.hardware.security.secureclock-V1-ndk_platform", "libbase", "libbinder_ndk", - "libcppbor", + "libcppbor_external", "libcrypto", "libkeymaster_portable", "libkeymint", "liblog", "libpuresoftkeymasterdevice", + "libremote_provisioner", "libutils", ], srcs: [ "service.cpp", ], } + +cc_library { + name: "libremote_provisioner", + vendor_available: true, + static_libs: [ + "libkeymint_remote_prov_support", + ], + shared_libs: [ + "android.hardware.security.keymint-V1-ndk_platform", + "libbinder_ndk", + "libcppbor_external", + "libcppcose", + "libcrypto", + "libkeymaster_portable", + "libkeymint", + "liblog", + "libpuresoftkeymasterdevice", + ], + export_include_dirs: [ + ".", + ], + srcs: [ + "RemotelyProvisionedComponent.cpp", + ], +} diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp new file mode 100644 index 0000000000..f2651fbce7 --- /dev/null +++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp @@ -0,0 +1,430 @@ +/* + * Copyright (C) 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. + */ + +#include "RemotelyProvisionedComponent.h" + +#include <assert.h> +#include <variant> + +#include <cppbor.h> +#include <cppbor_parse.h> + +#include <KeyMintUtils.h> +#include <cppcose/cppcose.h> +#include <keymaster/keymaster_configuration.h> +#include <remote_prov/remote_prov_utils.h> + +#include <openssl/bn.h> +#include <openssl/ec.h> +#include <openssl/rand.h> +#include <openssl/x509.h> + +namespace aidl::android::hardware::security::keymint { + +using ::std::string; +using ::std::tuple; +using ::std::unique_ptr; +using ::std::variant; +using ::std::vector; +using bytevec = ::std::vector<uint8_t>; + +using namespace cppcose; +using namespace keymaster; + +namespace { + +constexpr auto STATUS_FAILED = RemotelyProvisionedComponent::STATUS_FAILED; +constexpr auto STATUS_INVALID_EEK = RemotelyProvisionedComponent::STATUS_INVALID_EEK; +constexpr auto STATUS_INVALID_MAC = RemotelyProvisionedComponent::STATUS_INVALID_MAC; +constexpr uint32_t kAffinePointLength = 32; +struct AStatusDeleter { + void operator()(AStatus* p) { AStatus_delete(p); } +}; + +// TODO(swillden): Remove the dependency on AStatus stuff. The COSE lib should use something like +// StatusOr, but it shouldn't depend on AStatus. +class Status { + public: + Status() {} + Status(int32_t errCode, const std::string& errMsg) + : status_(AStatus_fromServiceSpecificErrorWithMessage(errCode, errMsg.c_str())) {} + explicit Status(const std::string& errMsg) + : status_(AStatus_fromServiceSpecificErrorWithMessage(STATUS_FAILED, errMsg.c_str())) {} + Status(AStatus* status) : status_(status) {} + Status(Status&&) = default; + Status(const Status&) = delete; + + operator ::ndk::ScopedAStatus() && { return ndk::ScopedAStatus(status_.release()); } + + bool isOk() { return !status_; } + + // Don't call getMessage() unless isOk() returns false; + const char* getMessage() const { return AStatus_getMessage(status_.get()); } + + private: + std::unique_ptr<AStatus, AStatusDeleter> status_; +}; + +template <typename T> +class StatusOr { + public: + StatusOr(AStatus* status) : status_(status) {} + StatusOr(Status status) : status_(std::move(status)) {} + StatusOr(T val) : value_(std::move(val)) {} + + bool isOk() { return status_.isOk(); } + + T* operator->() & { + assert(isOk()); + return &value_.value(); + } + T& operator*() & { + assert(isOk()); + return value_.value(); + } + T&& operator*() && { + assert(isOk()); + return std::move(value_).value(); + } + + const char* getMessage() const { + assert(!isOk()); + return status_.getMessage(); + } + + Status moveError() { + assert(!isOk()); + return std::move(status_); + } + + T moveValue() { return std::move(value_).value(); } + + private: + Status status_; + std::optional<T> value_; +}; + +StatusOr<std::pair<bytevec /* EEK pub */, bytevec /* EEK ID */>> validateAndExtractEekPubAndId( + bool testMode, const bytevec& endpointEncryptionCertChain) { + auto [item, newPos, errMsg] = cppbor::parse(endpointEncryptionCertChain); + + if (!item || !item->asArray()) { + return Status("Error parsing EEK chain" + errMsg); + } + + const cppbor::Array* certArr = item->asArray(); + bytevec lastPubKey; + for (int i = 0; i < certArr->size(); ++i) { + auto cosePubKey = verifyAndParseCoseSign1(testMode, certArr->get(i)->asArray(), + std::move(lastPubKey), bytevec{} /* AAD */); + if (!cosePubKey) { + return Status(STATUS_INVALID_EEK, + "Failed to validate EEK chain: " + cosePubKey.moveMessage()); + } + lastPubKey = *std::move(cosePubKey); + } + + auto eek = CoseKey::parseX25519(lastPubKey, true /* requireKid */); + if (!eek) return Status(STATUS_INVALID_EEK, "Failed to get EEK: " + eek.moveMessage()); + + return std::make_pair(eek->getBstrValue(CoseKey::PUBKEY_X).value(), + eek->getBstrValue(CoseKey::KEY_ID).value()); +} + +StatusOr<bytevec /* pubkeys */> validateAndExtractPubkeys(bool testMode, + const vector<MacedPublicKey>& keysToSign, + const bytevec& macKey) { + auto pubKeysToMac = cppbor::Array(); + for (auto& keyToSign : keysToSign) { + auto [macedKeyItem, _, coseMacErrMsg] = cppbor::parse(keyToSign.macedKey); + if (!macedKeyItem || !macedKeyItem->asArray() || + macedKeyItem->asArray()->size() != kCoseMac0EntryCount) { + return Status("Invalid COSE_Mac0 structure"); + } + + auto protectedParms = macedKeyItem->asArray()->get(kCoseMac0ProtectedParams)->asBstr(); + auto unprotectedParms = macedKeyItem->asArray()->get(kCoseMac0UnprotectedParams)->asBstr(); + auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr(); + auto tag = macedKeyItem->asArray()->get(kCoseMac0Tag)->asBstr(); + if (!protectedParms || !unprotectedParms || !payload || !tag) { + return Status("Invalid COSE_Mac0 contents"); + } + + auto [protectedMap, __, errMsg] = cppbor::parse(protectedParms); + if (!protectedMap || !protectedMap->asMap()) { + return Status("Invalid Mac0 protected: " + errMsg); + } + auto& algo = protectedMap->asMap()->get(ALGORITHM); + if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) { + return Status("Unsupported Mac0 algorithm"); + } + + auto pubKey = CoseKey::parse(payload->value(), EC2, ES256, P256); + if (!pubKey) return Status(pubKey.moveMessage()); + + bool testKey = static_cast<bool>(pubKey->getMap().get(CoseKey::TEST_KEY)); + if (testMode && !testKey) { + return Status(BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST, + "Production key in test request"); + } else if (!testMode && testKey) { + return Status(BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST, + "Test key in production request"); + } + + auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value()); + if (!macTag) return Status(STATUS_INVALID_MAC, macTag.moveMessage()); + if (macTag->size() != tag->value().size() || + CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) { + return Status(STATUS_INVALID_MAC, "MAC tag mismatch"); + } + + pubKeysToMac.add(pubKey->moveMap()); + } + + return pubKeysToMac.encode(); +} + +StatusOr<std::pair<bytevec, bytevec>> buildCosePublicKeyFromKmCert( + const keymaster_blob_t* km_cert) { + if (km_cert == nullptr) { + return Status(STATUS_FAILED, "km_cert is a nullptr"); + } + const uint8_t* temp = km_cert->data; + X509* cert = d2i_X509(NULL, &temp, km_cert->data_length); + if (cert == nullptr) { + return Status(STATUS_FAILED, "d2i_X509 returned null when attempting to get the cert."); + } + EVP_PKEY* pubKey = X509_get_pubkey(cert); + if (pubKey == nullptr) { + return Status(STATUS_FAILED, "Boringssl failed to get the public key from the cert"); + } + EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(pubKey); + if (ecKey == nullptr) { + return Status(STATUS_FAILED, + "The key in the certificate returned from GenerateKey is not " + "an EC key."); + } + const EC_POINT* jacobian_coords = EC_KEY_get0_public_key(ecKey); + BIGNUM x; + BIGNUM y; + BN_CTX* ctx = BN_CTX_new(); + if (ctx == nullptr) { + return Status(STATUS_FAILED, "Memory allocation failure for BN_CTX"); + } + if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ecKey), jacobian_coords, &x, &y, + ctx)) { + return Status(STATUS_FAILED, "Failed to get affine coordinates"); + } + bytevec x_bytestring(kAffinePointLength); + bytevec y_bytestring(kAffinePointLength); + if (BN_bn2binpad(&x, x_bytestring.data(), kAffinePointLength) != kAffinePointLength) { + return Status(STATUS_FAILED, "Wrote incorrect number of bytes for x coordinate"); + } + if (BN_bn2binpad(&y, y_bytestring.data(), kAffinePointLength) != kAffinePointLength) { + return Status(STATUS_FAILED, "Wrote incorrect number of bytes for y coordinate"); + } + BN_CTX_free(ctx); + return std::make_pair(x_bytestring, y_bytestring); +} + +cppbor::Array buildCertReqRecipients(const bytevec& pubkey, const bytevec& kid) { + return cppbor::Array() // Array of recipients + .add(cppbor::Array() // Recipient + .add(cppbor::Map() // Protected + .add(ALGORITHM, ECDH_ES_HKDF_256) + .canonicalize() + .encode()) + .add(cppbor::Map() // Unprotected + .add(COSE_KEY, cppbor::Map() + .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) + .add(CoseKey::CURVE, cppcose::X25519) + .add(CoseKey::PUBKEY_X, pubkey) + .canonicalize()) + .add(KEY_ID, kid) + .canonicalize()) + .add(cppbor::Null())); // No ciphertext +} + +static keymaster_key_param_t kKeyMintEcdsaP256Params[] = { + Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN), Authorization(TAG_ALGORITHM, KM_ALGORITHM_EC), + Authorization(TAG_KEY_SIZE, 256), Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256), + Authorization(TAG_EC_CURVE, KM_EC_CURVE_P_256), Authorization(TAG_NO_AUTH_REQUIRED), + // The certificate generated by KM will be discarded, these values don't matter. + Authorization(TAG_CERTIFICATE_NOT_BEFORE, 0), Authorization(TAG_CERTIFICATE_NOT_AFTER, 0)}; + +} // namespace + +RemotelyProvisionedComponent::RemotelyProvisionedComponent( + std::shared_ptr<keymint::AndroidKeyMintDevice> keymint) { + std::tie(devicePrivKey_, bcc_) = generateBcc(); + impl_ = keymint->getKeymasterImpl(); +} + +RemotelyProvisionedComponent::~RemotelyProvisionedComponent() {} + +ScopedAStatus RemotelyProvisionedComponent::generateEcdsaP256KeyPair(bool testMode, + MacedPublicKey* macedPublicKey, + bytevec* privateKeyHandle) { + // TODO(jbires): The following should move from ->GenerateKey to ->GenerateRKPKey and everything + // after the GenerateKey call should basically be moved into that new function call + // as well once the issue with libcppbor in system/keymaster is sorted out + GenerateKeyRequest request(impl_->message_version()); + request.key_description.Reinitialize(kKeyMintEcdsaP256Params, + array_length(kKeyMintEcdsaP256Params)); + GenerateKeyResponse response(impl_->message_version()); + impl_->GenerateKey(request, &response); + if (response.error != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(response.error); + } + + if (response.certificate_chain.entry_count != 1) { + // Error: Need the single non-signed certificate with the public key in it. + return Status(STATUS_FAILED, + "Expected to receive a single certificate from GenerateKey. Instead got: " + + std::to_string(response.certificate_chain.entry_count)); + } + auto affineCoords = buildCosePublicKeyFromKmCert(response.certificate_chain.begin()); + if (!affineCoords.isOk()) return affineCoords.moveError(); + cppbor::Map cosePublicKeyMap = cppbor::Map() + .add(CoseKey::KEY_TYPE, EC2) + .add(CoseKey::ALGORITHM, ES256) + .add(CoseKey::CURVE, cppcose::P256) + .add(CoseKey::PUBKEY_X, affineCoords->first) + .add(CoseKey::PUBKEY_Y, affineCoords->second); + if (testMode) { + cosePublicKeyMap.add(CoseKey::TEST_KEY, cppbor::Null()); + } + + bytevec cosePublicKey = cosePublicKeyMap.canonicalize().encode(); + + auto macedKey = constructCoseMac0(testMode ? remote_prov::kTestMacKey : macKey_, + {} /* externalAad */, cosePublicKey); + if (!macedKey) return Status(macedKey.moveMessage()); + + macedPublicKey->macedKey = macedKey->encode(); + *privateKeyHandle = km_utils::kmBlob2vector(response.key_blob); + return ScopedAStatus::ok(); +} + +ScopedAStatus RemotelyProvisionedComponent::generateCertificateRequest( + bool testMode, const vector<MacedPublicKey>& keysToSign, + const bytevec& endpointEncCertChain, const bytevec& challenge, bytevec* keysToSignMac, + ProtectedData* protectedData) { + auto pubKeysToSign = validateAndExtractPubkeys(testMode, keysToSign, + testMode ? remote_prov::kTestMacKey : macKey_); + if (!pubKeysToSign.isOk()) return pubKeysToSign.moveError(); + + bytevec ephemeralMacKey = remote_prov::randomBytes(SHA256_DIGEST_LENGTH); + + auto pubKeysToSignMac = generateCoseMac0Mac(ephemeralMacKey, bytevec{}, *pubKeysToSign); + if (!pubKeysToSignMac) return Status(pubKeysToSignMac.moveMessage()); + *keysToSignMac = *std::move(pubKeysToSignMac); + + bytevec devicePrivKey; + cppbor::Array bcc; + if (testMode) { + std::tie(devicePrivKey, bcc) = generateBcc(); + } else { + devicePrivKey = devicePrivKey_; + bcc = bcc_.clone(); + } + + auto signedMac = constructCoseSign1(devicePrivKey /* Signing key */, // + ephemeralMacKey /* Payload */, + cppbor::Array() /* AAD */ + .add(challenge) + .add(createDeviceInfo()) + .encode()); + if (!signedMac) return Status(signedMac.moveMessage()); + + bytevec ephemeralPrivKey(X25519_PRIVATE_KEY_LEN); + bytevec ephemeralPubKey(X25519_PUBLIC_VALUE_LEN); + X25519_keypair(ephemeralPubKey.data(), ephemeralPrivKey.data()); + + auto eek = validateAndExtractEekPubAndId(testMode, endpointEncCertChain); + if (!eek.isOk()) return eek.moveError(); + + auto sessionKey = x25519_HKDF_DeriveKey(ephemeralPubKey, ephemeralPrivKey, eek->first, + true /* senderIsA */); + if (!sessionKey) return Status(sessionKey.moveMessage()); + + auto coseEncrypted = + constructCoseEncrypt(*sessionKey, remote_prov::randomBytes(kAesGcmNonceLength), + cppbor::Array() // payload + .add(signedMac.moveValue()) + .add(std::move(bcc)) + .encode(), + {}, // aad + buildCertReqRecipients(ephemeralPubKey, eek->second)); + + if (!coseEncrypted) return Status(coseEncrypted.moveMessage()); + protectedData->protectedData = coseEncrypted->encode(); + + return ScopedAStatus::ok(); +} + +bytevec RemotelyProvisionedComponent::deriveBytesFromHbk(const string& context, + size_t numBytes) const { + bytevec fakeHbk(32, 0); + bytevec result(numBytes); + + // TODO(swillden): Figure out if HKDF can fail. It doesn't seem like it should be able to, + // but the function does return an error code. + HKDF(result.data(), numBytes, // + EVP_sha256(), // + fakeHbk.data(), fakeHbk.size(), // + nullptr /* salt */, 0 /* salt len */, // + reinterpret_cast<const uint8_t*>(context.data()), context.size()); + + return result; +} + +bytevec RemotelyProvisionedComponent::createDeviceInfo() const { + return cppbor::Map().encode(); +} + +std::pair<bytevec /* privKey */, cppbor::Array /* BCC */> +RemotelyProvisionedComponent::generateBcc() { + bytevec privKey(ED25519_PRIVATE_KEY_LEN); + bytevec pubKey(ED25519_PUBLIC_KEY_LEN); + + ED25519_keypair(pubKey.data(), privKey.data()); + + auto coseKey = cppbor::Map() + .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) + .add(CoseKey::ALGORITHM, EDDSA) + .add(CoseKey::CURVE, ED25519) + .add(CoseKey::KEY_OPS, VERIFY) + .add(CoseKey::PUBKEY_X, pubKey) + .canonicalize() + .encode(); + auto sign1Payload = cppbor::Map() + .add(1 /* Issuer */, "Issuer") + .add(2 /* Subject */, "Subject") + .add(-4670552 /* Subject Pub Key */, coseKey) + .add(-4670553 /* Key Usage */, + std::vector<uint8_t>(0x05) /* Big endian order */) + .canonicalize() + .encode(); + auto coseSign1 = constructCoseSign1(privKey, /* signing key */ + cppbor::Map(), /* extra protected */ + sign1Payload, {} /* AAD */); + assert(coseSign1); + + return {privKey, cppbor::Array().add(coseKey).add(coseSign1.moveValue())}; +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.h b/security/keymint/aidl/default/RemotelyProvisionedComponent.h new file mode 100644 index 0000000000..e8d2343091 --- /dev/null +++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#pragma once + +#include <AndroidKeyMintDevice.h> +#include <aidl/android/hardware/security/keymint/BnRemotelyProvisionedComponent.h> +#include <aidl/android/hardware/security/keymint/SecurityLevel.h> +#include <cppbor.h> +#include <keymaster/UniquePtr.h> +#include <keymaster/android_keymaster.h> + +namespace aidl::android::hardware::security::keymint { + +using ::ndk::ScopedAStatus; + +class RemotelyProvisionedComponent : public BnRemotelyProvisionedComponent { + public: + explicit RemotelyProvisionedComponent(std::shared_ptr<keymint::AndroidKeyMintDevice> keymint); + virtual ~RemotelyProvisionedComponent(); + + ScopedAStatus generateEcdsaP256KeyPair(bool testMode, MacedPublicKey* macedPublicKey, + std::vector<uint8_t>* privateKeyHandle) override; + + ScopedAStatus generateCertificateRequest(bool testMode, + const std::vector<MacedPublicKey>& keysToSign, + const std::vector<uint8_t>& endpointEncCertChain, + const std::vector<uint8_t>& challenge, + std::vector<uint8_t>* keysToSignMac, + ProtectedData* protectedData) override; + + private: + // TODO(swillden): Move these into an appropriate Context class. + std::vector<uint8_t> deriveBytesFromHbk(const std::string& context, size_t numBytes) const; + std::vector<uint8_t> createDeviceInfo() const; + std::pair<std::vector<uint8_t>, cppbor::Array> generateBcc(); + + std::vector<uint8_t> macKey_ = deriveBytesFromHbk("Key to MAC public keys", 32); + std::vector<uint8_t> devicePrivKey_; + cppbor::Array bcc_; + std::shared_ptr<::keymaster::AndroidKeymaster> impl_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/security/keymint/aidl/default/android.hardware.security.keymint-service.xml b/security/keymint/aidl/default/android.hardware.security.keymint-service.xml index 73d15a8024..4aa05efe16 100644 --- a/security/keymint/aidl/default/android.hardware.security.keymint-service.xml +++ b/security/keymint/aidl/default/android.hardware.security.keymint-service.xml @@ -3,4 +3,8 @@ <name>android.hardware.security.keymint</name> <fqname>IKeyMintDevice/default</fqname> </hal> + <hal format="aidl"> + <name>android.hardware.security.keymint</name> + <fqname>IRemotelyProvisionedComponent/default</fqname> + </hal> </manifest> diff --git a/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml b/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml new file mode 100644 index 0000000000..c0ff7752ba --- /dev/null +++ b/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml @@ -0,0 +1,6 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.security.secureclock</name> + <fqname>ISecureClock/default</fqname> + </hal> +</manifest> diff --git a/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml b/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml new file mode 100644 index 0000000000..d37981fe4d --- /dev/null +++ b/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml @@ -0,0 +1,6 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.security.sharedsecret</name> + <fqname>ISharedSecret/default</fqname> + </hal> +</manifest> diff --git a/security/keymint/aidl/default/service.cpp b/security/keymint/aidl/default/service.cpp index a710535fac..bcebbaf8cf 100644 --- a/security/keymint/aidl/default/service.cpp +++ b/security/keymint/aidl/default/service.cpp @@ -21,25 +21,42 @@ #include <android/binder_process.h> #include <AndroidKeyMintDevice.h> +#include <AndroidSecureClock.h> +#include <AndroidSharedSecret.h> #include <keymaster/soft_keymaster_logger.h> +#include "RemotelyProvisionedComponent.h" + using aidl::android::hardware::security::keymint::AndroidKeyMintDevice; +using aidl::android::hardware::security::keymint::RemotelyProvisionedComponent; using aidl::android::hardware::security::keymint::SecurityLevel; +using aidl::android::hardware::security::secureclock::AndroidSecureClock; +using aidl::android::hardware::security::sharedsecret::AndroidSharedSecret; + +template <typename T, class... Args> +std::shared_ptr<T> addService(Args&&... args) { + std::shared_ptr<T> ser = ndk::SharedRefBase::make<T>(std::forward<Args>(args)...); + auto instanceName = std::string(T::descriptor) + "/default"; + LOG(INFO) << "adding keymint service instance: " << instanceName; + binder_status_t status = + AServiceManager_addService(ser->asBinder().get(), instanceName.c_str()); + CHECK(status == STATUS_OK); + return ser; +} int main() { // Zero threads seems like a useless pool, but below we'll join this thread to it, increasing // the pool size to 1. ABinderProcess_setThreadPoolMaxThreadCount(0); + // Add Keymint Service std::shared_ptr<AndroidKeyMintDevice> keyMint = - ndk::SharedRefBase::make<AndroidKeyMintDevice>(SecurityLevel::SOFTWARE); - - keymaster::SoftKeymasterLogger logger; - const auto instanceName = std::string(AndroidKeyMintDevice::descriptor) + "/default"; - LOG(INFO) << "instance: " << instanceName; - binder_status_t status = - AServiceManager_addService(keyMint->asBinder().get(), instanceName.c_str()); - CHECK(status == STATUS_OK); - + addService<AndroidKeyMintDevice>(SecurityLevel::SOFTWARE); + // Add Secure Clock Service + addService<AndroidSecureClock>(keyMint); + // Add Shared Secret Service + addService<AndroidSharedSecret>(keyMint); + // Add Remotely Provisioned Component Service + addService<RemotelyProvisionedComponent>(keyMint); ABinderProcess_joinThreadPool(); return EXIT_FAILURE; // should not reach } diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp index f4ba9e7362..24fe61641c 100644 --- a/security/keymint/aidl/vts/functional/Android.bp +++ b/security/keymint/aidl/vts/functional/Android.bp @@ -21,6 +21,7 @@ cc_test { "use_libaidlvintf_gtest_helper_static", ], srcs: [ + "AttestKeyTest.cpp", "KeyMintTest.cpp", ], shared_libs: [ @@ -62,6 +63,36 @@ cc_test_library { static_libs: [ "android.hardware.security.keymint-V1-ndk_platform", "android.hardware.security.secureclock-V1-ndk_platform", - "libcppbor", + "libcppbor_external", + ], +} + +cc_test { + name: "VtsHalRemotelyProvisionedComponentTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsRemotelyProvisionedComponentTests.cpp", + ], + shared_libs: [ + "libbinder_ndk", + "libcppbor_external", + "libcrypto", + "libkeymaster_portable", + "libpuresoftkeymasterdevice", + ], + static_libs: [ + "android.hardware.security.keymint-V1-ndk_platform", + "libcppcose", + "libgmock_ndk", + "libremote_provisioner", + "libkeymint", + "libkeymint_remote_prov_support", + ], + test_suites: [ + "general-tests", + "vts", ], } diff --git a/security/keymint/aidl/vts/functional/AttestKeyTest.cpp b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp new file mode 100644 index 0000000000..7e7a466566 --- /dev/null +++ b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "keymint_1_attest_key_test" +#include <cutils/log.h> + +#include <keymint_support/key_param_output.h> +#include <keymint_support/openssl_utils.h> + +#include "KeyMintAidlTestBase.h" + +namespace aidl::android::hardware::security::keymint::test { + +namespace { + +vector<uint8_t> make_name_from_str(const string& name) { + X509_NAME_Ptr x509_name(X509_NAME_new()); + EXPECT_TRUE(x509_name.get() != nullptr); + if (!x509_name) return {}; + + EXPECT_EQ(1, X509_NAME_add_entry_by_txt(x509_name.get(), // + "CN", // + MBSTRING_ASC, + reinterpret_cast<const uint8_t*>(name.c_str()), + -1, // len + -1, // loc + 0 /* set */)); + + int len = i2d_X509_NAME(x509_name.get(), nullptr /* only return length */); + EXPECT_GT(len, 0); + + vector<uint8_t> retval(len); + uint8_t* p = retval.data(); + i2d_X509_NAME(x509_name.get(), &p); + + return retval; +} + +bool IsSelfSigned(const vector<Certificate>& chain) { + if (chain.size() != 1) return false; + return ChainSignaturesAreValid(chain); +} + +} // namespace + +using AttestKeyTest = KeyMintAidlTestBase; + +TEST_P(AttestKeyTest, AllRsaSizes) { + for (auto size : ValidKeySizes(Algorithm::RSA)) { + /* + * Create attestaton key. + */ + AttestationKey attest_key; + vector<KeyCharacteristics> attest_key_characteristics; + vector<Certificate> attest_key_cert_chain; + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(size, 65537) + .AttestKey() + .SetDefaultValidity(), + {} /* attestation signing key */, &attest_key.keyBlob, + &attest_key_characteristics, &attest_key_cert_chain)); + + EXPECT_EQ(attest_key_cert_chain.size(), 1); + EXPECT_TRUE(IsSelfSigned(attest_key_cert_chain)) << "Failed on size " << size; + + /* + * Use attestation key to sign RSA key + */ + attest_key.issuerSubjectName = make_name_from_str("Android Keystore Key"); + vector<uint8_t> attested_key_blob; + vector<KeyCharacteristics> attested_key_characteristics; + vector<Certificate> attested_key_cert_chain; + EXPECT_EQ(ErrorCode::OK, + GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(2048, 65537) + .Authorization(TAG_NO_AUTH_REQUIRED) + .AttestationChallenge("foo") + .AttestationApplicationId("bar") + .SetDefaultValidity(), + attest_key, &attested_key_blob, &attested_key_characteristics, + &attested_key_cert_chain)); + + CheckedDeleteKey(&attested_key_blob); + + AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics); + AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics); + EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(), + attested_key_cert_chain[0].encodedCertificate)); + + // Attestation by itself is not valid (last entry is not self-signed). + EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain)); + + // Appending the attest_key chain to the attested_key_chain should yield a valid chain. + if (attest_key_cert_chain.size() > 0) { + attested_key_cert_chain.push_back(attest_key_cert_chain[0]); + } + EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain)); + + /* + * Use attestation key to sign EC key + */ + EXPECT_EQ(ErrorCode::OK, + GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(EcCurve::P_256) + .Authorization(TAG_NO_AUTH_REQUIRED) + .AttestationChallenge("foo") + .AttestationApplicationId("bar") + .SetDefaultValidity(), + attest_key, &attested_key_blob, &attested_key_characteristics, + &attested_key_cert_chain)); + + CheckedDeleteKey(&attested_key_blob); + CheckedDeleteKey(&attest_key.keyBlob); + + hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics); + sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics); + EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(), + attested_key_cert_chain[0].encodedCertificate)); + + // Attestation by itself is not valid (last entry is not self-signed). + EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain)); + + // Appending the attest_key chain to the attested_key_chain should yield a valid chain. + if (attest_key_cert_chain.size() > 0) { + attested_key_cert_chain.push_back(attest_key_cert_chain[0]); + } + EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain)); + + // Bail early if anything failed. + if (HasFailure()) return; + } +} + +TEST_P(AttestKeyTest, AllEcCurves) { + for (auto curve : ValidCurves()) { + /* + * Create attestaton key. + */ + AttestationKey attest_key; + vector<KeyCharacteristics> attest_key_characteristics; + vector<Certificate> attest_key_cert_chain; + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(curve) + .AttestKey() + .SetDefaultValidity(), + {} /* attestation siging key */, &attest_key.keyBlob, + &attest_key_characteristics, &attest_key_cert_chain)); + + EXPECT_EQ(attest_key_cert_chain.size(), 1); + EXPECT_TRUE(IsSelfSigned(attest_key_cert_chain)) << "Failed on curve " << curve; + + /* + * Use attestation key to sign RSA key + */ + attest_key.issuerSubjectName = make_name_from_str("Android Keystore Key"); + vector<uint8_t> attested_key_blob; + vector<KeyCharacteristics> attested_key_characteristics; + vector<Certificate> attested_key_cert_chain; + EXPECT_EQ(ErrorCode::OK, + GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(2048, 65537) + .Authorization(TAG_NO_AUTH_REQUIRED) + .AttestationChallenge("foo") + .AttestationApplicationId("bar") + .SetDefaultValidity(), + attest_key, &attested_key_blob, &attested_key_characteristics, + &attested_key_cert_chain)); + + CheckedDeleteKey(&attested_key_blob); + + AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics); + AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics); + EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(), + attested_key_cert_chain[0].encodedCertificate)); + + // Attestation by itself is not valid (last entry is not self-signed). + EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain)); + + // Appending the attest_key chain to the attested_key_chain should yield a valid chain. + if (attest_key_cert_chain.size() > 0) { + attested_key_cert_chain.push_back(attest_key_cert_chain[0]); + } + EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain)); + + /* + * Use attestation key to sign EC key + */ + EXPECT_EQ(ErrorCode::OK, + GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(EcCurve::P_256) + .Authorization(TAG_NO_AUTH_REQUIRED) + .AttestationChallenge("foo") + .AttestationApplicationId("bar") + .SetDefaultValidity(), + attest_key, &attested_key_blob, &attested_key_characteristics, + &attested_key_cert_chain)); + + CheckedDeleteKey(&attested_key_blob); + CheckedDeleteKey(&attest_key.keyBlob); + + hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics); + sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics); + EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(), + attested_key_cert_chain[0].encodedCertificate)); + + // Attestation by itself is not valid (last entry is not self-signed). + EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain)); + + // Appending the attest_key chain to the attested_key_chain should yield a valid chain. + if (attest_key_cert_chain.size() > 0) { + attested_key_cert_chain.push_back(attest_key_cert_chain[0]); + } + EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain)); + + // Bail early if anything failed. + if (HasFailure()) return; + } +} + +INSTANTIATE_KEYMINT_AIDL_TEST(AttestKeyTest); + +} // namespace aidl::android::hardware::security::keymint::test diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp index 6555157e5c..d61a081232 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp @@ -22,15 +22,23 @@ #include <android-base/logging.h> #include <android/binder_manager.h> +#include <cutils/properties.h> +#include <openssl/mem.h> +#include <keymint_support/attestation_record.h> #include <keymint_support/key_param_output.h> #include <keymint_support/keymint_utils.h> +#include <keymint_support/openssl_utils.h> namespace aidl::android::hardware::security::keymint { using namespace std::literals::chrono_literals; using std::endl; using std::optional; +using std::unique_ptr; +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; ::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set) { if (set.size() == 0) @@ -45,7 +53,7 @@ using std::optional; namespace test { namespace { - +typedef KeyMintAidlTestBase::KeyData KeyData; // Predicate for testing basic characteristics validity in generation or import. bool KeyCharacteristicsBasicallyValid(SecurityLevel secLevel, const vector<KeyCharacteristics>& key_characteristics) { @@ -73,8 +81,67 @@ bool KeyCharacteristicsBasicallyValid(SecurityLevel secLevel, return true; } +// Extract attestation record from cert. Returned object is still part of cert; don't free it +// separately. +ASN1_OCTET_STRING* get_attestation_record(X509* certificate) { + ASN1_OBJECT_Ptr oid(OBJ_txt2obj(kAttestionRecordOid, 1 /* dotted string format */)); + EXPECT_TRUE(!!oid.get()); + if (!oid.get()) return nullptr; + + int location = X509_get_ext_by_OBJ(certificate, oid.get(), -1 /* search from beginning */); + EXPECT_NE(-1, location) << "Attestation extension not found in certificate"; + if (location == -1) return nullptr; + + X509_EXTENSION* attest_rec_ext = X509_get_ext(certificate, location); + EXPECT_TRUE(!!attest_rec_ext) + << "Found attestation extension but couldn't retrieve it? Probably a BoringSSL bug."; + if (!attest_rec_ext) return nullptr; + + ASN1_OCTET_STRING* attest_rec = X509_EXTENSION_get_data(attest_rec_ext); + EXPECT_TRUE(!!attest_rec) << "Attestation extension contained no data"; + return attest_rec; +} + +bool avb_verification_enabled() { + char value[PROPERTY_VALUE_MAX]; + return property_get("ro.boot.vbmeta.device_state", value, "") != 0; +} + +char nibble2hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +// Attestations don't contain everything in key authorization lists, so we need to filter the key +// lists to produce the lists that we expect to match the attestations. +auto kTagsToFilter = { + Tag::BLOB_USAGE_REQUIREMENTS, // + Tag::CREATION_DATETIME, // + Tag::EC_CURVE, + Tag::HARDWARE_TYPE, + Tag::INCLUDE_UNIQUE_ID, +}; + +AuthorizationSet filtered_tags(const AuthorizationSet& set) { + AuthorizationSet filtered; + std::remove_copy_if( + set.begin(), set.end(), std::back_inserter(filtered), [](const auto& entry) -> bool { + return std::find(kTagsToFilter.begin(), kTagsToFilter.end(), entry.tag) != + kTagsToFilter.end(); + }); + return filtered; +} + +string x509NameToStr(X509_NAME* name) { + char* s = X509_NAME_oneline(name, nullptr, 0); + string retval(s); + OPENSSL_free(s); + return retval; +} + } // namespace +bool KeyMintAidlTestBase::arm_deleteAllKeys = false; +bool KeyMintAidlTestBase::dump_Attestations = false; + ErrorCode KeyMintAidlTestBase::GetReturnErrorCode(const Status& result) { if (result.isOk()) return ErrorCode::OK; @@ -110,48 +177,48 @@ void KeyMintAidlTestBase::SetUp() { } ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc, + const optional<AttestationKey>& attest_key, vector<uint8_t>* key_blob, - vector<KeyCharacteristics>* key_characteristics) { + vector<KeyCharacteristics>* key_characteristics, + vector<Certificate>* cert_chain) { EXPECT_NE(key_blob, nullptr) << "Key blob pointer must not be null. Test bug"; EXPECT_NE(key_characteristics, nullptr) << "Previous characteristics not deleted before generating key. Test bug."; - // Aidl does not clear these output parameters if the function returns - // error. This is different from hal where output parameter is always - // cleared due to hal returning void. So now we need to do our own clearing - // of the output variables prior to calling keyMint aidl libraries. - key_blob->clear(); - key_characteristics->clear(); - cert_chain_.clear(); - KeyCreationResult creationResult; - Status result = keymint_->generateKey(key_desc.vector_data(), &creationResult); - + Status result = keymint_->generateKey(key_desc.vector_data(), attest_key, &creationResult); if (result.isOk()) { EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(), creationResult.keyCharacteristics); EXPECT_GT(creationResult.keyBlob.size(), 0); *key_blob = std::move(creationResult.keyBlob); *key_characteristics = std::move(creationResult.keyCharacteristics); - cert_chain_ = std::move(creationResult.certificateChain); + *cert_chain = std::move(creationResult.certificateChain); auto algorithm = key_desc.GetTagValue(TAG_ALGORITHM); EXPECT_TRUE(algorithm); if (algorithm && (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) { - EXPECT_GE(cert_chain_.size(), 1); - if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) EXPECT_GT(cert_chain_.size(), 1); + EXPECT_GE(cert_chain->size(), 1); + if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) { + if (attest_key) { + EXPECT_EQ(cert_chain->size(), 1); + } else { + EXPECT_GT(cert_chain->size(), 1); + } + } } else { // For symmetric keys there should be no certificates. - EXPECT_EQ(cert_chain_.size(), 0); + EXPECT_EQ(cert_chain->size(), 0); } } return GetReturnErrorCode(result); } -ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc) { - return GenerateKey(key_desc, &key_blob_, &key_characteristics_); +ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc, + const optional<AttestationKey>& attest_key) { + return GenerateKey(key_desc, attest_key, &key_blob_, &key_characteristics_, &cert_chain_); } ErrorCode KeyMintAidlTestBase::ImportKey(const AuthorizationSet& key_desc, KeyFormat format, @@ -166,7 +233,7 @@ ErrorCode KeyMintAidlTestBase::ImportKey(const AuthorizationSet& key_desc, KeyFo KeyCreationResult creationResult; result = keymint_->importKey(key_desc.vector_data(), format, vector<uint8_t>(key_material.begin(), key_material.end()), - &creationResult); + {} /* attestationSigningKeyBlob */, &creationResult); if (result.isOk()) { EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(), @@ -461,6 +528,34 @@ void KeyMintAidlTestBase::AbortIfNeeded() { } } +auto KeyMintAidlTestBase::ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation, + const string& message, const AuthorizationSet& in_params) + -> std::tuple<ErrorCode, string, AuthorizationSet /* out_params */> { + AuthorizationSet begin_out_params; + ErrorCode result = Begin(operation, key_blob, in_params, &begin_out_params); + AuthorizationSet out_params(std::move(begin_out_params)); + if (result != ErrorCode::OK) { + return {result, {}, out_params}; + } + + string output; + int32_t consumed = 0; + AuthorizationSet update_params; + AuthorizationSet update_out_params; + result = Update(update_params, message, &update_out_params, &output, &consumed); + out_params.push_back(update_out_params); + if (result != ErrorCode::OK) { + return {result, output, out_params}; + } + + string unused; + AuthorizationSet finish_params; + AuthorizationSet finish_out_params; + result = Finish(finish_params, message.substr(consumed), unused, &finish_out_params, &output); + out_params.push_back(finish_out_params); + return {result, output, out_params}; +} + string KeyMintAidlTestBase::ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation, const string& message, const AuthorizationSet& in_params, AuthorizationSet* out_params) { @@ -859,6 +954,269 @@ AuthorizationSet KeyMintAidlTestBase::SwEnforcedAuthorizations( return authList; } +ErrorCode KeyMintAidlTestBase::UseAesKey(const vector<uint8_t>& aesKeyBlob) { + auto [result, ciphertext, out_params] = ProcessMessage( + aesKeyBlob, KeyPurpose::ENCRYPT, "1234567890123456", + AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::NONE)); + return result; +} + +ErrorCode KeyMintAidlTestBase::UseHmacKey(const vector<uint8_t>& hmacKeyBlob) { + auto [result, mac, out_params] = ProcessMessage( + hmacKeyBlob, KeyPurpose::SIGN, "1234567890123456", + AuthorizationSetBuilder().Authorization(TAG_MAC_LENGTH, 128).Digest(Digest::SHA_2_256)); + return result; +} + +ErrorCode KeyMintAidlTestBase::UseRsaKey(const vector<uint8_t>& rsaKeyBlob) { + std::string message(2048 / 8, 'a'); + auto [result, signature, out_params] = ProcessMessage( + rsaKeyBlob, KeyPurpose::SIGN, message, + AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)); + return result; +} + +ErrorCode KeyMintAidlTestBase::UseEcdsaKey(const vector<uint8_t>& ecdsaKeyBlob) { + auto [result, signature, out_params] = + ProcessMessage(ecdsaKeyBlob, KeyPurpose::SIGN, "a", + AuthorizationSetBuilder().Digest(Digest::SHA_2_256)); + return result; +} + +bool verify_attestation_record(const string& challenge, // + const string& app_id, // + AuthorizationSet expected_sw_enforced, // + AuthorizationSet expected_hw_enforced, // + SecurityLevel security_level, + const vector<uint8_t>& attestation_cert) { + X509_Ptr cert(parse_cert_blob(attestation_cert)); + EXPECT_TRUE(!!cert.get()); + if (!cert.get()) return false; + + ASN1_OCTET_STRING* attest_rec = get_attestation_record(cert.get()); + EXPECT_TRUE(!!attest_rec); + if (!attest_rec) return false; + + AuthorizationSet att_sw_enforced; + AuthorizationSet att_hw_enforced; + uint32_t att_attestation_version; + uint32_t att_keymaster_version; + SecurityLevel att_attestation_security_level; + SecurityLevel att_keymaster_security_level; + vector<uint8_t> att_challenge; + vector<uint8_t> att_unique_id; + vector<uint8_t> att_app_id; + + auto error = parse_attestation_record(attest_rec->data, // + attest_rec->length, // + &att_attestation_version, // + &att_attestation_security_level, // + &att_keymaster_version, // + &att_keymaster_security_level, // + &att_challenge, // + &att_sw_enforced, // + &att_hw_enforced, // + &att_unique_id); + EXPECT_EQ(ErrorCode::OK, error); + if (error != ErrorCode::OK) return false; + + EXPECT_GE(att_attestation_version, 3U); + + expected_sw_enforced.push_back(TAG_ATTESTATION_APPLICATION_ID, + vector<uint8_t>(app_id.begin(), app_id.end())); + + EXPECT_GE(att_keymaster_version, 4U); + EXPECT_EQ(security_level, att_keymaster_security_level); + EXPECT_EQ(security_level, att_attestation_security_level); + + EXPECT_EQ(challenge.length(), att_challenge.size()); + EXPECT_EQ(0, memcmp(challenge.data(), att_challenge.data(), challenge.length())); + + char property_value[PROPERTY_VALUE_MAX] = {}; + // TODO(b/136282179): When running under VTS-on-GSI the TEE-backed + // keymaster implementation will report YYYYMM dates instead of YYYYMMDD + // for the BOOT_PATCH_LEVEL. + if (avb_verification_enabled()) { + for (int i = 0; i < att_hw_enforced.size(); i++) { + if (att_hw_enforced[i].tag == TAG_BOOT_PATCHLEVEL || + att_hw_enforced[i].tag == TAG_VENDOR_PATCHLEVEL) { + std::string date = + std::to_string(att_hw_enforced[i].value.get<KeyParameterValue::dateTime>()); + // strptime seems to require delimiters, but the tag value will + // be YYYYMMDD + date.insert(6, "-"); + date.insert(4, "-"); + EXPECT_EQ(date.size(), 10); + struct tm time; + strptime(date.c_str(), "%Y-%m-%d", &time); + + // Day of the month (0-31) + EXPECT_GE(time.tm_mday, 0); + EXPECT_LT(time.tm_mday, 32); + // Months since Jan (0-11) + EXPECT_GE(time.tm_mon, 0); + EXPECT_LT(time.tm_mon, 12); + // Years since 1900 + EXPECT_GT(time.tm_year, 110); + EXPECT_LT(time.tm_year, 200); + } + } + } + + // Check to make sure boolean values are properly encoded. Presence of a boolean tag + // indicates true. A provided boolean tag that can be pulled back out of the certificate + // indicates correct encoding. No need to check if it's in both lists, since the + // AuthorizationSet compare below will handle mismatches of tags. + if (security_level == SecurityLevel::SOFTWARE) { + EXPECT_TRUE(expected_sw_enforced.Contains(TAG_NO_AUTH_REQUIRED)); + } else { + EXPECT_TRUE(expected_hw_enforced.Contains(TAG_NO_AUTH_REQUIRED)); + } + + // Alternatively this checks the opposite - a false boolean tag (one that isn't provided in + // the authorization list during key generation) isn't being attested to in the certificate. + EXPECT_FALSE(expected_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + EXPECT_FALSE(att_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + EXPECT_FALSE(expected_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + EXPECT_FALSE(att_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + + if (att_hw_enforced.Contains(TAG_ALGORITHM, Algorithm::EC)) { + // For ECDSA keys, either an EC_CURVE or a KEY_SIZE can be specified, but one must be. + EXPECT_TRUE(att_hw_enforced.Contains(TAG_EC_CURVE) || + att_hw_enforced.Contains(TAG_KEY_SIZE)); + } + + // Test root of trust elements + vector<uint8_t> verified_boot_key; + VerifiedBoot verified_boot_state; + bool device_locked; + vector<uint8_t> verified_boot_hash; + error = parse_root_of_trust(attest_rec->data, attest_rec->length, &verified_boot_key, + &verified_boot_state, &device_locked, &verified_boot_hash); + EXPECT_EQ(ErrorCode::OK, error); + + if (avb_verification_enabled()) { + EXPECT_NE(property_get("ro.boot.vbmeta.digest", property_value, ""), 0); + string prop_string(property_value); + EXPECT_EQ(prop_string.size(), 64); + EXPECT_EQ(prop_string, bin2hex(verified_boot_hash)); + + EXPECT_NE(property_get("ro.boot.vbmeta.device_state", property_value, ""), 0); + if (!strcmp(property_value, "unlocked")) { + EXPECT_FALSE(device_locked); + } else { + EXPECT_TRUE(device_locked); + } + + // Check that the device is locked if not debuggable, e.g., user build + // images in CTS. For VTS, debuggable images are used to allow adb root + // and the device is unlocked. + if (!property_get_bool("ro.debuggable", false)) { + EXPECT_TRUE(device_locked); + } else { + EXPECT_FALSE(device_locked); + } + } + + // Verified boot key should be all 0's if the boot state is not verified or self signed + std::string empty_boot_key(32, '\0'); + std::string verified_boot_key_str((const char*)verified_boot_key.data(), + verified_boot_key.size()); + EXPECT_NE(property_get("ro.boot.verifiedbootstate", property_value, ""), 0); + if (!strcmp(property_value, "green")) { + EXPECT_EQ(verified_boot_state, VerifiedBoot::VERIFIED); + EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), + verified_boot_key.size())); + } else if (!strcmp(property_value, "yellow")) { + EXPECT_EQ(verified_boot_state, VerifiedBoot::SELF_SIGNED); + EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), + verified_boot_key.size())); + } else if (!strcmp(property_value, "orange")) { + EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED); + EXPECT_EQ(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), + verified_boot_key.size())); + } else if (!strcmp(property_value, "red")) { + EXPECT_EQ(verified_boot_state, VerifiedBoot::FAILED); + } else { + EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED); + EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), + verified_boot_key.size())); + } + + att_sw_enforced.Sort(); + expected_sw_enforced.Sort(); + auto a = filtered_tags(expected_sw_enforced); + auto b = filtered_tags(att_sw_enforced); + EXPECT_EQ(a, b); + + att_hw_enforced.Sort(); + expected_hw_enforced.Sort(); + EXPECT_EQ(filtered_tags(expected_hw_enforced), filtered_tags(att_hw_enforced)); + + return true; +} + +string bin2hex(const vector<uint8_t>& data) { + string retval; + retval.reserve(data.size() * 2 + 1); + for (uint8_t byte : data) { + retval.push_back(nibble2hex[0x0F & (byte >> 4)]); + retval.push_back(nibble2hex[0x0F & byte]); + } + return retval; +} + +AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain) { + std::stringstream cert_data; + + for (size_t i = 0; i < chain.size(); ++i) { + cert_data << bin2hex(chain[i].encodedCertificate) << std::endl; + + X509_Ptr key_cert(parse_cert_blob(chain[i].encodedCertificate)); + X509_Ptr signing_cert; + if (i < chain.size() - 1) { + signing_cert = parse_cert_blob(chain[i + 1].encodedCertificate); + } else { + signing_cert = parse_cert_blob(chain[i].encodedCertificate); + } + if (!key_cert.get() || !signing_cert.get()) return AssertionFailure() << cert_data.str(); + + EVP_PKEY_Ptr signing_pubkey(X509_get_pubkey(signing_cert.get())); + if (!signing_pubkey.get()) return AssertionFailure() << cert_data.str(); + + if (!X509_verify(key_cert.get(), signing_pubkey.get())) { + return AssertionFailure() + << "Verification of certificate " << i << " failed " + << "OpenSSL error string: " << ERR_error_string(ERR_get_error(), NULL) << '\n' + << cert_data.str(); + } + + string cert_issuer = x509NameToStr(X509_get_issuer_name(key_cert.get())); + string signer_subj = x509NameToStr(X509_get_subject_name(signing_cert.get())); + if (cert_issuer != signer_subj) { + return AssertionFailure() << "Cert " << i << " has wrong issuer.\n" << cert_data.str(); + } + + if (i == 0) { + string cert_sub = x509NameToStr(X509_get_subject_name(key_cert.get())); + if ("/CN=Android Keystore Key" != cert_sub) { + return AssertionFailure() + << "Leaf cert has wrong subject, should be CN=Android Keystore Key, was " + << cert_sub << '\n' + << cert_data.str(); + } + } + } + + if (KeyMintAidlTestBase::dump_Attestations) std::cout << cert_data.str(); + return AssertionSuccess(); +} + +X509_Ptr parse_cert_blob(const vector<uint8_t>& blob) { + const uint8_t* p = blob.data(); + return X509_Ptr(d2i_X509(nullptr /* allocate new */, &p, blob.size())); +} + } // namespace test } // namespace aidl::android::hardware::security::keymint diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h index 780971dc8c..452d2b6f6d 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h @@ -21,20 +21,27 @@ #include <binder/IServiceManager.h> #include <binder/ProcessState.h> #include <gtest/gtest.h> +#include <openssl/x509.h> #include <aidl/android/hardware/security/keymint/ErrorCode.h> #include <aidl/android/hardware/security/keymint/IKeyMintDevice.h> #include <keymint_support/authorization_set.h> +#include <keymint_support/openssl_utils.h> namespace aidl::android::hardware::security::keymint { ::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set); +inline bool operator==(const keymint::AuthorizationSet& a, const keymint::AuthorizationSet& b) { + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); +} + namespace test { using ::android::sp; using Status = ::ndk::ScopedAStatus; +using ::std::optional; using ::std::shared_ptr; using ::std::string; using ::std::vector; @@ -43,6 +50,14 @@ constexpr uint64_t kOpHandleSentinel = 0xFFFFFFFFFFFFFFFF; class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { public: + struct KeyData { + vector<uint8_t> blob; + vector<KeyCharacteristics> characteristics; + }; + + static bool arm_deleteAllKeys; + static bool dump_Attestations; + void SetUp() override; void TearDown() override { if (key_blob_.size()) { @@ -57,10 +72,18 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { uint32_t os_patch_level() { return os_patch_level_; } ErrorCode GetReturnErrorCode(const Status& result); - ErrorCode GenerateKey(const AuthorizationSet& key_desc, vector<uint8_t>* key_blob, - vector<KeyCharacteristics>* key_characteristics); - ErrorCode GenerateKey(const AuthorizationSet& key_desc); + ErrorCode GenerateKey(const AuthorizationSet& key_desc, vector<uint8_t>* key_blob, + vector<KeyCharacteristics>* key_characteristics) { + return GenerateKey(key_desc, std::nullopt /* attest_key */, key_blob, key_characteristics, + &cert_chain_); + } + ErrorCode GenerateKey(const AuthorizationSet& key_desc, + const optional<AttestationKey>& attest_key, vector<uint8_t>* key_blob, + vector<KeyCharacteristics>* key_characteristics, + vector<Certificate>* cert_chain); + ErrorCode GenerateKey(const AuthorizationSet& key_desc, + const optional<AttestationKey>& attest_key = std::nullopt); ErrorCode ImportKey(const AuthorizationSet& key_desc, KeyFormat format, const string& key_material, vector<uint8_t>* key_blob, @@ -106,7 +129,9 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { string ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation, const string& message, const AuthorizationSet& in_params, AuthorizationSet* out_params); - + std::tuple<ErrorCode, std::string /* processedMessage */, AuthorizationSet /* out_params */> + ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation, + const std::string& message, const AuthorizationSet& in_params); string SignMessage(const vector<uint8_t>& key_blob, const string& message, const AuthorizationSet& params); string SignMessage(const string& message, const AuthorizationSet& params); @@ -149,6 +174,56 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { std::pair<ErrorCode, vector<uint8_t>> UpgradeKey(const vector<uint8_t>& key_blob); + template <typename TagType> + std::tuple<KeyData /* aesKey */, KeyData /* hmacKey */, KeyData /* rsaKey */, + KeyData /* ecdsaKey */> + CreateTestKeys(TagType tagToTest, ErrorCode expectedReturn) { + /* AES */ + KeyData aesKeyData; + ErrorCode errorCode = GenerateKey(AuthorizationSetBuilder() + .AesEncryptionKey(128) + .Authorization(tagToTest) + .BlockMode(BlockMode::ECB) + .Padding(PaddingMode::NONE) + .Authorization(TAG_NO_AUTH_REQUIRED), + &aesKeyData.blob, &aesKeyData.characteristics); + EXPECT_EQ(expectedReturn, errorCode); + + /* HMAC */ + KeyData hmacKeyData; + errorCode = GenerateKey(AuthorizationSetBuilder() + .HmacKey(128) + .Authorization(tagToTest) + .Digest(Digest::SHA_2_256) + .Authorization(TAG_MIN_MAC_LENGTH, 128) + .Authorization(TAG_NO_AUTH_REQUIRED), + &hmacKeyData.blob, &hmacKeyData.characteristics); + EXPECT_EQ(expectedReturn, errorCode); + + /* RSA */ + KeyData rsaKeyData; + errorCode = GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(2048, 65537) + .Authorization(tagToTest) + .Digest(Digest::NONE) + .Padding(PaddingMode::NONE) + .Authorization(TAG_NO_AUTH_REQUIRED) + .SetDefaultValidity(), + &rsaKeyData.blob, &rsaKeyData.characteristics); + EXPECT_EQ(expectedReturn, errorCode); + + /* ECDSA */ + KeyData ecdsaKeyData; + errorCode = GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(256) + .Authorization(tagToTest) + .Digest(Digest::SHA_2_256) + .Authorization(TAG_NO_AUTH_REQUIRED) + .SetDefaultValidity(), + &ecdsaKeyData.blob, &ecdsaKeyData.characteristics); + EXPECT_EQ(expectedReturn, errorCode); + return {aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData}; + } bool IsSecure() const { return securityLevel_ != SecurityLevel::SOFTWARE; } SecurityLevel SecLevel() const { return securityLevel_; } @@ -182,6 +257,10 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { const vector<KeyCharacteristics>& key_characteristics); AuthorizationSet SwEnforcedAuthorizations( const vector<KeyCharacteristics>& key_characteristics); + ErrorCode UseAesKey(const vector<uint8_t>& aesKeyBlob); + ErrorCode UseHmacKey(const vector<uint8_t>& hmacKeyBlob); + ErrorCode UseRsaKey(const vector<uint8_t>& rsaKeyBlob); + ErrorCode UseEcdsaKey(const vector<uint8_t>& ecdsaKeyBlob); private: std::shared_ptr<IKeyMintDevice> keymint_; @@ -194,6 +273,16 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { long challenge_; }; +bool verify_attestation_record(const string& challenge, // + const string& app_id, // + AuthorizationSet expected_sw_enforced, // + AuthorizationSet expected_hw_enforced, // + SecurityLevel security_level, + const vector<uint8_t>& attestation_cert); +string bin2hex(const vector<uint8_t>& data); +X509_Ptr parse_cert_blob(const vector<uint8_t>& blob); +::testing::AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain); + #define INSTANTIATE_KEYMINT_AIDL_TEST(name) \ INSTANTIATE_TEST_SUITE_P(PerInstance, name, \ testing::ValuesIn(KeyMintAidlTestBase::build_params()), \ diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp index c849bade2e..71aae90f30 100644 --- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp +++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "keymint_5_test" +#define LOG_TAG "keymint_1_test" #include <cutils/log.h> #include <signal.h> @@ -23,34 +23,21 @@ #include <openssl/ec.h> #include <openssl/evp.h> #include <openssl/mem.h> -#include <openssl/x509.h> #include <openssl/x509v3.h> #include <cutils/properties.h> #include <aidl/android/hardware/security/keymint/KeyFormat.h> -#include <keymint_support/attestation_record.h> #include <keymint_support/key_param_output.h> #include <keymint_support/openssl_utils.h> #include "KeyMintAidlTestBase.h" -static bool arm_deleteAllKeys = false; -static bool dump_Attestations = false; - using aidl::android::hardware::security::keymint::AuthorizationSet; using aidl::android::hardware::security::keymint::KeyCharacteristics; using aidl::android::hardware::security::keymint::KeyFormat; -namespace aidl::android::hardware::security::keymint { - -bool operator==(const keymint::AuthorizationSet& a, const keymint::AuthorizationSet& b) { - return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); -} - -} // namespace aidl::android::hardware::security::keymint - namespace std { using namespace aidl::android::hardware::security::keymint; @@ -78,7 +65,8 @@ namespace aidl::android::hardware::security::keymint::test { namespace { template <TagType tag_type, Tag tag, typename ValueT> -bool contains(vector<KeyParameter>& set, TypedTag<tag_type, tag> ttag, ValueT expected_value) { +bool contains(const vector<KeyParameter>& set, TypedTag<tag_type, tag> ttag, + ValueT expected_value) { auto it = std::find_if(set.begin(), set.end(), [&](const KeyParameter& param) { if (auto p = authorizationValue(ttag, param)) { return *p == expected_value; @@ -89,7 +77,7 @@ bool contains(vector<KeyParameter>& set, TypedTag<tag_type, tag> ttag, ValueT ex } template <TagType tag_type, Tag tag> -bool contains(vector<KeyParameter>& set, TypedTag<tag_type, tag>) { +bool contains(const vector<KeyParameter>& set, TypedTag<tag_type, tag>) { auto it = std::find_if(set.begin(), set.end(), [&](const KeyParameter& param) { return param.tag == tag; }); return (it != set.end()); @@ -182,281 +170,6 @@ struct RSA_Delete { void operator()(RSA* p) { RSA_free(p); } }; -char nibble2hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - -string bin2hex(const vector<uint8_t>& data) { - string retval; - retval.reserve(data.size() * 2 + 1); - for (uint8_t byte : data) { - retval.push_back(nibble2hex[0x0F & (byte >> 4)]); - retval.push_back(nibble2hex[0x0F & byte]); - } - return retval; -} - -X509* parse_cert_blob(const vector<uint8_t>& blob) { - const uint8_t* p = blob.data(); - return d2i_X509(nullptr, &p, blob.size()); -} - -bool verify_chain(const vector<Certificate>& chain) { - for (size_t i = 0; i < chain.size(); ++i) { - X509_Ptr key_cert(parse_cert_blob(chain[i].encodedCertificate)); - X509_Ptr signing_cert; - if (i < chain.size() - 1) { - signing_cert.reset(parse_cert_blob(chain[i + 1].encodedCertificate)); - } else { - signing_cert.reset(parse_cert_blob(chain[i].encodedCertificate)); - } - EXPECT_TRUE(!!key_cert.get() && !!signing_cert.get()); - if (!key_cert.get() || !signing_cert.get()) return false; - - EVP_PKEY_Ptr signing_pubkey(X509_get_pubkey(signing_cert.get())); - EXPECT_TRUE(!!signing_pubkey.get()); - if (!signing_pubkey.get()) return false; - - EXPECT_EQ(1, X509_verify(key_cert.get(), signing_pubkey.get())) - << "Verification of certificate " << i << " failed " - << "OpenSSL error string: " << ERR_error_string(ERR_get_error(), NULL); - - char* cert_issuer = // - X509_NAME_oneline(X509_get_issuer_name(key_cert.get()), nullptr, 0); - char* signer_subj = - X509_NAME_oneline(X509_get_subject_name(signing_cert.get()), nullptr, 0); - EXPECT_STREQ(cert_issuer, signer_subj) << "Cert " << i << " has wrong issuer."; - if (i == 0) { - char* cert_sub = X509_NAME_oneline(X509_get_subject_name(key_cert.get()), nullptr, 0); - EXPECT_STREQ("/CN=Android Keystore Key", cert_sub) - << "Cert " << i << " has wrong subject."; - OPENSSL_free(cert_sub); - } - - OPENSSL_free(cert_issuer); - OPENSSL_free(signer_subj); - - if (dump_Attestations) std::cout << bin2hex(chain[i].encodedCertificate) << std::endl; - } - - return true; -} - -// Extract attestation record from cert. Returned object is still part of cert; don't free it -// separately. -ASN1_OCTET_STRING* get_attestation_record(X509* certificate) { - ASN1_OBJECT_Ptr oid(OBJ_txt2obj(kAttestionRecordOid, 1 /* dotted string format */)); - EXPECT_TRUE(!!oid.get()); - if (!oid.get()) return nullptr; - - int location = X509_get_ext_by_OBJ(certificate, oid.get(), -1 /* search from beginning */); - EXPECT_NE(-1, location) << "Attestation extension not found in certificate"; - if (location == -1) return nullptr; - - X509_EXTENSION* attest_rec_ext = X509_get_ext(certificate, location); - EXPECT_TRUE(!!attest_rec_ext) - << "Found attestation extension but couldn't retrieve it? Probably a BoringSSL bug."; - if (!attest_rec_ext) return nullptr; - - ASN1_OCTET_STRING* attest_rec = X509_EXTENSION_get_data(attest_rec_ext); - EXPECT_TRUE(!!attest_rec) << "Attestation extension contained no data"; - return attest_rec; -} - -bool tag_in_list(const KeyParameter& entry) { - // Attestations don't contain everything in key authorization lists, so we need to filter - // the key lists to produce the lists that we expect to match the attestations. - auto tag_list = { - Tag::BLOB_USAGE_REQUIREMENTS, // - Tag::CREATION_DATETIME, // - Tag::EC_CURVE, - Tag::HARDWARE_TYPE, - Tag::INCLUDE_UNIQUE_ID, - }; - return std::find(tag_list.begin(), tag_list.end(), entry.tag) != tag_list.end(); -} - -AuthorizationSet filtered_tags(const AuthorizationSet& set) { - AuthorizationSet filtered; - std::remove_copy_if(set.begin(), set.end(), std::back_inserter(filtered), tag_in_list); - return filtered; -} - -bool avb_verification_enabled() { - char value[PROPERTY_VALUE_MAX]; - return property_get("ro.boot.vbmeta.device_state", value, "") != 0; -} - -bool verify_attestation_record(const string& challenge, // - const string& app_id, // - AuthorizationSet expected_sw_enforced, // - AuthorizationSet expected_hw_enforced, // - SecurityLevel security_level, - const vector<uint8_t>& attestation_cert) { - X509_Ptr cert(parse_cert_blob(attestation_cert)); - EXPECT_TRUE(!!cert.get()); - if (!cert.get()) return false; - - ASN1_OCTET_STRING* attest_rec = get_attestation_record(cert.get()); - EXPECT_TRUE(!!attest_rec); - if (!attest_rec) return false; - - AuthorizationSet att_sw_enforced; - AuthorizationSet att_hw_enforced; - uint32_t att_attestation_version; - uint32_t att_keymaster_version; - SecurityLevel att_attestation_security_level; - SecurityLevel att_keymaster_security_level; - vector<uint8_t> att_challenge; - vector<uint8_t> att_unique_id; - vector<uint8_t> att_app_id; - - auto error = parse_attestation_record(attest_rec->data, // - attest_rec->length, // - &att_attestation_version, // - &att_attestation_security_level, // - &att_keymaster_version, // - &att_keymaster_security_level, // - &att_challenge, // - &att_sw_enforced, // - &att_hw_enforced, // - &att_unique_id); - EXPECT_EQ(ErrorCode::OK, error); - if (error != ErrorCode::OK) return false; - - EXPECT_GE(att_attestation_version, 3U); - - expected_sw_enforced.push_back(TAG_ATTESTATION_APPLICATION_ID, - vector<uint8_t>(app_id.begin(), app_id.end())); - - EXPECT_GE(att_keymaster_version, 4U); - EXPECT_EQ(security_level, att_keymaster_security_level); - EXPECT_EQ(security_level, att_attestation_security_level); - - EXPECT_EQ(challenge.length(), att_challenge.size()); - EXPECT_EQ(0, memcmp(challenge.data(), att_challenge.data(), challenge.length())); - - char property_value[PROPERTY_VALUE_MAX] = {}; - // TODO(b/136282179): When running under VTS-on-GSI the TEE-backed - // keymaster implementation will report YYYYMM dates instead of YYYYMMDD - // for the BOOT_PATCH_LEVEL. - if (avb_verification_enabled()) { - for (int i = 0; i < att_hw_enforced.size(); i++) { - if (att_hw_enforced[i].tag == TAG_BOOT_PATCHLEVEL || - att_hw_enforced[i].tag == TAG_VENDOR_PATCHLEVEL) { - std::string date = - std::to_string(att_hw_enforced[i].value.get<KeyParameterValue::dateTime>()); - // strptime seems to require delimiters, but the tag value will - // be YYYYMMDD - date.insert(6, "-"); - date.insert(4, "-"); - EXPECT_EQ(date.size(), 10); - struct tm time; - strptime(date.c_str(), "%Y-%m-%d", &time); - - // Day of the month (0-31) - EXPECT_GE(time.tm_mday, 0); - EXPECT_LT(time.tm_mday, 32); - // Months since Jan (0-11) - EXPECT_GE(time.tm_mon, 0); - EXPECT_LT(time.tm_mon, 12); - // Years since 1900 - EXPECT_GT(time.tm_year, 110); - EXPECT_LT(time.tm_year, 200); - } - } - } - - // Check to make sure boolean values are properly encoded. Presence of a boolean tag indicates - // true. A provided boolean tag that can be pulled back out of the certificate indicates correct - // encoding. No need to check if it's in both lists, since the AuthorizationSet compare below - // will handle mismatches of tags. - if (security_level == SecurityLevel::SOFTWARE) { - EXPECT_TRUE(expected_sw_enforced.Contains(TAG_NO_AUTH_REQUIRED)); - } else { - EXPECT_TRUE(expected_hw_enforced.Contains(TAG_NO_AUTH_REQUIRED)); - } - - // Alternatively this checks the opposite - a false boolean tag (one that isn't provided in - // the authorization list during key generation) isn't being attested to in the certificate. - EXPECT_FALSE(expected_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); - EXPECT_FALSE(att_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); - EXPECT_FALSE(expected_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); - EXPECT_FALSE(att_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); - - if (att_hw_enforced.Contains(TAG_ALGORITHM, Algorithm::EC)) { - // For ECDSA keys, either an EC_CURVE or a KEY_SIZE can be specified, but one must be. - EXPECT_TRUE(att_hw_enforced.Contains(TAG_EC_CURVE) || - att_hw_enforced.Contains(TAG_KEY_SIZE)); - } - - // Test root of trust elements - vector<uint8_t> verified_boot_key; - VerifiedBoot verified_boot_state; - bool device_locked; - vector<uint8_t> verified_boot_hash; - error = parse_root_of_trust(attest_rec->data, attest_rec->length, &verified_boot_key, - &verified_boot_state, &device_locked, &verified_boot_hash); - EXPECT_EQ(ErrorCode::OK, error); - - if (avb_verification_enabled()) { - EXPECT_NE(property_get("ro.boot.vbmeta.digest", property_value, ""), 0); - string prop_string(property_value); - EXPECT_EQ(prop_string.size(), 64); - EXPECT_EQ(prop_string, bin2hex(verified_boot_hash)); - - EXPECT_NE(property_get("ro.boot.vbmeta.device_state", property_value, ""), 0); - if (!strcmp(property_value, "unlocked")) { - EXPECT_FALSE(device_locked); - } else { - EXPECT_TRUE(device_locked); - } - - // Check that the device is locked if not debuggable, e.g., user build - // images in CTS. For VTS, debuggable images are used to allow adb root - // and the device is unlocked. - if (!property_get_bool("ro.debuggable", false)) { - EXPECT_TRUE(device_locked); - } else { - EXPECT_FALSE(device_locked); - } - } - - // Verified boot key should be all 0's if the boot state is not verified or self signed - std::string empty_boot_key(32, '\0'); - std::string verified_boot_key_str((const char*)verified_boot_key.data(), - verified_boot_key.size()); - EXPECT_NE(property_get("ro.boot.verifiedbootstate", property_value, ""), 0); - if (!strcmp(property_value, "green")) { - EXPECT_EQ(verified_boot_state, VerifiedBoot::VERIFIED); - EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), - verified_boot_key.size())); - } else if (!strcmp(property_value, "yellow")) { - EXPECT_EQ(verified_boot_state, VerifiedBoot::SELF_SIGNED); - EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), - verified_boot_key.size())); - } else if (!strcmp(property_value, "orange")) { - EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED); - EXPECT_EQ(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), - verified_boot_key.size())); - } else if (!strcmp(property_value, "red")) { - EXPECT_EQ(verified_boot_state, VerifiedBoot::FAILED); - } else { - EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED); - EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), - verified_boot_key.size())); - } - - att_sw_enforced.Sort(); - expected_sw_enforced.Sort(); - EXPECT_EQ(filtered_tags(expected_sw_enforced), filtered_tags(att_sw_enforced)); - - att_hw_enforced.Sort(); - expected_hw_enforced.Sort(); - EXPECT_EQ(filtered_tags(expected_hw_enforced), filtered_tags(att_hw_enforced)); - - return true; -} - std::string make_string(const uint8_t* data, size_t length) { return std::string(reinterpret_cast<const char*>(data), length); } @@ -544,7 +257,8 @@ TEST_P(NewKeyGenerationTest, Rsa) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .RsaSigningKey(key_size, 65537) .Digest(Digest::NONE) - .Padding(PaddingMode::NONE), + .Padding(PaddingMode::NONE) + .SetDefaultValidity(), &key_blob, &key_characteristics)); ASSERT_GT(key_blob.size(), 0U); @@ -580,7 +294,8 @@ TEST_P(NewKeyGenerationTest, RsaWithAttestation) { .Padding(PaddingMode::NONE) .AttestationChallenge(challenge) .AttestationApplicationId(app_id) - .Authorization(TAG_NO_AUTH_REQUIRED), + .Authorization(TAG_NO_AUTH_REQUIRED) + .SetDefaultValidity(), &key_blob, &key_characteristics)); ASSERT_GT(key_blob.size(), 0U); @@ -593,7 +308,7 @@ TEST_P(NewKeyGenerationTest, RsaWithAttestation) { << "Key size " << key_size << "missing"; EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U)); - EXPECT_TRUE(verify_chain(cert_chain_)); + EXPECT_TRUE(ChainSignaturesAreValid(cert_chain_)); ASSERT_GT(cert_chain_.size(), 0); AuthorizationSet hw_enforced = HwEnforcedAuthorizations(key_characteristics); @@ -620,7 +335,8 @@ TEST_P(NewKeyGenerationTest, LimitedUsageRsa) { .RsaSigningKey(key_size, 65537) .Digest(Digest::NONE) .Padding(PaddingMode::NONE) - .Authorization(TAG_USAGE_COUNT_LIMIT, 1), + .Authorization(TAG_USAGE_COUNT_LIMIT, 1) + .SetDefaultValidity(), &key_blob, &key_characteristics)); ASSERT_GT(key_blob.size(), 0U); @@ -665,7 +381,8 @@ TEST_P(NewKeyGenerationTest, LimitedUsageRsaWithAttestation) { .AttestationChallenge(challenge) .AttestationApplicationId(app_id) .Authorization(TAG_NO_AUTH_REQUIRED) - .Authorization(TAG_USAGE_COUNT_LIMIT, 1), + .Authorization(TAG_USAGE_COUNT_LIMIT, 1) + .SetDefaultValidity(), &key_blob, &key_characteristics)); ASSERT_GT(key_blob.size(), 0U); @@ -687,7 +404,7 @@ TEST_P(NewKeyGenerationTest, LimitedUsageRsaWithAttestation) { << "key usage count limit " << 1U << " missing"; // Check the usage count limit tag also appears in the attestation. - EXPECT_TRUE(verify_chain(cert_chain_)); + EXPECT_TRUE(ChainSignaturesAreValid(cert_chain_)); ASSERT_GT(cert_chain_.size(), 0); AuthorizationSet hw_enforced = HwEnforcedAuthorizations(key_characteristics); @@ -713,7 +430,8 @@ TEST_P(NewKeyGenerationTest, NoInvalidRsaSizes) { GenerateKey(AuthorizationSetBuilder() .RsaSigningKey(key_size, 65537) .Digest(Digest::NONE) - .Padding(PaddingMode::NONE), + .Padding(PaddingMode::NONE) + .SetDefaultValidity(), &key_blob, &key_characteristics)); } } @@ -729,7 +447,8 @@ TEST_P(NewKeyGenerationTest, RsaNoDefaultSize) { GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_ALGORITHM, Algorithm::RSA) .Authorization(TAG_RSA_PUBLIC_EXPONENT, 3U) - .SigningKey())); + .SigningKey() + .SetDefaultValidity())); } /* @@ -742,10 +461,11 @@ TEST_P(NewKeyGenerationTest, Ecdsa) { for (auto key_size : ValidKeySizes(Algorithm::EC)) { vector<uint8_t> key_blob; vector<KeyCharacteristics> key_characteristics; - ASSERT_EQ(ErrorCode::OK, - GenerateKey( - AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE), - &key_blob, &key_characteristics)); + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(key_size) + .Digest(Digest::NONE) + .SetDefaultValidity(), + &key_blob, &key_characteristics)); ASSERT_GT(key_blob.size(), 0U); CheckBaseParams(key_characteristics); @@ -772,7 +492,8 @@ TEST_P(NewKeyGenerationTest, LimitedUsageEcdsa) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .EcdsaSigningKey(key_size) .Digest(Digest::NONE) - .Authorization(TAG_USAGE_COUNT_LIMIT, 1), + .Authorization(TAG_USAGE_COUNT_LIMIT, 1) + .SetDefaultValidity(), &key_blob, &key_characteristics)); ASSERT_GT(key_blob.size(), 0U); @@ -807,7 +528,8 @@ TEST_P(NewKeyGenerationTest, EcdsaDefaultSize) { GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_ALGORITHM, Algorithm::EC) .SigningKey() - .Digest(Digest::NONE))); + .Digest(Digest::NONE) + .SetDefaultValidity())); } /* @@ -820,14 +542,17 @@ TEST_P(NewKeyGenerationTest, EcdsaInvalidSize) { for (auto key_size : InvalidKeySizes(Algorithm::EC)) { vector<uint8_t> key_blob; vector<KeyCharacteristics> key_characteristics; - ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, - GenerateKey( - AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE), - &key_blob, &key_characteristics)); + ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(key_size) + .Digest(Digest::NONE) + .SetDefaultValidity(), + &key_blob, &key_characteristics)); } - ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, - GenerateKey(AuthorizationSetBuilder().EcdsaSigningKey(190).Digest(Digest::NONE))); + ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(190) + .Digest(Digest::NONE) + .SetDefaultValidity())); } /* @@ -843,7 +568,8 @@ TEST_P(NewKeyGenerationTest, EcdsaMismatchKeySize) { GenerateKey(AuthorizationSetBuilder() .EcdsaSigningKey(224) .Authorization(TAG_EC_CURVE, EcCurve::P_256) - .Digest(Digest::NONE))); + .Digest(Digest::NONE) + .SetDefaultValidity())); } /* @@ -854,8 +580,10 @@ TEST_P(NewKeyGenerationTest, EcdsaMismatchKeySize) { TEST_P(NewKeyGenerationTest, EcdsaAllValidSizes) { auto valid_sizes = ValidKeySizes(Algorithm::EC); for (size_t size : valid_sizes) { - EXPECT_EQ(ErrorCode::OK, - GenerateKey(AuthorizationSetBuilder().EcdsaSigningKey(size).Digest(Digest::NONE))) + EXPECT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(size) + .Digest(Digest::NONE) + .SetDefaultValidity())) << "Failed to generate size: " << size; CheckedDeleteKey(); } @@ -874,8 +602,10 @@ TEST_P(NewKeyGenerationTest, EcdsaAllValidCurves) { digest = Digest::SHA_2_512; } for (auto curve : ValidCurves()) { - EXPECT_EQ(ErrorCode::OK, - GenerateKey(AuthorizationSetBuilder().EcdsaSigningKey(curve).Digest(digest))) + EXPECT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(curve) + .Digest(digest) + .SetDefaultValidity())) << "Failed to generate key on curve: " << curve; CheckedDeleteKey(); } @@ -1058,7 +788,8 @@ TEST_P(SigningOperationsTest, RsaSuccess) { .RsaSigningKey(2048, 65537) .Digest(Digest::NONE) .Padding(PaddingMode::NONE) - .Authorization(TAG_NO_AUTH_REQUIRED))); + .Authorization(TAG_NO_AUTH_REQUIRED) + .SetDefaultValidity())); string message = "12345678901234567890123456789012"; string signature = SignMessage( message, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)); @@ -1076,7 +807,8 @@ TEST_P(SigningOperationsTest, RsaUseRequiresCorrectAppIdAppData) { .Digest(Digest::NONE) .Padding(PaddingMode::NONE) .Authorization(TAG_APPLICATION_ID, "clientid") - .Authorization(TAG_APPLICATION_DATA, "appdata"))); + .Authorization(TAG_APPLICATION_DATA, "appdata") + .SetDefaultValidity())); EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::SIGN, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE))); @@ -1112,7 +844,8 @@ TEST_P(SigningOperationsTest, RsaPssSha256Success) { .RsaSigningKey(2048, 65537) .Digest(Digest::SHA_2_256) .Padding(PaddingMode::RSA_PSS) - .Authorization(TAG_NO_AUTH_REQUIRED))); + .Authorization(TAG_NO_AUTH_REQUIRED) + .SetDefaultValidity())); // Use large message, which won't work without digesting. string message(1024, 'a'); string signature = SignMessage( @@ -1131,7 +864,8 @@ TEST_P(SigningOperationsTest, RsaPaddingNoneDoesNotAllowOther) { .RsaSigningKey(2048, 65537) .Digest(Digest::NONE) .Authorization(TAG_NO_AUTH_REQUIRED) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); string message = "12345678901234567890123456789012"; string signature; @@ -1150,13 +884,13 @@ TEST_P(SigningOperationsTest, RsaPaddingNoneDoesNotAllowOther) { */ TEST_P(SigningOperationsTest, NoUserConfirmation) { if (SecLevel() == SecurityLevel::STRONGBOX) return; - ASSERT_EQ(ErrorCode::OK, - GenerateKey(AuthorizationSetBuilder() - .RsaSigningKey(1024, 65537) - .Digest(Digest::NONE) - .Padding(PaddingMode::NONE) - .Authorization(TAG_NO_AUTH_REQUIRED) - .Authorization(TAG_TRUSTED_CONFIRMATION_REQUIRED))); + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(1024, 65537) + .Digest(Digest::NONE) + .Padding(PaddingMode::NONE) + .Authorization(TAG_NO_AUTH_REQUIRED) + .Authorization(TAG_TRUSTED_CONFIRMATION_REQUIRED) + .SetDefaultValidity())); const string message = "12345678901234567890123456789012"; EXPECT_EQ(ErrorCode::OK, @@ -1176,7 +910,8 @@ TEST_P(SigningOperationsTest, RsaPkcs1Sha256Success) { .RsaSigningKey(2048, 65537) .Digest(Digest::SHA_2_256) .Authorization(TAG_NO_AUTH_REQUIRED) - .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN))); + .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN) + .SetDefaultValidity())); string message(1024, 'a'); string signature = SignMessage(message, AuthorizationSetBuilder() .Digest(Digest::SHA_2_256) @@ -1193,7 +928,8 @@ TEST_P(SigningOperationsTest, RsaPkcs1NoDigestSuccess) { .RsaSigningKey(2048, 65537) .Digest(Digest::NONE) .Authorization(TAG_NO_AUTH_REQUIRED) - .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN))); + .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN) + .SetDefaultValidity())); string message(53, 'a'); string signature = SignMessage(message, AuthorizationSetBuilder() .Digest(Digest::NONE) @@ -1211,7 +947,8 @@ TEST_P(SigningOperationsTest, RsaPkcs1NoDigestTooLong) { .RsaSigningKey(2048, 65537) .Digest(Digest::NONE) .Authorization(TAG_NO_AUTH_REQUIRED) - .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN))); + .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN) + .SetDefaultValidity())); string message(257, 'a'); EXPECT_EQ(ErrorCode::OK, @@ -1241,7 +978,8 @@ TEST_P(SigningOperationsTest, RsaPssSha512TooSmallKey) { .RsaSigningKey(1024, 65537) .Digest(Digest::SHA_2_512) .Authorization(TAG_NO_AUTH_REQUIRED) - .Padding(PaddingMode::RSA_PSS))); + .Padding(PaddingMode::RSA_PSS) + .SetDefaultValidity())); EXPECT_EQ(ErrorCode::INCOMPATIBLE_DIGEST, Begin(KeyPurpose::SIGN, AuthorizationSetBuilder() .Digest(Digest::SHA_2_512) @@ -1259,7 +997,8 @@ TEST_P(SigningOperationsTest, RsaNoPaddingTooLong) { .RsaSigningKey(2048, 65537) .Digest(Digest::NONE) .Authorization(TAG_NO_AUTH_REQUIRED) - .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN))); + .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN) + .SetDefaultValidity())); // One byte too long string message(2048 / 8 + 1, 'a'); ASSERT_EQ(ErrorCode::OK, @@ -1293,7 +1032,8 @@ TEST_P(SigningOperationsTest, RsaAbort) { .RsaSigningKey(2048, 65537) .Digest(Digest::NONE) .Authorization(TAG_NO_AUTH_REQUIRED) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); ASSERT_EQ(ErrorCode::OK, Begin(KeyPurpose::SIGN, @@ -1318,7 +1058,8 @@ TEST_P(SigningOperationsTest, RsaUnsupportedPadding) { .RsaSigningKey(2048, 65537) .Authorization(TAG_NO_AUTH_REQUIRED) .Digest(Digest::SHA_2_256 /* supported digest */) - .Padding(PaddingMode::PKCS7))); + .Padding(PaddingMode::PKCS7) + .SetDefaultValidity())); ASSERT_EQ( ErrorCode::UNSUPPORTED_PADDING_MODE, Begin(KeyPurpose::SIGN, @@ -1335,7 +1076,8 @@ TEST_P(SigningOperationsTest, RsaNoDigest) { .RsaSigningKey(2048, 65537) .Authorization(TAG_NO_AUTH_REQUIRED) .Digest(Digest::NONE) - .Padding(PaddingMode::RSA_PSS))); + .Padding(PaddingMode::RSA_PSS) + .SetDefaultValidity())); ASSERT_EQ(ErrorCode::INCOMPATIBLE_DIGEST, Begin(KeyPurpose::SIGN, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::RSA_PSS))); @@ -1356,7 +1098,8 @@ TEST_P(SigningOperationsTest, RsaNoPadding) { .RsaKey(2048, 65537) .Authorization(TAG_NO_AUTH_REQUIRED) .SigningKey() - .Digest(Digest::NONE))); + .Digest(Digest::NONE) + .SetDefaultValidity())); ASSERT_EQ(ErrorCode::UNSUPPORTED_PADDING_MODE, Begin(KeyPurpose::SIGN, AuthorizationSetBuilder().Digest(Digest::NONE))); } @@ -1371,7 +1114,8 @@ TEST_P(SigningOperationsTest, RsaTooShortMessage) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaSigningKey(2048, 65537) .Digest(Digest::NONE) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); // Barely shorter string message(2048 / 8 - 1, 'a'); @@ -1392,7 +1136,8 @@ TEST_P(SigningOperationsTest, RsaSignWithEncryptionKey) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) .Digest(Digest::NONE) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); ASSERT_EQ(ErrorCode::INCOMPATIBLE_PURPOSE, Begin(KeyPurpose::SIGN, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE))); @@ -1409,7 +1154,8 @@ TEST_P(SigningOperationsTest, RsaSignTooLargeMessage) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaSigningKey(2048, 65537) .Digest(Digest::NONE) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); // Largest possible message will always be larger than the public modulus. string message(2048 / 8, static_cast<char>(0xff)); @@ -1432,7 +1178,8 @@ TEST_P(SigningOperationsTest, EcdsaAllSizesAndHashes) { ErrorCode error = GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .EcdsaSigningKey(key_size) - .Digest(digest)); + .Digest(digest) + .SetDefaultValidity()); EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate ECDSA key with size " << key_size << " and digest " << digest; if (error != ErrorCode::OK) continue; @@ -1455,7 +1202,8 @@ TEST_P(SigningOperationsTest, EcdsaAllCurves) { ErrorCode error = GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .EcdsaSigningKey(curve) - .Digest(Digest::SHA_2_256)); + .Digest(Digest::SHA_2_256) + .SetDefaultValidity()); EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate ECDSA key with curve " << curve; if (error != ErrorCode::OK) continue; @@ -1477,7 +1225,8 @@ TEST_P(SigningOperationsTest, EcdsaNoDigestHugeData) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .EcdsaSigningKey(256) - .Digest(Digest::NONE))); + .Digest(Digest::NONE) + .SetDefaultValidity())); string message(1 * 1024, 'a'); SignMessage(message, AuthorizationSetBuilder().Digest(Digest::NONE)); } @@ -1493,7 +1242,8 @@ TEST_P(SigningOperationsTest, EcUseRequiresCorrectAppIdAppData) { .EcdsaSigningKey(256) .Digest(Digest::NONE) .Authorization(TAG_APPLICATION_ID, "clientid") - .Authorization(TAG_APPLICATION_DATA, "appdata"))); + .Authorization(TAG_APPLICATION_DATA, "appdata") + .SetDefaultValidity())); EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::SIGN, AuthorizationSetBuilder().Digest(Digest::NONE))); AbortIfNeeded(); @@ -1682,7 +1432,8 @@ TEST_P(VerificationOperationsTest, RsaSuccess) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaSigningKey(2048, 65537) .Digest(Digest::NONE) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); string message = "12345678901234567890123456789012"; string signature = SignMessage( message, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)); @@ -1702,7 +1453,8 @@ TEST_P(VerificationOperationsTest, RsaAllPaddingsAndDigests) { .Digest(ValidDigests(true /* withNone */, true /* withMD5 */)) .Padding(PaddingMode::NONE) .Padding(PaddingMode::RSA_PSS) - .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN); + .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN) + .SetDefaultValidity(); ASSERT_EQ(ErrorCode::OK, GenerateKey(authorizations)); @@ -1799,7 +1551,8 @@ TEST_P(VerificationOperationsTest, EcdsaAllDigestsAndCurves) { ErrorCode error = GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .EcdsaSigningKey(curve) - .Digest(digests)); + .Digest(digests) + .SetDefaultValidity()); EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate key for EC curve " << curve; if (error != ErrorCode::OK) { continue; @@ -1962,7 +1715,8 @@ TEST_P(ImportKeyTest, RsaSuccess) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaSigningKey(1024, 65537) .Digest(Digest::SHA_2_256) - .Padding(PaddingMode::RSA_PSS), + .Padding(PaddingMode::RSA_PSS) + .SetDefaultValidity(), KeyFormat::PKCS8, rsa_key)); CheckCryptoParam(TAG_ALGORITHM, Algorithm::RSA); @@ -1989,7 +1743,8 @@ TEST_P(ImportKeyTest, RsaKeySizeMismatch) { ImportKey(AuthorizationSetBuilder() .RsaSigningKey(2048 /* Doesn't match key */, 65537) .Digest(Digest::NONE) - .Padding(PaddingMode::NONE), + .Padding(PaddingMode::NONE) + .SetDefaultValidity(), KeyFormat::PKCS8, rsa_key)); } @@ -2004,7 +1759,8 @@ TEST_P(ImportKeyTest, RsaPublicExponentMismatch) { ImportKey(AuthorizationSetBuilder() .RsaSigningKey(1024, 3 /* Doesn't match key */) .Digest(Digest::NONE) - .Padding(PaddingMode::NONE), + .Padding(PaddingMode::NONE) + .SetDefaultValidity(), KeyFormat::PKCS8, rsa_key)); } @@ -2017,7 +1773,8 @@ TEST_P(ImportKeyTest, EcdsaSuccess) { ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .EcdsaSigningKey(256) - .Digest(Digest::SHA_2_256), + .Digest(Digest::SHA_2_256) + .SetDefaultValidity(), KeyFormat::PKCS8, ec_256_key)); CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC); @@ -2043,7 +1800,8 @@ TEST_P(ImportKeyTest, EcdsaP256RFC5915Success) { ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .EcdsaSigningKey(256) - .Digest(Digest::SHA_2_256), + .Digest(Digest::SHA_2_256) + .SetDefaultValidity(), KeyFormat::PKCS8, ec_256_key_rfc5915)); CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC); @@ -2068,7 +1826,8 @@ TEST_P(ImportKeyTest, EcdsaP256SEC1Success) { ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .EcdsaSigningKey(256) - .Digest(Digest::SHA_2_256), + .Digest(Digest::SHA_2_256) + .SetDefaultValidity(), KeyFormat::PKCS8, ec_256_key_sec1)); CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC); @@ -2094,7 +1853,8 @@ TEST_P(ImportKeyTest, Ecdsa521Success) { ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .EcdsaSigningKey(521) - .Digest(Digest::SHA_2_256), + .Digest(Digest::SHA_2_256) + .SetDefaultValidity(), KeyFormat::PKCS8, ec_521_key)); CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC); @@ -2119,7 +1879,8 @@ TEST_P(ImportKeyTest, EcdsaSizeMismatch) { ASSERT_EQ(ErrorCode::IMPORT_PARAMETER_MISMATCH, ImportKey(AuthorizationSetBuilder() .EcdsaSigningKey(224 /* Doesn't match key */) - .Digest(Digest::NONE), + .Digest(Digest::NONE) + .SetDefaultValidity(), KeyFormat::PKCS8, ec_256_key)); } @@ -2133,7 +1894,8 @@ TEST_P(ImportKeyTest, EcdsaCurveMismatch) { ASSERT_EQ(ErrorCode::IMPORT_PARAMETER_MISMATCH, ImportKey(AuthorizationSetBuilder() .EcdsaSigningKey(EcCurve::P_224 /* Doesn't match key */) - .Digest(Digest::NONE), + .Digest(Digest::NONE) + .SetDefaultValidity(), KeyFormat::PKCS8, ec_256_key)); } @@ -2254,7 +2016,8 @@ TEST_P(ImportWrappedKeyTest, Success) { .RsaEncryptionKey(2048, 65537) .Digest(Digest::SHA_2_256) .Padding(PaddingMode::RSA_OAEP) - .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY); + .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY) + .SetDefaultValidity(); ASSERT_EQ(ErrorCode::OK, ImportWrappedKey(wrapped_key, wrapping_key, wrapping_key_desc, zero_masking_key, @@ -2274,7 +2037,8 @@ TEST_P(ImportWrappedKeyTest, SuccessMasked) { .RsaEncryptionKey(2048, 65537) .Digest(Digest::SHA_2_256) .Padding(PaddingMode::RSA_OAEP) - .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY); + .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY) + .SetDefaultValidity(); ASSERT_EQ(ErrorCode::OK, ImportWrappedKey(wrapped_key_masked, wrapping_key, wrapping_key_desc, masking_key, @@ -2288,7 +2052,8 @@ TEST_P(ImportWrappedKeyTest, WrongMask) { .RsaEncryptionKey(2048, 65537) .Digest(Digest::SHA_2_256) .Padding(PaddingMode::RSA_OAEP) - .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY); + .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY) + .SetDefaultValidity(); ASSERT_EQ( ErrorCode::VERIFICATION_FAILED, @@ -2302,7 +2067,8 @@ TEST_P(ImportWrappedKeyTest, WrongPurpose) { auto wrapping_key_desc = AuthorizationSetBuilder() .RsaEncryptionKey(2048, 65537) .Digest(Digest::SHA_2_256) - .Padding(PaddingMode::RSA_OAEP); + .Padding(PaddingMode::RSA_OAEP) + .SetDefaultValidity(); ASSERT_EQ( ErrorCode::INCOMPATIBLE_PURPOSE, @@ -2325,7 +2091,8 @@ TEST_P(EncryptionOperationsTest, RsaNoPaddingSuccess) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); string message = string(2048 / 8, 'a'); auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE); @@ -2348,7 +2115,8 @@ TEST_P(EncryptionOperationsTest, RsaNoPaddingShortMessage) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); string message = "1"; auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE); @@ -2377,7 +2145,8 @@ TEST_P(EncryptionOperationsTest, RsaNoPaddingTooLong) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); string message(2048 / 8 + 1, 'a'); @@ -2410,7 +2179,8 @@ TEST_P(EncryptionOperationsTest, RsaOaepSuccess) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(key_size, 65537) .Padding(PaddingMode::RSA_OAEP) - .Digest(digests))); + .Digest(digests) + .SetDefaultValidity())); string message = "Hello"; @@ -2458,7 +2228,8 @@ TEST_P(EncryptionOperationsTest, RsaOaepInvalidDigest) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) .Padding(PaddingMode::RSA_OAEP) - .Digest(Digest::NONE))); + .Digest(Digest::NONE) + .SetDefaultValidity())); string message = "Hello World!"; auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_OAEP).Digest(Digest::NONE); @@ -2478,7 +2249,8 @@ TEST_P(EncryptionOperationsTest, RsaOaepDecryptWithWrongDigest) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(1024, 65537) .Padding(PaddingMode::RSA_OAEP) - .Digest(Digest::SHA_2_224, Digest::SHA_2_256))); + .Digest(Digest::SHA_2_224, Digest::SHA_2_256) + .SetDefaultValidity())); string message = "Hello World!"; string ciphertext = EncryptMessage( message, @@ -2503,7 +2275,8 @@ TEST_P(EncryptionOperationsTest, RsaOaepTooLarge) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) .Padding(PaddingMode::RSA_OAEP) - .Digest(Digest::SHA_2_256))); + .Digest(Digest::SHA_2_256) + .SetDefaultValidity())); constexpr size_t digest_size = 256 /* SHA_2_256 */ / 8; constexpr size_t oaep_overhead = 2 * digest_size + 2; string message(2048 / 8 - oaep_overhead + 1, 'a'); @@ -2531,7 +2304,8 @@ TEST_P(EncryptionOperationsTest, RsaOaepWithMGFDigestSuccess) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(key_size, 65537) .Padding(PaddingMode::RSA_OAEP) - .Digest(Digest::SHA_2_256))); + .Digest(Digest::SHA_2_256) + .SetDefaultValidity())); string message = "Hello"; @@ -2584,7 +2358,8 @@ TEST_P(EncryptionOperationsTest, RsaOaepWithMGFIncompatibleDigest) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) .Padding(PaddingMode::RSA_OAEP) - .Digest(Digest::SHA_2_256))); + .Digest(Digest::SHA_2_256) + .SetDefaultValidity())); string message = "Hello World!"; auto params = AuthorizationSetBuilder() @@ -2607,7 +2382,8 @@ TEST_P(EncryptionOperationsTest, RsaOaepWithMGFUnsupportedDigest) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) .Padding(PaddingMode::RSA_OAEP) - .Digest(Digest::SHA_2_256))); + .Digest(Digest::SHA_2_256) + .SetDefaultValidity())); string message = "Hello World!"; auto params = AuthorizationSetBuilder() @@ -2626,7 +2402,8 @@ TEST_P(EncryptionOperationsTest, RsaPkcs1Success) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) - .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT))); + .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT) + .SetDefaultValidity())); string message = "Hello World!"; auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT); @@ -2665,7 +2442,8 @@ TEST_P(EncryptionOperationsTest, RsaPkcs1TooLarge) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) - .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT))); + .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT) + .SetDefaultValidity())); string message(2048 / 8 - 10, 'a'); auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT); @@ -2685,7 +2463,8 @@ TEST_P(EncryptionOperationsTest, EcdsaEncrypt) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .EcdsaSigningKey(256) - .Digest(Digest::NONE))); + .Digest(Digest::NONE) + .SetDefaultValidity())); auto params = AuthorizationSetBuilder().Digest(Digest::NONE); ASSERT_EQ(ErrorCode::UNSUPPORTED_PURPOSE, Begin(KeyPurpose::ENCRYPT, params)); ASSERT_EQ(ErrorCode::UNSUPPORTED_PURPOSE, Begin(KeyPurpose::DECRYPT, params)); @@ -4333,7 +4112,8 @@ TEST_P(MaxOperationsTest, TestLimitRsa) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaSigningKey(1024, 65537) .NoDigestOrPadding() - .Authorization(TAG_MAX_USES_PER_BOOT, 3))); + .Authorization(TAG_MAX_USES_PER_BOOT, 3) + .SetDefaultValidity())); string message = "1234567890123456"; @@ -4452,7 +4232,8 @@ TEST_P(UsageCountLimitTest, TestSingleUseRsa) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaSigningKey(1024, 65537) .NoDigestOrPadding() - .Authorization(TAG_USAGE_COUNT_LIMIT, 1))); + .Authorization(TAG_USAGE_COUNT_LIMIT, 1) + .SetDefaultValidity())); // Check the usage count limit tag appears in the authorizations. AuthorizationSet auths; @@ -4495,7 +4276,8 @@ TEST_P(UsageCountLimitTest, TestLimitUseRsa) { .Authorization(TAG_NO_AUTH_REQUIRED) .RsaSigningKey(1024, 65537) .NoDigestOrPadding() - .Authorization(TAG_USAGE_COUNT_LIMIT, 3))); + .Authorization(TAG_USAGE_COUNT_LIMIT, 3) + .SetDefaultValidity())); // Check the usage count limit tag appears in the authorizations. AuthorizationSet auths; @@ -4527,6 +4309,57 @@ TEST_P(UsageCountLimitTest, TestLimitUseRsa) { } } +/* + * UsageCountLimitTest.TestSingleUseKeyAndRollbackResistance + * + * Verifies that when rollback resistance is supported by the KeyMint implementation with + * the secure hardware, the single use key with usage count limit tag = 1 must also be enforced + * in hardware. + */ +TEST_P(UsageCountLimitTest, TestSingleUseKeyAndRollbackResistance) { + if (SecLevel() == SecurityLevel::STRONGBOX) return; + + auto error = GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(2048, 65537) + .Digest(Digest::NONE) + .Padding(PaddingMode::NONE) + .Authorization(TAG_NO_AUTH_REQUIRED) + .Authorization(TAG_ROLLBACK_RESISTANCE) + .SetDefaultValidity()); + ASSERT_TRUE(error == ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE || error == ErrorCode::OK); + + if (error == ErrorCode::OK) { + // Rollback resistance is supported by KeyMint, verify it is enforced in hardware. + AuthorizationSet hardwareEnforced(SecLevelAuthorizations()); + ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE)); + ASSERT_EQ(ErrorCode::OK, DeleteKey()); + + // The KeyMint should also enforce single use key in hardware when it supports rollback + // resistance. + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .RsaSigningKey(1024, 65537) + .NoDigestOrPadding() + .Authorization(TAG_USAGE_COUNT_LIMIT, 1) + .SetDefaultValidity())); + + // Check the usage count limit tag appears in the hardware authorizations. + AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_); + EXPECT_TRUE(hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U)) + << "key usage count limit " << 1U << " missing"; + + string message = "1234567890123456"; + auto params = AuthorizationSetBuilder().NoDigestOrPadding(); + + // First usage of RSA key should work. + SignMessage(message, params); + + // Usage count limit tag is enforced by hardware. After using the key, the key blob + // must be invalidated from secure storage (such as RPMB partition). + EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::SIGN, params)); + } +} + INSTANTIATE_KEYMINT_AIDL_TEST(UsageCountLimitTest); typedef KeyMintAidlTestBase AddEntropyTest; @@ -4576,7 +4409,8 @@ TEST_P(KeyDeletionTest, DeleteKey) { .Digest(Digest::NONE) .Padding(PaddingMode::NONE) .Authorization(TAG_NO_AUTH_REQUIRED) - .Authorization(TAG_ROLLBACK_RESISTANCE)); + .Authorization(TAG_ROLLBACK_RESISTANCE) + .SetDefaultValidity()); ASSERT_TRUE(error == ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE || error == ErrorCode::OK); // Delete must work if rollback protection is implemented @@ -4609,7 +4443,8 @@ TEST_P(KeyDeletionTest, DeleteInvalidKey) { .Digest(Digest::NONE) .Padding(PaddingMode::NONE) .Authorization(TAG_NO_AUTH_REQUIRED) - .Authorization(TAG_ROLLBACK_RESISTANCE)); + .Authorization(TAG_ROLLBACK_RESISTANCE) + .SetDefaultValidity()); ASSERT_TRUE(error == ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE || error == ErrorCode::OK); // Delete must work if rollback protection is implemented @@ -4704,7 +4539,8 @@ TEST_P(ClearOperationsTest, TooManyOperations) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) .RsaEncryptionKey(2048, 65537) - .Padding(PaddingMode::NONE))); + .Padding(PaddingMode::NONE) + .SetDefaultValidity())); auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE); constexpr size_t max_operations = 100; // set to arbituary large number @@ -4835,7 +4671,8 @@ TEST_P(KeyAgreementTest, Ecdh) { .Authorization(TAG_PURPOSE, KeyPurpose::AGREE_KEY) .Authorization(TAG_ALGORITHM, Algorithm::EC) .Authorization(TAG_ATTESTATION_APPLICATION_ID, {0x61, 0x62}) - .Authorization(TAG_ATTESTATION_CHALLENGE, challenge))) + .Authorization(TAG_ATTESTATION_CHALLENGE, challenge) + .SetDefaultValidity())) << "Failed to generate key"; ASSERT_GT(cert_chain_.size(), 0); X509_Ptr kmKeyCert(parse_cert_blob(cert_chain_[0].encodedCertificate)); @@ -4890,17 +4727,122 @@ TEST_P(KeyAgreementTest, Ecdh) { INSTANTIATE_KEYMINT_AIDL_TEST(KeyAgreementTest); +typedef KeyMintAidlTestBase EarlyBootKeyTest; + +TEST_P(EarlyBootKeyTest, CreateEarlyBootKeys) { + auto [aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData] = + CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::OK); + + CheckedDeleteKey(&aesKeyData.blob); + CheckedDeleteKey(&hmacKeyData.blob); + CheckedDeleteKey(&rsaKeyData.blob); + CheckedDeleteKey(&ecdsaKeyData.blob); +} + +// This is a more comprenhensive test, but it can only be run on a machine which is still in early +// boot stage, which no proper Android device is by the time we can run VTS. To use this, +// un-disable it and modify vold to remove the call to earlyBootEnded(). Running the test will end +// early boot, so you'll have to reboot between runs. +TEST_P(EarlyBootKeyTest, DISABLED_FullTest) { + auto [aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData] = + CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::OK); + // TAG_EARLY_BOOT_ONLY should be in hw-enforced. + EXPECT_TRUE(HwEnforcedAuthorizations(aesKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY)); + EXPECT_TRUE( + HwEnforcedAuthorizations(hmacKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY)); + EXPECT_TRUE(HwEnforcedAuthorizations(rsaKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY)); + EXPECT_TRUE( + HwEnforcedAuthorizations(ecdsaKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY)); + + // Should be able to use keys, since early boot has not ended + EXPECT_EQ(ErrorCode::OK, UseAesKey(aesKeyData.blob)); + EXPECT_EQ(ErrorCode::OK, UseHmacKey(hmacKeyData.blob)); + EXPECT_EQ(ErrorCode::OK, UseRsaKey(rsaKeyData.blob)); + EXPECT_EQ(ErrorCode::OK, UseEcdsaKey(ecdsaKeyData.blob)); + + // End early boot + ErrorCode earlyBootResult = GetReturnErrorCode(keyMint().earlyBootEnded()); + EXPECT_EQ(earlyBootResult, ErrorCode::OK); + + // Should not be able to use already-created keys. + EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseAesKey(aesKeyData.blob)); + EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseHmacKey(hmacKeyData.blob)); + EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseRsaKey(rsaKeyData.blob)); + EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseEcdsaKey(ecdsaKeyData.blob)); + + CheckedDeleteKey(&aesKeyData.blob); + CheckedDeleteKey(&hmacKeyData.blob); + CheckedDeleteKey(&rsaKeyData.blob); + CheckedDeleteKey(&ecdsaKeyData.blob); + + // Should not be able to create new keys + std::tie(aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData) = + CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::EARLY_BOOT_ENDED); + + CheckedDeleteKey(&aesKeyData.blob); + CheckedDeleteKey(&hmacKeyData.blob); + CheckedDeleteKey(&rsaKeyData.blob); + CheckedDeleteKey(&ecdsaKeyData.blob); +} +INSTANTIATE_KEYMINT_AIDL_TEST(EarlyBootKeyTest); + +typedef KeyMintAidlTestBase UnlockedDeviceRequiredTest; + +// This may be a problematic test. It can't be run repeatedly without unlocking the device in +// between runs... and on most test devices there are no enrolled credentials so it can't be +// unlocked at all, meaning the only way to get the test to pass again on a properly-functioning +// device is to reboot it. For that reason, this is disabled by default. It can be used as part of +// a manual test process, which includes unlocking between runs, which is why it's included here. +// Well, that and the fact that it's the only test we can do without also making calls into the +// Gatekeeper HAL. We haven't written any cross-HAL tests, and don't know what all of the +// implications might be, so that may or may not be a solution. +TEST_P(UnlockedDeviceRequiredTest, DISABLED_KeysBecomeUnusable) { + auto [aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData] = + CreateTestKeys(TAG_UNLOCKED_DEVICE_REQUIRED, ErrorCode::OK); + + EXPECT_EQ(ErrorCode::OK, UseAesKey(aesKeyData.blob)); + EXPECT_EQ(ErrorCode::OK, UseHmacKey(hmacKeyData.blob)); + EXPECT_EQ(ErrorCode::OK, UseRsaKey(rsaKeyData.blob)); + EXPECT_EQ(ErrorCode::OK, UseEcdsaKey(ecdsaKeyData.blob)); + + ErrorCode rc = GetReturnErrorCode( + keyMint().deviceLocked(false /* passwordOnly */, {} /* verificationToken */)); + ASSERT_EQ(ErrorCode::OK, rc); + EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseAesKey(aesKeyData.blob)); + EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseHmacKey(hmacKeyData.blob)); + EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseRsaKey(rsaKeyData.blob)); + EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseEcdsaKey(ecdsaKeyData.blob)); + + CheckedDeleteKey(&aesKeyData.blob); + CheckedDeleteKey(&hmacKeyData.blob); + CheckedDeleteKey(&rsaKeyData.blob); + CheckedDeleteKey(&ecdsaKeyData.blob); +} +INSTANTIATE_KEYMINT_AIDL_TEST(UnlockedDeviceRequiredTest); + } // namespace aidl::android::hardware::security::keymint::test int main(int argc, char** argv) { + std::cout << "Testing "; + auto halInstances = + aidl::android::hardware::security::keymint::test::KeyMintAidlTestBase::build_params(); + std::cout << "HAL instances:\n"; + for (auto& entry : halInstances) { + std::cout << " " << entry << '\n'; + } + ::testing::InitGoogleTest(&argc, argv); for (int i = 1; i < argc; ++i) { if (argv[i][0] == '-') { if (std::string(argv[i]) == "--arm_deleteAllKeys") { - arm_deleteAllKeys = true; + aidl::android::hardware::security::keymint::test::KeyMintAidlTestBase:: + arm_deleteAllKeys = true; } if (std::string(argv[i]) == "--dump_attestations") { - dump_Attestations = true; + aidl::android::hardware::security::keymint::test::KeyMintAidlTestBase:: + dump_Attestations = true; + } else { + std::cout << "NOT dumping attestations" << std::endl; } } } diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp new file mode 100644 index 0000000000..45f9df6307 --- /dev/null +++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp @@ -0,0 +1,432 @@ +/* + * 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 "VtsRemotelyProvisionableComponentTests" + +#include <RemotelyProvisionedComponent.h> +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h> +#include <aidl/android/hardware/security/keymint/SecurityLevel.h> +#include <android/binder_manager.h> +#include <cppbor_parse.h> +#include <cppcose/cppcose.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <keymaster/keymaster_configuration.h> +#include <remote_prov/remote_prov_utils.h> + +namespace aidl::android::hardware::security::keymint::test { + +using ::std::string; +using ::std::vector; + +namespace { + +#define INSTANTIATE_REM_PROV_AIDL_TEST(name) \ + INSTANTIATE_TEST_SUITE_P( \ + PerInstance, name, \ + testing::ValuesIn(VtsRemotelyProvisionedComponentTests::build_params()), \ + ::android::PrintInstanceNameToString) + +using bytevec = std::vector<uint8_t>; +using testing::MatchesRegex; +using namespace remote_prov; +using namespace keymaster; + +bytevec string_to_bytevec(const char* s) { + const uint8_t* p = reinterpret_cast<const uint8_t*>(s); + return bytevec(p, p + strlen(s)); +} + +} // namespace + +class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + if (AServiceManager_isDeclared(GetParam().c_str())) { + ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str())); + provisionable_ = IRemotelyProvisionedComponent::fromBinder(binder); + } + ASSERT_NE(provisionable_, nullptr); + } + + static vector<string> build_params() { + auto params = ::android::getAidlHalInstanceNames(IRemotelyProvisionedComponent::descriptor); + return params; + } + + protected: + std::shared_ptr<IRemotelyProvisionedComponent> provisionable_; +}; + +using GenerateKeyTests = VtsRemotelyProvisionedComponentTests; + +INSTANTIATE_REM_PROV_AIDL_TEST(GenerateKeyTests); + +/** + * Generate and validate a production-mode key. MAC tag can't be verified. + */ +TEST_P(GenerateKeyTests, DISABLED_generateEcdsaP256Key_prodMode) { + MacedPublicKey macedPubKey; + bytevec privateKeyBlob; + bool testMode = false; + auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob); + ASSERT_TRUE(status.isOk()); + + auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey); + ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr; + + ASSERT_NE(coseMac0->asArray(), nullptr); + ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount); + + auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr(); + ASSERT_NE(protParms, nullptr); + ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}"); + + auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr(); + ASSERT_NE(unprotParms, nullptr); + ASSERT_EQ(unprotParms->value().size(), 0); + + auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr(); + ASSERT_NE(payload, nullptr); + auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value()); + ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr; + EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()), + MatchesRegex("{\n" + " 1 : 2,\n" + " 3 : -7,\n" + " -1 : 1,\n" + // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of + // 32 hexadecimal bytes, enclosed in braces and separated by commas. + // In this case, some Ed25519 public key. + " -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" + " -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" + "}")); + + auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr(); + ASSERT_TRUE(coseMac0Tag); + auto extractedTag = coseMac0Tag->value(); + EXPECT_EQ(extractedTag.size(), 32U); + + // Compare with tag generated with kTestMacKey. Shouldn't match. + auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */, + payload->value()); + ASSERT_TRUE(testTag) << "Tag calculation failed: " << testTag.message(); + + EXPECT_NE(*testTag, extractedTag); +} + +/** + * Generate and validate a test-mode key. + */ +TEST_P(GenerateKeyTests, DISABLED_generateEcdsaP256Key_testMode) { + MacedPublicKey macedPubKey; + bytevec privateKeyBlob; + bool testMode = true; + auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob); + ASSERT_TRUE(status.isOk()); + + auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey); + ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr; + + ASSERT_NE(coseMac0->asArray(), nullptr); + ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount); + + auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr(); + ASSERT_NE(protParms, nullptr); + ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}"); + + auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr(); + ASSERT_NE(unprotParms, nullptr); + ASSERT_EQ(unprotParms->value().size(), 0); + + auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr(); + ASSERT_NE(payload, nullptr); + auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value()); + ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr; + EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()), + MatchesRegex("{\n" + " 1 : 2,\n" + " 3 : -7,\n" + " -1 : 1,\n" + // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of + // 32 hexadecimal bytes, enclosed in braces and separated by commas. + // In this case, some Ed25519 public key. + " -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" + " -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" + " -70000 : null,\n" + "}")); + + auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr(); + ASSERT_TRUE(coseMac0); + auto extractedTag = coseMac0Tag->value(); + EXPECT_EQ(extractedTag.size(), 32U); + + // Compare with tag generated with kTestMacKey. Should match. + auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */, + payload->value()); + ASSERT_TRUE(testTag) << testTag.message(); + + EXPECT_EQ(*testTag, extractedTag); +} + +class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { + protected: + CertificateRequestTest() : eekId_(string_to_bytevec("eekid")) { + auto chain = generateEekChain(3, eekId_); + EXPECT_TRUE(chain) << chain.message(); + if (chain) eekChain_ = chain.moveValue(); + } + + void generateKeys(bool testMode, size_t numKeys) { + keysToSign_ = std::vector<MacedPublicKey>(numKeys); + cborKeysToSign_ = cppbor::Array(); + + for (auto& key : keysToSign_) { + bytevec privateKeyBlob; + auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &key, &privateKeyBlob); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto [parsedMacedKey, _, parseErr] = cppbor::parse(key.macedKey); + ASSERT_TRUE(parsedMacedKey) << "Failed parsing MACed key: " << parseErr; + ASSERT_TRUE(parsedMacedKey->asArray()) << "COSE_Mac0 not an array?"; + ASSERT_EQ(parsedMacedKey->asArray()->size(), kCoseMac0EntryCount); + + auto& payload = parsedMacedKey->asArray()->get(kCoseMac0Payload); + ASSERT_TRUE(payload); + ASSERT_TRUE(payload->asBstr()); + + cborKeysToSign_.add(cppbor::EncodedItem(payload->asBstr()->value())); + } + } + + bytevec eekId_; + EekChain eekChain_; + std::vector<MacedPublicKey> keysToSign_; + cppbor::Array cborKeysToSign_; +}; + +/** + * Generate an empty certificate request in test mode, and decrypt and verify the structure and + * content. + */ +TEST_P(CertificateRequestTest, DISABLED_EmptyRequest_testMode) { + bool testMode = true; + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest(testMode, {} /* keysToSign */, + eekChain_.chain, challenge, + &keysToSignMac, &protectedData); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData); + ASSERT_TRUE(parsedProtectedData) << protDataErrMsg; + ASSERT_TRUE(parsedProtectedData->asArray()); + ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount); + + auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData); + ASSERT_TRUE(senderPubkey) << senderPubkey.message(); + EXPECT_EQ(senderPubkey->second, eekId_); + + auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey, + senderPubkey->first, false /* senderIsA */); + ASSERT_TRUE(sessionKey) << sessionKey.message(); + + auto protectedDataPayload = + decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */); + ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message(); + + auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload); + ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg; + ASSERT_TRUE(parsedPayload->asArray()); + EXPECT_EQ(parsedPayload->asArray()->size(), 2U); + + auto& signedMac = parsedPayload->asArray()->get(0); + auto& bcc = parsedPayload->asArray()->get(1); + ASSERT_TRUE(signedMac && signedMac->asArray()); + ASSERT_TRUE(bcc && bcc->asArray()); + + // BCC is [ pubkey, + BccEntry] + auto bccContents = validateBcc(bcc->asArray()); + ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get()); + ASSERT_GT(bccContents->size(), 0U); + + auto& signingKey = bccContents->back().pubKey; + auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey, + cppbor::Array() // DeviceInfo + .add(challenge) // + .add(cppbor::Map()) + .encode()); + ASSERT_TRUE(macKey) << macKey.message(); + + auto coseMac0 = cppbor::Array() + .add(cppbor::Map() // protected + .add(ALGORITHM, HMAC_256) + .canonicalize() + .encode()) + .add(cppbor::Bstr()) // unprotected + .add(cppbor::Array().encode()) // payload (keysToSign) + .add(std::move(keysToSignMac)); // tag + + auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey); + ASSERT_TRUE(macPayload) << macPayload.message(); +} + +/** + * Generate an empty certificate request in prod mode. Generation will fail because we don't have a + * valid GEEK. + * + * TODO(swillden): Get a valid GEEK and use it so the generation can succeed, though we won't be + * able to decrypt. + */ +TEST_P(CertificateRequestTest, DISABLED_EmptyRequest_prodMode) { + bool testMode = false; + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest(testMode, {} /* keysToSign */, + eekChain_.chain, challenge, + &keysToSignMac, &protectedData); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); +} + +/** + * Generate a non-empty certificate request in test mode. Decrypt, parse and validate the contents. + */ +TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_testMode) { + bool testMode = true; + generateKeys(testMode, 4 /* numKeys */); + + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest( + testMode, keysToSign_, eekChain_.chain, challenge, &keysToSignMac, &protectedData); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData); + ASSERT_TRUE(parsedProtectedData) << protDataErrMsg; + ASSERT_TRUE(parsedProtectedData->asArray()); + ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount); + + auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData); + ASSERT_TRUE(senderPubkey) << senderPubkey.message(); + EXPECT_EQ(senderPubkey->second, eekId_); + + auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey, + senderPubkey->first, false /* senderIsA */); + ASSERT_TRUE(sessionKey) << sessionKey.message(); + + auto protectedDataPayload = + decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */); + ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message(); + + auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload); + ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg; + ASSERT_TRUE(parsedPayload->asArray()); + EXPECT_EQ(parsedPayload->asArray()->size(), 2U); + + auto& signedMac = parsedPayload->asArray()->get(0); + auto& bcc = parsedPayload->asArray()->get(1); + ASSERT_TRUE(signedMac && signedMac->asArray()); + ASSERT_TRUE(bcc); + + auto bccContents = validateBcc(bcc->asArray()); + ASSERT_TRUE(bccContents) << "\n" << prettyPrint(bcc.get()); + ASSERT_GT(bccContents->size(), 0U); + + auto& signingKey = bccContents->back().pubKey; + auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey, + cppbor::Array() // DeviceInfo + .add(challenge) // + .add(cppbor::Array()) + .encode()); + ASSERT_TRUE(macKey) << macKey.message(); + + auto coseMac0 = cppbor::Array() + .add(cppbor::Map() // protected + .add(ALGORITHM, HMAC_256) + .canonicalize() + .encode()) + .add(cppbor::Bstr()) // unprotected + .add(cborKeysToSign_.encode()) // payload + .add(std::move(keysToSignMac)); // tag + + auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey); + ASSERT_TRUE(macPayload) << macPayload.message(); +} + +/** + * Generate a non-empty certificate request in prod mode. Must fail because we don't have a valid + * GEEK. + * + * TODO(swillden): Get a valid GEEK and use it so the generation can succeed, though we won't be + * able to decrypt. + */ +TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_prodMode) { + bool testMode = false; + generateKeys(testMode, 4 /* numKeys */); + + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest( + testMode, keysToSign_, eekChain_.chain, challenge, &keysToSignMac, &protectedData); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); +} + +/** + * Generate a non-empty certificate request in test mode, with prod keys. Must fail with + * STATUS_PRODUCTION_KEY_IN_TEST_REQUEST. + */ +TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_prodKeyInTestCert) { + generateKeys(false /* testMode */, 2 /* numKeys */); + + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest(true /* testMode */, keysToSign_, + eekChain_.chain, challenge, + &keysToSignMac, &protectedData); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(status.getServiceSpecificError(), + BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); +} + +/** + * Generate a non-empty certificate request in prod mode, with test keys. Must fail with + * STATUS_TEST_KEY_IN_PRODUCTION_REQUEST. + */ +TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_testKeyInProdCert) { + generateKeys(true /* testMode */, 2 /* numKeys */); + + bytevec keysToSignMac; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + false /* testMode */, keysToSign_, eekChain_.chain, randomBytes(32) /* challenge */, + &keysToSignMac, &protectedData); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(status.getServiceSpecificError(), + BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); +} + +INSTANTIATE_REM_PROV_AIDL_TEST(CertificateRequestTest); + +} // namespace aidl::android::hardware::security::keymint::test diff --git a/security/keymint/aidl/vts/performance/Android.bp b/security/keymint/aidl/vts/performance/Android.bp new file mode 100644 index 0000000000..03240c3459 --- /dev/null +++ b/security/keymint/aidl/vts/performance/Android.bp @@ -0,0 +1,38 @@ +// +// Copyright (C) 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. +// + +cc_benchmark { + name: "VtsAidlKeyMintBenchmarkTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "KeyMintBenchmark.cpp", + ], + shared_libs: [ + "libbinder_ndk", + "libcrypto", + "libkeymint", + "libkeymint_support", + ], + static_libs: [ + "android.hardware.security.keymint-V1-ndk_platform", + "android.hardware.security.secureclock-V1-ndk_platform", + "libcppbor_external", + "libchrome", + ], +} diff --git a/security/keymint/aidl/vts/performance/KeyMintBenchmark.cpp b/security/keymint/aidl/vts/performance/KeyMintBenchmark.cpp new file mode 100644 index 0000000000..f87ca7821b --- /dev/null +++ b/security/keymint/aidl/vts/performance/KeyMintBenchmark.cpp @@ -0,0 +1,714 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "keymint_benchmark" + +#include <base/command_line.h> +#include <benchmark/benchmark.h> +#include <iostream> + +#include <aidl/Vintf.h> +#include <aidl/android/hardware/security/keymint/ErrorCode.h> +#include <aidl/android/hardware/security/keymint/IKeyMintDevice.h> +#include <android/binder_manager.h> +#include <binder/IServiceManager.h> +#include <keymint_support/authorization_set.h> + +#define SMALL_MESSAGE_SIZE 64 +#define MEDIUM_MESSAGE_SIZE 1024 +#define LARGE_MESSAGE_SIZE 131072 + +namespace aidl::android::hardware::security::keymint::test { + +::std::ostream& operator<<(::std::ostream& os, const keymint::AuthorizationSet& set); + +using ::android::sp; +using Status = ::ndk::ScopedAStatus; +using ::std::optional; +using ::std::shared_ptr; +using ::std::string; +using ::std::vector; + +class KeyMintBenchmarkTest { + public: + KeyMintBenchmarkTest() { + message_cache_.push_back(string(SMALL_MESSAGE_SIZE, 'x')); + message_cache_.push_back(string(MEDIUM_MESSAGE_SIZE, 'x')); + message_cache_.push_back(string(LARGE_MESSAGE_SIZE, 'x')); + } + + static KeyMintBenchmarkTest* newInstance(const char* instanceName) { + if (AServiceManager_isDeclared(instanceName)) { + ::ndk::SpAIBinder binder(AServiceManager_waitForService(instanceName)); + KeyMintBenchmarkTest* test = new KeyMintBenchmarkTest(); + test->InitializeKeyMint(IKeyMintDevice::fromBinder(binder)); + return test; + } else { + return nullptr; + } + } + + int getError() { return static_cast<int>(error_); } + + const string& GenerateMessage(int size) { + for (const string& message : message_cache_) { + if (message.size() == size) { + return message; + } + } + string message = string(size, 'x'); + message_cache_.push_back(message); + return std::move(message); + } + + optional<BlockMode> getBlockMode(string transform) { + if (transform.find("/ECB") != string::npos) { + return BlockMode::ECB; + } else if (transform.find("/CBC") != string::npos) { + return BlockMode::CBC; + } else if (transform.find("/CTR") != string::npos) { + return BlockMode::CTR; + } else if (transform.find("/GCM") != string::npos) { + return BlockMode::GCM; + } + return {}; + } + + PaddingMode getPadding(string transform, bool sign) { + if (transform.find("/PKCS7") != string::npos) { + return PaddingMode::PKCS7; + } else if (transform.find("/PSS") != string::npos) { + return PaddingMode::RSA_PSS; + } else if (transform.find("/OAEP") != string::npos) { + return PaddingMode::RSA_OAEP; + } else if (transform.find("/PKCS1") != string::npos) { + return sign ? PaddingMode::RSA_PKCS1_1_5_SIGN : PaddingMode::RSA_PKCS1_1_5_ENCRYPT; + } else if (sign && transform.find("RSA") != string::npos) { + // RSA defaults to PKCS1 for sign + return PaddingMode::RSA_PKCS1_1_5_SIGN; + } + return PaddingMode::NONE; + } + + optional<Algorithm> getAlgorithm(string transform) { + if (transform.find("AES") != string::npos) { + return Algorithm::AES; + } else if (transform.find("Hmac") != string::npos) { + return Algorithm::HMAC; + } else if (transform.find("DESede") != string::npos) { + return Algorithm::TRIPLE_DES; + } else if (transform.find("RSA") != string::npos) { + return Algorithm::RSA; + } else if (transform.find("EC") != string::npos) { + return Algorithm::EC; + } + std::cerr << "Can't find algorithm for " << transform << std::endl; + return {}; + } + + Digest getDigest(string transform) { + if (transform.find("MD5") != string::npos) { + return Digest::MD5; + } else if (transform.find("SHA1") != string::npos || + transform.find("SHA-1") != string::npos) { + return Digest::SHA1; + } else if (transform.find("SHA224") != string::npos) { + return Digest::SHA_2_224; + } else if (transform.find("SHA256") != string::npos) { + return Digest::SHA_2_256; + } else if (transform.find("SHA384") != string::npos) { + return Digest::SHA_2_384; + } else if (transform.find("SHA512") != string::npos) { + return Digest::SHA_2_512; + } else if (transform.find("RSA") != string::npos && + transform.find("OAEP") != string::npos) { + return Digest::SHA1; + } else if (transform.find("Hmac") != string::npos) { + return Digest::SHA_2_256; + } + return Digest::NONE; + } + + bool GenerateKey(string transform, int keySize, bool sign = false) { + if (transform == key_transform_) { + return true; + } else if (key_transform_ != "") { + // Deleting old key first + key_transform_ = ""; + if (DeleteKey() != ErrorCode::OK) { + return false; + } + } + std::optional<Algorithm> algorithm = getAlgorithm(transform); + if (!algorithm) { + std::cerr << "Error: invalid algorithm " << transform << std::endl; + return false; + } + key_transform_ = transform; + AuthorizationSetBuilder authSet = AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .Authorization(TAG_PURPOSE, KeyPurpose::ENCRYPT) + .Authorization(TAG_PURPOSE, KeyPurpose::DECRYPT) + .Authorization(TAG_PURPOSE, KeyPurpose::SIGN) + .Authorization(TAG_PURPOSE, KeyPurpose::VERIFY) + .Authorization(TAG_KEY_SIZE, keySize) + .Authorization(TAG_ALGORITHM, algorithm.value()) + .Digest(getDigest(transform)) + .Padding(getPadding(transform, sign)); + std::optional<BlockMode> blockMode = getBlockMode(transform); + if (blockMode) { + authSet.BlockMode(blockMode.value()); + if (blockMode == BlockMode::GCM) { + authSet.Authorization(TAG_MIN_MAC_LENGTH, 128); + } + } + if (algorithm == Algorithm::HMAC) { + authSet.Authorization(TAG_MIN_MAC_LENGTH, 128); + } + if (algorithm == Algorithm::RSA) { + authSet.Authorization(TAG_RSA_PUBLIC_EXPONENT, 65537U); + authSet.SetDefaultValidity(); + } + if (algorithm == Algorithm::EC) { + authSet.SetDefaultValidity(); + } + error_ = GenerateKey(authSet); + return error_ == ErrorCode::OK; + } + + AuthorizationSet getOperationParams(string transform, bool sign = false) { + AuthorizationSetBuilder builder = AuthorizationSetBuilder() + .Padding(getPadding(transform, sign)) + .Digest(getDigest(transform)); + std::optional<BlockMode> blockMode = getBlockMode(transform); + if (sign && (transform.find("Hmac") != string::npos)) { + builder.Authorization(TAG_MAC_LENGTH, 128); + } + if (blockMode) { + builder.BlockMode(*blockMode); + if (blockMode == BlockMode::GCM) { + builder.Authorization(TAG_MAC_LENGTH, 128); + } + } + return std::move(builder); + } + + optional<string> Process(const string& message, const AuthorizationSet& /*in_params*/, + AuthorizationSet* out_params, const string& signature = "") { + static const int HIDL_BUFFER_LIMIT = 1 << 14; // 16KB + ErrorCode result; + + // Update + AuthorizationSet update_params; + AuthorizationSet update_out_params; + string output; + string aidl_output; + int32_t input_consumed = 0; + int32_t aidl_input_consumed = 0; + while (message.length() - input_consumed > 0) { + result = Update(update_params, message.substr(input_consumed, HIDL_BUFFER_LIMIT), + &update_out_params, &aidl_output, &aidl_input_consumed); + if (result != ErrorCode::OK) { + error_ = result; + return {}; + } + output.append(aidl_output); + input_consumed += aidl_input_consumed; + aidl_output.clear(); + } + + // Finish + AuthorizationSet finish_params; + AuthorizationSet finish_out_params; + result = Finish(finish_params, message.substr(input_consumed), signature, + &finish_out_params, &aidl_output); + if (result != ErrorCode::OK) { + error_ = result; + return {}; + } + output.append(aidl_output); + out_params->push_back(finish_out_params); + return output; + } + + ErrorCode DeleteKey() { + Status result = keymint_->deleteKey(key_blob_); + key_blob_ = vector<uint8_t>(); + return GetReturnErrorCode(result); + } + + ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params, + AuthorizationSet* out_params) { + Status result; + BeginResult out; + result = keymint_->begin(purpose, key_blob_, in_params.vector_data(), HardwareAuthToken(), + &out); + if (result.isOk()) { + *out_params = out.params; + op_ = out.operation; + } + return GetReturnErrorCode(result); + } + + SecurityLevel securityLevel_; + string name_; + + private: + ErrorCode GenerateKey(const AuthorizationSet& key_desc, + const optional<AttestationKey>& attest_key = std::nullopt) { + key_blob_.clear(); + KeyCreationResult creationResult; + Status result = keymint_->generateKey(key_desc.vector_data(), attest_key, &creationResult); + if (result.isOk()) { + key_blob_ = std::move(creationResult.keyBlob); + creationResult.keyCharacteristics.clear(); + creationResult.certificateChain.clear(); + } + return GetReturnErrorCode(result); + } + + void InitializeKeyMint(std::shared_ptr<IKeyMintDevice> keyMint) { + if (!keyMint) { + std::cerr << "Trying initialize nullptr in InitializeKeyMint" << std::endl; + return; + } + keymint_ = std::move(keyMint); + KeyMintHardwareInfo info; + Status result = keymint_->getHardwareInfo(&info); + if (!result.isOk()) { + std::cerr << "InitializeKeyMint: getHardwareInfo failed with " + << result.getServiceSpecificError() << std::endl; + } + securityLevel_ = info.securityLevel; + name_.assign(info.keyMintName.begin(), info.keyMintName.end()); + } + + ErrorCode Finish(const AuthorizationSet& in_params, const string& input, + const string& signature, AuthorizationSet* out_params, string* output) { + Status result; + if (!op_) { + std::cerr << "Finish: Operation is nullptr" << std::endl; + return ErrorCode::UNEXPECTED_NULL_POINTER; + } + KeyParameterArray key_params; + key_params.params = in_params.vector_data(); + + KeyParameterArray in_keyParams; + in_keyParams.params = in_params.vector_data(); + + std::optional<KeyParameterArray> out_keyParams; + std::optional<vector<uint8_t>> o_put; + + vector<uint8_t> oPut; + result = op_->finish(in_keyParams, vector<uint8_t>(input.begin(), input.end()), + vector<uint8_t>(signature.begin(), signature.end()), {}, {}, + &out_keyParams, &oPut); + + if (result.isOk()) { + if (out_keyParams) { + out_params->push_back(AuthorizationSet(out_keyParams->params)); + } + output->append(oPut.begin(), oPut.end()); + } + op_.reset(); + return GetReturnErrorCode(result); + } + + ErrorCode Update(const AuthorizationSet& in_params, const string& input, + AuthorizationSet* out_params, string* output, int32_t* input_consumed) { + Status result; + if (!op_) { + std::cerr << "Update: Operation is nullptr" << std::endl; + return ErrorCode::UNEXPECTED_NULL_POINTER; + } + + KeyParameterArray key_params; + key_params.params = in_params.vector_data(); + + KeyParameterArray in_keyParams; + in_keyParams.params = in_params.vector_data(); + + std::optional<KeyParameterArray> out_keyParams; + std::optional<ByteArray> o_put; + result = op_->update(in_keyParams, vector<uint8_t>(input.begin(), input.end()), {}, {}, + &out_keyParams, &o_put, input_consumed); + + if (result.isOk()) { + if (o_put) { + output->append(o_put->data.begin(), o_put->data.end()); + } + + if (out_keyParams) { + out_params->push_back(AuthorizationSet(out_keyParams->params)); + } + } + + return GetReturnErrorCode(result); + } + + ErrorCode GetReturnErrorCode(const Status& result) { + error_ = static_cast<ErrorCode>(result.getServiceSpecificError()); + if (result.isOk()) return ErrorCode::OK; + + if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) { + return static_cast<ErrorCode>(result.getServiceSpecificError()); + } + + return ErrorCode::UNKNOWN_ERROR; + } + + std::shared_ptr<IKeyMintOperation> op_; + vector<Certificate> cert_chain_; + vector<uint8_t> key_blob_; + vector<KeyCharacteristics> key_characteristics_; + std::shared_ptr<IKeyMintDevice> keymint_; + std::vector<string> message_cache_; + std::string key_transform_; + ErrorCode error_; +}; + +KeyMintBenchmarkTest* keymintTest; + +static void settings(benchmark::internal::Benchmark* benchmark) { + benchmark->Unit(benchmark::kMillisecond); +} + +static void addDefaultLabel(benchmark::State& state) { + std::string secLevel; + switch (keymintTest->securityLevel_) { + case SecurityLevel::STRONGBOX: + secLevel = "STRONGBOX"; + break; + case SecurityLevel::SOFTWARE: + secLevel = "SOFTWARE"; + break; + case SecurityLevel::TRUSTED_ENVIRONMENT: + secLevel = "TEE"; + break; + case SecurityLevel::KEYSTORE: + secLevel = "KEYSTORE"; + break; + } + state.SetLabel("hardware_name:" + keymintTest->name_ + " sec_level:" + secLevel); +} + +// clang-format off +#define BENCHMARK_KM(func, transform, keySize) \ + BENCHMARK_CAPTURE(func, transform/keySize, #transform "/" #keySize, keySize)->Apply(settings); +#define BENCHMARK_KM_MSG(func, transform, keySize, msgSize) \ + BENCHMARK_CAPTURE(func, transform/keySize/msgSize, #transform "/" #keySize "/" #msgSize, \ + keySize, msgSize) \ + ->Apply(settings); + +#define BENCHMARK_KM_ALL_MSGS(func, transform, keySize) \ + BENCHMARK_KM_MSG(func, transform, keySize, SMALL_MESSAGE_SIZE) \ + BENCHMARK_KM_MSG(func, transform, keySize, MEDIUM_MESSAGE_SIZE) \ + BENCHMARK_KM_MSG(func, transform, keySize, LARGE_MESSAGE_SIZE) + +#define BENCHMARK_KM_CIPHER(transform, keySize, msgSize) \ + BENCHMARK_KM_MSG(encrypt, transform, keySize, msgSize) \ + BENCHMARK_KM_MSG(decrypt, transform, keySize, msgSize) + +#define BENCHMARK_KM_CIPHER_ALL_MSGS(transform, keySize) \ + BENCHMARK_KM_ALL_MSGS(encrypt, transform, keySize) \ + BENCHMARK_KM_ALL_MSGS(decrypt, transform, keySize) + +#define BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, keySize) \ + BENCHMARK_KM_ALL_MSGS(sign, transform, keySize) \ + BENCHMARK_KM_ALL_MSGS(verify, transform, keySize) +// clang-format on + +/* + * ============= KeyGen TESTS ================== + */ +static void keygen(benchmark::State& state, string transform, int keySize) { + addDefaultLabel(state); + for (auto _ : state) { + if (!keymintTest->GenerateKey(transform, keySize)) { + state.SkipWithError( + ("Key generation error, " + std::to_string(keymintTest->getError())).c_str()); + } + state.PauseTiming(); + + keymintTest->DeleteKey(); + state.ResumeTiming(); + } +} + +BENCHMARK_KM(keygen, AES, 128); +BENCHMARK_KM(keygen, AES, 256); + +BENCHMARK_KM(keygen, RSA, 2048); +BENCHMARK_KM(keygen, RSA, 3072); +BENCHMARK_KM(keygen, RSA, 4096); + +BENCHMARK_KM(keygen, EC, 224); +BENCHMARK_KM(keygen, EC, 256); +BENCHMARK_KM(keygen, EC, 384); +BENCHMARK_KM(keygen, EC, 521); + +BENCHMARK_KM(keygen, DESede, 168); + +BENCHMARK_KM(keygen, Hmac, 64); +BENCHMARK_KM(keygen, Hmac, 128); +BENCHMARK_KM(keygen, Hmac, 256); +BENCHMARK_KM(keygen, Hmac, 512); + +/* + * ============= SIGNATURE TESTS ================== + */ + +static void sign(benchmark::State& state, string transform, int keySize, int msgSize) { + addDefaultLabel(state); + if (!keymintTest->GenerateKey(transform, keySize, true)) { + state.SkipWithError( + ("Key generation error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + + auto in_params = keymintTest->getOperationParams(transform, true); + AuthorizationSet out_params; + string message = keymintTest->GenerateMessage(msgSize); + + for (auto _ : state) { + state.PauseTiming(); + ErrorCode error = keymintTest->Begin(KeyPurpose::SIGN, in_params, &out_params); + if (error != ErrorCode::OK) { + state.SkipWithError( + ("Error beginning sign, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + state.ResumeTiming(); + out_params.Clear(); + if (!keymintTest->Process(message, in_params, &out_params)) { + state.SkipWithError(("Sign error, " + std::to_string(keymintTest->getError())).c_str()); + break; + } + } +} + +static void verify(benchmark::State& state, string transform, int keySize, int msgSize) { + addDefaultLabel(state); + if (!keymintTest->GenerateKey(transform, keySize, true)) { + state.SkipWithError( + ("Key generation error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + AuthorizationSet out_params; + auto in_params = keymintTest->getOperationParams(transform, true); + string message = keymintTest->GenerateMessage(msgSize); + ErrorCode error = keymintTest->Begin(KeyPurpose::SIGN, in_params, &out_params); + if (error != ErrorCode::OK) { + state.SkipWithError( + ("Error beginning sign, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + std::optional<string> signature = keymintTest->Process(message, in_params, &out_params); + if (!signature) { + state.SkipWithError(("Sign error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + out_params.Clear(); + if (transform.find("Hmac") != string::npos) { + in_params = keymintTest->getOperationParams(transform, false); + } + for (auto _ : state) { + state.PauseTiming(); + error = keymintTest->Begin(KeyPurpose::VERIFY, in_params, &out_params); + if (error != ErrorCode::OK) { + state.SkipWithError( + ("Verify begin error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + state.ResumeTiming(); + if (!keymintTest->Process(message, in_params, &out_params, *signature)) { + state.SkipWithError( + ("Verify error, " + std::to_string(keymintTest->getError())).c_str()); + break; + } + } +} + +// clang-format off +#define BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(transform) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 64) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 128) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 256) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 512) + +BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA1) +BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA256) +BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA224) +BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA256) +BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA384) +BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA512) + +#define BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(transform) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 224) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 256) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 384) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 521) + +BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(NONEwithECDSA); +BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA1withECDSA); +BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA224withECDSA); +BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA256withECDSA); +BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA384withECDSA); +BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA512withECDSA); + +#define BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(transform) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 2048) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 3072) \ + BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 4096) + +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(MD5withRSA); +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA1withRSA); +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA224withRSA); +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA384withRSA); +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA512withRSA); + +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(MD5withRSA/PSS); +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA1withRSA/PSS); +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA224withRSA/PSS); +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA384withRSA/PSS); +BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA512withRSA/PSS); +// clang-format on + +/* + * ============= CIPHER TESTS ================== + */ + +static void encrypt(benchmark::State& state, string transform, int keySize, int msgSize) { + addDefaultLabel(state); + if (!keymintTest->GenerateKey(transform, keySize)) { + state.SkipWithError( + ("Key generation error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + auto in_params = keymintTest->getOperationParams(transform); + AuthorizationSet out_params; + string message = keymintTest->GenerateMessage(msgSize); + + for (auto _ : state) { + state.PauseTiming(); + auto error = keymintTest->Begin(KeyPurpose::ENCRYPT, in_params, &out_params); + if (error != ErrorCode::OK) { + state.SkipWithError( + ("Encryption begin error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + out_params.Clear(); + state.ResumeTiming(); + if (!keymintTest->Process(message, in_params, &out_params)) { + state.SkipWithError( + ("Encryption error, " + std::to_string(keymintTest->getError())).c_str()); + break; + } + } +} + +static void decrypt(benchmark::State& state, string transform, int keySize, int msgSize) { + addDefaultLabel(state); + if (!keymintTest->GenerateKey(transform, keySize)) { + state.SkipWithError( + ("Key generation error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + AuthorizationSet out_params; + AuthorizationSet in_params = keymintTest->getOperationParams(transform); + string message = keymintTest->GenerateMessage(msgSize); + auto error = keymintTest->Begin(KeyPurpose::ENCRYPT, in_params, &out_params); + if (error != ErrorCode::OK) { + state.SkipWithError( + ("Encryption begin error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + auto encryptedMessage = keymintTest->Process(message, in_params, &out_params); + if (!encryptedMessage) { + state.SkipWithError( + ("Encryption error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + in_params.push_back(out_params); + out_params.Clear(); + for (auto _ : state) { + state.PauseTiming(); + error = keymintTest->Begin(KeyPurpose::DECRYPT, in_params, &out_params); + if (error != ErrorCode::OK) { + state.SkipWithError( + ("Decryption begin error, " + std::to_string(keymintTest->getError())).c_str()); + return; + } + state.ResumeTiming(); + if (!keymintTest->Process(*encryptedMessage, in_params, &out_params)) { + state.SkipWithError( + ("Decryption error, " + std::to_string(keymintTest->getError())).c_str()); + break; + } + } +} + +// clang-format off +// AES +#define BENCHMARK_KM_CIPHER_ALL_AES_KEYS(transform) \ + BENCHMARK_KM_CIPHER_ALL_MSGS(transform, 128) \ + BENCHMARK_KM_CIPHER_ALL_MSGS(transform, 256) + +BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/CBC/NoPadding); +BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/CBC/PKCS7Padding); +BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/CTR/NoPadding); +BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/ECB/NoPadding); +BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/ECB/PKCS7Padding); +BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/GCM/NoPadding); + +// Triple DES +BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/CBC/NoPadding, 168); +BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/CBC/PKCS7Padding, 168); +BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/ECB/NoPadding, 168); +BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/ECB/PKCS7Padding, 168); + +#define BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(transform, msgSize) \ + BENCHMARK_KM_CIPHER(transform, 2048, msgSize) \ + BENCHMARK_KM_CIPHER(transform, 3072, msgSize) \ + BENCHMARK_KM_CIPHER(transform, 4096, msgSize) + +BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(RSA/ECB/NoPadding, SMALL_MESSAGE_SIZE); +BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(RSA/ECB/PKCS1Padding, SMALL_MESSAGE_SIZE); +BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(RSA/ECB/OAEPPadding, SMALL_MESSAGE_SIZE); + +// clang-format on +} // namespace aidl::android::hardware::security::keymint::test + +int main(int argc, char** argv) { + ::benchmark::Initialize(&argc, argv); + base::CommandLine::Init(argc, argv); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + auto service_name = command_line->GetSwitchValueASCII("service_name"); + if (service_name.empty()) { + service_name = + std::string( + aidl::android::hardware::security::keymint::IKeyMintDevice::descriptor) + + "/default"; + } + std::cerr << service_name << std::endl; + aidl::android::hardware::security::keymint::test::keymintTest = + aidl::android::hardware::security::keymint::test::KeyMintBenchmarkTest::newInstance( + service_name.c_str()); + if (!aidl::android::hardware::security::keymint::test::keymintTest) { + return 1; + } + ::benchmark::RunSpecifiedBenchmarks(); +} diff --git a/security/keymint/aidl/vts/performance/README b/security/keymint/aidl/vts/performance/README new file mode 100644 index 0000000000..1221ad8577 --- /dev/null +++ b/security/keymint/aidl/vts/performance/README @@ -0,0 +1,28 @@ +# KeyMint Benchmark + +The KeyMint Benchmark is a standalone tool for measuring the performance of + KeyMint implementations. + +## Building + +Build: +`m VtsAidlKeyMintBenchmarkTest` + +Transfer to device/emulator: +`adb sync data` + +The benchmark executable will be located at + `data/benchmarktest/VtsAidlKeyMintBenchmarkTest` on the device. + +## Usage + +KeyMint Benchmark is built on [Google microbenchmark +library](https://github.com/google/benchmark). All of the commandline arguments +provided by the microbenchmark library are valid, such as +`--benchmark_filter=<regex>` or `benchmark_out_format={json|console|csv}`. +In addition to the command line arguments provided by microbenchmark, +`--service_name=<service_name>` is provided to allow specification of the KeyMint +fully qualified service name, e.g. specify +`--service_name=android.hardware.security.keymint.IKeyMintDevice/default` to +benchmark default implementation of KeyMint. + diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp index fde6b57683..02558745d5 100644 --- a/security/keymint/support/Android.bp +++ b/security/keymint/support/Android.bp @@ -37,3 +37,40 @@ cc_library { "libutils", ], } + +cc_library { + name: "libkeymint_remote_prov_support", + vendor_available: true, + srcs: [ + "remote_prov_utils.cpp", + ], + export_include_dirs: [ + "include", + ], + shared_libs: [ + "libcppcose", + "libcppbor_external", + "libcrypto", + ], +} + +cc_library { + name: "libcppcose", + vendor_available: true, + srcs: [ + "cppcose.cpp", + ], + export_include_dirs: [ + "include", + ], + shared_libs: [ + "libbinder_ndk", + "libcppbor_external", + "libcrypto", + "liblog", + ], + static_libs: [ + // TODO(swillden): Remove keymint NDK + "android.hardware.security.keymint-V1-ndk_platform", + ], +} diff --git a/security/keymint/support/authorization_set.cpp b/security/keymint/support/authorization_set.cpp index 3d44dff27c..25eace3caf 100644 --- a/security/keymint/support/authorization_set.cpp +++ b/security/keymint/support/authorization_set.cpp @@ -191,6 +191,10 @@ AuthorizationSetBuilder& AuthorizationSetBuilder::EncryptionKey() { return Authorization(TAG_PURPOSE, KeyPurpose::DECRYPT); } +AuthorizationSetBuilder& AuthorizationSetBuilder::AttestKey() { + return Authorization(TAG_PURPOSE, KeyPurpose::ATTEST_KEY); +} + AuthorizationSetBuilder& AuthorizationSetBuilder::NoDigestOrPadding() { Authorization(TAG_DIGEST, Digest::NONE); return Authorization(TAG_PADDING, PaddingMode::NONE); @@ -243,4 +247,12 @@ AuthorizationSetBuilder& AuthorizationSetBuilder::Padding( return *this; } +AuthorizationSetBuilder& AuthorizationSetBuilder::SetDefaultValidity() { + // Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to + // GeneralizedTime 999912312359559, which is 253402300799000 ms from Jan 1, 1970. + constexpr uint64_t kUndefinedExpirationDateTime = 253402300799000; + Authorization(TAG_CERTIFICATE_NOT_BEFORE, 0); + return Authorization(TAG_CERTIFICATE_NOT_AFTER, kUndefinedExpirationDateTime); +} + } // namespace aidl::android::hardware::security::keymint diff --git a/security/keymint/support/cppcose.cpp b/security/keymint/support/cppcose.cpp new file mode 100644 index 0000000000..c626adeccb --- /dev/null +++ b/security/keymint/support/cppcose.cpp @@ -0,0 +1,467 @@ +/* + * 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. + */ + +#include <cppcose/cppcose.h> + +#include <stdio.h> +#include <iostream> + +#include <cppbor.h> +#include <cppbor_parse.h> + +#include <openssl/err.h> + +namespace cppcose { + +namespace { + +ErrMsgOr<bssl::UniquePtr<EVP_CIPHER_CTX>> aesGcmInitAndProcessAad(const bytevec& key, + const bytevec& nonce, + const bytevec& aad, + bool encrypt) { + if (key.size() != kAesGcmKeySize) return "Invalid key size"; + + bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new()); + if (!ctx) return "Failed to allocate cipher context"; + + if (!EVP_CipherInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr /* engine */, key.data(), + nonce.data(), encrypt ? 1 : 0)) { + return "Failed to initialize cipher"; + } + + int outlen; + if (!aad.empty() && !EVP_CipherUpdate(ctx.get(), nullptr /* out; null means AAD */, &outlen, + aad.data(), aad.size())) { + return "Failed to process AAD"; + } + + return std::move(ctx); +} + +} // namespace + +ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad, + const bytevec& payload) { + auto macStructure = cppbor::Array() + .add("MAC0") + .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode()) + .add(externalAad) + .add(payload) + .encode(); + + bytevec macTag(SHA256_DIGEST_LENGTH); + uint8_t* out = macTag.data(); + unsigned int outLen; + out = HMAC(EVP_sha256(), // + macKey.data(), macKey.size(), // + macStructure.data(), macStructure.size(), // + out, &outLen); + + assert(out != nullptr && outLen == macTag.size()); + if (out == nullptr || outLen != macTag.size()) { + return "Error computing public key MAC"; + } + + return macTag; +} + +ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad, + const bytevec& payload) { + auto tag = generateCoseMac0Mac(macKey, externalAad, payload); + if (!tag) return tag.moveMessage(); + + return cppbor::Array() + .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode()) + .add(cppbor::Bstr() /* unprotected */) + .add(payload) + .add(tag.moveValue()); +} + +ErrMsgOr<bytevec /* payload */> parseCoseMac0(const cppbor::Item* macItem) { + auto mac = macItem ? macItem->asArray() : nullptr; + if (!mac || mac->size() != kCoseMac0EntryCount) { + return "Invalid COSE_Mac0"; + } + + auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr(); + auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr(); + auto payload = mac->get(kCoseMac0Payload)->asBstr(); + auto tag = mac->get(kCoseMac0Tag)->asBstr(); + if (!protectedParms || !unprotectedParms || !payload || !tag) { + return "Invalid COSE_Mac0 contents"; + } + + return payload->value(); +} + +ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem, + const bytevec& macKey) { + auto mac = macItem ? macItem->asArray() : nullptr; + if (!mac || mac->size() != kCoseMac0EntryCount) { + return "Invalid COSE_Mac0"; + } + + auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr(); + auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr(); + auto payload = mac->get(kCoseMac0Payload)->asBstr(); + auto tag = mac->get(kCoseMac0Tag)->asBstr(); + if (!protectedParms || !unprotectedParms || !payload || !tag) { + return "Invalid COSE_Mac0 contents"; + } + + auto [protectedMap, _, errMsg] = cppbor::parse(protectedParms); + if (!protectedMap || !protectedMap->asMap()) { + return "Invalid Mac0 protected: " + errMsg; + } + auto& algo = protectedMap->asMap()->get(ALGORITHM); + if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) { + return "Unsupported Mac0 algorithm"; + } + + auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value()); + if (!macTag) return macTag.moveMessage(); + + if (macTag->size() != tag->value().size() || + CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) { + return "MAC tag mismatch"; + } + + return payload->value(); +} + +ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams, + const bytevec& payload, const bytevec& aad) { + bytevec signatureInput = cppbor::Array() + .add("Signature1") // + .add(protectedParams) + .add(aad) + .add(payload) + .encode(); + + if (key.size() != ED25519_PRIVATE_KEY_LEN) return "Invalid signing key"; + bytevec signature(ED25519_SIGNATURE_LEN); + if (!ED25519_sign(signature.data(), signatureInput.data(), signatureInput.size(), key.data())) { + return "Signing failed"; + } + + return signature; +} + +ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, cppbor::Map protectedParams, + const bytevec& payload, const bytevec& aad) { + bytevec protParms = protectedParams.add(ALGORITHM, EDDSA).canonicalize().encode(); + auto signature = createCoseSign1Signature(key, protParms, payload, aad); + if (!signature) return signature.moveMessage(); + + return cppbor::Array() + .add(protParms) + .add(bytevec{} /* unprotected parameters */) + .add(payload) + .add(*signature); +} + +ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload, + const bytevec& aad) { + return constructCoseSign1(key, {} /* protectedParams */, payload, aad); +} + +ErrMsgOr<bytevec> verifyAndParseCoseSign1(bool ignoreSignature, const cppbor::Array* coseSign1, + const bytevec& signingCoseKey, const bytevec& aad) { + if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) { + return "Invalid COSE_Sign1"; + } + + const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr(); + const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr(); + const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr(); + const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr(); + + if (!protectedParams || !unprotectedParams || !payload || !signature) { + return "Invalid COSE_Sign1"; + } + + auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams); + if (!parsedProtParams) { + return errMsg + " when parsing protected params."; + } + if (!parsedProtParams->asMap()) { + return "Protected params must be a map"; + } + + auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); + if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) { + return "Unsupported signature algorithm"; + } + + if (!ignoreSignature) { + bool selfSigned = signingCoseKey.empty(); + auto key = CoseKey::parseEd25519(selfSigned ? payload->value() : signingCoseKey); + if (!key) return "Bad signing key: " + key.moveMessage(); + + bytevec signatureInput = cppbor::Array() + .add("Signature1") + .add(*protectedParams) + .add(aad) + .add(*payload) + .encode(); + + if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), + key->getBstrValue(CoseKey::PUBKEY_X)->data())) { + return "Signature verification failed"; + } + } + + return payload->value(); +} + +ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce, + const bytevec& protectedParams, + const bytevec& plaintextPayload, const bytevec& aad) { + auto ciphertext = aesGcmEncrypt(key, nonce, + cppbor::Array() // Enc strucure as AAD + .add("Encrypt") // Context + .add(protectedParams) // Protected + .add(aad) // External AAD + .encode(), + plaintextPayload); + + if (!ciphertext) return ciphertext.moveMessage(); + return ciphertext.moveValue(); +} + +ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce, + const bytevec& plaintextPayload, const bytevec& aad, + cppbor::Array recipients) { + auto encryptProtectedHeader = cppbor::Map() // + .add(ALGORITHM, AES_GCM_256) + .canonicalize() + .encode(); + + auto ciphertext = + createCoseEncryptCiphertext(key, nonce, encryptProtectedHeader, plaintextPayload, aad); + if (!ciphertext) return ciphertext.moveMessage(); + + return cppbor::Array() + .add(encryptProtectedHeader) // Protected + .add(cppbor::Map().add(IV, nonce).canonicalize()) // Unprotected + .add(*ciphertext) // Payload + .add(std::move(recipients)); +} + +ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> getSenderPubKeyFromCoseEncrypt( + const cppbor::Item* coseEncrypt) { + if (!coseEncrypt || !coseEncrypt->asArray() || + coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) { + return "Invalid COSE_Encrypt"; + } + + auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients); + if (!recipients || !recipients->asArray() || recipients->asArray()->size() != 1) { + return "Invalid recipients list"; + } + + auto& recipient = recipients->asArray()->get(0); + if (!recipient || !recipient->asArray() || recipient->asArray()->size() != 3) { + return "Invalid COSE_recipient"; + } + + auto& ciphertext = recipient->asArray()->get(2); + if (!ciphertext->asSimple() || !ciphertext->asSimple()->asNull()) { + return "Unexpected value in recipients ciphertext field " + + cppbor::prettyPrint(ciphertext.get()); + } + + auto& protParms = recipient->asArray()->get(0); + if (!protParms || !protParms->asBstr()) return "Invalid protected params"; + auto [parsedProtParms, _, errMsg] = cppbor::parse(protParms->asBstr()); + if (!parsedProtParms) return "Failed to parse protected params: " + errMsg; + if (!parsedProtParms->asMap()) return "Invalid protected params"; + + auto& algorithm = parsedProtParms->asMap()->get(ALGORITHM); + if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != ECDH_ES_HKDF_256) { + return "Invalid algorithm"; + } + + auto& unprotParms = recipient->asArray()->get(1); + if (!unprotParms || !unprotParms->asMap()) return "Invalid unprotected params"; + + auto& senderCoseKey = unprotParms->asMap()->get(COSE_KEY); + if (!senderCoseKey || !senderCoseKey->asMap()) return "Invalid sender COSE_Key"; + + auto& keyType = senderCoseKey->asMap()->get(CoseKey::KEY_TYPE); + if (!keyType || !keyType->asInt() || keyType->asInt()->value() != OCTET_KEY_PAIR) { + return "Invalid key type"; + } + + auto& curve = senderCoseKey->asMap()->get(CoseKey::CURVE); + if (!curve || !curve->asInt() || curve->asInt()->value() != X25519) { + return "Unsupported curve"; + } + + auto& pubkey = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X); + if (!pubkey || !pubkey->asBstr() || + pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) { + return "Invalid X25519 public key"; + } + + auto& key_id = unprotParms->asMap()->get(KEY_ID); + if (key_id && key_id->asBstr()) { + return std::make_pair(pubkey->asBstr()->value(), key_id->asBstr()->value()); + } + + // If no key ID, just return an empty vector. + return std::make_pair(pubkey->asBstr()->value(), bytevec{}); +} + +ErrMsgOr<bytevec> decryptCoseEncrypt(const bytevec& key, const cppbor::Item* coseEncrypt, + const bytevec& external_aad) { + if (!coseEncrypt || !coseEncrypt->asArray() || + coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) { + return "Invalid COSE_Encrypt"; + } + + auto& protParms = coseEncrypt->asArray()->get(kCoseEncryptProtectedParams); + auto& unprotParms = coseEncrypt->asArray()->get(kCoseEncryptUnprotectedParams); + auto& ciphertext = coseEncrypt->asArray()->get(kCoseEncryptPayload); + auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients); + + if (!protParms || !protParms->asBstr() || !unprotParms || !ciphertext || !recipients) { + return "Invalid COSE_Encrypt"; + } + + auto [parsedProtParams, _, errMsg] = cppbor::parse(protParms->asBstr()->value()); + if (!parsedProtParams) { + return errMsg + " when parsing protected params."; + } + if (!parsedProtParams->asMap()) { + return "Protected params must be a map"; + } + + auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); + if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != AES_GCM_256) { + return "Unsupported encryption algorithm"; + } + + if (!unprotParms->asMap() || unprotParms->asMap()->size() != 1) { + return "Invalid unprotected params"; + } + + auto& nonce = unprotParms->asMap()->get(IV); + if (!nonce || !nonce->asBstr() || nonce->asBstr()->value().size() != kAesGcmNonceLength) { + return "Invalid nonce"; + } + + if (!ciphertext->asBstr()) return "Invalid ciphertext"; + + auto aad = cppbor::Array() // Enc strucure as AAD + .add("Encrypt") // Context + .add(protParms->asBstr()->value()) // Protected + .add(external_aad) // External AAD + .encode(); + + return aesGcmDecrypt(key, nonce->asBstr()->value(), aad, ciphertext->asBstr()->value()); +} + +ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA, + const bytevec& pubKeyB, bool senderIsA) { + bytevec rawSharedKey(X25519_SHARED_KEY_LEN); + if (!::X25519(rawSharedKey.data(), privKeyA.data(), pubKeyB.data())) { + return "ECDH operation failed"; + } + + bytevec kdfContext = cppbor::Array() + .add(AES_GCM_256) + .add(cppbor::Array() // Sender Info + .add(cppbor::Bstr("client")) + .add(bytevec{} /* nonce */) + .add(senderIsA ? pubKeyA : pubKeyB)) + .add(cppbor::Array() // Recipient Info + .add(cppbor::Bstr("server")) + .add(bytevec{} /* nonce */) + .add(senderIsA ? pubKeyB : pubKeyA)) + .add(cppbor::Array() // SuppPubInfo + .add(128) // output key length + .add(bytevec{})) // protected + .encode(); + + bytevec retval(SHA256_DIGEST_LENGTH); + bytevec salt{}; + if (!HKDF(retval.data(), retval.size(), // + EVP_sha256(), // + rawSharedKey.data(), rawSharedKey.size(), // + salt.data(), salt.size(), // + kdfContext.data(), kdfContext.size())) { + return "ECDH HKDF failed"; + } + + return retval; +} + +ErrMsgOr<bytevec> aesGcmEncrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad, + const bytevec& plaintext) { + auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, true /* encrypt */); + if (!ctx) return ctx.moveMessage(); + + bytevec ciphertext(plaintext.size() + kAesGcmTagSize); + int outlen; + if (!EVP_CipherUpdate(ctx->get(), ciphertext.data(), &outlen, plaintext.data(), + plaintext.size())) { + return "Failed to encrypt plaintext"; + } + assert(plaintext.size() == outlen); + + if (!EVP_CipherFinal_ex(ctx->get(), ciphertext.data() + outlen, &outlen)) { + return "Failed to finalize encryption"; + } + assert(outlen == 0); + + if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize, + ciphertext.data() + plaintext.size())) { + return "Failed to retrieve tag"; + } + + return ciphertext; +} + +ErrMsgOr<bytevec> aesGcmDecrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad, + const bytevec& ciphertextWithTag) { + auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, false /* encrypt */); + if (!ctx) return ctx.moveMessage(); + + if (ciphertextWithTag.size() < kAesGcmTagSize) return "Missing tag"; + + bytevec plaintext(ciphertextWithTag.size() - kAesGcmTagSize); + int outlen; + if (!EVP_CipherUpdate(ctx->get(), plaintext.data(), &outlen, ciphertextWithTag.data(), + ciphertextWithTag.size() - kAesGcmTagSize)) { + return "Failed to decrypt plaintext"; + } + assert(plaintext.size() == outlen); + + bytevec tag(ciphertextWithTag.end() - kAesGcmTagSize, ciphertextWithTag.end()); + if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_SET_TAG, kAesGcmTagSize, tag.data())) { + return "Failed to set tag: " + std::to_string(ERR_peek_last_error()); + } + + if (!EVP_CipherFinal_ex(ctx->get(), nullptr, &outlen)) { + return "Failed to finalize encryption"; + } + assert(outlen == 0); + + return plaintext; +} + +} // namespace cppcose diff --git a/security/keymint/support/include/cppcose/cppcose.h b/security/keymint/support/include/cppcose/cppcose.h new file mode 100644 index 0000000000..a936bfdb5a --- /dev/null +++ b/security/keymint/support/include/cppcose/cppcose.h @@ -0,0 +1,288 @@ +/* + * 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. + */ + +#pragma once + +#include <memory> +#include <optional> +#include <string> +#include <vector> + +#include <cppbor.h> +#include <cppbor_parse.h> + +#include <openssl/cipher.h> +#include <openssl/curve25519.h> +#include <openssl/digest.h> +#include <openssl/hkdf.h> +#include <openssl/hmac.h> +#include <openssl/mem.h> +#include <openssl/sha.h> + +namespace cppcose { + +using bytevec = std::vector<uint8_t>; + +constexpr int kCoseSign1EntryCount = 4; +constexpr int kCoseSign1ProtectedParams = 0; +constexpr int kCoseSign1UnprotectedParams = 1; +constexpr int kCoseSign1Payload = 2; +constexpr int kCoseSign1Signature = 3; + +constexpr int kCoseMac0EntryCount = 4; +constexpr int kCoseMac0ProtectedParams = 0; +constexpr int kCoseMac0UnprotectedParams = 1; +constexpr int kCoseMac0Payload = 2; +constexpr int kCoseMac0Tag = 3; + +constexpr int kCoseEncryptEntryCount = 4; +constexpr int kCoseEncryptProtectedParams = 0; +constexpr int kCoseEncryptUnprotectedParams = 1; +constexpr int kCoseEncryptPayload = 2; +constexpr int kCoseEncryptRecipients = 3; + +enum Label : int { + ALGORITHM = 1, + KEY_ID = 4, + IV = 5, + COSE_KEY = -1, +}; + +enum CoseKeyAlgorithm : int { + AES_GCM_256 = 3, + HMAC_256 = 5, + ES256 = -7, // ECDSA with SHA-256 + EDDSA = -8, + ECDH_ES_HKDF_256 = -25, +}; + +enum CoseKeyCurve : int { P256 = 1, X25519 = 4, ED25519 = 6 }; +enum CoseKeyType : int { OCTET_KEY_PAIR = 1, EC2 = 2, SYMMETRIC_KEY = 4 }; +enum CoseKeyOps : int { SIGN = 1, VERIFY = 2, ENCRYPT = 3, DECRYPT = 4 }; + +constexpr int kAesGcmNonceLength = 12; +constexpr int kAesGcmTagSize = 16; +constexpr int kAesGcmKeySize = 32; + +template <typename T> +class ErrMsgOr { + public: + ErrMsgOr(std::string errMsg) : errMsg_(std::move(errMsg)) {} + ErrMsgOr(const char* errMsg) : errMsg_(errMsg) {} + ErrMsgOr(T val) : value_(std::move(val)) {} + + operator bool() const { return value_.has_value(); } + + T* operator->() & { + assert(value_); + return &value_.value(); + } + T& operator*() & { + assert(value_); + return value_.value(); + }; + T&& operator*() && { + assert(value_); + return std::move(value_).value(); + }; + + const std::string& message() { return errMsg_; } + std::string moveMessage() { return std::move(errMsg_); } + + T moveValue() { + assert(value_); + return std::move(value_).value(); + } + + private: + std::string errMsg_; + std::optional<T> value_; +}; + +class CoseKey { + public: + CoseKey() {} + CoseKey(const CoseKey&) = delete; + CoseKey(CoseKey&&) = default; + + enum Label : int { + KEY_TYPE = 1, + KEY_ID = 2, + ALGORITHM = 3, + KEY_OPS = 4, + CURVE = -1, + PUBKEY_X = -2, + PUBKEY_Y = -3, + PRIVATE_KEY = -4, + TEST_KEY = -70000 // Application-defined + }; + + static ErrMsgOr<CoseKey> parse(const bytevec& coseKey) { + auto [parsedKey, _, errMsg] = cppbor::parse(coseKey); + if (!parsedKey) return errMsg + " when parsing key"; + if (!parsedKey->asMap()) return "CoseKey must be a map"; + return CoseKey(static_cast<cppbor::Map*>(parsedKey.release())); + } + + static ErrMsgOr<CoseKey> parse(const bytevec& coseKey, CoseKeyType expectedKeyType, + CoseKeyAlgorithm expectedAlgorithm, CoseKeyCurve expectedCurve) { + auto key = parse(coseKey); + if (!key) return key; + + if (!key->checkIntValue(CoseKey::KEY_TYPE, expectedKeyType) || + !key->checkIntValue(CoseKey::ALGORITHM, expectedAlgorithm) || + !key->checkIntValue(CoseKey::CURVE, expectedCurve)) { + return "Unexpected key type:"; + } + + return key; + } + + static ErrMsgOr<CoseKey> parseEd25519(const bytevec& coseKey) { + auto key = parse(coseKey, OCTET_KEY_PAIR, EDDSA, ED25519); + if (!key) return key; + + auto& pubkey = key->getMap().get(PUBKEY_X); + if (!pubkey || !pubkey->asBstr() || + pubkey->asBstr()->value().size() != ED25519_PUBLIC_KEY_LEN) { + return "Invalid Ed25519 public key"; + } + + return key; + } + + static ErrMsgOr<CoseKey> parseX25519(const bytevec& coseKey, bool requireKid) { + auto key = parse(coseKey, OCTET_KEY_PAIR, ECDH_ES_HKDF_256, X25519); + if (!key) return key; + + auto& pubkey = key->getMap().get(PUBKEY_X); + if (!pubkey || !pubkey->asBstr() || + pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) { + return "Invalid X25519 public key"; + } + + auto& kid = key->getMap().get(KEY_ID); + if (requireKid && (!kid || !kid->asBstr())) { + return "Missing KID"; + } + + return key; + } + + static ErrMsgOr<CoseKey> parseP256(const bytevec& coseKey) { + auto key = parse(coseKey, EC2, ES256, P256); + if (!key) return key; + + auto& pubkey_x = key->getMap().get(PUBKEY_X); + auto& pubkey_y = key->getMap().get(PUBKEY_Y); + if (!pubkey_x || !pubkey_y || !pubkey_x->asBstr() || !pubkey_y->asBstr() || + pubkey_x->asBstr()->value().size() != 32 || pubkey_y->asBstr()->value().size() != 32) { + return "Invalid P256 public key"; + } + + return key; + } + + std::optional<int> getIntValue(Label label) { + const auto& value = key_->get(label); + if (!value || !value->asInt()) return {}; + return value->asInt()->value(); + } + + std::optional<bytevec> getBstrValue(Label label) { + const auto& value = key_->get(label); + if (!value || !value->asBstr()) return {}; + return value->asBstr()->value(); + } + + const cppbor::Map& getMap() const { return *key_; } + cppbor::Map&& moveMap() { return std::move(*key_); } + + bool checkIntValue(Label label, int expectedValue) { + const auto& value = key_->get(label); + return value && value->asInt() && value->asInt()->value() == expectedValue; + } + + void add(Label label, int value) { key_->add(label, value); } + void add(Label label, bytevec value) { key_->add(label, std::move(value)); } + + bytevec encode() { return key_->canonicalize().encode(); } + + private: + CoseKey(cppbor::Map* parsedKey) : key_(parsedKey) {} + + // This is the full parsed key structure. + std::unique_ptr<cppbor::Map> key_; +}; + +ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad, + const bytevec& payload); +ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad, + const bytevec& payload); +ErrMsgOr<bytevec /* payload */> parseCoseMac0(const cppbor::Item* macItem); +ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem, + const bytevec& macKey); + +ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams, + const bytevec& payload, const bytevec& aad); +ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload, + const bytevec& aad); +ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, cppbor::Map extraProtectedFields, + const bytevec& payload, const bytevec& aad); +/** + * Verify and parse a COSE_Sign1 message, returning the payload. + * + * @param ignoreSignature indicates whether signature verification should be skipped. If true, no + * verification of the signature will be done. + * + * @param coseSign1 is the COSE_Sign1 to verify and parse. + * + * @param signingCoseKey is a CBOR-encoded COSE_Key to use to verify the signature. The bytevec may + * be empty, in which case the function assumes that coseSign1's payload is the COSE_Key to + * use, i.e. that coseSign1 is a self-signed "certificate". + */ +ErrMsgOr<bytevec /* payload */> verifyAndParseCoseSign1(bool ignoreSignature, + const cppbor::Array* coseSign1, + const bytevec& signingCoseKey, + const bytevec& aad); + +ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce, + const bytevec& protectedParams, const bytevec& aad); +ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce, + const bytevec& plaintextPayload, const bytevec& aad, + cppbor::Array recipients); +ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> getSenderPubKeyFromCoseEncrypt( + const cppbor::Item* encryptItem); +inline ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> +getSenderPubKeyFromCoseEncrypt(const std::unique_ptr<cppbor::Item>& encryptItem) { + return getSenderPubKeyFromCoseEncrypt(encryptItem.get()); +} + +ErrMsgOr<bytevec /* plaintextPayload */> decryptCoseEncrypt(const bytevec& key, + const cppbor::Item* encryptItem, + const bytevec& aad); + +ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& senderPubKey, const bytevec& senderPrivKey, + const bytevec& recipientPubKey, bool senderIsA); + +ErrMsgOr<bytevec /* ciphertextWithTag */> aesGcmEncrypt(const bytevec& key, const bytevec& nonce, + const bytevec& aad, + const bytevec& plaintext); +ErrMsgOr<bytevec /* plaintext */> aesGcmDecrypt(const bytevec& key, const bytevec& nonce, + const bytevec& aad, + const bytevec& ciphertextWithTag); + +} // namespace cppcose diff --git a/security/keymint/support/include/keymint_support/authorization_set.h b/security/keymint/support/include/keymint_support/authorization_set.h index 1407c5f62b..ca51b08ea9 100644 --- a/security/keymint/support/include/keymint_support/authorization_set.h +++ b/security/keymint/support/include/keymint_support/authorization_set.h @@ -288,6 +288,7 @@ class AuthorizationSetBuilder : public AuthorizationSet { AuthorizationSetBuilder& SigningKey(); AuthorizationSetBuilder& EncryptionKey(); + AuthorizationSetBuilder& AttestKey(); AuthorizationSetBuilder& NoDigestOrPadding(); @@ -300,6 +301,8 @@ class AuthorizationSetBuilder : public AuthorizationSet { AuthorizationSetBuilder& Digest(std::vector<Digest> digests); AuthorizationSetBuilder& Padding(std::initializer_list<PaddingMode> paddings); + AuthorizationSetBuilder& SetDefaultValidity(); + AuthorizationSetBuilder& AttestationChallenge(const std::string& challenge) { return Authorization(TAG_ATTESTATION_CHALLENGE, challenge); } diff --git a/security/keymint/support/include/keymint_support/keymint_tags.h b/security/keymint/support/include/keymint_support/keymint_tags.h index 43cfb63a2f..479a11d137 100644 --- a/security/keymint/support/include/keymint_support/keymint_tags.h +++ b/security/keymint/support/include/keymint_support/keymint_tags.h @@ -126,6 +126,10 @@ DECLARE_TYPED_TAG(USER_ID); DECLARE_TYPED_TAG(USER_SECURE_ID); DECLARE_TYPED_TAG(VENDOR_PATCHLEVEL); DECLARE_TYPED_TAG(RSA_OAEP_MGF_DIGEST); +DECLARE_TYPED_TAG(CERTIFICATE_SERIAL); +DECLARE_TYPED_TAG(CERTIFICATE_SUBJECT); +DECLARE_TYPED_TAG(CERTIFICATE_NOT_BEFORE); +DECLARE_TYPED_TAG(CERTIFICATE_NOT_AFTER); #undef DECLARE_TYPED_TAG diff --git a/security/keymint/support/include/keymint_support/openssl_utils.h b/security/keymint/support/include/keymint_support/openssl_utils.h index c3bc60b051..a0212aabd4 100644 --- a/security/keymint/support/include/keymint_support/openssl_utils.h +++ b/security/keymint/support/include/keymint_support/openssl_utils.h @@ -34,13 +34,14 @@ typedef UniquePtrDeleter<EVP_PKEY, EVP_PKEY_free> EVP_PKEY_Delete; typedef std::unique_ptr<type, UniquePtrDeleter<type, type##_free>> type##_Ptr; MAKE_OPENSSL_PTR_TYPE(ASN1_OBJECT) -MAKE_OPENSSL_PTR_TYPE(EC_KEY) +MAKE_OPENSSL_PTR_TYPE(BN_CTX) MAKE_OPENSSL_PTR_TYPE(EC_GROUP) +MAKE_OPENSSL_PTR_TYPE(EC_KEY) MAKE_OPENSSL_PTR_TYPE(EVP_PKEY) MAKE_OPENSSL_PTR_TYPE(EVP_PKEY_CTX) MAKE_OPENSSL_PTR_TYPE(RSA) MAKE_OPENSSL_PTR_TYPE(X509) -MAKE_OPENSSL_PTR_TYPE(BN_CTX) +MAKE_OPENSSL_PTR_TYPE(X509_NAME) typedef std::unique_ptr<BIGNUM, UniquePtrDeleter<BIGNUM, BN_free>> BIGNUM_Ptr; diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h new file mode 100644 index 0000000000..5e205a27a9 --- /dev/null +++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 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. + */ + +#pragma once + +#include <vector> + +#include <cppcose/cppcose.h> + +namespace aidl::android::hardware::security::keymint::remote_prov { + +using bytevec = std::vector<uint8_t>; +using namespace cppcose; + +extern bytevec kTestMacKey; + +/** + * Generates random bytes. + */ +bytevec randomBytes(size_t numBytes); + +struct EekChain { + bytevec chain; + bytevec last_pubkey; + bytevec last_privkey; +}; + +/** + * Generates an X25518 EEK with the specified eekId and an Ed25519 chain of the + * specified length. All keys are generated randomly. + */ +ErrMsgOr<EekChain> generateEekChain(size_t length, const bytevec& eekId); + +struct BccEntryData { + bytevec pubKey; +}; + +/** + * Validates the provided CBOR-encoded BCC, returning a vector of BccEntryData + * structs containing the BCC entry contents. If an entry contains no firmware + * digest, the corresponding BccEntryData.firmwareDigest will have length zero + * (there's no way to distinguish between an empty and missing firmware digest, + * which seems fine). + */ +ErrMsgOr<std::vector<BccEntryData>> validateBcc(const cppbor::Array* bcc); + +} // namespace aidl::android::hardware::security::keymint::remote_prov diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp new file mode 100644 index 0000000000..111cb309b0 --- /dev/null +++ b/security/keymint/support/remote_prov_utils.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 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. + */ + +#include <remote_prov/remote_prov_utils.h> + +#include <openssl/rand.h> + +#include <cppbor.h> + +namespace aidl::android::hardware::security::keymint::remote_prov { + +bytevec kTestMacKey(32 /* count */, 0 /* byte value */); + +bytevec randomBytes(size_t numBytes) { + bytevec retval(numBytes); + RAND_bytes(retval.data(), numBytes); + return retval; +} + +ErrMsgOr<EekChain> generateEekChain(size_t length, const bytevec& eekId) { + auto eekChain = cppbor::Array(); + + bytevec prev_priv_key; + for (size_t i = 0; i < length - 1; ++i) { + bytevec pub_key(ED25519_PUBLIC_KEY_LEN); + bytevec priv_key(ED25519_PRIVATE_KEY_LEN); + + ED25519_keypair(pub_key.data(), priv_key.data()); + + // The first signing key is self-signed. + if (prev_priv_key.empty()) prev_priv_key = priv_key; + + auto coseSign1 = constructCoseSign1(prev_priv_key, + cppbor::Map() /* payload CoseKey */ + .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) + .add(CoseKey::ALGORITHM, EDDSA) + .add(CoseKey::CURVE, ED25519) + .add(CoseKey::PUBKEY_X, pub_key) + .canonicalize() + .encode(), + {} /* AAD */); + if (!coseSign1) return coseSign1.moveMessage(); + eekChain.add(coseSign1.moveValue()); + } + + bytevec pub_key(X25519_PUBLIC_VALUE_LEN); + bytevec priv_key(X25519_PRIVATE_KEY_LEN); + X25519_keypair(pub_key.data(), priv_key.data()); + + auto coseSign1 = constructCoseSign1(prev_priv_key, + cppbor::Map() /* payload CoseKey */ + .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) + .add(CoseKey::KEY_ID, eekId) + .add(CoseKey::ALGORITHM, ECDH_ES_HKDF_256) + .add(CoseKey::CURVE, cppcose::X25519) + .add(CoseKey::PUBKEY_X, pub_key) + .canonicalize() + .encode(), + {} /* AAD */); + if (!coseSign1) return coseSign1.moveMessage(); + eekChain.add(coseSign1.moveValue()); + + return EekChain{eekChain.encode(), pub_key, priv_key}; +} + +ErrMsgOr<bytevec> verifyAndParseCoseSign1Cwt(bool ignoreSignature, const cppbor::Array* coseSign1, + const bytevec& signingCoseKey, const bytevec& aad) { + if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) { + return "Invalid COSE_Sign1"; + } + + const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr(); + const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr(); + const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr(); + const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr(); + + if (!protectedParams || !unprotectedParams || !payload || !signature) { + return "Invalid COSE_Sign1"; + } + + auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams); + if (!parsedProtParams) { + return errMsg + " when parsing protected params."; + } + if (!parsedProtParams->asMap()) { + return "Protected params must be a map"; + } + + auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); + if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) { + return "Unsupported signature algorithm"; + } + + // TODO(jbires): Handle CWTs as the CoseSign1 payload in a less hacky way. Since the CWT payload + // is extremely remote provisioning specific, probably just make a separate + // function there. + auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(payload); + if (!parsedPayload) return payloadErrMsg + " when parsing key"; + if (!parsedPayload->asMap()) return "CWT must be a map"; + auto serializedKey = parsedPayload->asMap()->get(-4670552)->clone(); + if (!serializedKey || !serializedKey->asBstr()) return "Could not find key entry"; + + if (!ignoreSignature) { + bool selfSigned = signingCoseKey.empty(); + auto key = CoseKey::parseEd25519(selfSigned ? serializedKey->asBstr()->value() + : signingCoseKey); + if (!key) return "Bad signing key: " + key.moveMessage(); + + bytevec signatureInput = cppbor::Array() + .add("Signature1") + .add(*protectedParams) + .add(aad) + .add(*payload) + .encode(); + + if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), + key->getBstrValue(CoseKey::PUBKEY_X)->data())) { + return "Signature verification failed"; + } + } + + return serializedKey->asBstr()->value(); +} +ErrMsgOr<std::vector<BccEntryData>> validateBcc(const cppbor::Array* bcc) { + if (!bcc || bcc->size() == 0) return "Invalid BCC"; + + std::vector<BccEntryData> result; + + bytevec prevKey; + // TODO(jbires): Actually process the pubKey at the start of the new bcc entry + for (size_t i = 1; i < bcc->size(); ++i) { + const cppbor::Array* entry = bcc->get(i)->asArray(); + if (!entry || entry->size() != kCoseSign1EntryCount) { + return "Invalid BCC entry " + std::to_string(i) + ": " + prettyPrint(entry); + } + auto payload = verifyAndParseCoseSign1Cwt(false /* ignoreSignature */, entry, + std::move(prevKey), bytevec{} /* AAD */); + if (!payload) { + return "Failed to verify entry " + std::to_string(i) + ": " + payload.moveMessage(); + } + + auto& certProtParms = entry->get(kCoseSign1ProtectedParams); + if (!certProtParms || !certProtParms->asBstr()) return "Invalid prot params"; + auto [parsedProtParms, _, errMsg] = cppbor::parse(certProtParms->asBstr()->value()); + if (!parsedProtParms || !parsedProtParms->asMap()) return "Invalid prot params"; + + result.push_back(BccEntryData{*payload}); + + // This entry's public key is the signing key for the next entry. + prevKey = payload.moveValue(); + } + + return result; +} + +} // namespace aidl::android::hardware::security::keymint::remote_prov diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl index c16b312cda..377889716a 100644 --- a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl +++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl @@ -1,4 +1,17 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * 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. + * limitations under the License. + *//////////////////////////////////////////////////////////////////////////////// // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// @@ -20,5 +33,5 @@ package android.hardware.security.secureclock; @VintfStability interface ISecureClock { android.hardware.security.secureclock.TimeStampToken generateTimeStamp(in long challenge); - const String TIME_STAMP_MAC_LABEL = "Time Verification"; + const String TIME_STAMP_MAC_LABEL = "Auth Verification"; } diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl index 21eeb74519..00a8bb256d 100644 --- a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl +++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl @@ -1,4 +1,18 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl index f01fdc74c0..bebeb5cb9c 100644 --- a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl +++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl @@ -1,4 +1,18 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * Copyright 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. + *//////////////////////////////////////////////////////////////////////////////// // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl index 7d416dda7a..577dd8f231 100644 --- a/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl +++ b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl @@ -33,7 +33,7 @@ interface ISecureClock { * String used as context in the HMAC computation signing the generated time stamp. * See TimeStampToken.mac for details. */ - const String TIME_STAMP_MAC_LABEL = "Time Verification"; + const String TIME_STAMP_MAC_LABEL = "Auth Verification"; /** * Generates an authenticated timestamp. diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl index 3fb586078a..dd957325e9 100644 --- a/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl +++ b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl @@ -39,18 +39,20 @@ parcelable TimeStampToken { * 32-byte HMAC-SHA256 of the above values, computed as: * * HMAC(H, - * ISecureClock.TIME_STAMP_MAC_LABEL || challenge || timestamp) + * ISecureClock.TIME_STAMP_MAC_LABEL || challenge || timestamp || securityLevel ) * * where: * * ``ISecureClock.TIME_STAMP_MAC_LABEL'' is a sting constant defined in ISecureClock.aidl. * - * ``H'' is the shared HMAC key (see computeSharedHmac() in ISharedHmacSecret). + * ``H'' is the shared HMAC key (see computeSharedHmac() in ISharedSecret). * * ``||'' represents concatenation * * The representation of challenge and timestamp is as 64-bit unsigned integers in big-endian - * order. securityLevel is represented as a 32-bit unsigned integer in big-endian order. + * order. SecurityLevel is represented as a 32-bit unsigned integer in big-endian order as + * described in android.hardware.security.keymint.SecurityLevel. It represents the security + * level of the secure clock environment. */ byte[] mac; } diff --git a/security/secureclock/aidl/vts/functional/Android.bp b/security/secureclock/aidl/vts/functional/Android.bp new file mode 100644 index 0000000000..1619eab235 --- /dev/null +++ b/security/secureclock/aidl/vts/functional/Android.bp @@ -0,0 +1,43 @@ +// +// 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. +// + +cc_test { + name: "VtsAidlSecureClockTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + cflags: [ + "-Wall", + "-Wextra", + ], + srcs: [ + "SecureClockAidlTest.cpp", + ], + shared_libs: [ + "libbinder_ndk", + "libcrypto", + "libkeymint", + ], + static_libs: [ + "android.hardware.security.keymint-V1-ndk_platform", + "android.hardware.security.secureclock-V1-ndk_platform", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/security/secureclock/aidl/vts/functional/AndroidTest.xml b/security/secureclock/aidl/vts/functional/AndroidTest.xml new file mode 100644 index 0000000000..4861c7c7af --- /dev/null +++ b/security/secureclock/aidl/vts/functional/AndroidTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs VtsAidlSecureClockTargetTest."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" + value="VtsAidlSecureClockTargetTest->/data/local/tmp/VtsAidlSecureClockTargetTest" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="VtsAidlSecureClockTargetTest" /> + <option name="native-test-timeout" value="900000"/> + </test> +</configuration> diff --git a/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp new file mode 100644 index 0000000000..9ca1ee8af1 --- /dev/null +++ b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp @@ -0,0 +1,193 @@ +/* + * 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 "secureclock_test" +#include <android-base/logging.h> + +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <aidl/android/hardware/security/keymint/ErrorCode.h> +#include <aidl/android/hardware/security/secureclock/ISecureClock.h> +#include <android/binder_manager.h> +#include <binder/ProcessState.h> +#include <gtest/gtest.h> +#include <vector> + +namespace aidl::android::hardware::security::secureclock::test { +using Status = ::ndk::ScopedAStatus; +using ::aidl::android::hardware::security::keymint::ErrorCode; +using ::std::shared_ptr; +using ::std::string; +using ::std::vector; + +class SecureClockAidlTest : public ::testing::TestWithParam<string> { + public: + struct TimestampTokenResult { + ErrorCode error; + TimeStampToken token; + }; + + TimestampTokenResult getTimestampToken(int64_t in_challenge) { + TimestampTokenResult result; + result.error = + GetReturnErrorCode(secureClock_->generateTimeStamp(in_challenge, &result.token)); + return result; + } + + uint64_t getTime() { + struct timespec timespec; + EXPECT_EQ(0, clock_gettime(CLOCK_BOOTTIME, ×pec)); + return timespec.tv_sec * 1000 + timespec.tv_nsec / 1000000; + } + + int sleep_ms(uint32_t milliseconds) { + struct timespec sleep_time = {static_cast<time_t>(milliseconds / 1000), + static_cast<long>(milliseconds % 1000) * 1000000}; + while (sleep_time.tv_sec || sleep_time.tv_nsec) { + if (nanosleep(&sleep_time /* to wait */, + &sleep_time /* remaining (on interrruption) */) == 0) { + sleep_time = {}; + } else { + if (errno != EINTR) return errno; + } + } + return 0; + } + + ErrorCode GetReturnErrorCode(const Status& result) { + if (result.isOk()) return ErrorCode::OK; + + if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) { + return static_cast<ErrorCode>(result.getServiceSpecificError()); + } + + return ErrorCode::UNKNOWN_ERROR; + } + + void InitializeSecureClock(std::shared_ptr<ISecureClock> secureClock) { + ASSERT_NE(secureClock, nullptr); + secureClock_ = secureClock; + } + + ISecureClock& secureClock() { return *secureClock_; } + + static vector<string> build_params() { + auto params = ::android::getAidlHalInstanceNames(ISecureClock::descriptor); + return params; + } + + void SetUp() override { + if (AServiceManager_isDeclared(GetParam().c_str())) { + ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str())); + InitializeSecureClock(ISecureClock::fromBinder(binder)); + } else { + InitializeSecureClock(nullptr); + } + } + + void TearDown() override {} + + private: + std::shared_ptr<ISecureClock> secureClock_; +}; + +/* + * The precise capabilities required to generate TimeStampToken will vary depending on the specific + * vendor implementations. The only thing we really can test is that tokens can be created by + * secureclock services, and that the timestamps increase as expected. + */ +TEST_P(SecureClockAidlTest, TestCreation) { + auto result1 = getTimestampToken(1 /* challenge */); + auto result1_time = getTime(); + EXPECT_EQ(ErrorCode::OK, result1.error); + EXPECT_EQ(1U, result1.token.challenge); + EXPECT_GT(result1.token.timestamp.milliSeconds, 0U); + + unsigned long time_to_sleep = 200; + sleep_ms(time_to_sleep); + + auto result2 = getTimestampToken(2 /* challenge */); + auto result2_time = getTime(); + EXPECT_EQ(ErrorCode::OK, result2.error); + EXPECT_EQ(2U, result2.token.challenge); + EXPECT_GT(result2.token.timestamp.milliSeconds, 0U); + + auto host_time_delta = result2_time - result1_time; + + EXPECT_GE(host_time_delta, time_to_sleep) + << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much"; + EXPECT_LE(host_time_delta, time_to_sleep + 100) + << "The getTimestampToken call took " << (host_time_delta - time_to_sleep) + << " ms? That's awful!"; + EXPECT_GE(result2.token.timestamp.milliSeconds, result1.token.timestamp.milliSeconds); + unsigned long km_time_delta = + result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds; + // 20 ms of slop just to avoid test flakiness. + EXPECT_LE(host_time_delta, km_time_delta + 20); + EXPECT_LE(km_time_delta, host_time_delta + 20); + ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size()); + ASSERT_NE(0, + memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size())); +} + +/* + * Test that the mac changes when the time stamp changes. This is does not guarantee that the time + * stamp is included in the mac but on failure we know that it is not. Other than in the test + * case above we call getTimestampToken with the exact same set of parameters. + */ +TEST_P(SecureClockAidlTest, MacChangesOnChangingTimestamp) { + auto result1 = getTimestampToken(0 /* challenge */); + auto result1_time = getTime(); + EXPECT_EQ(ErrorCode::OK, result1.error); + EXPECT_EQ(0U, result1.token.challenge); + EXPECT_GT(result1.token.timestamp.milliSeconds, 0U); + + unsigned long time_to_sleep = 200; + sleep_ms(time_to_sleep); + + auto result2 = getTimestampToken(1 /* challenge */); + auto result2_time = getTime(); + EXPECT_EQ(ErrorCode::OK, result2.error); + EXPECT_EQ(1U, result2.token.challenge); + EXPECT_GT(result2.token.timestamp.milliSeconds, 0U); + + auto host_time_delta = result2_time - result1_time; + + EXPECT_GE(host_time_delta, time_to_sleep) + << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much"; + EXPECT_LE(host_time_delta, time_to_sleep + 100) + << "The getTimestampToken call took " << (host_time_delta - time_to_sleep) + << " ms? That's awful!"; + + EXPECT_GE(result2.token.timestamp.milliSeconds, result1.token.timestamp.milliSeconds); + unsigned long km_time_delta = + result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds; + + EXPECT_LE(host_time_delta, km_time_delta + 20); + EXPECT_LE(km_time_delta, host_time_delta + 20); + ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size()); + ASSERT_NE(0, + memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size())); +} + +INSTANTIATE_TEST_SUITE_P(PerInstance, SecureClockAidlTest, + testing::ValuesIn(SecureClockAidlTest::build_params()), + ::android::PrintInstanceNameToString); +} // namespace aidl::android::hardware::security::secureclock::test + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}
\ No newline at end of file diff --git a/security/sharedsecret/aidl/vts/functional/Android.bp b/security/sharedsecret/aidl/vts/functional/Android.bp new file mode 100644 index 0000000000..76bf7ff762 --- /dev/null +++ b/security/sharedsecret/aidl/vts/functional/Android.bp @@ -0,0 +1,43 @@ +// +// 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. +// + +cc_test { + name: "VtsAidlSharedSecretTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "SharedSecretAidlTest.cpp", + ], + cflags: [ + "-Wall", + "-Wextra", + ], + shared_libs: [ + "libbinder_ndk", + "libcrypto", + "libkeymint", + ], + static_libs: [ + "android.hardware.security.keymint-V1-ndk_platform", + "android.hardware.security.sharedsecret-V1-ndk_platform", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/security/sharedsecret/aidl/vts/functional/AndroidTest.xml b/security/sharedsecret/aidl/vts/functional/AndroidTest.xml new file mode 100644 index 0000000000..c6697bcda5 --- /dev/null +++ b/security/sharedsecret/aidl/vts/functional/AndroidTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs VtsAidlSharedSecretTargetTest."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" + value="VtsAidlSharedSecretTargetTest->/data/local/tmp/VtsAidlSharedSecretTargetTest" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="VtsAidlSharedSecretTargetTest" /> + <option name="native-test-timeout" value="900000"/> + </test> +</configuration> diff --git a/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp b/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp new file mode 100644 index 0000000000..83f6ef39db --- /dev/null +++ b/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp @@ -0,0 +1,268 @@ +/* + * 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 "sharedsecret_test" +#include <android-base/logging.h> + +#include <aidl/Vintf.h> +#include <aidl/android/hardware/security/keymint/ErrorCode.h> +#include <aidl/android/hardware/security/sharedsecret/ISharedSecret.h> +#include <android/binder_manager.h> +#include <gtest/gtest.h> +#include <vector> + +namespace aidl::android::hardware::security::sharedsecret::test { +using ::aidl::android::hardware::security::keymint::ErrorCode; +using ::std::shared_ptr; +using ::std::vector; +using Status = ::ndk::ScopedAStatus; + +class SharedSecretAidlTest : public ::testing::Test { + public: + struct GetParamsResult { + ErrorCode error; + SharedSecretParameters params; + auto tie() { return std::tie(error, params); } + }; + + struct ComputeResult { + ErrorCode error; + vector<uint8_t> sharing_check; + auto tie() { return std::tie(error, sharing_check); } + }; + + GetParamsResult getSharedSecretParameters(shared_ptr<ISharedSecret>& sharedSecret) { + SharedSecretParameters params; + auto error = GetReturnErrorCode(sharedSecret->getSharedSecretParameters(¶ms)); + EXPECT_EQ(ErrorCode::OK, error); + GetParamsResult result; + result.tie() = std::tie(error, params); + return result; + } + + vector<SharedSecretParameters> getAllSharedSecretParameters() { + vector<SharedSecretParameters> paramsVec; + for (auto& sharedSecret : allSharedSecrets_) { + auto result = getSharedSecretParameters(sharedSecret); + EXPECT_EQ(ErrorCode::OK, result.error); + if (result.error == ErrorCode::OK) paramsVec.push_back(std::move(result.params)); + } + return paramsVec; + } + + ComputeResult computeSharedSecret(shared_ptr<ISharedSecret>& sharedSecret, + const vector<SharedSecretParameters>& params) { + std::vector<uint8_t> sharingCheck; + auto error = GetReturnErrorCode(sharedSecret->computeSharedSecret(params, &sharingCheck)); + ComputeResult result; + result.tie() = std::tie(error, sharingCheck); + return result; + } + + vector<ComputeResult> computeAllSharedSecrets(const vector<SharedSecretParameters>& params) { + vector<ComputeResult> result; + for (auto& sharedSecret : allSharedSecrets_) { + result.push_back(computeSharedSecret(sharedSecret, params)); + } + return result; + } + + vector<vector<uint8_t>> copyNonces(const vector<SharedSecretParameters>& paramsVec) { + vector<vector<uint8_t>> nonces; + for (auto& param : paramsVec) { + nonces.push_back(param.nonce); + } + return nonces; + } + + void verifyResponses(const vector<uint8_t>& expected, const vector<ComputeResult>& responses) { + for (auto& response : responses) { + EXPECT_EQ(ErrorCode::OK, response.error); + EXPECT_EQ(expected, response.sharing_check) << "Sharing check values should match."; + } + } + + ErrorCode GetReturnErrorCode(const Status& result) { + if (result.isOk()) return ErrorCode::OK; + if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) { + return static_cast<ErrorCode>(result.getServiceSpecificError()); + } + return ErrorCode::UNKNOWN_ERROR; + } + + static shared_ptr<ISharedSecret> getSharedSecretService(const char* name) { + if (AServiceManager_isDeclared(name)) { + ::ndk::SpAIBinder binder(AServiceManager_waitForService(name)); + return ISharedSecret::fromBinder(binder); + } + return nullptr; + } + + const vector<shared_ptr<ISharedSecret>>& allSharedSecrets() { return allSharedSecrets_; } + + static void SetUpTestCase() { + if (allSharedSecrets_.empty()) { + auto names = ::android::getAidlHalInstanceNames(ISharedSecret::descriptor); + for (const auto& name : names) { + auto servicePtr = getSharedSecretService(name.c_str()); + if (servicePtr != nullptr) allSharedSecrets_.push_back(std::move(servicePtr)); + } + } + } + static void TearDownTestCase() {} + void SetUp() override {} + void TearDown() override {} + + private: + static vector<shared_ptr<ISharedSecret>> allSharedSecrets_; +}; + +vector<shared_ptr<ISharedSecret>> SharedSecretAidlTest::allSharedSecrets_; + +TEST_F(SharedSecretAidlTest, GetParameters) { + auto sharedSecrets = allSharedSecrets(); + for (auto sharedSecret : sharedSecrets) { + auto result1 = getSharedSecretParameters(sharedSecret); + EXPECT_EQ(ErrorCode::OK, result1.error); + auto result2 = getSharedSecretParameters(sharedSecret); + EXPECT_EQ(ErrorCode::OK, result2.error); + ASSERT_EQ(result1.params.seed, result2.params.seed) + << "A given shared secret service should always return the same seed."; + ASSERT_EQ(result1.params.nonce, result2.params.nonce) + << "A given shared secret service should always return the same nonce until " + "restart."; + } +} + +TEST_F(SharedSecretAidlTest, ComputeSharedSecret) { + auto params = getAllSharedSecretParameters(); + ASSERT_EQ(allSharedSecrets().size(), params.size()) + << "One or more shared secret services failed to provide parameters."; + auto nonces = copyNonces(params); + EXPECT_EQ(allSharedSecrets().size(), nonces.size()); + std::sort(nonces.begin(), nonces.end()); + std::unique(nonces.begin(), nonces.end()); + EXPECT_EQ(allSharedSecrets().size(), nonces.size()); + + auto responses = computeAllSharedSecrets(params); + ASSERT_GT(responses.size(), 0U); + verifyResponses(responses[0].sharing_check, responses); + + // Do it a second time. Should get the same answers. + params = getAllSharedSecretParameters(); + ASSERT_EQ(allSharedSecrets().size(), params.size()) + << "One or more shared secret services failed to provide parameters."; + + responses = computeAllSharedSecrets(params); + ASSERT_GT(responses.size(), 0U); + ASSERT_EQ(32U, responses[0].sharing_check.size()); + verifyResponses(responses[0].sharing_check, responses); +} + +template <class F> +class final_action { + public: + explicit final_action(F f) : f_(std::move(f)) {} + ~final_action() { f_(); } + + private: + F f_; +}; + +template <class F> +inline final_action<F> finally(const F& f) { + return final_action<F>(f); +} + +TEST_F(SharedSecretAidlTest, ComputeSharedSecretCorruptNonce) { + auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); }); + + auto params = getAllSharedSecretParameters(); + ASSERT_EQ(allSharedSecrets().size(), params.size()) + << "One or more shared secret services failed to provide parameters."; + + // All should be well in the normal case + auto responses = computeAllSharedSecrets(params); + + ASSERT_GT(responses.size(), 0U); + vector<uint8_t> correct_response = responses[0].sharing_check; + verifyResponses(correct_response, responses); + + // Pick a random param, a random byte within the param's nonce, and a random bit within + // the byte. Flip that bit. + size_t param_to_tweak = rand() % params.size(); + uint8_t byte_to_tweak = rand() % sizeof(params[param_to_tweak].nonce); + uint8_t bit_to_tweak = rand() % 8; + params[param_to_tweak].nonce[byte_to_tweak] ^= (1 << bit_to_tweak); + + responses = computeAllSharedSecrets(params); + for (size_t i = 0; i < responses.size(); ++i) { + if (i == param_to_tweak) { + EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error) + << "Shared secret service that provided tweaked param should fail to compute " + "shared secret"; + } else { + EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed"; + EXPECT_NE(correct_response, responses[i].sharing_check) + << "Others should calculate a different shared secret, due to the tweaked " + "nonce."; + } + } +} + +TEST_F(SharedSecretAidlTest, ComputeSharedSecretCorruptSeed) { + auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); }); + auto params = getAllSharedSecretParameters(); + ASSERT_EQ(allSharedSecrets().size(), params.size()) + << "One or more shared secret service failed to provide parameters."; + + // All should be well in the normal case + auto responses = computeAllSharedSecrets(params); + + ASSERT_GT(responses.size(), 0U); + vector<uint8_t> correct_response = responses[0].sharing_check; + verifyResponses(correct_response, responses); + + // Pick a random param and modify the seed. We just increase the seed length by 1. It doesn't + // matter what value is in the additional byte; it changes the seed regardless. + auto param_to_tweak = rand() % params.size(); + auto& to_tweak = params[param_to_tweak].seed; + ASSERT_TRUE(to_tweak.size() == 32 || to_tweak.size() == 0); + if (!to_tweak.size()) { + to_tweak.resize(32); // Contents don't matter; a little randomization is nice. + } + to_tweak[0]++; + + responses = computeAllSharedSecrets(params); + for (size_t i = 0; i < responses.size(); ++i) { + if (i == param_to_tweak) { + EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error) + << "Shared secret service that provided tweaked param should fail to compute " + "shared secret"; + } else { + EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed"; + EXPECT_NE(correct_response, responses[i].sharing_check) + << "Others should calculate a different shared secret, due to the tweaked " + "nonce."; + } + } +} +} // namespace aidl::android::hardware::security::sharedsecret::test + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tetheroffload/control/1.0/vts/functional/VtsHalTetheroffloadControlV1_0TargetTest.cpp b/tetheroffload/control/1.0/vts/functional/VtsHalTetheroffloadControlV1_0TargetTest.cpp index ad4ef125ce..ea9bcb5d09 100644 --- a/tetheroffload/control/1.0/vts/functional/VtsHalTetheroffloadControlV1_0TargetTest.cpp +++ b/tetheroffload/control/1.0/vts/functional/VtsHalTetheroffloadControlV1_0TargetTest.cpp @@ -469,7 +469,7 @@ TEST_P(OffloadControlTestV1_0_HalStarted, RemoveDownstreamBogusPrefixFails) { } } -GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OffloadControlHidlTestBase); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OffloadControlTestV1_0_HalNotStarted); INSTANTIATE_TEST_CASE_P( PerInstance, OffloadControlTestV1_0_HalNotStarted, testing::Combine(testing::ValuesIn(android::hardware::getAllHalInstanceNames( @@ -478,7 +478,7 @@ INSTANTIATE_TEST_CASE_P( IOffloadControl::descriptor))), android::hardware::PrintInstanceTupleNameToString<>); -GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OffloadControlHidlTest); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OffloadControlTestV1_0_HalStarted); INSTANTIATE_TEST_CASE_P( PerInstance, OffloadControlTestV1_0_HalStarted, testing::Combine(testing::ValuesIn(android::hardware::getAllHalInstanceNames( diff --git a/biometrics/face/1.1/Android.bp b/tv/cec/1.1/Android.bp index 14a86f14b8..c2d4e54407 100644 --- a/biometrics/face/1.1/Android.bp +++ b/tv/cec/1.1/Android.bp @@ -1,13 +1,15 @@ // This file is autogenerated by hidl-gen -Landroidbp. hidl_interface { - name: "android.hardware.biometrics.face@1.1", + name: "android.hardware.tv.cec@1.1", root: "android.hardware", srcs: [ - "IBiometricsFace.hal", + "types.hal", + "IHdmiCec.hal", + "IHdmiCecCallback.hal", ], interfaces: [ - "android.hardware.biometrics.face@1.0", + "android.hardware.tv.cec@1.0", "android.hidl.base@1.0", ], gen_java: true, diff --git a/tv/cec/1.1/IHdmiCec.hal b/tv/cec/1.1/IHdmiCec.hal new file mode 100644 index 0000000000..fe7bedf195 --- /dev/null +++ b/tv/cec/1.1/IHdmiCec.hal @@ -0,0 +1,70 @@ +/* + * Copyright (C) 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.tv.cec@1.1; + +import @1.0::IHdmiCec; +import @1.0::Result; +import @1.0::SendMessageResult; + +import IHdmiCecCallback; + +/** + * HDMI-CEC HAL interface definition. + */ +interface IHdmiCec extends @1.0::IHdmiCec { + /** + * Passes the logical address that must be used in this system. + * + * HAL must use it to configure the hardware so that the CEC commands + * addressed the given logical address can be filtered in. This method must + * be able to be called as many times as necessary in order to support + * multiple logical devices. + * + * @param addr Logical address that must be used in this system. It must be + * in the range of valid logical addresses for the call to succeed. + * @return result Result status of the operation. SUCCESS if successful, + * FAILURE_INVALID_ARGS if the given logical address is invalid, + * FAILURE_BUSY if device or resource is busy + */ + addLogicalAddress_1_1(CecLogicalAddress addr) generates (Result result); + + /** + * Transmits HDMI-CEC message to other HDMI device. + * + * The method must be designed to return in a certain amount of time and not + * hanging forever which may happen if CEC signal line is pulled low for + * some reason. + * + * It must try retransmission at least once as specified in the section '7.1 + * Frame Re-transmissions' of the CEC Spec 1.4b. + * + * @param message CEC message to be sent to other HDMI device. + * @return result Result status of the operation. SUCCESS if successful, + * NACK if the sent message is not acknowledged, + * BUSY if the CEC bus is busy. + */ + sendMessage_1_1(CecMessage message) generates (SendMessageResult result); + + /** + * Sets a callback that HDMI-CEC HAL must later use for incoming CEC + * messages or internal HDMI events. + * + * @param callback Callback object to pass hdmi events to the system. The + * previously registered callback must be replaced with this one. + */ + setCallback_1_1(IHdmiCecCallback callback); +}; diff --git a/tv/cec/1.1/IHdmiCecCallback.hal b/tv/cec/1.1/IHdmiCecCallback.hal new file mode 100644 index 0000000000..3928f186cc --- /dev/null +++ b/tv/cec/1.1/IHdmiCecCallback.hal @@ -0,0 +1,30 @@ +/* + * Copyright (C) 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.tv.cec@1.1; + +import @1.0::IHdmiCecCallback; + +/** + * Callbacks from the HAL implementation to notify the system of new events. + */ +interface IHdmiCecCallback extends @1.0::IHdmiCecCallback { + /** + * The callback function that must be called by HAL implementation to notify + * the system of new CEC message arrival. + */ + oneway onCecMessage_1_1(CecMessage message); +}; diff --git a/tv/cec/1.1/default/Android.bp b/tv/cec/1.1/default/Android.bp new file mode 100644 index 0000000000..e0dff0d033 --- /dev/null +++ b/tv/cec/1.1/default/Android.bp @@ -0,0 +1,23 @@ +cc_binary { + name: "android.hardware.tv.cec@1.1-service", + defaults: ["hidl_defaults"], + vintf_fragments: ["android.hardware.tv.cec@1.1-service.xml"], + relative_install_path: "hw", + vendor: true, + init_rc: ["android.hardware.tv.cec@1.1-service.rc"], + srcs: [ + "serviceMock.cpp", + "HdmiCecMock.cpp", + ], + + shared_libs: [ + "liblog", + "libcutils", + "libbase", + "libutils", + "libhardware", + "libhidlbase", + "android.hardware.tv.cec@1.0", + "android.hardware.tv.cec@1.1", + ], +} diff --git a/tv/cec/1.1/default/HdmiCecMock.cpp b/tv/cec/1.1/default/HdmiCecMock.cpp new file mode 100644 index 0000000000..f65bab9865 --- /dev/null +++ b/tv/cec/1.1/default/HdmiCecMock.cpp @@ -0,0 +1,371 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "android.hardware.tv.cec@1.1" +#include <android-base/logging.h> +#include <utils/Log.h> + +#include <hardware/hardware.h> +#include <hardware/hdmi_cec.h> +#include "HdmiCecMock.h" + +namespace android { +namespace hardware { +namespace tv { +namespace cec { +namespace V1_1 { +namespace implementation { + +class WrappedCallback : public ::android::hardware::tv::cec::V1_1::IHdmiCecCallback { + public: + WrappedCallback(sp<::android::hardware::tv::cec::V1_0::IHdmiCecCallback> callback) { + mCallback = callback; + } + + Return<void> onCecMessage(const ::android::hardware::tv::cec::V1_0::CecMessage& message) { + mCallback->onCecMessage(message); + return Void(); + } + Return<void> onCecMessage_1_1(const ::android::hardware::tv::cec::V1_1::CecMessage& message) { + ::android::hardware::tv::cec::V1_0::CecMessage cecMessage; + cecMessage.initiator = + ::android::hardware::tv::cec::V1_0::CecLogicalAddress(message.initiator); + cecMessage.destination = + ::android::hardware::tv::cec::V1_0::CecLogicalAddress(message.destination); + cecMessage.body = message.body; + mCallback->onCecMessage(cecMessage); + return Void(); + } + Return<void> onHotplugEvent(const ::android::hardware::tv::cec::V1_0::HotplugEvent& event) { + mCallback->onHotplugEvent(event); + return Void(); + } + + private: + sp<::android::hardware::tv::cec::V1_0::IHdmiCecCallback> mCallback; +}; + +/* + * (*set_option)() passes flags controlling the way HDMI-CEC service works down + * to HAL implementation. Those flags will be used in case the feature needs + * update in HAL itself, firmware or microcontroller. + */ +void HdmiCecMock::cec_set_option(int flag, int value) { + // maintain options and set them accordingly + switch (flag) { + case HDMI_OPTION_WAKEUP: + mOptionWakeUp = value; + break; + case HDMI_OPTION_ENABLE_CEC: + mOptionEnableCec = value; + break; + case HDMI_OPTION_SYSTEM_CEC_CONTROL: + mOptionSystemCecControl = value; + break; + case HDMI_OPTION_SET_LANG: + mOptionLanguage = value; + break; + } +} + +// Methods from ::android::hardware::tv::cec::V1_0::IHdmiCec follow. +Return<Result> HdmiCecMock::addLogicalAddress(CecLogicalAddress addr) { + return addLogicalAddress_1_1(::android::hardware::tv::cec::V1_1::CecLogicalAddress(addr)); +} + +Return<void> HdmiCecMock::clearLogicalAddress() { + // remove logical address from the list + mLogicalAddresses = {}; + return Void(); +} + +Return<void> HdmiCecMock::getPhysicalAddress(getPhysicalAddress_cb _hidl_cb) { + // maintain a physical address and return it + // default 0xFFFF, update on hotplug event + _hidl_cb(Result::SUCCESS, mPhysicalAddress); + return Void(); +} + +Return<SendMessageResult> HdmiCecMock::sendMessage(const CecMessage& message) { + ::android::hardware::tv::cec::V1_1::CecMessage cecMessage; + cecMessage.initiator = ::android::hardware::tv::cec::V1_1::CecLogicalAddress(message.initiator); + cecMessage.destination = + ::android::hardware::tv::cec::V1_1::CecLogicalAddress(message.destination); + cecMessage.body = message.body; + return sendMessage_1_1(cecMessage); +} + +Return<void> HdmiCecMock::setCallback(const sp<IHdmiCecCallback>& callback) { + return setCallback_1_1(new WrappedCallback(callback)); +} + +Return<int32_t> HdmiCecMock::getCecVersion() { + // maintain a cec version and return it + return mCecVersion; +} + +Return<uint32_t> HdmiCecMock::getVendorId() { + return mCecVendorId; +} + +Return<void> HdmiCecMock::getPortInfo(getPortInfo_cb _hidl_cb) { + // TODO ready port info from device specific config + _hidl_cb(mPortInfo); + return Void(); +} + +Return<void> HdmiCecMock::setOption(OptionKey key, bool value) { + cec_set_option(static_cast<int>(key), value ? 1 : 0); + return Void(); +} + +Return<void> HdmiCecMock::setLanguage(const hidl_string& language) { + if (language.size() != 3) { + LOG(ERROR) << "Wrong language code: expected 3 letters, but it was " << language.size() + << "."; + return Void(); + } + // TODO validate if language is a valid language code + const char* languageStr = language.c_str(); + int convertedLanguage = ((languageStr[0] & 0xFF) << 16) | ((languageStr[1] & 0xFF) << 8) | + (languageStr[2] & 0xFF); + cec_set_option(HDMI_OPTION_SET_LANG, convertedLanguage); + return Void(); +} + +Return<void> HdmiCecMock::enableAudioReturnChannel(int32_t portId __unused, bool enable __unused) { + // Maintain ARC status + return Void(); +} + +Return<bool> HdmiCecMock::isConnected(int32_t portId) { + // maintain port connection status and update on hotplug event + if (portId < mTotalPorts && portId >= 0) { + return mPortConnectionStatus[portId]; + } + return false; +} + +// Methods from ::android::hardware::tv::cec::V1_1::IHdmiCec follow. +Return<Result> HdmiCecMock::addLogicalAddress_1_1( + ::android::hardware::tv::cec::V1_1::CecLogicalAddress addr) { + // have a list to maintain logical addresses + int size = mLogicalAddresses.size(); + mLogicalAddresses.resize(size + 1); + mLogicalAddresses[size + 1] = addr; + return Result::SUCCESS; +} + +Return<SendMessageResult> HdmiCecMock::sendMessage_1_1( + const ::android::hardware::tv::cec::V1_1::CecMessage& message) { + if (message.body.size() == 0) { + return SendMessageResult::NACK; + } + sendMessageToFifo(message); + return SendMessageResult::SUCCESS; +} + +Return<void> HdmiCecMock::setCallback_1_1( + const sp<::android::hardware::tv::cec::V1_1::IHdmiCecCallback>& callback) { + if (mCallback != nullptr) { + mCallback = nullptr; + } + + if (callback != nullptr) { + mCallback = callback; + mCallback->linkToDeath(this, 0 /*cookie*/); + + mInputFile = open(CEC_MSG_IN_FIFO, O_RDWR); + mOutputFile = open(CEC_MSG_OUT_FIFO, O_RDWR); + pthread_create(&mThreadId, NULL, __threadLoop, this); + pthread_setname_np(mThreadId, "hdmi_cec_loop"); + } + return Void(); +} + +void* HdmiCecMock::__threadLoop(void* user) { + HdmiCecMock* const self = static_cast<HdmiCecMock*>(user); + self->threadLoop(); + return 0; +} + +int HdmiCecMock::readMessageFromFifo(unsigned char* buf, int msgCount) { + if (msgCount <= 0 || !buf) { + return 0; + } + + int ret = -1; + /* maybe blocked at driver */ + ret = read(mInputFile, buf, msgCount); + if (ret < 0) { + ALOGE("[halimp] read :%s failed, ret:%d\n", CEC_MSG_IN_FIFO, ret); + return -1; + } + + return ret; +} + +int HdmiCecMock::sendMessageToFifo(const ::android::hardware::tv::cec::V1_1::CecMessage& message) { + unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH]; + int ret = -1; + + memset(msgBuf, 0, sizeof(msgBuf)); + msgBuf[0] = ((static_cast<uint8_t>(message.initiator) & 0xf) << 4) | + (static_cast<uint8_t>(message.destination) & 0xf); + + size_t length = std::min(static_cast<size_t>(message.body.size()), + static_cast<size_t>(MaxLength::MESSAGE_BODY)); + for (size_t i = 0; i < length; ++i) { + msgBuf[i + 1] = static_cast<unsigned char>(message.body[i]); + } + + // open the output pipe for writing outgoing cec message + mOutputFile = open(CEC_MSG_OUT_FIFO, O_WRONLY); + if (mOutputFile < 0) { + ALOGD("[halimp] file open failed for writing"); + return -1; + } + + // write message into the output pipe + ret = write(mOutputFile, msgBuf, length + 1); + close(mOutputFile); + if (ret < 0) { + ALOGE("[halimp] write :%s failed, ret:%d\n", CEC_MSG_OUT_FIFO, ret); + return -1; + } + return ret; +} + +void HdmiCecMock::printCecMsgBuf(const char* msg_buf, int len) { + char buf[64] = {}; + int i, size = 0; + memset(buf, 0, sizeof(buf)); + for (i = 0; i < len; i++) { + size += sprintf(buf + size, " %02x", msg_buf[i]); + } + ALOGD("[halimp] %s, msg:%s", __FUNCTION__, buf); +} + +void HdmiCecMock::handleHotplugMessage(unsigned char* msgBuf) { + HotplugEvent hotplugEvent{.connected = ((msgBuf[3]) & 0xf) > 0, + .portId = static_cast<uint32_t>(msgBuf[0] & 0xf)}; + + if (hotplugEvent.portId >= mPortInfo.size()) { + ALOGD("[halimp] ignore hot plug message, id %x does not exist", hotplugEvent.portId); + return; + } + + ALOGD("[halimp] hot plug port id %x, is connected %x", (msgBuf[0] & 0xf), (msgBuf[3] & 0xf)); + if (mPortInfo[hotplugEvent.portId].type == HdmiPortType::OUTPUT) { + mPhysicalAddress = + ((hotplugEvent.connected == 0) ? 0xffff : ((msgBuf[1] << 8) | (msgBuf[2]))); + mPortInfo[hotplugEvent.portId].physicalAddress = mPhysicalAddress; + ALOGD("[halimp] hot plug physical address %x", mPhysicalAddress); + } + + // todo update connection status + + if (mCallback != nullptr) { + mCallback->onHotplugEvent(hotplugEvent); + } +} + +void HdmiCecMock::handleCecMessage(unsigned char* msgBuf, int megSize) { + ::android::hardware::tv::cec::V1_1::CecMessage message; + size_t length = std::min(static_cast<size_t>(megSize - 1), + static_cast<size_t>(MaxLength::MESSAGE_BODY)); + message.body.resize(length); + + for (size_t i = 0; i < length; ++i) { + message.body[i] = static_cast<uint8_t>(msgBuf[i + 1]); + ALOGD("[halimp] msg body %x", message.body[i]); + } + + message.initiator = static_cast<::android::hardware::tv::cec::V1_1::CecLogicalAddress>( + (msgBuf[0] >> 4) & 0xf); + ALOGD("[halimp] msg init %x", message.initiator); + message.destination = static_cast<::android::hardware::tv::cec::V1_1::CecLogicalAddress>( + (msgBuf[0] >> 0) & 0xf); + ALOGD("[halimp] msg dest %x", message.destination); + + // messageValidateAndHandle(&event); + + if (mCallback != nullptr) { + mCallback->onCecMessage_1_1(message); + } +} + +void HdmiCecMock::threadLoop() { + ALOGD("[halimp] threadLoop start."); + unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH]; + int r = -1; + + // open the input pipe + while (mInputFile < 0) { + usleep(1000 * 1000); + mInputFile = open(CEC_MSG_IN_FIFO, O_RDONLY); + } + ALOGD("[halimp] file open ok, fd = %d.", mInputFile); + + while (mCecThreadRun) { + if (!mOptionSystemCecControl) { + usleep(1000 * 1000); + continue; + } + + memset(msgBuf, 0, sizeof(msgBuf)); + // try to get a message from dev. + // echo -n -e '\x04\x83' >> /dev/cec + r = readMessageFromFifo(msgBuf, CEC_MESSAGE_BODY_MAX_LENGTH); + if (r <= 1) { + // ignore received ping messages + continue; + } + + printCecMsgBuf((const char*)msgBuf, r); + + if (((msgBuf[0] >> 4) & 0xf) == 0xf) { + // the message is a hotplug event + handleHotplugMessage(msgBuf); + continue; + } + + handleCecMessage(msgBuf, r); + } + + ALOGD("[halimp] thread end."); + // mCecDevice.mExited = true; +} + +HdmiCecMock::HdmiCecMock() { + ALOGE("[halimp] Opening a virtual HAL for testing and virtual machine."); + mCallback = nullptr; + mPortInfo.resize(mTotalPorts); + mPortConnectionStatus.resize(mTotalPorts); + mPortInfo[0] = {.type = HdmiPortType::OUTPUT, + .portId = static_cast<uint32_t>(1), + .cecSupported = true, + .arcSupported = false, + .physicalAddress = mPhysicalAddress}; + mPortConnectionStatus[0] = false; +} + +} // namespace implementation +} // namespace V1_1 +} // namespace cec +} // namespace tv +} // namespace hardware +} // namespace android
\ No newline at end of file diff --git a/tv/cec/1.1/default/HdmiCecMock.h b/tv/cec/1.1/default/HdmiCecMock.h new file mode 100644 index 0000000000..0205f8d95f --- /dev/null +++ b/tv/cec/1.1/default/HdmiCecMock.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 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. + */ + +#include <android/hardware/tv/cec/1.1/IHdmiCec.h> +#include <hidl/Status.h> +#include <algorithm> +#include <vector> + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace cec { +namespace V1_1 { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::tv::cec::V1_0::CecLogicalAddress; +using ::android::hardware::tv::cec::V1_0::CecMessage; +using ::android::hardware::tv::cec::V1_0::HdmiPortInfo; +using ::android::hardware::tv::cec::V1_0::HdmiPortType; +using ::android::hardware::tv::cec::V1_0::HotplugEvent; +using ::android::hardware::tv::cec::V1_0::IHdmiCecCallback; +using ::android::hardware::tv::cec::V1_0::MaxLength; +using ::android::hardware::tv::cec::V1_0::OptionKey; +using ::android::hardware::tv::cec::V1_0::Result; +using ::android::hardware::tv::cec::V1_0::SendMessageResult; +using ::android::hardware::tv::cec::V1_1::IHdmiCec; + +#define CEC_MSG_IN_FIFO "/dev/cec_in_pipe" +#define CEC_MSG_OUT_FIFO "/dev/cec_out_pipe" + +struct HdmiCecMock : public IHdmiCec, public hidl_death_recipient { + HdmiCecMock(); + // Methods from ::android::hardware::tv::cec::V1_0::IHdmiCec follow. + Return<Result> addLogicalAddress(CecLogicalAddress addr) override; + Return<void> clearLogicalAddress() override; + Return<void> getPhysicalAddress(getPhysicalAddress_cb _hidl_cb) override; + Return<SendMessageResult> sendMessage(const CecMessage& message) override; + Return<void> setCallback( + const sp<::android::hardware::tv::cec::V1_0::IHdmiCecCallback>& callback) override; + Return<int32_t> getCecVersion() override; + Return<uint32_t> getVendorId() override; + Return<void> getPortInfo(getPortInfo_cb _hidl_cb) override; + Return<void> setOption(OptionKey key, bool value) override; + Return<void> setLanguage(const hidl_string& language) override; + Return<void> enableAudioReturnChannel(int32_t portId, bool enable) override; + Return<bool> isConnected(int32_t portId) override; + + // Methods from ::android::hardware::tv::cec::V1_1::IHdmiCec follow. + Return<Result> addLogicalAddress_1_1( + ::android::hardware::tv::cec::V1_1::CecLogicalAddress addr) override; + Return<SendMessageResult> sendMessage_1_1( + const ::android::hardware::tv::cec::V1_1::CecMessage& message) override; + Return<void> setCallback_1_1( + const sp<::android::hardware::tv::cec::V1_1::IHdmiCecCallback>& callback) override; + + virtual void serviceDied(uint64_t /*cookie*/, + const wp<::android::hidl::base::V1_0::IBase>& /*who*/) { + setCallback(nullptr); + } + + void cec_set_option(int flag, int value); + void printCecMsgBuf(const char* msg_buf, int len); + + private: + static void* __threadLoop(void* data); + void threadLoop(); + int readMessageFromFifo(unsigned char* buf, int msgCount); + int sendMessageToFifo(const ::android::hardware::tv::cec::V1_1::CecMessage& message); + void handleHotplugMessage(unsigned char* msgBuf); + void handleCecMessage(unsigned char* msgBuf, int length); + + private: + sp<::android::hardware::tv::cec::V1_1::IHdmiCecCallback> mCallback; + + // Variables for the virtual cec hal impl + uint16_t mPhysicalAddress = 0xFFFF; + vector<::android::hardware::tv::cec::V1_1::CecLogicalAddress> mLogicalAddresses; + int32_t mCecVersion = 0x06; + uint32_t mCecVendorId = 0x01; + + // Port configuration + int mTotalPorts = 1; + hidl_vec<HdmiPortInfo> mPortInfo; + hidl_vec<bool> mPortConnectionStatus; + + // CEC Option value + int mOptionWakeUp = 0; + int mOptionEnableCec = 0; + int mOptionSystemCecControl = 0; + int mOptionLanguage = 0; + + // Testing variables + // Input file descriptor + int mInputFile; + // Output file descriptor + int mOutputFile; + bool mCecThreadRun = true; + pthread_t mThreadId = 0; +}; +} // namespace implementation +} // namespace V1_1 +} // namespace cec +} // namespace tv +} // namespace hardware +} // namespace android
\ No newline at end of file diff --git a/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.rc b/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.rc new file mode 100644 index 0000000000..e150c91cc7 --- /dev/null +++ b/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.rc @@ -0,0 +1,6 @@ +service vendor.cec-hal-1-1 /vendor/bin/hw/android.hardware.tv.cec@1.1-service + interface android.hardware.tv.cec@1.0::IHdmiCec default + interface android.hardware.tv.cec@1.1::IHdmiCec default + class hal + user system + group system
\ No newline at end of file diff --git a/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.xml b/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.xml new file mode 100644 index 0000000000..492369e67f --- /dev/null +++ b/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.xml @@ -0,0 +1,11 @@ +<manifest version="1.0" type="device"> + <hal format="hidl"> + <name>android.hardware.tv.cec</name> + <transport>hwbinder</transport> + <version>1.1</version> + <interface> + <name>IHdmiCec</name> + <instance>default</instance> + </interface> + </hal> +</manifest> diff --git a/tv/cec/1.1/default/serviceMock.cpp b/tv/cec/1.1/default/serviceMock.cpp new file mode 100644 index 0000000000..72fc311ddc --- /dev/null +++ b/tv/cec/1.1/default/serviceMock.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "android.hardware.tv.cec@1.1-service-shim" + +#include <android/hardware/tv/cec/1.1/IHdmiCec.h> +#include <hidl/LegacySupport.h> +#include "HdmiCecMock.h" + +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; +using android::hardware::tv::cec::V1_1::IHdmiCec; +using android::hardware::tv::cec::V1_1::implementation::HdmiCecMock; + +int main() { + configureRpcThreadpool(8, true /* callerWillJoin */); + + // Setup hwbinder service + android::sp<IHdmiCec> service = new HdmiCecMock(); + android::status_t status; + status = service->registerAsService(); + LOG_ALWAYS_FATAL_IF(status != android::OK, "Error while registering mock cec service: %d", + status); + + joinRpcThreadpool(); + return 0; +} diff --git a/tv/cec/1.1/types.hal b/tv/cec/1.1/types.hal new file mode 100644 index 0000000000..a117519ed0 --- /dev/null +++ b/tv/cec/1.1/types.hal @@ -0,0 +1,45 @@ +/* + * Copyright (C) 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.tv.cec@1.1; + +import @1.0::CecLogicalAddress; +import @1.0::CecMessageType; + +enum CecLogicalAddress : @1.0::CecLogicalAddress { + BACKUP_1 = 12, + BACKUP_2 = 13, +}; + +enum CecMessageType : @1.0::CecMessageType { + GIVE_FEATURES = 0xA5, + REPORT_FEATURES = 0xA6, + REQUEST_CURRENT_LATENCY = 0xA7, + REPORT_CURRENT_LATENCY = 0xA8, +}; + +struct CecMessage { + /** logical address of the initiator */ + CecLogicalAddress initiator; + + /** logical address of destination */ + CecLogicalAddress destination; + + /** + * The maximum size of body is 15 (MaxLength::MESSAGE_BODY) as specified in + * the section 6 of the CEC Spec 1.4b. Overflowed data must be ignored. */ + vec<uint8_t> body; +}; diff --git a/tv/cec/1.1/vts/functional/Android.bp b/tv/cec/1.1/vts/functional/Android.bp new file mode 100644 index 0000000000..5fc7093fa8 --- /dev/null +++ b/tv/cec/1.1/vts/functional/Android.bp @@ -0,0 +1,14 @@ +cc_test { + name: "VtsHalTvCecV1_1TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: ["VtsHalTvCecV1_1TargetTest.cpp"], + static_libs: [ + "android.hardware.tv.cec@1.1", + "android.hardware.tv.cec@1.0", + ], + test_suites: [ + "general-tests", + "vts", + ], + disable_framework: true, +} diff --git a/tv/cec/1.1/vts/functional/VtsHalTvCecV1_1TargetTest.cpp b/tv/cec/1.1/vts/functional/VtsHalTvCecV1_1TargetTest.cpp new file mode 100644 index 0000000000..1eb4643565 --- /dev/null +++ b/tv/cec/1.1/vts/functional/VtsHalTvCecV1_1TargetTest.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "HdmiCec_hal_test" +#include <android-base/logging.h> + +#include <android/hardware/tv/cec/1.1/IHdmiCec.h> +#include <android/hardware/tv/cec/1.1/types.h> +#include <utils/Log.h> +#include <sstream> +#include <vector> + +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> + +using ::android::sp; +using ::android::hardware::hidl_death_recipient; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::tv::cec::V1_0::CecDeviceType; +using ::android::hardware::tv::cec::V1_0::HdmiPortInfo; +using ::android::hardware::tv::cec::V1_0::HdmiPortType; +using ::android::hardware::tv::cec::V1_0::HotplugEvent; +using ::android::hardware::tv::cec::V1_0::OptionKey; +using ::android::hardware::tv::cec::V1_0::Result; +using ::android::hardware::tv::cec::V1_0::SendMessageResult; +using ::android::hardware::tv::cec::V1_1::CecLogicalAddress; +using ::android::hardware::tv::cec::V1_1::CecMessage; +using ::android::hardware::tv::cec::V1_1::IHdmiCec; +using ::android::hardware::tv::cec::V1_1::IHdmiCecCallback; + +#define CEC_VERSION 0x05 +#define INCORRECT_VENDOR_ID 0x00 + +// The main test class for TV CEC HAL. +class HdmiCecTest : public ::testing::TestWithParam<std::string> { + public: + void SetUp() override { + hdmiCec = IHdmiCec::getService(GetParam()); + ASSERT_NE(hdmiCec, nullptr); + ALOGI("%s: getService() for hdmiCec is %s", __func__, + hdmiCec->isRemote() ? "remote" : "local"); + + hdmiCec_death_recipient = new HdmiCecDeathRecipient(); + hdmiCecCallback = new CecCallback(); + ASSERT_NE(hdmiCec_death_recipient, nullptr); + ASSERT_TRUE(hdmiCec->linkToDeath(hdmiCec_death_recipient, 0).isOk()); + } + + std::vector<int> getDeviceTypes() { + std::vector<int> deviceTypes; + FILE* p = popen("getprop ro.hdmi.device_type", "re"); + if (p) { + char* line = NULL; + size_t len = 0; + if (getline(&line, &len, p) > 0) { + std::istringstream stream(line); + std::string number{}; + while (std::getline(stream, number, ',')) { + deviceTypes.push_back(stoi(number)); + } + } + pclose(p); + } + return deviceTypes; + } + + bool hasDeviceType(CecDeviceType type) { + std::vector<int> deviceTypes = getDeviceTypes(); + return std::find(deviceTypes.begin(), deviceTypes.end(), (int)type) != deviceTypes.end(); + } + + class CecCallback : public IHdmiCecCallback { + public: + Return<void> onCecMessage( + const ::android::hardware::tv::cec::V1_0::CecMessage& /* message */) { + return Void(); + } + Return<void> onCecMessage_1_1( + const ::android::hardware::tv::cec::V1_1::CecMessage& /* message */) { + return Void(); + } + Return<void> onHotplugEvent( + const ::android::hardware::tv::cec::V1_0::HotplugEvent& /* event */) { + return Void(); + } + }; + + class HdmiCecDeathRecipient : public hidl_death_recipient { + public: + void serviceDied(uint64_t /*cookie*/, + const android::wp<::android::hidl::base::V1_0::IBase>& /*who*/) override { + FAIL(); + } + }; + + sp<IHdmiCec> hdmiCec; + sp<IHdmiCecCallback> hdmiCecCallback; + sp<HdmiCecDeathRecipient> hdmiCec_death_recipient; +}; + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HdmiCecTest); +INSTANTIATE_TEST_SUITE_P( + PerInstance, HdmiCecTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IHdmiCec::descriptor)), + android::hardware::PrintInstanceNameToString); + +TEST_P(HdmiCecTest, ClearAddLogicalAddress) { + hdmiCec->clearLogicalAddress(); + Return<Result> ret = hdmiCec->addLogicalAddress_1_1(CecLogicalAddress::PLAYBACK_3); + EXPECT_EQ(ret, Result::SUCCESS); +} + +TEST_P(HdmiCecTest, SendMessage) { + CecMessage message; + message.initiator = CecLogicalAddress::PLAYBACK_1; + message.destination = CecLogicalAddress::BROADCAST; + message.body.resize(1); + message.body[0] = 131; + SendMessageResult ret = hdmiCec->sendMessage_1_1(message); + EXPECT_EQ(ret, SendMessageResult::SUCCESS); +} + +TEST_P(HdmiCecTest, CecVersion) { + Return<int32_t> ret = hdmiCec->getCecVersion(); + EXPECT_GE(ret, CEC_VERSION); +} + +TEST_P(HdmiCecTest, SetCallback) { + Return<void> ret = hdmiCec->setCallback_1_1(new CecCallback()); + ASSERT_TRUE(ret.isOk()); +} + +TEST_P(HdmiCecTest, VendorId) { + Return<uint32_t> ret = hdmiCec->getVendorId(); + EXPECT_NE(ret, INCORRECT_VENDOR_ID); +} + +TEST_P(HdmiCecTest, GetPortInfo) { + hidl_vec<HdmiPortInfo> ports; + Return<void> ret = + hdmiCec->getPortInfo([&ports](hidl_vec<HdmiPortInfo> list) { ports = list; }); + ASSERT_TRUE(ret.isOk()); + bool cecSupportedOnDevice = false; + for (size_t i = 0; i < ports.size(); ++i) { + EXPECT_TRUE((ports[i].type == HdmiPortType::OUTPUT) || + (ports[i].type == HdmiPortType::INPUT)); + if (ports[i].portId == 0) { + ALOGW("%s: Port id should start from 1", __func__); + } + cecSupportedOnDevice = cecSupportedOnDevice | ports[i].cecSupported; + } + EXPECT_NE(cecSupportedOnDevice, false) << "At least one port should support CEC"; +} + +TEST_P(HdmiCecTest, SetOption) { + Return<void> wakeup = hdmiCec->setOption(OptionKey::WAKEUP, false); + ASSERT_TRUE(wakeup.isOk()); + Return<void> enableCec = hdmiCec->setOption(OptionKey::ENABLE_CEC, false); + ASSERT_TRUE(enableCec.isOk()); + Return<void> systemCecControl = hdmiCec->setOption(OptionKey::SYSTEM_CEC_CONTROL, true); + ASSERT_TRUE(systemCecControl.isOk()); + // Restore option keys to their default values + hdmiCec->setOption(OptionKey::WAKEUP, true); + hdmiCec->setOption(OptionKey::ENABLE_CEC, true); + hdmiCec->setOption(OptionKey::SYSTEM_CEC_CONTROL, false); +} + +TEST_P(HdmiCecTest, SetLanguage) { + Return<void> ret = hdmiCec->setLanguage("eng"); + ASSERT_TRUE(ret.isOk()); +} + +TEST_P(HdmiCecTest, EnableAudioReturnChannel) { + hidl_vec<HdmiPortInfo> ports; + Return<void> ret = + hdmiCec->getPortInfo([&ports](hidl_vec<HdmiPortInfo> list) { ports = list; }); + for (size_t i = 0; i < ports.size(); ++i) { + if (ports[i].arcSupported) { + Return<void> ret = hdmiCec->enableAudioReturnChannel(ports[i].portId, true); + ASSERT_TRUE(ret.isOk()); + } + } +}
\ No newline at end of file diff --git a/tv/tuner/1.1/default/Frontend.cpp b/tv/tuner/1.1/default/Frontend.cpp index 6956f30634..243891c996 100644 --- a/tv/tuner/1.1/default/Frontend.cpp +++ b/tv/tuner/1.1/default/Frontend.cpp @@ -196,7 +196,7 @@ Return<void> Frontend::getStatus(const hidl_vec<FrontendStatusType>& statusTypes } case FrontendStatusType::MODULATION: { FrontendModulationStatus modulationStatus; - modulationStatus.isdbt(FrontendIsdbtModulation::MOD_16QAM); // value = 1 << 3 + modulationStatus.isdbs(FrontendIsdbsModulation::MOD_BPSK); // value = 1 << 1 status.modulation(modulationStatus); break; } @@ -281,12 +281,14 @@ Return<void> Frontend::getStatusExt1_1(const hidl_vec<V1_1::FrontendStatusTypeEx for (int i = 0; i < statusTypes.size(); i++) { V1_1::FrontendStatusTypeExt1_1 type = statusTypes[i]; V1_1::FrontendStatusExt1_1 status; + // assign randomly selected values for testing. + // TODO: assign status values according to the frontend type switch (type) { case V1_1::FrontendStatusTypeExt1_1::MODULATIONS: { vector<V1_1::FrontendModulation> modulations; V1_1::FrontendModulation modulation; - modulation.isdbt(FrontendIsdbtModulation::MOD_16QAM); // value = 1 << 3 + modulation.isdbs(FrontendIsdbsModulation::MOD_BPSK); // value = 1 << 1 modulations.push_back(modulation); status.modulations(modulations); break; @@ -347,7 +349,7 @@ Return<void> Frontend::getStatusExt1_1(const hidl_vec<V1_1::FrontendStatusTypeEx } case V1_1::FrontendStatusTypeExt1_1::ROLL_OFF: { V1_1::FrontendRollOff rollOff; - rollOff.dvbs(FrontendDvbsRolloff::ROLLOFF_0_35); + rollOff.isdbs(FrontendIsdbsRolloff::ROLLOFF_0_35); status.rollOff(rollOff); break; } diff --git a/tv/tuner/1.1/default/Tuner.cpp b/tv/tuner/1.1/default/Tuner.cpp index c3dcd1d8e4..6cc99494bb 100644 --- a/tv/tuner/1.1/default/Tuner.cpp +++ b/tv/tuner/1.1/default/Tuner.cpp @@ -34,7 +34,7 @@ Tuner::Tuner() { // Static Frontends array to maintain local frontends information // Array index matches their FrontendId in the default impl mFrontendSize = 9; - mFrontends[0] = new Frontend(FrontendType::DVBT, 0, this); + mFrontends[0] = new Frontend(FrontendType::ISDBS, 0, this); mFrontends[1] = new Frontend(FrontendType::ATSC, 1, this); mFrontends[2] = new Frontend(FrontendType::DVBC, 2, this); mFrontends[3] = new Frontend(FrontendType::DVBS, 3, this); @@ -47,7 +47,7 @@ Tuner::Tuner() { FrontendInfo::FrontendCapabilities caps; caps = FrontendInfo::FrontendCapabilities(); - caps.dvbtCaps(FrontendDvbtCapabilities()); + caps.isdbsCaps(FrontendIsdbsCapabilities()); mFrontendCaps[0] = caps; caps = FrontendInfo::FrontendCapabilities(); @@ -168,6 +168,8 @@ Return<void> Tuner::getFrontendInfo(FrontendId frontendId, getFrontendInfo_cb _h FrontendStatusType::PLP_ID, FrontendStatusType::LAYER_ERROR, FrontendStatusType::ATSC3_PLP_INFO, + static_cast<FrontendStatusType>(V1_1::FrontendStatusTypeExt1_1::MODULATIONS), + static_cast<FrontendStatusType>(V1_1::FrontendStatusTypeExt1_1::ROLL_OFF), }; // assign randomly selected values for testing. info = { diff --git a/usb/1.3/Android.bp b/usb/1.3/Android.bp new file mode 100644 index 0000000000..17367d3d8d --- /dev/null +++ b/usb/1.3/Android.bp @@ -0,0 +1,16 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.usb@1.3", + root: "android.hardware", + srcs: [ + "IUsb.hal", + ], + interfaces: [ + "android.hardware.usb@1.0", + "android.hardware.usb@1.1", + "android.hardware.usb@1.2", + "android.hidl.base@1.0", + ], + gen_java: true, +} diff --git a/usb/1.3/IUsb.hal b/usb/1.3/IUsb.hal new file mode 100644 index 0000000000..3d1d380578 --- /dev/null +++ b/usb/1.3/IUsb.hal @@ -0,0 +1,32 @@ +/* + * Copyright (C) 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.usb@1.3; + +import android.hardware.usb@1.2::IUsb; + +interface IUsb extends @1.2::IUsb { + /** + * This function is used to enable/disable USB controller when some + * scenarios need. This function can stop and restore USB data signaling. + * + * @param enable true Enable USB data signaling. + * false Disable USB data signaling. + * @return true enable or disable USB data successfully + * false if something wrong + */ + enableUsbDataSignal(bool enable) generates(bool result); +}; diff --git a/usb/1.3/vts/OWNERS b/usb/1.3/vts/OWNERS new file mode 100644 index 0000000000..a6a1e5416a --- /dev/null +++ b/usb/1.3/vts/OWNERS @@ -0,0 +1,2 @@ +albertccwang@google.com +badhri@google.com diff --git a/usb/1.3/vts/functional/Android.bp b/usb/1.3/vts/functional/Android.bp new file mode 100644 index 0000000000..b62bb9d03f --- /dev/null +++ b/usb/1.3/vts/functional/Android.bp @@ -0,0 +1,31 @@ +// +// Copyright (C) 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. +// + +cc_test { + name: "VtsHalUsbV1_3TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: ["VtsHalUsbV1_3TargetTest.cpp"], + static_libs: [ + "android.hardware.usb@1.0", + "android.hardware.usb@1.1", + "android.hardware.usb@1.2", + "android.hardware.usb@1.3", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/usb/1.3/vts/functional/VtsHalUsbV1_3TargetTest.cpp b/usb/1.3/vts/functional/VtsHalUsbV1_3TargetTest.cpp new file mode 100644 index 0000000000..ed35d4223e --- /dev/null +++ b/usb/1.3/vts/functional/VtsHalUsbV1_3TargetTest.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "VtsHalUsbV1_3TargetTest" +#include <android-base/logging.h> + +#include <android/hardware/usb/1.3/IUsb.h> + +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> + +#include <log/log.h> +#include <stdlib.h> +#include <condition_variable> + +using ::android::sp; +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_memory; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::usb::V1_0::Status; +using ::android::hardware::usb::V1_3::IUsb; +using ::android::hidl::base::V1_0::IBase; + +// The main test class for the USB hidl HAL +class UsbHidlTest : public ::testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + ALOGI(__FUNCTION__); + usb = IUsb::getService(GetParam()); + ASSERT_NE(usb, nullptr); + } + + virtual void TearDown() override { ALOGI("Teardown"); } + + // USB hidl hal Proxy + sp<IUsb> usb; +}; + +/* + * Check to see if enable usb data signal succeeds. + * HAL service should call enableUsbDataSignal. + */ +TEST_P(UsbHidlTest, enableUsbDataSignal) { + Return<bool> ret = usb->enableUsbDataSignal(true); + ASSERT_TRUE(ret.isOk()); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(UsbHidlTest); +INSTANTIATE_TEST_SUITE_P( + PerInstance, UsbHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IUsb::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/wifi/1.5/IWifiChip.hal b/wifi/1.5/IWifiChip.hal index 209190a759..5a3e2883d5 100644 --- a/wifi/1.5/IWifiChip.hal +++ b/wifi/1.5/IWifiChip.hal @@ -236,19 +236,54 @@ interface IWifiChip extends @1.4::IWifiChip { setCountryCode(int8_t[2] code) generates (WifiStatus status); /** + * Usable Wifi channels filter masks. + */ + enum UsableChannelFilter : uint32_t { + /** + * Filter Wifi channels that should be avoided due to extreme + * cellular coexistence restrictions. Some Wifi channels can have + * extreme interference from/to cellular due to short frequency + * seperation with neighboring cellular channels or when there + * is harmonic and intermodulation interference. Channels which + * only have some performance degradation (e.g. power back off is + * sufficient to deal with coexistence issue) can be included and + * should not be filtered out. + */ + CELLULAR_COEXISTENCE = 1 << 0, + /** + * Filter based on concurrency state. + * Examples: + * - 5GHz SAP operation may be supported in standalone mode, but if + * there is STA connection on 5GHz DFS channel, none of the 5GHz + * channels are usable for SAP if device does not support DFS SAP mode. + * - P2P GO may not be supported on indoor channels in EU during + * standalone mode but if there is a STA connection on indoor channel, + * P2P GO may be supported by some vendors on the same STA channel. + */ + CONCURRENCY = 1 << 1, + }; + + /** * Retrieve list of usable Wifi channels for the specified band & * operational modes. * * The list of usable Wifi channels in a given band depends on factors - * like current country code, operational mode (e.g. STA, SAP, CLI, GO, - * TDLS, NAN) and any hard restrictons due to DFS, LTE Coex and - * MCC(multi channel-concurrency). + * like current country code, operational mode (e.g. STA, SAP, WFD-CLI, + * WFD-GO, TDLS, NAN) and other restrictons due to DFS, cellular coexistence + * and conncurency state of the device. * * @param band |WifiBand| for which list of usable channels is requested. * @param ifaceModeMask Bitmask of the modes represented by |WifiIfaceMode| * Bitmask respresents all the modes that the caller is interested - * in (e.g. STA, SAP, CLI, GO, TDLS, NAN). - * Note: Bitmask does not represent concurrency matrix. + * in (e.g. STA, SAP, CLI, GO, TDLS, NAN). E.g. If the caller is + * interested in knowing usable channels for P2P CLI, P2P GO & NAN, + * ifaceModeMask would be set to + * IFACE_MODE_P2P_CLIENT|IFACE_MODE_P2P_GO|IFACE_MODE_NAN. + * @param filterMask Bitmask of filters represented by + * |UsableChannelFilter|. Specifies whether driver should filter + * channels based on additional criteria. If no filter is specified + * driver should return usable channels purely based on regulatory + * constraints. * @return status WifiStatus of the operation. * Possible status codes: * |WifiStatusCode.SUCCESS|, @@ -257,10 +292,15 @@ interface IWifiChip extends @1.4::IWifiChip { * |WifiStatusCode.FAILURE_UNKNOWN| * @return channels List of channels represented by |WifiUsableChannel| * Each entry represents a channel frequency, bandwidth and - * bitmask of operational modes (e.g. STA, SAP, CLI, GO, TDLS, NAN) - * allowed on that channel. - * Note: Bitmask does not represent concurrency matrix. + * bitmask of modes (e.g. STA, SAP, CLI, GO, TDLS, NAN) that are + * allowed on that channel. E.g. If only STA mode can be supported + * on an indoor channel, only the IFACE_MODE_STA bit would be set + * for that channel. If 5GHz SAP cannot be supported, then none of + * the 5GHz channels will have IFACE_MODE_SOFTAP bit set. + * Note: Bits do not represent concurrency state. Each bit only + * represents whether particular mode is allowed on that channel. */ - getUsableChannels(WifiBand band, bitfield<WifiIfaceMode> ifaceModeMask) + getUsableChannels(WifiBand band, bitfield<WifiIfaceMode> ifaceModeMask, + bitfield<UsableChannelFilter> filterMask) generates (WifiStatus status, vec<WifiUsableChannel> channels); }; diff --git a/wifi/1.5/default/hidl_struct_util.cpp b/wifi/1.5/default/hidl_struct_util.cpp index d44b7b34d2..50e1bb6cdd 100644 --- a/wifi/1.5/default/hidl_struct_util.cpp +++ b/wifi/1.5/default/hidl_struct_util.cpp @@ -443,6 +443,20 @@ uint32_t convertLegacyWifiInterfaceModeToHidl(uint32_t legacy_iface_mask) { return hidl_iface_mask; } +uint32_t convertHidlUsableChannelFilterToLegacy(uint32_t hidl_filter_mask) { + uint32_t legacy_filter_mask = 0; + if (hidl_filter_mask & + IWifiChip::UsableChannelFilter::CELLULAR_COEXISTENCE) { + legacy_filter_mask |= + legacy_hal::WIFI_USABLE_CHANNEL_FILTER_CELLULAR_COEXISTENCE; + } + if (hidl_filter_mask & IWifiChip::UsableChannelFilter::CONCURRENCY) { + legacy_filter_mask |= + legacy_hal::WIFI_USABLE_CHANNEL_FILTER_CONCURRENCY; + } + return legacy_filter_mask; +} + bool convertLegacyWifiUsableChannelToHidl( const legacy_hal::wifi_usable_channel& legacy_usable_channel, V1_5::WifiUsableChannel* hidl_usable_channel) { diff --git a/wifi/1.5/default/hidl_struct_util.h b/wifi/1.5/default/hidl_struct_util.h index c0d7bf8f0c..8b81033fac 100644 --- a/wifi/1.5/default/hidl_struct_util.h +++ b/wifi/1.5/default/hidl_struct_util.h @@ -208,6 +208,7 @@ bool convertLegacyVectorOfRttResultToHidl( std::vector<V1_4::RttResult>* hidl_results); uint32_t convertHidlWifiBandToLegacyMacBand(V1_5::WifiBand band); uint32_t convertHidlWifiIfaceModeToLegacy(uint32_t hidl_iface_mask); +uint32_t convertHidlUsableChannelFilterToLegacy(uint32_t hidl_filter_mask); bool convertLegacyWifiUsableChannelsToHidl( const std::vector<legacy_hal::wifi_usable_channel>& legacy_usable_channels, std::vector<V1_5::WifiUsableChannel>* hidl_usable_channels); diff --git a/wifi/1.5/default/wifi_chip.cpp b/wifi/1.5/default/wifi_chip.cpp index f7c21639f6..8280954f95 100644 --- a/wifi/1.5/default/wifi_chip.cpp +++ b/wifi/1.5/default/wifi_chip.cpp @@ -741,10 +741,11 @@ Return<void> WifiChip::setCountryCode(const hidl_array<int8_t, 2>& code, Return<void> WifiChip::getUsableChannels( WifiBand band, hidl_bitfield<WifiIfaceMode> ifaceModeMask, + hidl_bitfield<UsableChannelFilter> filterMask, getUsableChannels_cb _hidl_cb) { return validateAndCall(this, WifiStatusCode::ERROR_WIFI_CHIP_INVALID, &WifiChip::getUsableChannelsInternal, _hidl_cb, band, - ifaceModeMask); + ifaceModeMask, filterMask); } void WifiChip::QcRemoveAndClearDynamicIfaces() { @@ -1547,13 +1548,17 @@ WifiStatus WifiChip::setCountryCodeInternal(const std::array<int8_t, 2>& code) { } std::pair<WifiStatus, std::vector<WifiUsableChannel>> -WifiChip::getUsableChannelsInternal(WifiBand band, uint32_t ifaceModeMask) { +WifiChip::getUsableChannelsInternal(WifiBand band, uint32_t ifaceModeMask, + uint32_t filterMask) { legacy_hal::wifi_error legacy_status; std::vector<legacy_hal::wifi_usable_channel> legacy_usable_channels; std::tie(legacy_status, legacy_usable_channels) = legacy_hal_.lock()->getUsableChannels( hidl_struct_util::convertHidlWifiBandToLegacyMacBand(band), - hidl_struct_util::convertHidlWifiIfaceModeToLegacy(ifaceModeMask)); + hidl_struct_util::convertHidlWifiIfaceModeToLegacy(ifaceModeMask), + hidl_struct_util::convertHidlUsableChannelFilterToLegacy( + filterMask)); + if (legacy_status != legacy_hal::WIFI_SUCCESS) { return {createWifiStatusFromLegacyError(legacy_status), {}}; } diff --git a/wifi/1.5/default/wifi_chip.h b/wifi/1.5/default/wifi_chip.h index 3248222bd7..e57d10810f 100644 --- a/wifi/1.5/default/wifi_chip.h +++ b/wifi/1.5/default/wifi_chip.h @@ -180,9 +180,10 @@ class WifiChip : public V1_5::IWifiChip { setCoexUnsafeChannels_cb hidl_status_cb) override; Return<void> setCountryCode(const hidl_array<int8_t, 2>& code, setCountryCode_cb _hidl_cb) override; - Return<void> getUsableChannels(WifiBand band, - hidl_bitfield<WifiIfaceMode> ifaceModeMask, - getUsableChannels_cb _hidl_cb) override; + Return<void> getUsableChannels( + WifiBand band, hidl_bitfield<WifiIfaceMode> ifaceModeMask, + hidl_bitfield<UsableChannelFilter> filterMask, + getUsableChannels_cb _hidl_cb) override; private: void invalidateAndRemoveAllIfaces(); @@ -265,7 +266,8 @@ class WifiChip : public V1_5::IWifiChip { std::vector<CoexUnsafeChannel> unsafe_channels, uint32_t restrictions); WifiStatus setCountryCodeInternal(const std::array<int8_t, 2>& code); std::pair<WifiStatus, std::vector<WifiUsableChannel>> - getUsableChannelsInternal(WifiBand band, uint32_t ifaceModeMask); + getUsableChannelsInternal(WifiBand band, uint32_t ifaceModeMask, + uint32_t filterMask); WifiStatus handleChipConfiguration( std::unique_lock<std::recursive_mutex>* lock, ChipModeId mode_id); WifiStatus registerDebugRingBufferCallback(); diff --git a/wifi/1.5/default/wifi_legacy_hal.cpp b/wifi/1.5/default/wifi_legacy_hal.cpp index 94603b3053..f5ca753824 100644 --- a/wifi/1.5/default/wifi_legacy_hal.cpp +++ b/wifi/1.5/default/wifi_legacy_hal.cpp @@ -1638,12 +1638,14 @@ wifi_error WifiLegacyHal::setDtimConfig(const std::string& iface_name, } std::pair<wifi_error, std::vector<wifi_usable_channel>> -WifiLegacyHal::getUsableChannels(uint32_t band_mask, uint32_t iface_mode_mask) { +WifiLegacyHal::getUsableChannels(uint32_t band_mask, uint32_t iface_mode_mask, + uint32_t filter_mask) { std::vector<wifi_usable_channel> channels; channels.resize(kMaxWifiUsableChannels); uint32_t size = 0; wifi_error status = global_func_table_.wifi_get_usable_channels( - global_handle_, band_mask, iface_mode_mask, channels.size(), &size, + global_handle_, band_mask, iface_mode_mask, filter_mask, + channels.size(), &size, reinterpret_cast<wifi_usable_channel*>(channels.data())); CHECK(size >= 0 && size <= kMaxWifiUsableChannels); channels.resize(size); diff --git a/wifi/1.5/default/wifi_legacy_hal.h b/wifi/1.5/default/wifi_legacy_hal.h index dc641aef28..03ca8412ce 100644 --- a/wifi/1.5/default/wifi_legacy_hal.h +++ b/wifi/1.5/default/wifi_legacy_hal.h @@ -313,6 +313,8 @@ using ::WIFI_SUCCESS; using ::wifi_tx_packet_fate; using ::wifi_tx_report; using ::wifi_usable_channel; +using ::WIFI_USABLE_CHANNEL_FILTER_CELLULAR_COEXISTENCE; +using ::WIFI_USABLE_CHANNEL_FILTER_CONCURRENCY; using ::WLAN_MAC_2_4_BAND; using ::WLAN_MAC_5_0_BAND; using ::WLAN_MAC_60_0_BAND; @@ -705,7 +707,7 @@ class WifiLegacyHal { // Retrieve the list of usable channels in the requested bands // for the requested modes std::pair<wifi_error, std::vector<wifi_usable_channel>> getUsableChannels( - uint32_t band_mask, uint32_t iface_mode_mask); + uint32_t band_mask, uint32_t iface_mode_mask, uint32_t filter_mask); private: // Retrieve interface handles for all the available interfaces. diff --git a/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp b/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp index 509f1bddc7..5ac747de22 100644 --- a/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp +++ b/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp @@ -195,10 +195,12 @@ TEST_P(WifiChipHidlTest, setCountryCode) { TEST_P(WifiChipHidlTest, getUsableChannels) { uint32_t ifaceModeMask = WifiIfaceMode::IFACE_MODE_P2P_CLIENT | WifiIfaceMode::IFACE_MODE_P2P_GO; + uint32_t filterMask = IWifiChip::UsableChannelFilter::CELLULAR_COEXISTENCE | + IWifiChip::UsableChannelFilter::CONCURRENCY; configureChipForIfaceType(IfaceType::STA, true); WifiBand band = WifiBand::BAND_24GHZ_5GHZ_6GHZ; - const auto& statusNonEmpty = - HIDL_INVOKE(wifi_chip_, getUsableChannels, band, ifaceModeMask); + const auto& statusNonEmpty = HIDL_INVOKE(wifi_chip_, getUsableChannels, + band, ifaceModeMask, filterMask); if (statusNonEmpty.first.code != WifiStatusCode::SUCCESS) { EXPECT_EQ(WifiStatusCode::ERROR_NOT_SUPPORTED, statusNonEmpty.first.code); |