diff options
author | Scott Lobdell <slobdell@google.com> | 2021-04-08 04:26:21 +0000 |
---|---|---|
committer | Scott Lobdell <slobdell@google.com> | 2021-04-08 04:26:21 +0000 |
commit | 95a4eaee873adf20b90a1d3d74c650c96a9271f2 (patch) | |
tree | 47c85878a2730bd38bffb1ee4c138764e4a57479 | |
parent | 0c2e5fb06bd4257044c4761e89705268421c77b3 (diff) | |
parent | aa0540c86a939e6c0f63e17d13d2aed3d9b53777 (diff) |
Merge SP1A.210407.002
Change-Id: I59c8a9fe4c458698011cf3ced77bcd2c4818a138
132 files changed, 3696 insertions, 1042 deletions
diff --git a/audio/effect/all-versions/default/util/EffectUtils.cpp b/audio/effect/all-versions/default/util/EffectUtils.cpp index b4382dc2d6..1156d211a1 100644 --- a/audio/effect/all-versions/default/util/EffectUtils.cpp +++ b/audio/effect/all-versions/default/util/EffectUtils.cpp @@ -25,8 +25,6 @@ #include "util/EffectUtils.h" -#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) - using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils; using ::android::hardware::audio::common::CPP_VERSION::implementation::UuidUtils; using ::android::hardware::audio::common::utils::EnumBitfield; @@ -154,6 +152,29 @@ status_t EffectUtils::effectConfigToHal(const EffectConfig& config, effect_confi return result; } +template <std::size_t N> +inline hidl_string charBufferFromHal(const char (&halBuf)[N]) { + // Even if the original field contains a non-terminated string, hidl_string + // adds a NUL terminator. + return hidl_string(halBuf, strnlen(halBuf, N)); +} + +template <std::size_t N> +inline status_t charBufferToHal(const hidl_string& str, char (&halBuf)[N], const char* fieldName) { + static_assert(N > 0); + const size_t halBufChars = N - 1; // Reserve one character for terminating NUL. + status_t result = NO_ERROR; + size_t strSize = str.size(); + if (strSize > halBufChars) { + ALOGE("%s is too long: %zu (%zu max)", fieldName, strSize, halBufChars); + strSize = halBufChars; + result = BAD_VALUE; + } + strncpy(halBuf, str.c_str(), strSize); + halBuf[strSize] = '\0'; + return result; +} + status_t EffectUtils::effectDescriptorFromHal(const effect_descriptor_t& halDescriptor, EffectDescriptor* descriptor) { UuidUtils::uuidFromHal(halDescriptor.type, &descriptor->type); @@ -166,9 +187,8 @@ status_t EffectUtils::effectDescriptorFromHal(const effect_descriptor_t& halDesc memcpy(descriptor->implementor.data(), halDescriptor.implementor, descriptor->implementor.size()); #else - descriptor->name = hidl_string(halDescriptor.name, ARRAY_SIZE(halDescriptor.name)); - descriptor->implementor = - hidl_string(halDescriptor.implementor, ARRAY_SIZE(halDescriptor.implementor)); + descriptor->name = charBufferFromHal(halDescriptor.name); + descriptor->implementor = charBufferFromHal(halDescriptor.implementor); #endif return NO_ERROR; } @@ -186,25 +206,11 @@ status_t EffectUtils::effectDescriptorToHal(const EffectDescriptor& descriptor, memcpy(halDescriptor->implementor, descriptor.implementor.data(), descriptor.implementor.size()); #else - // According to 'dumpEffectDescriptor' 'name' and 'implementor' must be NUL-terminated. - size_t nameSize = descriptor.name.size(); - if (nameSize >= ARRAY_SIZE(halDescriptor->name)) { - ALOGE("effect name is too long: %zu (%zu max)", nameSize, - ARRAY_SIZE(halDescriptor->name) - 1); - nameSize = ARRAY_SIZE(halDescriptor->name) - 1; - result = BAD_VALUE; - } - strncpy(halDescriptor->name, descriptor.name.c_str(), nameSize); - halDescriptor->name[nameSize] = '\0'; - size_t implementorSize = descriptor.implementor.size(); - if (implementorSize >= ARRAY_SIZE(halDescriptor->implementor)) { - ALOGE("effect implementor is too long: %zu (%zu max)", implementorSize, - ARRAY_SIZE(halDescriptor->implementor) - 1); - implementorSize = ARRAY_SIZE(halDescriptor->implementor) - 1; - result = BAD_VALUE; - } - strncpy(halDescriptor->implementor, descriptor.implementor.c_str(), implementorSize); - halDescriptor->implementor[implementorSize] = '\0'; + // According to 'dumpEffectDescriptor', 'name' and 'implementor' must be NUL-terminated. + CONVERT_CHECKED(charBufferToHal(descriptor.name, halDescriptor->name, "effect name"), result); + CONVERT_CHECKED(charBufferToHal(descriptor.implementor, halDescriptor->implementor, + "effect implementor"), + result); #endif return result; } diff --git a/audio/effect/all-versions/default/util/tests/effectutils_tests.cpp b/audio/effect/all-versions/default/util/tests/effectutils_tests.cpp index f3651de236..d021fa0b92 100644 --- a/audio/effect/all-versions/default/util/tests/effectutils_tests.cpp +++ b/audio/effect/all-versions/default/util/tests/effectutils_tests.cpp @@ -154,3 +154,20 @@ TEST(EffectUtils, ConvertDescriptor) { EXPECT_EQ(NO_ERROR, EffectUtils::effectDescriptorFromHal(halDesc, &descBack)); EXPECT_EQ(desc, descBack); } + +TEST(EffectUtils, ConvertNameAndImplementor) { + for (size_t i = 0; i < EFFECT_STRING_LEN_MAX; ++i) { + effect_descriptor_t halDesc{}; + for (size_t c = 0; c < i; ++c) { // '<' to accommodate NUL terminator. + halDesc.name[c] = halDesc.implementor[c] = 'A' + static_cast<char>(c); + } + EffectDescriptor desc; + EXPECT_EQ(NO_ERROR, EffectUtils::effectDescriptorFromHal(halDesc, &desc)); + effect_descriptor_t halDescBack; + EXPECT_EQ(NO_ERROR, EffectUtils::effectDescriptorToHal(desc, &halDescBack)); + EXPECT_EQ(i, strlen(halDescBack.name)); + EXPECT_EQ(i, strlen(halDescBack.implementor)); + EXPECT_EQ(0, strcmp(halDesc.name, halDescBack.name)); + EXPECT_EQ(0, strcmp(halDesc.implementor, halDescBack.implementor)); + } +} diff --git a/automotive/audiocontrol/aidl/default/Android.bp b/automotive/audiocontrol/aidl/default/Android.bp index 878bee19cb..7694bdf160 100644 --- a/automotive/audiocontrol/aidl/default/Android.bp +++ b/automotive/audiocontrol/aidl/default/Android.bp @@ -27,10 +27,8 @@ cc_binary { init_rc: ["audiocontrol-default.rc"], vintf_fragments: ["audiocontrol-default.xml"], vendor: true, - generated_headers: ["audio_policy_configuration_V7_0"], - generated_sources: ["audio_policy_configuration_V7_0"], - header_libs: ["libxsdc-utils"], shared_libs: [ + "android.hardware.audio.common@7.0-enums", "android.frameworks.automotive.powerpolicy-V1-ndk_platform", "android.hardware.automotive.audiocontrol-V1-ndk_platform", "libbase", @@ -38,7 +36,6 @@ cc_binary { "libcutils", "liblog", "libpowerpolicyclient", - "libxml2", ], srcs: [ "AudioControl.cpp", diff --git a/automotive/audiocontrol/aidl/default/AudioControl.cpp b/automotive/audiocontrol/aidl/default/AudioControl.cpp index b076d01282..c0bc796cdf 100644 --- a/automotive/audiocontrol/aidl/default/AudioControl.cpp +++ b/automotive/audiocontrol/aidl/default/AudioControl.cpp @@ -14,6 +14,9 @@ * limitations under the License. */ +#define LOG_TAG "AudioControl" +// #define LOG_NDEBUG 0 + #include "AudioControl.h" #include <aidl/android/hardware/automotive/audiocontrol/AudioFocusChange.h> @@ -24,7 +27,7 @@ #include <android-base/parseint.h> #include <android-base/strings.h> -#include <android_audio_policy_configuration_V7_0.h> +#include <android_audio_policy_configuration_V7_0-enums.h> #include <private/android_filesystem_config.h> #include <stdio.h> @@ -33,6 +36,7 @@ namespace aidl::android::hardware::automotive::audiocontrol { using ::android::base::EqualsIgnoreCase; using ::android::base::ParseInt; +using ::std::shared_ptr; using ::std::string; namespace xsd { @@ -68,7 +72,7 @@ ndk::ScopedAStatus AudioControl::registerFocusListener( const shared_ptr<IFocusListener>& in_listener) { LOG(DEBUG) << "registering focus listener"; - if (in_listener.get()) { + if (in_listener) { std::atomic_store(&mFocusListener, in_listener); } else { LOG(ERROR) << "Unexpected nullptr for listener resulting in no-op."; @@ -80,9 +84,10 @@ ndk::ScopedAStatus AudioControl::setBalanceTowardRight(float value) { if (isValidValue(value)) { // Just log in this default mock implementation LOG(INFO) << "Balance set to " << value; - } else { - LOG(ERROR) << "Balance value out of range -1 to 1 at " << value; + return ndk::ScopedAStatus::ok(); } + + LOG(ERROR) << "Balance value out of range -1 to 1 at " << value; return ndk::ScopedAStatus::ok(); } @@ -90,9 +95,10 @@ ndk::ScopedAStatus AudioControl::setFadeTowardFront(float value) { if (isValidValue(value)) { // Just log in this default mock implementation LOG(INFO) << "Fader set to " << value; - } else { - LOG(ERROR) << "Fader value out of range -1 to 1 at " << value; + return ndk::ScopedAStatus::ok(); } + + LOG(ERROR) << "Fader value out of range -1 to 1 at " << value; return ndk::ScopedAStatus::ok(); } @@ -194,7 +200,7 @@ binder_status_t AudioControl::cmdRequestFocus(int fd, const char** args, uint32_ } string usage = string(args[1]); - if (xsd::stringToAudioUsage(usage) == xsd::AudioUsage::UNKNOWN) { + if (xsd::isUnknownAudioUsage(usage)) { dprintf(fd, "Unknown usage provided: %s. Please see audio_policy_configuration.xsd V7_0 " "for supported values\n", @@ -236,7 +242,7 @@ binder_status_t AudioControl::cmdAbandonFocus(int fd, const char** args, uint32_ } string usage = string(args[1]); - if (xsd::stringToAudioUsage(usage) == xsd::AudioUsage::UNKNOWN) { + if (xsd::isUnknownAudioUsage(usage)) { dprintf(fd, "Unknown usage provided: %s. Please see audio_policy_configuration.xsd V7_0 " "for supported values\n", diff --git a/automotive/audiocontrol/aidl/default/AudioControl.h b/automotive/audiocontrol/aidl/default/AudioControl.h index ab0b1b3051..cca9c44004 100644 --- a/automotive/audiocontrol/aidl/default/AudioControl.h +++ b/automotive/audiocontrol/aidl/default/AudioControl.h @@ -23,8 +23,6 @@ namespace aidl::android::hardware::automotive::audiocontrol { -using ::std::shared_ptr; - class AudioControl : public BnAudioControl { public: ndk::ScopedAStatus onAudioFocusChange(const std::string& in_usage, int32_t in_zoneId, @@ -34,7 +32,7 @@ class AudioControl : public BnAudioControl { ndk::ScopedAStatus onDevicesToMuteChange( const std::vector<MutingInfo>& in_mutingInfos) override; ndk::ScopedAStatus registerFocusListener( - const shared_ptr<IFocusListener>& in_listener) override; + const std::shared_ptr<IFocusListener>& in_listener) override; ndk::ScopedAStatus setBalanceTowardRight(float in_value) override; ndk::ScopedAStatus setFadeTowardFront(float in_value) override; binder_status_t dump(int fd, const char** args, uint32_t numArgs) override; @@ -44,7 +42,7 @@ class AudioControl : public BnAudioControl { // a single instance of CarAudioService. As such, it doesn't have explicit serialization. // If a different AudioControl implementation were to have multiple threads leveraging this // listener, then it should also include mutexes or make the listener atomic. - shared_ptr<IFocusListener> mFocusListener; + std::shared_ptr<IFocusListener> mFocusListener; binder_status_t cmdHelp(int fd) const; binder_status_t cmdRequestFocus(int fd, const char** args, uint32_t numArgs); diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h index 5dca1dc0ff..19b0a35e8d 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h @@ -1222,7 +1222,7 @@ const ConfigDeclaration kVehicleProperties[]{ { .config = { - .prop = toInt(VehicleProperty::CLUSTER_NAVIGATION_STATE_LEGACY), + .prop = toInt(VehicleProperty::CLUSTER_NAVIGATION_STATE), .access = VehiclePropertyAccess::WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, }, diff --git a/automotive/vehicle/2.0/types.hal b/automotive/vehicle/2.0/types.hal index eabd7e11fe..1fe4bac6e3 100644 --- a/automotive/vehicle/2.0/types.hal +++ b/automotive/vehicle/2.0/types.hal @@ -3201,7 +3201,7 @@ enum VehicleProperty : int32_t { * @change_mode VehiclePropertyChangeMode:ON_CHANGE * @access VehiclePropertyAccess:WRITE */ - CLUSTER_NAVIGATION_STATE_LEGACY = ( + CLUSTER_NAVIGATION_STATE = ( 0x0F38 | VehiclePropertyGroup:SYSTEM | VehiclePropertyType:BYTES diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl index f9c13e6801..97348737ee 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl @@ -213,6 +213,7 @@ interface ISession { * 1) Any face is detected and the framework is notified via * ISessionCallback#onInteractiondetected * 2) The operation was cancelled by the framework (see ICancellationSignal) + * 3) An error occurred, for example ERROR::TIMEOUT * * Note that if the operation is canceled, the implementation must notify the framework via * ISessionCallback#onError with Error::CANCELED. diff --git a/bluetooth/1.0/vts/functional/VtsHalBluetoothV1_0TargetTest.cpp b/bluetooth/1.0/vts/functional/VtsHalBluetoothV1_0TargetTest.cpp index 76777dcd73..8ea1ddd085 100644 --- a/bluetooth/1.0/vts/functional/VtsHalBluetoothV1_0TargetTest.cpp +++ b/bluetooth/1.0/vts/functional/VtsHalBluetoothV1_0TargetTest.cpp @@ -179,7 +179,7 @@ class BluetoothHidlTest : public ::testing::TestWithParam<std::string> { bluetooth_cb->SetWaitTimeout(kCallbackNameScoEventReceived, WAIT_FOR_SCO_DATA_TIMEOUT); - EXPECT_TRUE( + ASSERT_TRUE( bluetooth_cb->WaitForCallback(kCallbackNameInitializationComplete) .no_timeout); @@ -289,7 +289,7 @@ class BluetoothHidlTest : public ::testing::TestWithParam<std::string> { void BluetoothHidlTest::handle_no_ops() { while (event_queue.size() > 0) { hidl_vec<uint8_t> event = event_queue.front(); - EXPECT_GE(event.size(), + ASSERT_GE(event.size(), static_cast<size_t>(EVENT_COMMAND_COMPLETE_STATUS_BYTE)); bool event_is_no_op = (event[EVENT_CODE_BYTE] == EVENT_COMMAND_COMPLETE) && @@ -327,7 +327,7 @@ void BluetoothHidlTest::wait_for_event(bool timeout_is_error = true) { bluetooth_cb->WaitForCallback(kCallbackNameHciEventReceived).no_timeout; EXPECT_TRUE(no_timeout || !timeout_is_error); if (no_timeout && timeout_is_error) { - EXPECT_LT(static_cast<size_t>(0), event_queue.size()); + ASSERT_LT(static_cast<size_t>(0), event_queue.size()); } if (event_queue.size() == 0) { // WaitForCallback timed out. @@ -343,12 +343,12 @@ void BluetoothHidlTest::wait_for_command_complete_event(hidl_vec<uint8_t> cmd) { hidl_vec<uint8_t> event = event_queue.front(); event_queue.pop(); - EXPECT_GT(event.size(), + ASSERT_GT(event.size(), static_cast<size_t>(EVENT_COMMAND_COMPLETE_STATUS_BYTE)); - EXPECT_EQ(EVENT_COMMAND_COMPLETE, event[EVENT_CODE_BYTE]); - EXPECT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); - EXPECT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); - EXPECT_EQ(HCI_STATUS_SUCCESS, event[EVENT_COMMAND_COMPLETE_STATUS_BYTE]); + ASSERT_EQ(EVENT_COMMAND_COMPLETE, event[EVENT_CODE_BYTE]); + ASSERT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); + ASSERT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); + ASSERT_EQ(HCI_STATUS_SUCCESS, event[EVENT_COMMAND_COMPLETE_STATUS_BYTE]); } // Send the command to read the controller's buffer sizes. @@ -362,10 +362,10 @@ void BluetoothHidlTest::setBufferSizes() { hidl_vec<uint8_t> event = event_queue.front(); event_queue.pop(); - EXPECT_EQ(EVENT_COMMAND_COMPLETE, event[EVENT_CODE_BYTE]); - EXPECT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); - EXPECT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); - EXPECT_EQ(HCI_STATUS_SUCCESS, event[EVENT_COMMAND_COMPLETE_STATUS_BYTE]); + ASSERT_EQ(EVENT_COMMAND_COMPLETE, event[EVENT_CODE_BYTE]); + ASSERT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); + ASSERT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); + ASSERT_EQ(HCI_STATUS_SUCCESS, event[EVENT_COMMAND_COMPLETE_STATUS_BYTE]); max_acl_data_packet_length = event[EVENT_COMMAND_COMPLETE_STATUS_BYTE + 1] + @@ -415,10 +415,10 @@ void BluetoothHidlTest::sendAndCheckHCI(int num_packets) { size_t compare_length = (cmd.size() > static_cast<size_t>(0xff) ? static_cast<size_t>(0xff) : cmd.size()); - EXPECT_GT(event.size(), compare_length + EVENT_FIRST_PAYLOAD_BYTE - 1); + ASSERT_GT(event.size(), compare_length + EVENT_FIRST_PAYLOAD_BYTE - 1); - EXPECT_EQ(EVENT_LOOPBACK_COMMAND, event[EVENT_CODE_BYTE]); - EXPECT_EQ(compare_length, event[EVENT_LENGTH_BYTE]); + ASSERT_EQ(EVENT_LOOPBACK_COMMAND, event[EVENT_CODE_BYTE]); + ASSERT_EQ(compare_length, event[EVENT_LENGTH_BYTE]); // Don't compare past the end of the event. if (compare_length + EVENT_FIRST_PAYLOAD_BYTE > event.size()) { @@ -455,12 +455,12 @@ void BluetoothHidlTest::sendAndCheckSCO(int num_packets, size_t size, bluetooth->sendScoData(sco_vector); // Check the loopback of the SCO packet - EXPECT_TRUE(bluetooth_cb->WaitForCallback(kCallbackNameScoEventReceived) + ASSERT_TRUE(bluetooth_cb->WaitForCallback(kCallbackNameScoEventReceived) .no_timeout); hidl_vec<uint8_t> sco_loopback = sco_queue.front(); sco_queue.pop(); - EXPECT_EQ(sco_packet.size(), sco_loopback.size()); + ASSERT_EQ(sco_packet.size(), sco_loopback.size()); size_t successful_bytes = 0; for (size_t i = 0; i < sco_packet.size(); i++) { @@ -474,7 +474,7 @@ void BluetoothHidlTest::sendAndCheckSCO(int num_packets, size_t size, break; } } - EXPECT_EQ(sco_packet.size(), successful_bytes + 1); + ASSERT_EQ(sco_packet.size(), successful_bytes + 1); } logger.setTotalBytes(num_packets * size * 2); } @@ -500,26 +500,15 @@ void BluetoothHidlTest::sendAndCheckACL(int num_packets, size_t size, bluetooth->sendAclData(acl_vector); // Check the loopback of the ACL packet - EXPECT_TRUE(bluetooth_cb->WaitForCallback(kCallbackNameAclEventReceived) + ASSERT_TRUE(bluetooth_cb->WaitForCallback(kCallbackNameAclEventReceived) .no_timeout); hidl_vec<uint8_t> acl_loopback = acl_queue.front(); acl_queue.pop(); EXPECT_EQ(acl_packet.size(), acl_loopback.size()); - size_t successful_bytes = 0; - - for (size_t i = 0; i < acl_packet.size(); i++) { - if (acl_packet[i] == acl_loopback[i]) { - successful_bytes = i; - } else { - ALOGE("Miscompare at %d (expected %x, got %x)", static_cast<int>(i), - acl_packet[i], acl_loopback[i]); - ALOGE("At %d (expected %x, got %x)", static_cast<int>(i + 1), - acl_packet[i + 1], acl_loopback[i + 1]); - break; - } + for (size_t i = 0; i < acl_packet.size() && i < acl_loopback.size(); i++) { + EXPECT_EQ(acl_packet[i], acl_loopback[i]) << " at byte number " << i; } - EXPECT_EQ(acl_packet.size(), successful_bytes + 1); } logger.setTotalBytes(num_packets * size * 2); } @@ -560,22 +549,22 @@ void BluetoothHidlTest::enterLoopbackMode(std::vector<uint16_t>& sco_handles, wait_for_event(false); if (event_queue.size() == 0) { // Fail if there was no event received or no connections completed. - EXPECT_TRUE(command_complete_received); - EXPECT_LT(0, connection_event_count); + ASSERT_TRUE(command_complete_received); + ASSERT_LT(0, connection_event_count); return; } hidl_vec<uint8_t> event = event_queue.front(); event_queue.pop(); - EXPECT_GT(event.size(), + ASSERT_GT(event.size(), static_cast<size_t>(EVENT_COMMAND_COMPLETE_STATUS_BYTE)); if (event[EVENT_CODE_BYTE] == EVENT_CONNECTION_COMPLETE) { - EXPECT_GT(event.size(), + ASSERT_GT(event.size(), static_cast<size_t>(EVENT_CONNECTION_COMPLETE_TYPE)); - EXPECT_EQ(event[EVENT_LENGTH_BYTE], + ASSERT_EQ(event[EVENT_LENGTH_BYTE], EVENT_CONNECTION_COMPLETE_PARAM_LENGTH); uint8_t connection_type = event[EVENT_CONNECTION_COMPLETE_TYPE]; - EXPECT_TRUE(connection_type == EVENT_CONNECTION_COMPLETE_TYPE_SCO || + ASSERT_TRUE(connection_type == EVENT_CONNECTION_COMPLETE_TYPE_SCO || connection_type == EVENT_CONNECTION_COMPLETE_TYPE_ACL); // Save handles @@ -590,10 +579,10 @@ void BluetoothHidlTest::enterLoopbackMode(std::vector<uint16_t>& sco_handles, event[EVENT_CONNECTION_COMPLETE_TYPE], handle); connection_event_count++; } else { - EXPECT_EQ(EVENT_COMMAND_COMPLETE, event[EVENT_CODE_BYTE]); - EXPECT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); - EXPECT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); - EXPECT_EQ(HCI_STATUS_SUCCESS, event[EVENT_COMMAND_COMPLETE_STATUS_BYTE]); + ASSERT_EQ(EVENT_COMMAND_COMPLETE, event[EVENT_CODE_BYTE]); + ASSERT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); + ASSERT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); + ASSERT_EQ(HCI_STATUS_SUCCESS, event[EVENT_COMMAND_COMPLETE_STATUS_BYTE]); command_complete_received = true; } } @@ -620,15 +609,15 @@ TEST_P(BluetoothHidlTest, HciVersionTest) { hidl_vec<uint8_t> event = event_queue.front(); event_queue.pop(); - EXPECT_GT(event.size(), static_cast<size_t>(EVENT_LOCAL_LMP_VERSION_BYTE)); + ASSERT_GT(event.size(), static_cast<size_t>(EVENT_LOCAL_LMP_VERSION_BYTE)); - EXPECT_EQ(EVENT_COMMAND_COMPLETE, event[EVENT_CODE_BYTE]); - EXPECT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); - EXPECT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); - EXPECT_EQ(HCI_STATUS_SUCCESS, event[EVENT_COMMAND_COMPLETE_STATUS_BYTE]); + ASSERT_EQ(EVENT_COMMAND_COMPLETE, event[EVENT_CODE_BYTE]); + ASSERT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); + ASSERT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); + ASSERT_EQ(HCI_STATUS_SUCCESS, event[EVENT_COMMAND_COMPLETE_STATUS_BYTE]); - EXPECT_LE(HCI_MINIMUM_HCI_VERSION, event[EVENT_LOCAL_HCI_VERSION_BYTE]); - EXPECT_LE(HCI_MINIMUM_LMP_VERSION, event[EVENT_LOCAL_LMP_VERSION_BYTE]); + ASSERT_LE(HCI_MINIMUM_HCI_VERSION, event[EVENT_LOCAL_HCI_VERSION_BYTE]); + ASSERT_LE(HCI_MINIMUM_LMP_VERSION, event[EVENT_LOCAL_LMP_VERSION_BYTE]); } // Send an unknown HCI command and wait for the error message. @@ -642,18 +631,18 @@ TEST_P(BluetoothHidlTest, HciUnknownCommand) { hidl_vec<uint8_t> event = event_queue.front(); event_queue.pop(); - EXPECT_GT(event.size(), + ASSERT_GT(event.size(), static_cast<size_t>(EVENT_COMMAND_COMPLETE_STATUS_BYTE)); if (event[EVENT_CODE_BYTE] == EVENT_COMMAND_COMPLETE) { - EXPECT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); - EXPECT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); - EXPECT_EQ(HCI_STATUS_UNKNOWN_HCI_COMMAND, + ASSERT_EQ(cmd[0], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE]); + ASSERT_EQ(cmd[1], event[EVENT_COMMAND_COMPLETE_OPCODE_LSBYTE + 1]); + ASSERT_EQ(HCI_STATUS_UNKNOWN_HCI_COMMAND, event[EVENT_COMMAND_COMPLETE_STATUS_BYTE]); } else { - EXPECT_EQ(EVENT_COMMAND_STATUS, event[EVENT_CODE_BYTE]); - EXPECT_EQ(cmd[0], event[EVENT_COMMAND_STATUS_OPCODE_LSBYTE]); - EXPECT_EQ(cmd[1], event[EVENT_COMMAND_STATUS_OPCODE_LSBYTE + 1]); - EXPECT_EQ(HCI_STATUS_UNKNOWN_HCI_COMMAND, + ASSERT_EQ(EVENT_COMMAND_STATUS, event[EVENT_CODE_BYTE]); + ASSERT_EQ(cmd[0], event[EVENT_COMMAND_STATUS_OPCODE_LSBYTE]); + ASSERT_EQ(cmd[1], event[EVENT_COMMAND_STATUS_OPCODE_LSBYTE + 1]); + ASSERT_EQ(HCI_STATUS_UNKNOWN_HCI_COMMAND, event[EVENT_COMMAND_STATUS_STATUS_BYTE]); } } @@ -678,7 +667,7 @@ TEST_P(BluetoothHidlTest, LoopbackModeSinglePackets) { // This should work, but breaks on some current platforms. Figure out how to // grandfather older devices but test new ones. if (0 && sco_connection_handles.size() > 0) { - EXPECT_LT(0, max_sco_data_packet_length); + ASSERT_LT(0, max_sco_data_packet_length); sendAndCheckSCO(1, max_sco_data_packet_length, sco_connection_handles[0]); int sco_packets_sent = 1; int completed_packets = @@ -690,7 +679,7 @@ TEST_P(BluetoothHidlTest, LoopbackModeSinglePackets) { } if (acl_connection_handles.size() > 0) { - EXPECT_LT(0, max_acl_data_packet_length); + ASSERT_LT(0, max_acl_data_packet_length); sendAndCheckACL(1, max_acl_data_packet_length, acl_connection_handles[0]); int acl_packets_sent = 1; int completed_packets = @@ -715,7 +704,7 @@ TEST_P(BluetoothHidlTest, LoopbackModeBandwidth) { // This should work, but breaks on some current platforms. Figure out how to // grandfather older devices but test new ones. if (0 && sco_connection_handles.size() > 0) { - EXPECT_LT(0, max_sco_data_packet_length); + ASSERT_LT(0, max_sco_data_packet_length); sendAndCheckSCO(NUM_SCO_PACKETS_BANDWIDTH, max_sco_data_packet_length, sco_connection_handles[0]); int sco_packets_sent = NUM_SCO_PACKETS_BANDWIDTH; @@ -728,7 +717,7 @@ TEST_P(BluetoothHidlTest, LoopbackModeBandwidth) { } if (acl_connection_handles.size() > 0) { - EXPECT_LT(0, max_acl_data_packet_length); + ASSERT_LT(0, max_acl_data_packet_length); sendAndCheckACL(NUM_ACL_PACKETS_BANDWIDTH, max_acl_data_packet_length, acl_connection_handles[0]); int acl_packets_sent = NUM_ACL_PACKETS_BANDWIDTH; diff --git a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp index b2fd402ed8..ed3b1faa53 100644 --- a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp +++ b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp @@ -125,6 +125,7 @@ using ::android::hardware::camera::device::V3_4::PhysicalCameraMetadata; using ::android::hardware::camera::metadata::V3_4:: CameraMetadataEnumAndroidSensorInfoColorFilterArrangement; using ::android::hardware::camera::metadata::V3_4::CameraMetadataTag; +using ::android::hardware::camera::metadata::V3_6::CameraMetadataEnumAndroidSensorPixelMode; using ::android::hardware::camera::provider::V2_4::ICameraProvider; using ::android::hardware::camera::provider::V2_4::ICameraProviderCallback; using ::android::hardware::camera::provider::V2_6::CameraIdAndStreamCombination; @@ -767,6 +768,8 @@ public: sp<device::V3_7::ICameraDeviceSession> *session3_7 /*out*/); void castDevice(const sp<device::V3_2::ICameraDevice> &device, int32_t deviceVersion, sp<device::V3_5::ICameraDevice> *device3_5/*out*/); + void castDevice3_7(const sp<device::V3_2::ICameraDevice>& device, int32_t deviceVersion, + sp<device::V3_7::ICameraDevice>* device3_7 /*out*/); void createStreamConfiguration(const ::android::hardware::hidl_vec<V3_2::Stream>& streams3_2, StreamConfigurationMode configMode, ::android::hardware::camera::device::V3_2::StreamConfiguration *config3_2, @@ -785,6 +788,16 @@ public: sp<DeviceCb> *outCb /*out*/, uint32_t *jpegBufferSize /*out*/, bool *useHalBufManager /*out*/); + void configureStreams3_7(const std::string& name, int32_t deviceVersion, + sp<ICameraProvider> provider, PixelFormat format, + sp<device::V3_7::ICameraDeviceSession>* session3_7 /*out*/, + V3_2::Stream* previewStream /*out*/, + device::V3_6::HalStreamConfiguration* halStreamConfig /*out*/, + bool* supportsPartialResults /*out*/, + uint32_t* partialResultCount /*out*/, bool* useHalBufManager /*out*/, + sp<DeviceCb>* outCb /*out*/, uint32_t streamConfigCounter, + bool maxResolution); + void configurePreviewStreams3_4(const std::string &name, int32_t deviceVersion, sp<ICameraProvider> provider, const AvailableStream *previewThreshold, @@ -846,6 +859,10 @@ public: hidl_vec<int32_t> streamIds, sp<DeviceCb> cb, uint32_t streamConfigCounter = 0); + void verifyBuffersReturned(sp<device::V3_7::ICameraDeviceSession> session, + hidl_vec<int32_t> streamIds, sp<DeviceCb> cb, + uint32_t streamConfigCounter = 0); + void verifySessionReconfigurationQuery(sp<device::V3_5::ICameraDeviceSession> session3_5, camera_metadata* oldSessionParams, camera_metadata* newSessionParams); @@ -853,12 +870,15 @@ public: static bool isDepthOnly(const camera_metadata_t* staticMeta); - static Status getAvailableOutputStreams(const camera_metadata_t *staticMeta, - std::vector<AvailableStream> &outputStreams, - const AvailableStream *threshold = nullptr); + static bool isUltraHighResolution(const camera_metadata_t* staticMeta); + + static Status getAvailableOutputStreams(const camera_metadata_t* staticMeta, + std::vector<AvailableStream>& outputStreams, + const AvailableStream* threshold = nullptr, + bool maxResolution = false); static Status getMaxOutputSizeForFormat(const camera_metadata_t* staticMeta, PixelFormat format, - Size* size); + Size* size, bool maxResolution = false); static Status getMandatoryConcurrentStreams(const camera_metadata_t* staticMeta, std::vector<AvailableStream>* outputStreams); @@ -4841,6 +4861,184 @@ TEST_P(CameraHidlTest, processMultiCaptureRequestPreview) { } } +// Generate and verify an ultra high resolution capture request +TEST_P(CameraHidlTest, processUltraHighResolutionRequest) { + hidl_vec<hidl_string> cameraDeviceNames = getCameraDeviceNames(mProvider); + uint64_t bufferId = 1; + uint32_t frameNumber = 1; + ::android::hardware::hidl_vec<uint8_t> settings; + + for (const auto& name : cameraDeviceNames) { + int deviceVersion = getCameraDeviceVersion(name, mProviderType); + if (deviceVersion < CAMERA_DEVICE_API_VERSION_3_7) { + continue; + } + std::string version, deviceId; + ASSERT_TRUE(::matchDeviceName(name, mProviderType, &version, &deviceId)); + camera_metadata_t* staticMeta; + Return<void> ret; + sp<ICameraDeviceSession> session; + openEmptyDeviceSession(name, mProvider, &session, &staticMeta); + if (!isUltraHighResolution(staticMeta)) { + free_camera_metadata(staticMeta); + ret = session->close(); + ASSERT_TRUE(ret.isOk()); + continue; + } + android::hardware::camera::common::V1_0::helper::CameraMetadata defaultSettings; + ret = session->constructDefaultRequestSettings( + RequestTemplate::STILL_CAPTURE, + [&defaultSettings](auto status, const auto& req) mutable { + ASSERT_EQ(Status::OK, status); + + const camera_metadata_t* metadata = + reinterpret_cast<const camera_metadata_t*>(req.data()); + size_t expectedSize = req.size(); + int result = validate_camera_metadata_structure(metadata, &expectedSize); + ASSERT_TRUE((result == 0) || (result == CAMERA_METADATA_VALIDATION_SHIFTED)); + + size_t entryCount = get_camera_metadata_entry_count(metadata); + ASSERT_GT(entryCount, 0u); + defaultSettings = metadata; + }); + ASSERT_TRUE(ret.isOk()); + uint8_t sensorPixelMode = + static_cast<uint8_t>(ANDROID_SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION); + ASSERT_EQ(::android::OK, + defaultSettings.update(ANDROID_SENSOR_PIXEL_MODE, &sensorPixelMode, 1)); + + const camera_metadata_t* settingsBuffer = defaultSettings.getAndLock(); + settings.setToExternal( + reinterpret_cast<uint8_t*>(const_cast<camera_metadata_t*>(settingsBuffer)), + get_camera_metadata_size(settingsBuffer)); + + free_camera_metadata(staticMeta); + ret = session->close(); + ASSERT_TRUE(ret.isOk()); + V3_6::HalStreamConfiguration halStreamConfig; + bool supportsPartialResults = false; + bool useHalBufManager = false; + uint32_t partialResultCount = 0; + V3_2::Stream previewStream; + sp<device::V3_7::ICameraDeviceSession> session3_7; + sp<DeviceCb> cb; + std::list<PixelFormat> pixelFormats = {PixelFormat::YCBCR_420_888, PixelFormat::RAW16}; + for (PixelFormat format : pixelFormats) { + configureStreams3_7(name, deviceVersion, mProvider, format, &session3_7, &previewStream, + &halStreamConfig, &supportsPartialResults, &partialResultCount, + &useHalBufManager, &cb, 0, /*maxResolution*/ true); + ASSERT_NE(session3_7, nullptr); + + std::shared_ptr<ResultMetadataQueue> resultQueue; + auto resultQueueRet = session3_7->getCaptureResultMetadataQueue( + [&resultQueue](const auto& descriptor) { + resultQueue = std::make_shared<ResultMetadataQueue>(descriptor); + if (!resultQueue->isValid() || resultQueue->availableToWrite() <= 0) { + ALOGE("%s: HAL returns empty result metadata fmq," + " not use it", + __func__); + resultQueue = nullptr; + // Don't use the queue onwards. + } + }); + ASSERT_TRUE(resultQueueRet.isOk()); + + std::vector<hidl_handle> graphicBuffers; + graphicBuffers.reserve(halStreamConfig.streams.size()); + ::android::hardware::hidl_vec<StreamBuffer> outputBuffers; + outputBuffers.resize(halStreamConfig.streams.size()); + InFlightRequest inflightReq = {static_cast<ssize_t>(halStreamConfig.streams.size()), + false, + supportsPartialResults, + partialResultCount, + std::unordered_set<std::string>(), + resultQueue}; + + size_t k = 0; + for (const auto& halStream : halStreamConfig.streams) { + hidl_handle buffer_handle; + if (useHalBufManager) { + outputBuffers[k] = {halStream.v3_4.v3_3.v3_2.id, + 0, + buffer_handle, + BufferStatus::OK, + nullptr, + nullptr}; + } else { + allocateGraphicBuffer( + previewStream.width, previewStream.height, + android_convertGralloc1To0Usage(halStream.v3_4.v3_3.v3_2.producerUsage, + halStream.v3_4.v3_3.v3_2.consumerUsage), + halStream.v3_4.v3_3.v3_2.overrideFormat, &buffer_handle); + graphicBuffers.push_back(buffer_handle); + outputBuffers[k] = {halStream.v3_4.v3_3.v3_2.id, + bufferId, + buffer_handle, + BufferStatus::OK, + nullptr, + nullptr}; + bufferId++; + } + k++; + } + + StreamBuffer emptyInputBuffer = {-1, 0, nullptr, BufferStatus::ERROR, nullptr, nullptr}; + V3_4::CaptureRequest request3_4; + request3_4.v3_2.frameNumber = frameNumber; + request3_4.v3_2.fmqSettingsSize = 0; + request3_4.v3_2.settings = settings; + request3_4.v3_2.inputBuffer = emptyInputBuffer; + request3_4.v3_2.outputBuffers = outputBuffers; + V3_7::CaptureRequest request3_7; + request3_7.v3_4 = request3_4; + request3_7.inputWidth = 0; + request3_7.inputHeight = 0; + + { + std::unique_lock<std::mutex> l(mLock); + mInflightMap.clear(); + mInflightMap.add(frameNumber, &inflightReq); + } + + Status stat = Status::INTERNAL_ERROR; + uint32_t numRequestProcessed = 0; + hidl_vec<BufferCache> cachesToRemove; + Return<void> returnStatus = session3_7->processCaptureRequest_3_7( + {request3_7}, cachesToRemove, + [&stat, &numRequestProcessed](auto s, uint32_t n) { + stat = s; + numRequestProcessed = n; + }); + ASSERT_TRUE(returnStatus.isOk()); + ASSERT_EQ(Status::OK, stat); + ASSERT_EQ(numRequestProcessed, 1u); + + { + std::unique_lock<std::mutex> l(mLock); + while (!inflightReq.errorCodeValid && + ((0 < inflightReq.numBuffersLeft) || (!inflightReq.haveResultMetadata))) { + auto timeout = std::chrono::system_clock::now() + + std::chrono::seconds(kStreamBufferTimeoutSec); + ASSERT_NE(std::cv_status::timeout, mResultCondition.wait_until(l, timeout)); + } + + ASSERT_FALSE(inflightReq.errorCodeValid); + ASSERT_NE(inflightReq.resultOutputBuffers.size(), 0u); + } + if (useHalBufManager) { + hidl_vec<int32_t> streamIds(halStreamConfig.streams.size()); + for (size_t i = 0; i < streamIds.size(); i++) { + streamIds[i] = halStreamConfig.streams[i].v3_4.v3_3.v3_2.id; + } + verifyBuffersReturned(session3_7, streamIds, cb); + } + + ret = session3_7->close(); + ASSERT_TRUE(ret.isOk()); + } + } +} + // Generate and verify a burst containing alternating sensor sensitivity values TEST_P(CameraHidlTest, processCaptureRequestBurstISO) { hidl_vec<hidl_string> cameraDeviceNames = getCameraDeviceNames(mProvider); @@ -5537,21 +5735,26 @@ TEST_P(CameraHidlTest, providerDeviceStateNotification) { // Retrieve all valid output stream resolutions from the camera // static characteristics. -Status CameraHidlTest::getAvailableOutputStreams(const camera_metadata_t *staticMeta, - std::vector<AvailableStream> &outputStreams, - const AvailableStream *threshold) { +Status CameraHidlTest::getAvailableOutputStreams(const camera_metadata_t* staticMeta, + std::vector<AvailableStream>& outputStreams, + const AvailableStream* threshold, + bool maxResolution) { AvailableStream depthPreviewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, static_cast<int32_t>(PixelFormat::Y16)}; if (nullptr == staticMeta) { return Status::ILLEGAL_ARGUMENT; } + int scalerTag = maxResolution + ? ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION + : ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS; + int depthTag = maxResolution + ? ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION + : ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS; camera_metadata_ro_entry scalarEntry; camera_metadata_ro_entry depthEntry; - int foundScalar = find_camera_metadata_ro_entry(staticMeta, - ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &scalarEntry); - int foundDepth = find_camera_metadata_ro_entry(staticMeta, - ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS, &depthEntry); + int foundScalar = find_camera_metadata_ro_entry(staticMeta, scalerTag, &scalarEntry); + int foundDepth = find_camera_metadata_ro_entry(staticMeta, depthTag, &depthEntry); if ((0 != foundScalar || (0 != (scalarEntry.count % 4))) && (0 != foundDepth || (0 != (depthEntry.count % 4)))) { return Status::ILLEGAL_ARGUMENT; @@ -5619,9 +5822,12 @@ Status CameraHidlTest::getMandatoryConcurrentStreams(const camera_metadata_t* st } Status CameraHidlTest::getMaxOutputSizeForFormat(const camera_metadata_t* staticMeta, - PixelFormat format, Size* size) { + PixelFormat format, Size* size, + bool maxResolution) { std::vector<AvailableStream> outputStreams; - if (size == nullptr || getAvailableOutputStreams(staticMeta, outputStreams) != Status::OK) { + if (size == nullptr || + getAvailableOutputStreams(staticMeta, outputStreams, + /*threshold*/ nullptr, maxResolution) != Status::OK) { return Status::ILLEGAL_ARGUMENT; } Size maxSize; @@ -6065,6 +6271,148 @@ void CameraHidlTest::createStreamConfiguration( *config3_2 = {streams3_2, configMode}; } +// Configure streams +void CameraHidlTest::configureStreams3_7( + const std::string& name, int32_t deviceVersion, sp<ICameraProvider> provider, + PixelFormat format, sp<device::V3_7::ICameraDeviceSession>* session3_7 /*out*/, + V3_2::Stream* previewStream /*out*/, + device::V3_6::HalStreamConfiguration* halStreamConfig /*out*/, + bool* supportsPartialResults /*out*/, uint32_t* partialResultCount /*out*/, + bool* useHalBufManager /*out*/, sp<DeviceCb>* outCb /*out*/, uint32_t streamConfigCounter, + bool maxResolution) { + ASSERT_NE(nullptr, session3_7); + ASSERT_NE(nullptr, halStreamConfig); + ASSERT_NE(nullptr, previewStream); + ASSERT_NE(nullptr, supportsPartialResults); + ASSERT_NE(nullptr, partialResultCount); + ASSERT_NE(nullptr, useHalBufManager); + ASSERT_NE(nullptr, outCb); + + std::vector<AvailableStream> outputStreams; + ::android::sp<ICameraDevice> device3_x; + ALOGI("configureStreams: Testing camera device %s", name.c_str()); + Return<void> ret; + ret = provider->getCameraDeviceInterface_V3_x(name, [&](auto status, const auto& device) { + ALOGI("getCameraDeviceInterface_V3_x returns status:%d", (int)status); + ASSERT_EQ(Status::OK, status); + ASSERT_NE(device, nullptr); + device3_x = device; + }); + ASSERT_TRUE(ret.isOk()); + + camera_metadata_t* staticMeta; + ret = device3_x->getCameraCharacteristics([&](Status s, CameraMetadata metadata) { + ASSERT_EQ(Status::OK, s); + staticMeta = + clone_camera_metadata(reinterpret_cast<const camera_metadata_t*>(metadata.data())); + ASSERT_NE(nullptr, staticMeta); + }); + ASSERT_TRUE(ret.isOk()); + + camera_metadata_ro_entry entry; + auto status = + find_camera_metadata_ro_entry(staticMeta, ANDROID_REQUEST_PARTIAL_RESULT_COUNT, &entry); + if ((0 == status) && (entry.count > 0)) { + *partialResultCount = entry.data.i32[0]; + *supportsPartialResults = (*partialResultCount > 1); + } + + sp<DeviceCb> cb = new DeviceCb(this, deviceVersion, staticMeta); + sp<ICameraDeviceSession> session; + ret = device3_x->open(cb, [&session](auto status, const auto& newSession) { + ALOGI("device::open returns status:%d", (int)status); + ASSERT_EQ(Status::OK, status); + ASSERT_NE(newSession, nullptr); + session = newSession; + }); + ASSERT_TRUE(ret.isOk()); + *outCb = cb; + + sp<device::V3_3::ICameraDeviceSession> session3_3; + sp<device::V3_4::ICameraDeviceSession> session3_4; + sp<device::V3_5::ICameraDeviceSession> session3_5; + sp<device::V3_6::ICameraDeviceSession> session3_6; + castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6, + session3_7); + ASSERT_NE(nullptr, (*session3_7).get()); + + *useHalBufManager = false; + status = find_camera_metadata_ro_entry( + staticMeta, ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION, &entry); + if ((0 == status) && (entry.count == 1)) { + *useHalBufManager = (entry.data.u8[0] == + ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_HIDL_DEVICE_3_5); + } + + outputStreams.clear(); + Size maxSize; + auto rc = getMaxOutputSizeForFormat(staticMeta, format, &maxSize, maxResolution); + ASSERT_EQ(Status::OK, rc); + free_camera_metadata(staticMeta); + + ::android::hardware::hidl_vec<V3_7::Stream> streams3_7(1); + streams3_7[0].groupId = -1; + streams3_7[0].sensorPixelModesUsed = { + CameraMetadataEnumAndroidSensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION}; + streams3_7[0].v3_4.bufferSize = 0; + streams3_7[0].v3_4.v3_2.id = 0; + streams3_7[0].v3_4.v3_2.streamType = StreamType::OUTPUT; + streams3_7[0].v3_4.v3_2.width = static_cast<uint32_t>(maxSize.width); + streams3_7[0].v3_4.v3_2.height = static_cast<uint32_t>(maxSize.height); + streams3_7[0].v3_4.v3_2.format = static_cast<PixelFormat>(format); + streams3_7[0].v3_4.v3_2.usage = GRALLOC1_CONSUMER_USAGE_CPU_READ; + streams3_7[0].v3_4.v3_2.dataSpace = 0; + streams3_7[0].v3_4.v3_2.rotation = StreamRotation::ROTATION_0; + + ::android::hardware::camera::device::V3_7::StreamConfiguration config3_7; + config3_7.streams = streams3_7; + config3_7.operationMode = StreamConfigurationMode::NORMAL_MODE; + config3_7.streamConfigCounter = streamConfigCounter; + config3_7.multiResolutionInputImage = false; + RequestTemplate reqTemplate = RequestTemplate::STILL_CAPTURE; + ret = (*session3_7) + ->constructDefaultRequestSettings(reqTemplate, + [&config3_7](auto status, const auto& req) { + ASSERT_EQ(Status::OK, status); + config3_7.sessionParams = req; + }); + ASSERT_TRUE(ret.isOk()); + + ASSERT_TRUE(deviceVersion >= CAMERA_DEVICE_API_VERSION_3_7); + sp<device::V3_7::ICameraDevice> cameraDevice3_7 = nullptr; + castDevice3_7(device3_x, deviceVersion, &cameraDevice3_7); + ASSERT_NE(cameraDevice3_7, nullptr); + bool supported = false; + ret = cameraDevice3_7->isStreamCombinationSupported_3_7( + config3_7, [&supported](Status s, bool combStatus) { + ASSERT_TRUE((Status::OK == s) || (Status::METHOD_NOT_SUPPORTED == s)); + if (Status::OK == s) { + supported = combStatus; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(supported, true); + + if (*session3_7 != nullptr) { + ret = (*session3_7) + ->configureStreams_3_7( + config3_7, + [&](Status s, device::V3_6::HalStreamConfiguration halConfig) { + ASSERT_EQ(Status::OK, s); + *halStreamConfig = halConfig; + if (*useHalBufManager) { + hidl_vec<V3_4::Stream> streams(1); + hidl_vec<V3_2::HalStream> halStreams(1); + streams[0] = streams3_7[0].v3_4; + halStreams[0] = halConfig.streams[0].v3_4.v3_3.v3_2; + cb->setCurrentStreamConfig(streams, halStreams); + } + }); + } + *previewStream = streams3_7[0].v3_4.v3_2; + ASSERT_TRUE(ret.isOk()); +} + // Configure multiple preview streams using different physical ids. void CameraHidlTest::configurePreviewStreams3_4(const std::string &name, int32_t deviceVersion, sp<ICameraProvider> provider, @@ -6362,6 +6710,21 @@ void CameraHidlTest::configureOfflineStillStream(const std::string &name, ASSERT_TRUE(ret.isOk()); } +bool CameraHidlTest::isUltraHighResolution(const camera_metadata_t* staticMeta) { + camera_metadata_ro_entry scalarEntry; + int rc = find_camera_metadata_ro_entry(staticMeta, ANDROID_REQUEST_AVAILABLE_CAPABILITIES, + &scalarEntry); + if (rc == 0) { + for (uint32_t i = 0; i < scalarEntry.count; i++) { + if (scalarEntry.data.u8[i] == + ANDROID_REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR) { + return true; + } + } + } + return false; +} + bool CameraHidlTest::isDepthOnly(const camera_metadata_t* staticMeta) { camera_metadata_ro_entry scalarEntry; camera_metadata_ro_entry depthEntry; @@ -6590,6 +6953,17 @@ void CameraHidlTest::configureSingleStream( ASSERT_TRUE(ret.isOk()); } +void CameraHidlTest::castDevice3_7(const sp<device::V3_2::ICameraDevice>& device, + int32_t deviceVersion, + sp<device::V3_7::ICameraDevice>* device3_7 /*out*/) { + ASSERT_NE(nullptr, device3_7); + if (deviceVersion >= CAMERA_DEVICE_API_VERSION_3_7) { + auto castResult = device::V3_7::ICameraDevice::castFrom(device); + ASSERT_TRUE(castResult.isOk()); + *device3_7 = castResult; + } +} + void CameraHidlTest::castDevice(const sp<device::V3_2::ICameraDevice> &device, int32_t deviceVersion, sp<device::V3_5::ICameraDevice> *device3_5/*out*/) { ASSERT_NE(nullptr, device3_5); @@ -7303,6 +7677,13 @@ void CameraHidlTest::verifyBuffersReturned( cb->waitForBuffersReturned(); } +void CameraHidlTest::verifyBuffersReturned(sp<device::V3_7::ICameraDeviceSession> session3_7, + hidl_vec<int32_t> streamIds, sp<DeviceCb> cb, + uint32_t streamConfigCounter) { + session3_7->signalStreamFlush(streamIds, /*streamConfigCounter*/ streamConfigCounter); + cb->waitForBuffersReturned(); +} + void CameraHidlTest::verifyLogicalCameraResult(const camera_metadata_t* staticMetadata, const ::android::hardware::camera::common::V1_0::helper::CameraMetadata& resultMetadata) { std::unordered_set<std::string> physicalIds; diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index b0fea9eb71..4177af56a8 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -419,7 +419,7 @@ </hal> <hal format="aidl" optional="false"> <name>android.hardware.power</name> - <version>1</version> + <version>1-2</version> <interface> <name>IPower</name> <instance>default</instance> diff --git a/current.txt b/current.txt index 6c576cab7a..270880fbf1 100644 --- a/current.txt +++ b/current.txt @@ -782,6 +782,8 @@ cd84ab19c590e0e73dd2307b591a3093ee18147ef95e6d5418644463a6620076 android.hardwar f729ee6a5f136b25d79ea6895d24700fce413df555baaecf2c39e4440d15d043 android.hardware.neuralnetworks@1.0::types a84f8dac7a9b75de1cc2936a9b429b9b62b32a31ea88ca52c29f98f5ddc0fa95 android.hardware.neuralnetworks@1.2::types cd331b92312d16ab89f475c39296abbf539efc4114a8c5c2b136ad99b904ef33 android.hardware.neuralnetworks@1.3::types +c3fec5bd470984402997f78a74b6511efc4063b270f2bd9ee7b78f48b683a1bb android.hardware.neuralnetworks@1.3::IDevice +0fdfad62c2ec33b52e6687004e5a1971c02d10b93ee4d26df5ccff7ce032494a android.hardware.neuralnetworks@1.3::IPreparedModel 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.0/default/Android.bp b/drm/1.0/default/Android.bp index af1c076e0d..cbdab4ffe0 100644 --- a/drm/1.0/default/Android.bp +++ b/drm/1.0/default/Android.bp @@ -32,6 +32,7 @@ cc_library_static { "-Werror", "-Wextra", "-Wall", + "-Wthread-safety", ], shared_libs: [ "liblog", @@ -42,7 +43,7 @@ cc_library_static { export_header_lib_headers: [ "libutils_headers", ], - export_include_dirs : ["include"] + export_include_dirs: ["include"], } soong_config_module_type { @@ -59,8 +60,8 @@ android_hardware_drm_1_0_multilib { soong_config_variables: { TARGET_ENABLE_MEDIADRM_64: { compile_multilib: "both", - } - } + }, + }, } android_hardware_drm_1_0_multilib { @@ -69,8 +70,8 @@ android_hardware_drm_1_0_multilib { soong_config_variables: { TARGET_ENABLE_MEDIADRM_64: { compile_multilib: "first", - } - } + }, + }, } cc_defaults { @@ -98,7 +99,7 @@ cc_binary { name: "android.hardware.drm@1.0-service", defaults: [ "android.hardware.drm@1.0-multilib-exe", - "android.hardware.drm@1.0-service-defaults" + "android.hardware.drm@1.0-service-defaults", ], init_rc: ["android.hardware.drm@1.0-service.rc"], srcs: ["service.cpp"], @@ -110,7 +111,7 @@ cc_binary { name: "android.hardware.drm@1.0-service-lazy", defaults: [ "android.hardware.drm@1.0-multilib-exe", - "android.hardware.drm@1.0-service-defaults" + "android.hardware.drm@1.0-service-defaults", ], overrides: ["android.hardware.drm@1.0-service"], init_rc: ["android.hardware.drm@1.0-service-lazy.rc"], diff --git a/drm/1.0/default/CryptoPlugin.cpp b/drm/1.0/default/CryptoPlugin.cpp index e6d4e8447b..8dea7e9324 100644 --- a/drm/1.0/default/CryptoPlugin.cpp +++ b/drm/1.0/default/CryptoPlugin.cpp @@ -53,6 +53,8 @@ namespace implementation { uint32_t bufferId) { sp<IMemory> hidlMemory = mapMemory(base); + std::lock_guard<std::mutex> shared_buffer_lock(mSharedBufferLock); + // allow mapMemory to return nullptr mSharedBufferMap[bufferId] = hidlMemory; return Void(); @@ -65,7 +67,7 @@ namespace implementation { const SharedBuffer& source, uint64_t offset, const DestinationBuffer& destination, decrypt_cb _hidl_cb) { - + std::unique_lock<std::mutex> shared_buffer_lock(mSharedBufferLock); if (mSharedBufferMap.find(source.bufferId) == mSharedBufferMap.end()) { _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "source decrypt buffer base not set"); return Void(); @@ -79,7 +81,7 @@ namespace implementation { } } - android::CryptoPlugin::Mode legacyMode; + android::CryptoPlugin::Mode legacyMode = android::CryptoPlugin::kMode_Unencrypted; switch(mode) { case Mode::UNENCRYPTED: legacyMode = android::CryptoPlugin::kMode_Unencrypted; @@ -146,7 +148,10 @@ namespace implementation { return Void(); } - if (destBuffer.offset + destBuffer.size > destBase->getSize()) { + size_t totalSize = 0; + if (__builtin_add_overflow(destBuffer.offset, destBuffer.size, &totalSize) || + totalSize > destBase->getSize()) { + android_errorWriteLog(0x534e4554, "176496353"); _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size"); return Void(); } @@ -157,7 +162,7 @@ namespace implementation { } base = static_cast<uint8_t *>(static_cast<void *>(destBase->getPointer())); - destPtr = static_cast<void *>(base + destination.nonsecureMemory.offset); + destPtr = static_cast<void*>(base + destination.nonsecureMemory.offset); } else if (destination.type == BufferType::NATIVE_HANDLE) { if (!secure) { _hidl_cb(Status::BAD_VALUE, 0, "native handle destination must be secure"); @@ -170,6 +175,10 @@ namespace implementation { _hidl_cb(Status::BAD_VALUE, 0, "invalid destination type"); return Void(); } + + // release mSharedBufferLock + shared_buffer_lock.unlock(); + ssize_t result = mLegacyPlugin->decrypt(secure, keyId.data(), iv.data(), legacyMode, legacyPattern, srcPtr, legacySubSamples.get(), subSamples.size(), destPtr, &detailMessage); diff --git a/drm/1.0/default/CryptoPlugin.h b/drm/1.0/default/CryptoPlugin.h index 11cc2aae47..0d091fae65 100644 --- a/drm/1.0/default/CryptoPlugin.h +++ b/drm/1.0/default/CryptoPlugin.h @@ -17,11 +17,14 @@ #ifndef ANDROID_HARDWARE_DRM_V1_0__CRYPTOPLUGIN_H #define ANDROID_HARDWARE_DRM_V1_0__CRYPTOPLUGIN_H -#include <android/hidl/memory/1.0/IMemory.h> +#include <android-base/thread_annotations.h> #include <android/hardware/drm/1.0/ICryptoPlugin.h> +#include <android/hidl/memory/1.0/IMemory.h> #include <hidl/Status.h> #include <media/hardware/CryptoAPI.h> +#include <mutex> + namespace android { namespace hardware { namespace drm { @@ -60,19 +63,21 @@ struct CryptoPlugin : public ICryptoPlugin { Return<void> setSharedBufferBase(const ::android::hardware::hidl_memory& base, uint32_t bufferId) override; - Return<void> decrypt(bool secure, const hidl_array<uint8_t, 16>& keyId, - const hidl_array<uint8_t, 16>& iv, Mode mode, const Pattern& pattern, - const hidl_vec<SubSample>& subSamples, const SharedBuffer& source, - uint64_t offset, const DestinationBuffer& destination, - decrypt_cb _hidl_cb) override; + Return<void> decrypt( + bool secure, const hidl_array<uint8_t, 16>& keyId, const hidl_array<uint8_t, 16>& iv, + Mode mode, const Pattern& pattern, const hidl_vec<SubSample>& subSamples, + const SharedBuffer& source, uint64_t offset, const DestinationBuffer& destination, + decrypt_cb _hidl_cb) override NO_THREAD_SAFETY_ANALYSIS; // use unique_lock -private: + private: android::CryptoPlugin *mLegacyPlugin; - std::map<uint32_t, sp<IMemory> > mSharedBufferMap; + std::map<uint32_t, sp<IMemory>> mSharedBufferMap GUARDED_BY(mSharedBufferLock); CryptoPlugin() = delete; CryptoPlugin(const CryptoPlugin &) = delete; void operator=(const CryptoPlugin &) = delete; + + std::mutex mSharedBufferLock; }; } // namespace implementation diff --git a/gnss/common/utils/default/NmeaFixInfo.cpp b/gnss/common/utils/default/NmeaFixInfo.cpp index 43e008bb72..c7ee13488b 100644 --- a/gnss/common/utils/default/NmeaFixInfo.cpp +++ b/gnss/common/utils/default/NmeaFixInfo.cpp @@ -202,8 +202,15 @@ std::unique_ptr<V2_0::GnssLocation> NmeaFixInfo::getLocationFromInputStr( uint32_t fixId = 0; double lastTimeStamp = 0; for (const auto& line : nmeaRecords) { + if (line.compare(0, strlen(GPGA_RECORD_TAG), GPGA_RECORD_TAG) != 0 && + line.compare(0, strlen(GPRMC_RECORD_TAG), GPRMC_RECORD_TAG) != 0) { + continue; + } std::vector<std::string> sentenceValues; splitStr(line, COMMA_SEPARATOR, sentenceValues); + if (sentenceValues.size() < MIN_COL_NUM) { + continue; + } double currentTimeStamp = std::stof(sentenceValues[1]); // If see a new timestamp, report correct location. if ((currentTimeStamp - lastTimeStamp) > TIMESTAMP_EPSILON && diff --git a/gnss/common/utils/default/include/NmeaFixInfo.h b/gnss/common/utils/default/include/NmeaFixInfo.h index 06eae7eda9..c96eecea00 100644 --- a/gnss/common/utils/default/include/NmeaFixInfo.h +++ b/gnss/common/utils/default/include/NmeaFixInfo.h @@ -32,6 +32,7 @@ constexpr char GPRMC_RECORD_TAG[] = "$GPRMC"; constexpr char LINE_SEPARATOR = '\n'; constexpr char COMMA_SEPARATOR = ','; constexpr double TIMESTAMP_EPSILON = 0.001; +constexpr int MIN_COL_NUM = 13; /** Helper class to parse and store the GNSS fix details information. */ class NmeaFixInfo { diff --git a/gnss/common/utils/default/include/v2_1/GnssTemplate.h b/gnss/common/utils/default/include/v2_1/GnssTemplate.h index 79c78c3f07..a6e8f58206 100644 --- a/gnss/common/utils/default/include/v2_1/GnssTemplate.h +++ b/gnss/common/utils/default/include/v2_1/GnssTemplate.h @@ -196,6 +196,7 @@ std::unique_ptr<V2_0::GnssLocation> GnssTemplate<T_IGnss>::getLocationFromHW() { return nullptr; } while (true) { + memset(inputBuffer, 0, INPUT_BUFFER_SIZE); bytes_read = read(mGnssFd, &inputBuffer, INPUT_BUFFER_SIZE); if (bytes_read <= 0) { break; @@ -218,9 +219,14 @@ Return<bool> GnssTemplate<T_IGnss>::start() { auto svStatus = filterBlocklistedSatellitesV2_1(Utils::getMockSvInfoListV2_1()); this->reportSvStatus(svStatus); auto currentLocation = getLocationFromHW(); - if (mGnssFd != -1 && currentLocation != nullptr) { + if (mGnssFd != -1) { // Only report location if the return from hardware is valid - this->reportLocation(*currentLocation); + // note that we can not merge these two "if" together, if didn't + // get location from hardware, we shouldn't report location, not + // report the "default" one. + if (currentLocation != nullptr) { + this->reportLocation(*currentLocation); + } } else { if (sGnssCallback_2_1 != nullptr || sGnssCallback_2_0 != nullptr) { const auto location = Utils::getMockLocationV2_0(); @@ -259,6 +265,7 @@ Return<bool> GnssTemplate<T_IGnss>::stop() { if (mGnssFd != -1) { close(mGnssFd); mGnssFd = -1; + mHardwareModeChecked = false; } return true; } diff --git a/graphics/composer/2.1/utils/hal/include/composer-hal/2.1/ComposerClient.h b/graphics/composer/2.1/utils/hal/include/composer-hal/2.1/ComposerClient.h index 3d74af4a41..46f5d6e1e0 100644 --- a/graphics/composer/2.1/utils/hal/include/composer-hal/2.1/ComposerClient.h +++ b/graphics/composer/2.1/utils/hal/include/composer-hal/2.1/ComposerClient.h @@ -98,7 +98,7 @@ class ComposerClientImpl : public Interface { // display change and thus the framework may want to reallocate buffers. We // need to free all cached handles, since they are holding a strong reference // to the underlying buffers. - cleanDisplayResources(display); + cleanDisplayResources(display, mResources, mHal); mResources->removeDisplay(display); } mResources->addPhysicalDisplay(display); @@ -125,56 +125,6 @@ class ComposerClientImpl : public Interface { Hal* const mHal; const sp<IComposerCallback> mCallback; ComposerResources* const mResources; - - void cleanDisplayResources(Display display) { - size_t cacheSize; - Error err = mResources->getDisplayClientTargetCacheSize(display, &cacheSize); - if (err == Error::NONE) { - for (int slot = 0; slot < cacheSize; slot++) { - ComposerResources::ReplacedHandle replacedBuffer(/*isBuffer*/ true); - // Replace the buffer slots with NULLs. Keep the old handle until it is - // replaced in ComposerHal, otherwise we risk leaving a dangling pointer. - const native_handle_t* clientTarget = nullptr; - err = mResources->getDisplayClientTarget(display, slot, /*useCache*/ true, - /*rawHandle*/ nullptr, &clientTarget, - &replacedBuffer); - if (err != Error::NONE) { - continue; - } - const std::vector<hwc_rect_t> damage; - err = mHal->setClientTarget(display, clientTarget, /*fence*/ -1, 0, damage); - ALOGE_IF(err != Error::NONE, - "Can't clean slot %d of the client target buffer" - "cache for display %" PRIu64, - slot, display); - } - } else { - ALOGE("Can't clean client target cache for display %" PRIu64, display); - } - - err = mResources->getDisplayOutputBufferCacheSize(display, &cacheSize); - if (err == Error::NONE) { - for (int slot = 0; slot < cacheSize; slot++) { - // Replace the buffer slots with NULLs. Keep the old handle until it is - // replaced in ComposerHal, otherwise we risk leaving a dangling pointer. - ComposerResources::ReplacedHandle replacedBuffer(/*isBuffer*/ true); - const native_handle_t* outputBuffer = nullptr; - err = mResources->getDisplayOutputBuffer(display, slot, /*useCache*/ true, - /*rawHandle*/ nullptr, &outputBuffer, - &replacedBuffer); - if (err != Error::NONE) { - continue; - } - err = mHal->setOutputBuffer(display, outputBuffer, /*fence*/ -1); - ALOGE_IF(err != Error::NONE, - "Can't clean slot %d of the output buffer cache" - "for display %" PRIu64, - slot, display); - } - } else { - ALOGE("Can't clean output buffer cache for display %" PRIu64, display); - } - } }; Return<void> registerCallback(const sp<IComposerCallback>& callback) override { @@ -380,6 +330,57 @@ class ComposerClientImpl : public Interface { return std::make_unique<ComposerCommandEngine>(mHal, mResources.get()); } + static void cleanDisplayResources(Display display, ComposerResources* const resources, + Hal* const hal) { + size_t cacheSize; + Error err = resources->getDisplayClientTargetCacheSize(display, &cacheSize); + if (err == Error::NONE) { + for (int slot = 0; slot < cacheSize; slot++) { + ComposerResources::ReplacedHandle replacedBuffer(/*isBuffer*/ true); + // Replace the buffer slots with NULLs. Keep the old handle until it is + // replaced in ComposerHal, otherwise we risk leaving a dangling pointer. + const native_handle_t* clientTarget = nullptr; + err = resources->getDisplayClientTarget(display, slot, /*useCache*/ true, + /*rawHandle*/ nullptr, &clientTarget, + &replacedBuffer); + if (err != Error::NONE) { + continue; + } + const std::vector<hwc_rect_t> damage; + err = hal->setClientTarget(display, clientTarget, /*fence*/ -1, 0, damage); + ALOGE_IF(err != Error::NONE, + "Can't clean slot %d of the client target buffer" + "cache for display %" PRIu64, + slot, display); + } + } else { + ALOGE("Can't clean client target cache for display %" PRIu64, display); + } + + err = resources->getDisplayOutputBufferCacheSize(display, &cacheSize); + if (err == Error::NONE) { + for (int slot = 0; slot < cacheSize; slot++) { + // Replace the buffer slots with NULLs. Keep the old handle until it is + // replaced in ComposerHal, otherwise we risk leaving a dangling pointer. + ComposerResources::ReplacedHandle replacedBuffer(/*isBuffer*/ true); + const native_handle_t* outputBuffer = nullptr; + err = resources->getDisplayOutputBuffer(display, slot, /*useCache*/ true, + /*rawHandle*/ nullptr, &outputBuffer, + &replacedBuffer); + if (err != Error::NONE) { + continue; + } + err = hal->setOutputBuffer(display, outputBuffer, /*fence*/ -1); + ALOGE_IF(err != Error::NONE, + "Can't clean slot %d of the output buffer cache" + "for display %" PRIu64, + slot, display); + } + } else { + ALOGE("Can't clean output buffer cache for display %" PRIu64, display); + } + } + void destroyResources() { // We want to call hwc2_close here (and move hwc2_open to the // constructor), with the assumption that hwc2_close would diff --git a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h index c889069143..3464342719 100644 --- a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h +++ b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h @@ -45,12 +45,21 @@ class ComposerClientImpl : public V2_3::hal::detail::ComposerClientImpl<Interfac class HalEventCallback : public Hal::EventCallback_2_4 { public: - HalEventCallback(const sp<IComposerCallback> callback, + HalEventCallback(Hal* hal, const sp<IComposerCallback> callback, V2_1::hal::ComposerResources* resources) - : mCallback(callback), mResources(resources) {} + : mHal(hal), mCallback(callback), mResources(resources) {} void onHotplug(Display display, IComposerCallback::Connection connected) override { if (connected == IComposerCallback::Connection::CONNECTED) { + if (mResources->hasDisplay(display)) { + // This is a subsequent hotplug "connected" for a display. This signals a + // display change and thus the framework may want to reallocate buffers. We + // need to free all cached handles, since they are holding a strong reference + // to the underlying buffers. + V2_1::hal::detail::ComposerClientImpl<Interface, Hal>::cleanDisplayResources( + display, mResources, mHal); + mResources->removeDisplay(display); + } mResources->addPhysicalDisplay(display); } else if (connected == IComposerCallback::Connection::DISCONNECTED) { mResources->removeDisplay(display); @@ -91,13 +100,15 @@ class ComposerClientImpl : public V2_3::hal::detail::ComposerClientImpl<Interfac } protected: + Hal* const mHal; const sp<IComposerCallback> mCallback; V2_1::hal::ComposerResources* const mResources; }; Return<void> registerCallback_2_4(const sp<IComposerCallback>& callback) override { // no locking as we require this function to be called only once - mHalEventCallback_2_4 = std::make_unique<HalEventCallback>(callback, mResources.get()); + mHalEventCallback_2_4 = + std::make_unique<HalEventCallback>(mHal, callback, mResources.get()); mHal->registerEventCallback_2_4(mHalEventCallback_2_4.get()); return Void(); } diff --git a/identity/TEST_MAPPING b/identity/TEST_MAPPING new file mode 100644 index 0000000000..f35f4b7ef9 --- /dev/null +++ b/identity/TEST_MAPPING @@ -0,0 +1,13 @@ +{ + "presubmit": [ + { + "name": "CtsIdentityTestCases" + }, + { + "name": "VtsHalIdentityTargetTest" + }, + { + "name": "android.hardware.identity-support-lib-test" + } + ] +} diff --git a/light/2.0/default/Light.cpp b/light/2.0/default/Light.cpp index 5484d2db43..3febf6b183 100644 --- a/light/2.0/default/Light.cpp +++ b/light/2.0/default/Light.cpp @@ -140,7 +140,7 @@ light_device_t* getLightDevice(const char* name) { ret = hwModule->methods->open(hwModule, name, reinterpret_cast<hw_device_t**>(&lightDevice)); if (ret != 0) { - ALOGE("light_open %s %s failed: %d", LIGHTS_HARDWARE_MODULE_ID, name, ret); + ALOGI("light_open %s %s failed: %d", LIGHTS_HARDWARE_MODULE_ID, name, ret); } } else { ALOGE("hw_get_module %s %s failed: %d", LIGHTS_HARDWARE_MODULE_ID, name, ret); 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 832930359e..7849ca7a46 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 @@ -44,7 +44,9 @@ class Burst final : public nn::IBurst { 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; + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) const override; private: const nn::SharedPreparedModel kPreparedModel; 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 b695f48550..1baabdf562 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 @@ -22,10 +22,15 @@ #include <android-base/logging.h> #include <android/hardware/neuralnetworks/1.0/types.h> #include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> #include <nnapi/Types.h> +#include <nnapi/Validation.h> +#include <nnapi/hal/HandleError.h> namespace android::hardware::neuralnetworks::V1_0::utils { +constexpr auto kVersion = nn::Version::ANDROID_OC_MR1; + template <typename Type> nn::Result<void> validate(const Type& halObject) { const auto maybeCanonical = nn::convert(halObject); @@ -45,6 +50,15 @@ bool valid(const Type& halObject) { } template <typename Type> +nn::GeneralResult<void> compliantVersion(const Type& canonical) { + const auto version = NN_TRY(hal::utils::makeGeneralFailure(nn::validate(canonical))); + if (version > kVersion) { + return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion; + } + return {}; +} + +template <typename Type> auto convertFromNonCanonical(const Type& nonCanonicalObject) -> decltype(convert(nn::convert(nonCanonicalObject).value())) { return convert(NN_TRY(nn::convert(nonCanonicalObject))); diff --git a/neuralnetworks/1.0/utils/src/Burst.cpp b/neuralnetworks/1.0/utils/src/Burst.cpp index 971ad08015..e3a97579cd 100644 --- a/neuralnetworks/1.0/utils/src/Burst.cpp +++ b/neuralnetworks/1.0/utils/src/Burst.cpp @@ -20,6 +20,7 @@ #include <nnapi/IBurst.h> #include <nnapi/IPreparedModel.h> #include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> #include <nnapi/Types.h> #include <memory> @@ -48,8 +49,10 @@ Burst::OptionalCacheHold Burst::cacheMemory(const nn::SharedMemory& /*memory*/) } nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> Burst::execute( - const nn::Request& request, nn::MeasureTiming measure) const { - return kPreparedModel->execute(request, measure, {}, {}); + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) const { + return kPreparedModel->execute(request, measure, deadline, loopTimeoutDuration); } } // namespace android::hardware::neuralnetworks::V1_0::utils diff --git a/neuralnetworks/1.0/utils/src/Conversions.cpp b/neuralnetworks/1.0/utils/src/Conversions.cpp index 700b050ce7..c0498eb876 100644 --- a/neuralnetworks/1.0/utils/src/Conversions.cpp +++ b/neuralnetworks/1.0/utils/src/Conversions.cpp @@ -35,6 +35,8 @@ #include <utility> #include <variant> +#include "Utils.h" + namespace { template <typename Type> @@ -42,8 +44,6 @@ 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_OC_MR1; - } // namespace namespace android::nn { @@ -53,13 +53,13 @@ using hardware::hidl_memory; using hardware::hidl_vec; template <typename Input> -using unvalidatedConvertOutput = +using UnvalidatedConvertOutput = std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; template <typename Type> -GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvert( +GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( const hidl_vec<Type>& arguments) { - std::vector<unvalidatedConvertOutput<Type>> canonical; + std::vector<UnvalidatedConvertOutput<Type>> canonical; canonical.reserve(arguments.size()); for (const auto& argument : arguments) { canonical.push_back(NN_TRY(nn::unvalidatedConvert(argument))); @@ -68,16 +68,9 @@ GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvert( } template <typename Type> -decltype(nn::unvalidatedConvert(std::declval<Type>())) validatedConvert(const Type& halObject) { +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; - } + NN_TRY(hal::V1_0::utils::compliantVersion(canonical)); return canonical; } @@ -248,13 +241,13 @@ namespace android::hardware::neuralnetworks::V1_0::utils { namespace { template <typename Input> -using unvalidatedConvertOutput = +using UnvalidatedConvertOutput = std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; template <typename Type> -nn::GeneralResult<hidl_vec<unvalidatedConvertOutput<Type>>> unvalidatedConvert( +nn::GeneralResult<hidl_vec<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( const std::vector<Type>& arguments) { - hidl_vec<unvalidatedConvertOutput<Type>> halObject(arguments.size()); + hidl_vec<UnvalidatedConvertOutput<Type>> halObject(arguments.size()); for (size_t i = 0; i < arguments.size(); ++i) { halObject[i] = NN_TRY(utils::unvalidatedConvert(arguments[i])); } @@ -262,15 +255,8 @@ nn::GeneralResult<hidl_vec<unvalidatedConvertOutput<Type>>> unvalidatedConvert( } template <typename Type> -decltype(utils::unvalidatedConvert(std::declval<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; - } +nn::GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& canonical) { + NN_TRY(compliantVersion(canonical)); return utils::unvalidatedConvert(canonical); } diff --git a/neuralnetworks/1.0/utils/test/MockDevice.h b/neuralnetworks/1.0/utils/test/MockDevice.h index 0fb59e3a63..7c399ec580 100644 --- a/neuralnetworks/1.0/utils/test/MockDevice.h +++ b/neuralnetworks/1.0/utils/test/MockDevice.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE_H #include <android/hardware/neuralnetworks/1.0/IDevice.h> #include <gmock/gmock.h> @@ -83,4 +83,4 @@ inline void MockDevice::simulateCrash() { } // namespace android::hardware::neuralnetworks::V1_0::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE_H diff --git a/neuralnetworks/1.0/utils/test/MockPreparedModel.h b/neuralnetworks/1.0/utils/test/MockPreparedModel.h index 7a48a834ac..03f1a4bea3 100644 --- a/neuralnetworks/1.0/utils/test/MockPreparedModel.h +++ b/neuralnetworks/1.0/utils/test/MockPreparedModel.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL_H #include <android/hardware/neuralnetworks/1.0/IPreparedModel.h> #include <gmock/gmock.h> @@ -82,4 +82,4 @@ inline void MockPreparedModel::simulateCrash() { } // namespace android::hardware::neuralnetworks::V1_0::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL_H diff --git a/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp index a5cbc72a71..f19ed7756e 100644 --- a/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp +++ b/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp @@ -224,7 +224,19 @@ TEST(PreparedModelTest, executeFencedNotSupported) { EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); } -// TODO: test burst execution if/when it is added to nn::IPreparedModel. +TEST(PreparedModelTest, configureExecutionBurst) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} TEST(PreparedModelTest, getUnderlyingResource) { // setup test 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 09597a31f8..a8cf8cf7b9 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 @@ -22,12 +22,16 @@ #include <android-base/logging.h> #include <android/hardware/neuralnetworks/1.1/types.h> #include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> #include <nnapi/Types.h> +#include <nnapi/Validation.h> #include <nnapi/hal/1.0/Conversions.h> +#include <nnapi/hal/HandleError.h> namespace android::hardware::neuralnetworks::V1_1::utils { constexpr auto kDefaultExecutionPreference = ExecutionPreference::FAST_SINGLE_ANSWER; +constexpr auto kVersion = nn::Version::ANDROID_P; template <typename Type> nn::Result<void> validate(const Type& halObject) { @@ -48,6 +52,15 @@ bool valid(const Type& halObject) { } template <typename Type> +nn::GeneralResult<void> compliantVersion(const Type& canonical) { + const auto version = NN_TRY(hal::utils::makeGeneralFailure(nn::validate(canonical))); + if (version > kVersion) { + return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion; + } + return {}; +} + +template <typename Type> auto convertFromNonCanonical(const Type& nonCanonicalObject) -> decltype(convert(nn::convert(nonCanonicalObject).value())) { return convert(NN_TRY(nn::convert(nonCanonicalObject))); diff --git a/neuralnetworks/1.1/utils/src/Conversions.cpp b/neuralnetworks/1.1/utils/src/Conversions.cpp index d07f7d00bc..467ceb389b 100644 --- a/neuralnetworks/1.1/utils/src/Conversions.cpp +++ b/neuralnetworks/1.1/utils/src/Conversions.cpp @@ -35,11 +35,7 @@ #include <type_traits> #include <utility> -namespace { - -constexpr auto kVersion = android::nn::Version::ANDROID_P; - -} // namespace +#include "Utils.h" namespace android::nn { namespace { @@ -47,13 +43,13 @@ namespace { using hardware::hidl_vec; template <typename Input> -using unvalidatedConvertOutput = +using UnvalidatedConvertOutput = std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; template <typename Type> -GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvert( +GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( const hidl_vec<Type>& arguments) { - std::vector<unvalidatedConvertOutput<Type>> canonical; + std::vector<UnvalidatedConvertOutput<Type>> canonical; canonical.reserve(arguments.size()); for (const auto& argument : arguments) { canonical.push_back(NN_TRY(nn::unvalidatedConvert(argument))); @@ -62,16 +58,9 @@ GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvert( } template <typename Type> -decltype(nn::unvalidatedConvert(std::declval<Type>())) validatedConvert(const Type& halObject) { +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; - } + NN_TRY(hal::V1_1::utils::compliantVersion(canonical)); return canonical; } @@ -180,13 +169,13 @@ nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory } template <typename Input> -using unvalidatedConvertOutput = +using UnvalidatedConvertOutput = std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; template <typename Type> -nn::GeneralResult<hidl_vec<unvalidatedConvertOutput<Type>>> unvalidatedConvert( +nn::GeneralResult<hidl_vec<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( const std::vector<Type>& arguments) { - hidl_vec<unvalidatedConvertOutput<Type>> halObject(arguments.size()); + hidl_vec<UnvalidatedConvertOutput<Type>> halObject(arguments.size()); for (size_t i = 0; i < arguments.size(); ++i) { halObject[i] = NN_TRY(unvalidatedConvert(arguments[i])); } @@ -194,16 +183,9 @@ nn::GeneralResult<hidl_vec<unvalidatedConvertOutput<Type>>> unvalidatedConvert( } template <typename Type> -decltype(utils::unvalidatedConvert(std::declval<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); +nn::GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& canonical) { + NN_TRY(compliantVersion(canonical)); + return unvalidatedConvert(canonical); } } // anonymous namespace diff --git a/neuralnetworks/1.1/utils/test/MockDevice.h b/neuralnetworks/1.1/utils/test/MockDevice.h index 3b92e58102..db7392d2cb 100644 --- a/neuralnetworks/1.1/utils/test/MockDevice.h +++ b/neuralnetworks/1.1/utils/test/MockDevice.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE_H #include <android/hardware/neuralnetworks/1.1/IDevice.h> #include <gmock/gmock.h> @@ -92,4 +92,4 @@ inline void MockDevice::simulateCrash() { } // namespace android::hardware::neuralnetworks::V1_1::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE_H diff --git a/neuralnetworks/1.1/utils/test/MockPreparedModel.h b/neuralnetworks/1.1/utils/test/MockPreparedModel.h index aba731efa1..257397da2c 100644 --- a/neuralnetworks/1.1/utils/test/MockPreparedModel.h +++ b/neuralnetworks/1.1/utils/test/MockPreparedModel.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL_H #include <android/hardware/neuralnetworks/1.0/IPreparedModel.h> #include <gmock/gmock.h> @@ -41,4 +41,4 @@ inline sp<MockPreparedModel> MockPreparedModel::create() { } // namespace android::hardware::neuralnetworks::V1_0::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL_H diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h index 6b6fc71f65..9669d8c031 100644 --- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h +++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h @@ -57,7 +57,8 @@ class ExecutionBurstController final : public nn::IBurst { public: using FallbackFunction = std::function<nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>( - const nn::Request&, nn::MeasureTiming)>; + const nn::Request&, nn::MeasureTiming, const nn::OptionalTimePoint&, + const nn::OptionalDuration&)>; /** * NN runtime memory cache. @@ -168,7 +169,9 @@ class ExecutionBurstController final : public nn::IBurst { // See IBurst::execute for information on this method. nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( - const nn::Request& request, nn::MeasureTiming measure) const override; + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) const override; private: mutable std::atomic_flag mExecutionInFlight = ATOMIC_FLAG_INIT; 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 323311439f..09691b65ee 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 @@ -22,19 +22,25 @@ #include <android-base/logging.h> #include <android/hardware/neuralnetworks/1.2/types.h> #include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> #include <nnapi/Types.h> +#include <nnapi/Validation.h> #include <nnapi/hal/1.0/Conversions.h> #include <nnapi/hal/1.1/Conversions.h> +#include <nnapi/hal/1.1/Utils.h> +#include <nnapi/hal/HandleError.h> #include <limits> namespace android::hardware::neuralnetworks::V1_2::utils { using CacheToken = hidl_array<uint8_t, static_cast<size_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>; +using V1_1::utils::kDefaultExecutionPreference; constexpr auto kDefaultMesaureTiming = MeasureTiming::NO; constexpr auto kNoTiming = Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(), .timeInDriver = std::numeric_limits<uint64_t>::max()}; +constexpr auto kVersion = nn::Version::ANDROID_Q; template <typename Type> nn::Result<void> validate(const Type& halObject) { @@ -55,6 +61,15 @@ bool valid(const Type& halObject) { } template <typename Type> +nn::GeneralResult<void> compliantVersion(const Type& canonical) { + const auto version = NN_TRY(hal::utils::makeGeneralFailure(nn::validate(canonical))); + if (version > kVersion) { + return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion; + } + return {}; +} + +template <typename Type> auto convertFromNonCanonical(const Type& nonCanonicalObject) -> decltype(convert(nn::convert(nonCanonicalObject).value())) { return convert(NN_TRY(nn::convert(nonCanonicalObject))); diff --git a/neuralnetworks/1.2/utils/src/Conversions.cpp b/neuralnetworks/1.2/utils/src/Conversions.cpp index 2c45583d0c..29945b75e5 100644 --- a/neuralnetworks/1.2/utils/src/Conversions.cpp +++ b/neuralnetworks/1.2/utils/src/Conversions.cpp @@ -37,6 +37,8 @@ #include <type_traits> #include <utility> +#include "Utils.h" + namespace { template <typename Type> @@ -45,50 +47,23 @@ constexpr std::underlying_type_t<Type> underlyingType(Type value) { } using HalDuration = std::chrono::duration<uint64_t, std::micro>; -constexpr auto kVersion = android::nn::Version::ANDROID_Q; -constexpr uint64_t kNoTiming = std::numeric_limits<uint64_t>::max(); } // namespace namespace android::nn { namespace { -constexpr bool validOperandType(OperandType operandType) { - switch (operandType) { - case OperandType::FLOAT32: - case OperandType::INT32: - case OperandType::UINT32: - case OperandType::TENSOR_FLOAT32: - case OperandType::TENSOR_INT32: - case OperandType::TENSOR_QUANT8_ASYMM: - case OperandType::BOOL: - case OperandType::TENSOR_QUANT16_SYMM: - case OperandType::TENSOR_FLOAT16: - case OperandType::TENSOR_BOOL8: - case OperandType::FLOAT16: - case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: - case OperandType::TENSOR_QUANT16_ASYMM: - case OperandType::TENSOR_QUANT8_SYMM: - case OperandType::OEM: - case OperandType::TENSOR_OEM_BYTE: - return true; - default: - break; - } - return isExtension(operandType); -} - using hardware::hidl_handle; using hardware::hidl_vec; template <typename Input> -using unvalidatedConvertOutput = +using UnvalidatedConvertOutput = std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; template <typename Type> -GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvertVec( +GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( const hidl_vec<Type>& arguments) { - std::vector<unvalidatedConvertOutput<Type>> canonical; + std::vector<UnvalidatedConvertOutput<Type>> canonical; canonical.reserve(arguments.size()); for (const auto& argument : arguments) { canonical.push_back(NN_TRY(nn::unvalidatedConvert(argument))); @@ -97,29 +72,16 @@ GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvertVec } template <typename Type> -GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvert( - const hidl_vec<Type>& arguments) { - return unvalidatedConvertVec(arguments); -} - -template <typename Type> -decltype(nn::unvalidatedConvert(std::declval<Type>())) validatedConvert(const Type& halObject) { +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; - } + NN_TRY(hal::V1_2::utils::compliantVersion(canonical)); return canonical; } template <typename Type> -GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> validatedConvert( +GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> validatedConvert( const hidl_vec<Type>& arguments) { - std::vector<unvalidatedConvertOutput<Type>> canonical; + std::vector<UnvalidatedConvertOutput<Type>> canonical; canonical.reserve(arguments.size()); for (const auto& argument : arguments) { canonical.push_back(NN_TRY(validatedConvert(argument))); @@ -145,8 +107,7 @@ GeneralResult<Capabilities> unvalidatedConvert(const hal::V1_2::Capabilities& ca const bool validOperandTypes = std::all_of( capabilities.operandPerformance.begin(), capabilities.operandPerformance.end(), [](const hal::V1_2::Capabilities::OperandPerformance& operandPerformance) { - const auto maybeType = unvalidatedConvert(operandPerformance.type); - return !maybeType.has_value() ? false : validOperandType(maybeType.value()); + return validatedConvert(operandPerformance.type).has_value(); }); if (!validOperandTypes) { return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) @@ -275,6 +236,7 @@ GeneralResult<MeasureTiming> unvalidatedConvert(const hal::V1_2::MeasureTiming& GeneralResult<Timing> unvalidatedConvert(const hal::V1_2::Timing& timing) { constexpr uint64_t kMaxTiming = std::chrono::floor<HalDuration>(Duration::max()).count(); constexpr auto convertTiming = [](uint64_t halTiming) -> OptionalDuration { + constexpr uint64_t kNoTiming = std::numeric_limits<uint64_t>::max(); if (halTiming == kNoTiming) { return {}; } @@ -378,25 +340,19 @@ nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory } template <typename Input> -using unvalidatedConvertOutput = +using UnvalidatedConvertOutput = std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; template <typename Type> -nn::GeneralResult<hidl_vec<unvalidatedConvertOutput<Type>>> unvalidatedConvertVec( +nn::GeneralResult<hidl_vec<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( const std::vector<Type>& arguments) { - hidl_vec<unvalidatedConvertOutput<Type>> halObject(arguments.size()); + hidl_vec<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<hidl_vec<unvalidatedConvertOutput<Type>>> unvalidatedConvert( - const std::vector<Type>& arguments) { - return unvalidatedConvertVec(arguments); -} - nn::GeneralResult<Operand::ExtraParams> makeExtraParams(nn::Operand::NoParams /*noParams*/) { return Operand::ExtraParams{}; } @@ -416,22 +372,15 @@ nn::GeneralResult<Operand::ExtraParams> makeExtraParams( } template <typename Type> -decltype(utils::unvalidatedConvert(std::declval<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); +nn::GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& canonical) { + NN_TRY(compliantVersion(canonical)); + return unvalidatedConvert(canonical); } template <typename Type> -nn::GeneralResult<hidl_vec<unvalidatedConvertOutput<Type>>> validatedConvert( +nn::GeneralResult<hidl_vec<UnvalidatedConvertOutput<Type>>> validatedConvert( const std::vector<Type>& arguments) { - hidl_vec<unvalidatedConvertOutput<Type>> halObject(arguments.size()); + hidl_vec<UnvalidatedConvertOutput<Type>> halObject(arguments.size()); for (size_t i = 0; i < arguments.size(); ++i) { halObject[i] = NN_TRY(validatedConvert(arguments[i])); } @@ -469,7 +418,7 @@ nn::GeneralResult<Capabilities> unvalidatedConvert(const nn::Capabilities& capab capabilities.operandPerformance.asVector().end(), std::back_inserter(operandPerformance), [](const nn::Capabilities::OperandPerformance& operandPerformance) { - return nn::validOperandType(operandPerformance.type); + return compliantVersion(operandPerformance.type).has_value(); }); return Capabilities{ @@ -570,6 +519,7 @@ nn::GeneralResult<MeasureTiming> unvalidatedConvert(const nn::MeasureTiming& mea nn::GeneralResult<Timing> unvalidatedConvert(const nn::Timing& timing) { constexpr auto convertTiming = [](nn::OptionalDuration canonicalTiming) -> uint64_t { + constexpr uint64_t kNoTiming = std::numeric_limits<uint64_t>::max(); if (!canonicalTiming.has_value()) { return kNoTiming; } diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp index eedf5916bc..7a17f257c2 100644 --- a/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp +++ b/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp @@ -276,7 +276,9 @@ ExecutionBurstController::OptionalCacheHold ExecutionBurstController::cacheMemor } nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> -ExecutionBurstController::execute(const nn::Request& request, nn::MeasureTiming measure) const { +ExecutionBurstController::execute(const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) const { // This is the first point when we know an execution is occurring, so begin to collect // systraces. Note that the first point we can begin collecting systraces in // ExecutionBurstServer is when the RequestChannelReceiver realizes there is data in the FMQ, so @@ -289,7 +291,7 @@ ExecutionBurstController::execute(const nn::Request& request, nn::MeasureTiming version > nn::Version::ANDROID_Q) { // fallback to another execution path if the packet could not be sent if (kFallback) { - return kFallback(request, measure); + return kFallback(request, measure, deadline, loopTimeoutDuration); } return NN_ERROR() << "Request object has features not supported by IBurst::execute"; } @@ -323,7 +325,7 @@ ExecutionBurstController::execute(const nn::Request& request, nn::MeasureTiming if (!sendStatus.ok()) { // fallback to another execution path if the packet could not be sent if (kFallback) { - return kFallback(request, measure); + return kFallback(request, measure, deadline, loopTimeoutDuration); } return NN_ERROR() << "Error sending FMQ packet: " << sendStatus.error(); } diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp index 50af881d23..c67159e895 100644 --- a/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp +++ b/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp @@ -259,7 +259,7 @@ nn::ExecutionResult<std::pair<hidl_vec<OutputShape>, Timing>> ExecutionBurstServ nn::MeasureTiming canonicalMeasure = NN_TRY(makeExecutionFailure(nn::convert(measure))); const auto [outputShapes, timing] = - NN_TRY(mBurstExecutor->execute(canonicalRequest, canonicalMeasure)); + NN_TRY(mBurstExecutor->execute(canonicalRequest, canonicalMeasure, {}, {})); return std::make_pair(NN_TRY(makeExecutionFailure(convert(outputShapes))), NN_TRY(makeExecutionFailure(convert(timing)))); diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp index ca3a52c17b..1bdde1e71a 100644 --- a/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp +++ b/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp @@ -520,6 +520,8 @@ nn::Result<std::vector<FmqRequestDatum>> RequestChannelReceiver::getPacketBlocki } return packet; } + + std::this_thread::yield(); } // If we get to this point, we either stopped polling because it was taking too long or polling @@ -665,6 +667,8 @@ nn::Result<std::vector<FmqResultDatum>> ResultChannelReceiver::getPacketBlocking } return packet; } + + std::this_thread::yield(); } // If we get to this point, we either stopped polling because it was taking too long or polling diff --git a/neuralnetworks/1.2/utils/src/PreparedModel.cpp b/neuralnetworks/1.2/utils/src/PreparedModel.cpp index 71a4ea872b..b209a44eba 100644 --- a/neuralnetworks/1.2/utils/src/PreparedModel.cpp +++ b/neuralnetworks/1.2/utils/src/PreparedModel.cpp @@ -122,10 +122,12 @@ PreparedModel::executeFenced(const nn::Request& /*request*/, nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const { auto self = shared_from_this(); - auto fallback = [preparedModel = std::move(self)](const nn::Request& request, - nn::MeasureTiming measure) + auto fallback = [preparedModel = std::move(self)]( + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) -> nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> { - return preparedModel->execute(request, measure, {}, {}); + return preparedModel->execute(request, measure, deadline, loopTimeoutDuration); }; const auto pollingTimeWindow = getBurstControllerPollingTimeWindow(); return ExecutionBurstController::create(kPreparedModel, std::move(fallback), pollingTimeWindow); diff --git a/neuralnetworks/1.2/utils/test/MockBurstContext.h b/neuralnetworks/1.2/utils/test/MockBurstContext.h new file mode 100644 index 0000000000..e364178ce4 --- /dev/null +++ b/neuralnetworks/1.2/utils/test/MockBurstContext.h @@ -0,0 +1,36 @@ +/* + * 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_1_2_UTILS_TEST_MOCK_BURST_CONTEXT_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_BURST_CONTEXT_H + +#include <android/hardware/neuralnetworks/1.2/IBurstContext.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_2::utils { + +class MockBurstContext final : public IBurstContext { + public: + // V1_2 methods below. + MOCK_METHOD(Return<void>, freeMemory, (int32_t slot), (override)); +}; + +} // namespace android::hardware::neuralnetworks::V1_2::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_BURST_CONTEXT_H diff --git a/neuralnetworks/1.2/utils/test/MockDevice.h b/neuralnetworks/1.2/utils/test/MockDevice.h index b4599430a2..0d34c70f1b 100644 --- a/neuralnetworks/1.2/utils/test/MockDevice.h +++ b/neuralnetworks/1.2/utils/test/MockDevice.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE_H #include <android/hardware/neuralnetworks/1.2/IDevice.h> #include <gmock/gmock.h> @@ -114,4 +114,4 @@ inline void MockDevice::simulateCrash() { } // namespace android::hardware::neuralnetworks::V1_2::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE_H diff --git a/neuralnetworks/1.2/utils/test/MockPreparedModel.h b/neuralnetworks/1.2/utils/test/MockPreparedModel.h index f5fd1f3204..bd81712731 100644 --- a/neuralnetworks/1.2/utils/test/MockPreparedModel.h +++ b/neuralnetworks/1.2/utils/test/MockPreparedModel.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL_H #include <android/hardware/neuralnetworks/1.2/IPreparedModel.h> #include <gmock/gmock.h> @@ -98,4 +98,4 @@ inline void MockPreparedModel::simulateCrash() { } // namespace android::hardware::neuralnetworks::V1_2::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL_H diff --git a/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp index 5062ac9a97..d297b1a417 100644 --- a/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp +++ b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp @@ -16,6 +16,8 @@ #include "MockPreparedModel.h" +#include "MockBurstContext.h" + #include <android/hardware/neuralnetworks/1.2/IExecutionCallback.h> #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -67,6 +69,17 @@ auto makeExecuteAsynchronously(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus return launchStatus; }; } +auto makeConfigureExecutionBurstReturn(V1_0::ErrorStatus status, + const sp<MockBurstContext>& burstContext) { + return [status, burstContext]( + const sp<V1_2::IBurstCallback>& /*callback*/, + const MQDescriptorSync<V1_2::FmqRequestDatum>& /*requestChannel*/, + const MQDescriptorSync<V1_2::FmqResultDatum>& /*resultChannel*/, + V1_2::IPreparedModel::configureExecutionBurst_cb cb) -> hardware::Return<void> { + cb(status, burstContext); + return hardware::Void(); + }; +} std::function<hardware::Status()> makeTransportFailure(status_t status) { return [status] { return hardware::Status::fromStatusT(status); }; @@ -321,7 +334,76 @@ TEST(PreparedModelTest, executeFencedNotSupported) { EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); } -// TODO: test burst execution if/when it is added to nn::IPreparedModel. +TEST(PreparedModelTest, configureExecutionBurst) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto mockBurstContext = sp<MockBurstContext>::make(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_, _, _, _)) + .Times(1) + .WillOnce(makeConfigureExecutionBurstReturn(V1_0::ErrorStatus::NONE, mockBurstContext)); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(PreparedModelTest, configureExecutionBurstError) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_, _, _, _)) + .Times(1) + .WillOnce( + makeConfigureExecutionBurstReturn(V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, configureExecutionBurstTransportFailure) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, configureExecutionBurstDeadObject) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} TEST(PreparedModelTest, getUnderlyingResource) { // setup test diff --git a/neuralnetworks/1.3/IDevice.hal b/neuralnetworks/1.3/IDevice.hal index e0b04a8b62..de889e4b25 100644 --- a/neuralnetworks/1.3/IDevice.hal +++ b/neuralnetworks/1.3/IDevice.hal @@ -131,6 +131,14 @@ interface IDevice extends @1.2::IDevice { * 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 deadline is represented as nanoseconds since the epoch of the steady + * clock (as if from std::chrono::steady_clock::time_point), but the service + * may convert it to the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * Optionally, the driver may save the prepared model to cache during the * asynchronous preparation. Any error that occurs when saving to cache must @@ -249,7 +257,15 @@ interface IDevice extends @1.2::IDevice { * 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. + * described above. The deadline is represented as nanoseconds since the + * epoch of the steady clock (as if from + * std::chrono::steady_clock::time_point), but the service may convert it to + * the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * 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 diff --git a/neuralnetworks/1.3/IPreparedModel.hal b/neuralnetworks/1.3/IPreparedModel.hal index e7d63f4851..8b86a1a20e 100644 --- a/neuralnetworks/1.3/IPreparedModel.hal +++ b/neuralnetworks/1.3/IPreparedModel.hal @@ -74,6 +74,14 @@ interface IPreparedModel extends @1.2::IPreparedModel { * 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 deadline is represented as nanoseconds since the epoch of the steady + * clock (as if from std::chrono::steady_clock::time_point), but the service + * may convert it to the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * Any number of calls to the execute* and executeSynchronously* functions, * in any combination, may be made concurrently, even on the same @@ -150,6 +158,14 @@ interface IPreparedModel extends @1.2::IPreparedModel { * 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 deadline is represented as nanoseconds since the epoch of the steady + * clock (as if from std::chrono::steady_clock::time_point), but the service + * may convert it to the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * Any number of calls to the execute* and executeSynchronously* functions, * in any combination, may be made concurrently, even on the same @@ -231,6 +247,14 @@ interface IPreparedModel extends @1.2::IPreparedModel { * {@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 deadline is represented as nanoseconds since the epoch of the steady + * clock (as if from std::chrono::steady_clock::time_point), but the service + * may convert it to the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * 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 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 3ce412cde6..1d76caaba6 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 @@ -22,14 +22,25 @@ #include <android-base/logging.h> #include <android/hardware/neuralnetworks/1.3/types.h> #include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> #include <nnapi/Types.h> +#include <nnapi/Validation.h> #include <nnapi/hal/1.0/Conversions.h> #include <nnapi/hal/1.1/Conversions.h> +#include <nnapi/hal/1.1/Utils.h> #include <nnapi/hal/1.2/Conversions.h> +#include <nnapi/hal/1.2/Utils.h> +#include <nnapi/hal/HandleError.h> namespace android::hardware::neuralnetworks::V1_3::utils { +using V1_1::utils::kDefaultExecutionPreference; +using V1_2::utils::CacheToken; +using V1_2::utils::kDefaultMesaureTiming; +using V1_2::utils::kNoTiming; + constexpr auto kDefaultPriority = Priority::MEDIUM; +constexpr auto kVersion = nn::Version::ANDROID_R; template <typename Type> nn::Result<void> validate(const Type& halObject) { @@ -50,6 +61,15 @@ bool valid(const Type& halObject) { } template <typename Type> +nn::GeneralResult<void> compliantVersion(const Type& canonical) { + const auto version = NN_TRY(hal::utils::makeGeneralFailure(nn::validate(canonical))); + if (version > kVersion) { + return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion; + } + return {}; +} + +template <typename Type> auto convertFromNonCanonical(const Type& nonCanonicalObject) -> decltype(convert(nn::convert(nonCanonicalObject).value())) { return convert(NN_TRY(nn::convert(nonCanonicalObject))); diff --git a/neuralnetworks/1.3/utils/src/Conversions.cpp b/neuralnetworks/1.3/utils/src/Conversions.cpp index 8b45f715ab..e8a4f55afd 100644 --- a/neuralnetworks/1.3/utils/src/Conversions.cpp +++ b/neuralnetworks/1.3/utils/src/Conversions.cpp @@ -38,55 +38,47 @@ #include <type_traits> #include <utility> +#include "Utils.h" + namespace { +std::chrono::nanoseconds makeNanosFromUint64(uint64_t nanoseconds) { + constexpr auto kMaxCount = std::chrono::nanoseconds::max().count(); + using CommonType = std::common_type_t<std::chrono::nanoseconds::rep, uint64_t>; + const auto count = std::min<CommonType>(kMaxCount, nanoseconds); + return std::chrono::nanoseconds{static_cast<std::chrono::nanoseconds::rep>(count)}; +} + +uint64_t makeUint64FromNanos(std::chrono::nanoseconds nanoseconds) { + if (nanoseconds < std::chrono::nanoseconds::zero()) { + return 0; + } + constexpr auto kMaxCount = std::numeric_limits<uint64_t>::max(); + using CommonType = std::common_type_t<std::chrono::nanoseconds::rep, uint64_t>; + const auto count = std::min<CommonType>(kMaxCount, nanoseconds.count()); + return static_cast<uint64_t>(count); +} + 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_R; - } // namespace namespace android::nn { namespace { -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: - case nn::OperandType::OEM: - case nn::OperandType::TENSOR_OEM_BYTE: - return true; - } - return nn::isExtension(operandType); -} - using hardware::hidl_vec; template <typename Input> -using unvalidatedConvertOutput = +using UnvalidatedConvertOutput = std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; template <typename Type> -GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvertVec( +GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( const hidl_vec<Type>& arguments) { - std::vector<unvalidatedConvertOutput<Type>> canonical; + std::vector<UnvalidatedConvertOutput<Type>> canonical; canonical.reserve(arguments.size()); for (const auto& argument : arguments) { canonical.push_back(NN_TRY(nn::unvalidatedConvert(argument))); @@ -95,29 +87,16 @@ GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvertVec } template <typename Type> -GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> unvalidatedConvert( - const hidl_vec<Type>& arguments) { - return unvalidatedConvertVec(arguments); -} - -template <typename Type> -decltype(nn::unvalidatedConvert(std::declval<Type>())) validatedConvert(const Type& halObject) { +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; - } + NN_TRY(hal::V1_3::utils::compliantVersion(canonical)); return canonical; } template <typename Type> -GeneralResult<std::vector<unvalidatedConvertOutput<Type>>> validatedConvert( +GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> validatedConvert( const hidl_vec<Type>& arguments) { - std::vector<unvalidatedConvertOutput<Type>> canonical; + std::vector<UnvalidatedConvertOutput<Type>> canonical; canonical.reserve(arguments.size()); for (const auto& argument : arguments) { canonical.push_back(NN_TRY(validatedConvert(argument))); @@ -143,8 +122,7 @@ GeneralResult<Capabilities> unvalidatedConvert(const hal::V1_3::Capabilities& ca const bool validOperandTypes = std::all_of( capabilities.operandPerformance.begin(), capabilities.operandPerformance.end(), [](const hal::V1_3::Capabilities::OperandPerformance& operandPerformance) { - const auto maybeType = unvalidatedConvert(operandPerformance.type); - return !maybeType.has_value() ? false : validOperandType(maybeType.value()); + return validatedConvert(operandPerformance.type).has_value(); }); if (!validOperandTypes) { return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) @@ -276,8 +254,32 @@ GeneralResult<OptionalTimePoint> unvalidatedConvert( switch (optionalTimePoint.getDiscriminator()) { case Discriminator::none: return {}; - case Discriminator::nanosecondsSinceEpoch: - return TimePoint{Duration{optionalTimePoint.nanosecondsSinceEpoch()}}; + case Discriminator::nanosecondsSinceEpoch: { + const auto currentSteadyTime = std::chrono::steady_clock::now(); + const auto currentBootTime = Clock::now(); + + const auto timeSinceEpoch = + makeNanosFromUint64(optionalTimePoint.nanosecondsSinceEpoch()); + const auto steadyTimePoint = std::chrono::steady_clock::time_point{timeSinceEpoch}; + + // Both steadyTimePoint and currentSteadyTime are guaranteed to be non-negative, so this + // subtraction will never overflow or underflow. + const auto timeRemaining = steadyTimePoint - currentSteadyTime; + + // currentBootTime is guaranteed to be non-negative, so this code only protects against + // an overflow. + nn::TimePoint bootTimePoint; + constexpr auto kZeroNano = std::chrono::nanoseconds::zero(); + constexpr auto kMaxTime = nn::TimePoint::max(); + if (timeRemaining > kZeroNano && currentBootTime > kMaxTime - timeRemaining) { + bootTimePoint = kMaxTime; + } else { + bootTimePoint = currentBootTime + timeRemaining; + } + + constexpr auto kZeroTime = nn::TimePoint{}; + return std::max(bootTimePoint, kZeroTime); + } } return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Invalid OptionalTimePoint discriminator " @@ -401,25 +403,19 @@ nn::GeneralResult<V1_2::Model::ExtensionNameAndPrefix> unvalidatedConvert( } template <typename Input> -using unvalidatedConvertOutput = +using UnvalidatedConvertOutput = std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>; template <typename Type> -nn::GeneralResult<hidl_vec<unvalidatedConvertOutput<Type>>> unvalidatedConvertVec( +nn::GeneralResult<hidl_vec<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( const std::vector<Type>& arguments) { - hidl_vec<unvalidatedConvertOutput<Type>> halObject(arguments.size()); + hidl_vec<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<hidl_vec<unvalidatedConvertOutput<Type>>> unvalidatedConvert( - const std::vector<Type>& arguments) { - return unvalidatedConvertVec(arguments); -} - nn::GeneralResult<Request::MemoryPool> makeMemoryPool(const nn::SharedMemory& memory) { Request::MemoryPool ret; ret.hidlMemory(NN_TRY(unvalidatedConvert(memory))); @@ -439,22 +435,15 @@ nn::GeneralResult<Request::MemoryPool> makeMemoryPool(const nn::SharedBuffer& /* using utils::unvalidatedConvert; template <typename Type> -decltype(unvalidatedConvert(std::declval<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; - } +nn::GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& canonical) { + NN_TRY(compliantVersion(canonical)); return unvalidatedConvert(canonical); } template <typename Type> -nn::GeneralResult<hidl_vec<unvalidatedConvertOutput<Type>>> validatedConvert( +nn::GeneralResult<hidl_vec<UnvalidatedConvertOutput<Type>>> validatedConvert( const std::vector<Type>& arguments) { - hidl_vec<unvalidatedConvertOutput<Type>> halObject(arguments.size()); + hidl_vec<UnvalidatedConvertOutput<Type>> halObject(arguments.size()); for (size_t i = 0; i < arguments.size(); ++i) { halObject[i] = NN_TRY(validatedConvert(arguments[i])); } @@ -482,7 +471,7 @@ nn::GeneralResult<Capabilities> unvalidatedConvert(const nn::Capabilities& capab capabilities.operandPerformance.asVector().end(), std::back_inserter(operandPerformance), [](const nn::Capabilities::OperandPerformance& operandPerformance) { - return nn::validOperandType(operandPerformance.type); + return compliantVersion(operandPerformance.type).has_value(); }); return Capabilities{ @@ -601,9 +590,33 @@ nn::GeneralResult<Request::MemoryPool> unvalidatedConvert( nn::GeneralResult<OptionalTimePoint> unvalidatedConvert( const nn::OptionalTimePoint& optionalTimePoint) { + const auto currentSteadyTime = std::chrono::steady_clock::now(); + const auto currentBootTime = nn::Clock::now(); + OptionalTimePoint ret; if (optionalTimePoint.has_value()) { - const auto count = optionalTimePoint.value().time_since_epoch().count(); + const auto bootTimePoint = optionalTimePoint.value(); + + if (bootTimePoint < nn::TimePoint{}) { + return NN_ERROR() << "Trying to cast invalid time point"; + } + + // Both bootTimePoint and currentBootTime are guaranteed to be non-negative, so this + // subtraction will never overflow or underflow. + const auto timeRemaining = bootTimePoint - currentBootTime; + + // currentSteadyTime is guaranteed to be non-negative, so this code only protects against an + // overflow. + std::chrono::steady_clock::time_point steadyTimePoint; + constexpr auto kZeroNano = std::chrono::nanoseconds::zero(); + constexpr auto kMaxTime = std::chrono::steady_clock::time_point::max(); + if (timeRemaining > kZeroNano && currentSteadyTime > kMaxTime - timeRemaining) { + steadyTimePoint = kMaxTime; + } else { + steadyTimePoint = currentSteadyTime + timeRemaining; + } + + const uint64_t count = makeUint64FromNanos(steadyTimePoint.time_since_epoch()); ret.nanosecondsSinceEpoch(count); } return ret; diff --git a/neuralnetworks/1.3/utils/src/PreparedModel.cpp b/neuralnetworks/1.3/utils/src/PreparedModel.cpp index 64275a3729..fd7f8f2ba8 100644 --- a/neuralnetworks/1.3/utils/src/PreparedModel.cpp +++ b/neuralnetworks/1.3/utils/src/PreparedModel.cpp @@ -201,10 +201,12 @@ PreparedModel::executeFenced(const nn::Request& request, const std::vector<nn::S nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const { auto self = shared_from_this(); - auto fallback = [preparedModel = std::move(self)](const nn::Request& request, - nn::MeasureTiming measure) + auto fallback = [preparedModel = std::move(self)]( + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) -> nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> { - return preparedModel->execute(request, measure, {}, {}); + return preparedModel->execute(request, measure, deadline, loopTimeoutDuration); }; const auto pollingTimeWindow = V1_2::utils::getBurstControllerPollingTimeWindow(); return V1_2::utils::ExecutionBurstController::create(kPreparedModel, std::move(fallback), diff --git a/neuralnetworks/1.3/utils/test/MockBuffer.h b/neuralnetworks/1.3/utils/test/MockBuffer.h index fb31b51261..a67c5f613e 100644 --- a/neuralnetworks/1.3/utils/test/MockBuffer.h +++ b/neuralnetworks/1.3/utils/test/MockBuffer.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER_H #include <android/hardware/neuralnetworks/1.3/IBuffer.h> #include <gmock/gmock.h> @@ -40,4 +40,4 @@ inline sp<MockBuffer> MockBuffer::create() { } // namespace android::hardware::neuralnetworks::V1_3::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER_H diff --git a/neuralnetworks/1.3/utils/test/MockBurstContext.h b/neuralnetworks/1.3/utils/test/MockBurstContext.h new file mode 100644 index 0000000000..e102b46dfa --- /dev/null +++ b/neuralnetworks/1.3/utils/test/MockBurstContext.h @@ -0,0 +1,36 @@ +/* + * 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_1_3_UTILS_TEST_MOCK_BURST_CONTEXT_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BURST_CONTEXT_H + +#include <android/hardware/neuralnetworks/1.2/IBurstContext.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_3::utils { + +class MockBurstContext final : public V1_2::IBurstContext { + public: + // V1_2 methods below. + MOCK_METHOD(Return<void>, freeMemory, (int32_t slot), (override)); +}; + +} // namespace android::hardware::neuralnetworks::V1_3::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BURST_CONTEXT_H diff --git a/neuralnetworks/1.3/utils/test/MockDevice.h b/neuralnetworks/1.3/utils/test/MockDevice.h index 85d3750d22..b79037f5c4 100644 --- a/neuralnetworks/1.3/utils/test/MockDevice.h +++ b/neuralnetworks/1.3/utils/test/MockDevice.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE_H #include <android/hardware/neuralnetworks/1.3/IDevice.h> #include <gmock/gmock.h> @@ -136,4 +136,4 @@ inline void MockDevice::simulateCrash() { } // namespace android::hardware::neuralnetworks::V1_3::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE_H diff --git a/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h b/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h index fc08a7fc70..04c0a9298b 100644 --- a/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h +++ b/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK_H #include <android/hardware/neuralnetworks/1.3/IFencedExecutionCallback.h> #include <gmock/gmock.h> @@ -39,4 +39,4 @@ inline sp<MockFencedExecutionCallback> MockFencedExecutionCallback::create() { } // namespace android::hardware::neuralnetworks::V1_3::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK_H diff --git a/neuralnetworks/1.3/utils/test/MockPreparedModel.h b/neuralnetworks/1.3/utils/test/MockPreparedModel.h index e44152426b..ef64fa45c8 100644 --- a/neuralnetworks/1.3/utils/test/MockPreparedModel.h +++ b/neuralnetworks/1.3/utils/test/MockPreparedModel.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL_H #include <android/hardware/neuralnetworks/1.3/IPreparedModel.h> #include <gmock/gmock.h> @@ -118,4 +118,4 @@ inline void MockPreparedModel::simulateCrash() { } // namespace android::hardware::neuralnetworks::V1_3::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL_H diff --git a/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp index 11796ddc2d..5303c2ad23 100644 --- a/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp +++ b/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "MockBurstContext.h" #include "MockFencedExecutionCallback.h" #include "MockPreparedModel.h" @@ -96,6 +97,17 @@ auto makeExecuteFencedCallbackReturn(V1_3::ErrorStatus status, const V1_2::Timin return hardware::Void(); }; } +auto makeConfigureExecutionBurstReturn(V1_0::ErrorStatus status, + const sp<MockBurstContext>& burstContext) { + return [status, burstContext]( + const sp<V1_2::IBurstCallback>& /*callback*/, + const MQDescriptorSync<V1_2::FmqRequestDatum>& /*requestChannel*/, + const MQDescriptorSync<V1_2::FmqResultDatum>& /*resultChannel*/, + V1_2::IPreparedModel::configureExecutionBurst_cb cb) -> hardware::Return<void> { + cb(status, burstContext); + return hardware::Void(); + }; +} std::function<hardware::Status()> makeTransportFailure(status_t status) { return [status] { return hardware::Status::fromStatusT(status); }; @@ -450,7 +462,76 @@ TEST(PreparedModelTest, executeFencedDeadObject) { EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); } -// TODO: test burst execution if/when it is added to nn::IPreparedModel. +TEST(PreparedModelTest, configureExecutionBurst) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto mockBurstContext = sp<MockBurstContext>::make(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_, _, _, _)) + .Times(1) + .WillOnce(makeConfigureExecutionBurstReturn(V1_0::ErrorStatus::NONE, mockBurstContext)); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(PreparedModelTest, configureExecutionBurstError) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_, _, _, _)) + .Times(1) + .WillOnce( + makeConfigureExecutionBurstReturn(V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, configureExecutionBurstTransportFailure) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, configureExecutionBurstDeadObject) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} TEST(PreparedModelTest, getUnderlyingResource) { // setup test diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl index e17e0cd765..c5b4ab1b8f 100644 --- a/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl @@ -307,10 +307,10 @@ interface IDevice { * @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. + * in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME, &ts) + * or ::android::base::boot_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 @@ -396,10 +396,10 @@ interface IDevice { * 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. + * in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME, &ts) or + * ::android::base::boot_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 diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl index 2a9757b323..bfab9067d1 100644 --- a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl @@ -73,8 +73,8 @@ interface IPreparedModel { * 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 + * in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME, &ts) or + * ::android::base::boot_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 @@ -138,8 +138,8 @@ interface IPreparedModel { * 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 + * in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME, &ts) or + * ::android::base::boot_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 diff --git a/neuralnetworks/aidl/utils/Android.bp b/neuralnetworks/aidl/utils/Android.bp index 476dac9d4d..ad961cfe99 100644 --- a/neuralnetworks/aidl/utils/Android.bp +++ b/neuralnetworks/aidl/utils/Android.bp @@ -34,7 +34,6 @@ cc_library_static { "libarect", "neuralnetworks_types", "neuralnetworks_utils_hal_common", - "neuralnetworks_utils_hal_1_0", ], shared_libs: [ "android.hardware.neuralnetworks-V1-ndk_platform", diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Burst.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Burst.h new file mode 100644 index 0000000000..008e4e4fe3 --- /dev/null +++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Burst.h @@ -0,0 +1,111 @@ +/* + * 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_BURST_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_BURST_H + +#include <aidl/android/hardware/neuralnetworks/IBurst.h> +#include <android-base/scopeguard.h> +#include <android-base/thread_annotations.h> +#include <nnapi/IBurst.h> +#include <nnapi/Result.h> +#include <nnapi/Types.h> +#include <nnapi/hal/CommonUtils.h> + +#include <atomic> +#include <memory> +#include <mutex> +#include <optional> +#include <unordered_map> +#include <utility> + +// See hardware/interfaces/neuralnetworks/utils/README.md for more information on AIDL interface +// lifetimes across processes and for protecting asynchronous calls across AIDL. + +namespace aidl::android::hardware::neuralnetworks::utils { + +// Class that adapts aidl_hal::IBurst to nn::IBurst. +class Burst final : public nn::IBurst { + struct PrivateConstructorTag {}; + + public: + /** + * Thread-safe, self-cleaning cache that relates an nn::Memory object to a unique int64_t + * identifier. + */ + class MemoryCache : public std::enable_shared_from_this<MemoryCache> { + public: + using Task = std::function<void()>; + using Cleanup = ::android::base::ScopeGuard<Task>; + using SharedCleanup = std::shared_ptr<const Cleanup>; + using WeakCleanup = std::weak_ptr<const Cleanup>; + + explicit MemoryCache(std::shared_ptr<aidl_hal::IBurst> burst); + + /** + * Get or cache a memory object in the MemoryCache object. + * + * @param memory Memory object to be cached while the returned `SharedCleanup` is alive. + * @return A pair of (1) a unique identifier for the cache entry and (2) a ref-counted + * "hold" object which preserves the cache as long as the hold object is alive. + */ + std::pair<int64_t, SharedCleanup> getOrCacheMemory(const nn::SharedMemory& memory); + + /** + * Get a cached memory object in the MemoryCache object if it exists, otherwise + * std::nullopt. + * + * @param memory Memory object to be cached while the returned `SharedCleanup` is alive. + * @return A pair of (1) a unique identifier for the cache entry and (2) a ref-counted + * "hold" object which preserves the cache as long as the hold object is alive. IF the + * cache entry is not present, std::nullopt is returned instead. + */ + std::optional<std::pair<int64_t, SharedCleanup>> getMemoryIfAvailable( + const nn::SharedMemory& memory); + + private: + void tryFreeMemory(const nn::SharedMemory& memory, int64_t identifier); + + const std::shared_ptr<aidl_hal::IBurst> kBurst; + std::mutex mMutex; + int64_t mUnusedIdentifier GUARDED_BY(mMutex) = 0; + std::unordered_map<nn::SharedMemory, std::pair<int64_t, WeakCleanup>> mCache + GUARDED_BY(mMutex); + }; + + static nn::GeneralResult<std::shared_ptr<const Burst>> create( + std::shared_ptr<aidl_hal::IBurst> burst); + + Burst(PrivateConstructorTag tag, std::shared_ptr<aidl_hal::IBurst> burst); + + // See IBurst::cacheMemory for information. + OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override; + + // See IBurst::execute for information. + nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) const override; + + private: + mutable std::atomic_flag mExecutionInFlight = ATOMIC_FLAG_INIT; + const std::shared_ptr<aidl_hal::IBurst> kBurst; + const std::shared_ptr<MemoryCache> kMemoryCache; +}; + +} // namespace aidl::android::hardware::neuralnetworks::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_BURST_H diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h index 4922a6ea1e..5eab9ffaf6 100644 --- a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h +++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h @@ -99,6 +99,9 @@ GeneralResult<SharedHandle> unvalidatedConvert( const ::aidl::android::hardware::common::NativeHandle& handle); GeneralResult<SyncFence> unvalidatedConvert(const ndk::ScopedFileDescriptor& syncFence); +GeneralResult<std::vector<Operation>> unvalidatedConvert( + const std::vector<aidl_hal::Operation>& operations); + GeneralResult<Capabilities> convert(const aidl_hal::Capabilities& capabilities); GeneralResult<DeviceType> convert(const aidl_hal::DeviceType& deviceType); GeneralResult<ErrorStatus> convert(const aidl_hal::ErrorStatus& errorStatus); @@ -106,16 +109,13 @@ 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<Timing> convert(const aidl_hal::Timing& timing); GeneralResult<SyncFence> convert(const ndk::ScopedFileDescriptor& syncFence); GeneralResult<std::vector<Extension>> convert(const std::vector<aidl_hal::Extension>& extension); -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<OutputShape>> convert( const std::vector<aidl_hal::OutputShape>& outputShapes); diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/PreparedModel.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/PreparedModel.h index 9b28588610..abce6cc3aa 100644 --- a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/PreparedModel.h +++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/PreparedModel.h @@ -22,7 +22,6 @@ #include <nnapi/Result.h> #include <nnapi/Types.h> #include <nnapi/hal/CommonUtils.h> -#include <nnapi/hal/aidl/ProtectCallback.h> #include <memory> #include <tuple> @@ -35,8 +34,7 @@ namespace aidl::android::hardware::neuralnetworks::utils { // Class that adapts aidl_hal::IPreparedModel to nn::IPreparedModel. -class PreparedModel final : public nn::IPreparedModel, - public std::enable_shared_from_this<PreparedModel> { +class PreparedModel final : public nn::IPreparedModel { struct PrivateConstructorTag {}; public: diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h index 58dcfe3af6..316d34fdfe 100644 --- a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h +++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h @@ -21,6 +21,7 @@ #include <android-base/logging.h> #include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> #include <nnapi/Types.h> #include <nnapi/Validation.h> #include <nnapi/hal/HandleError.h> @@ -48,6 +49,22 @@ bool valid(const Type& halObject) { return result.has_value(); } +template <typename Type> +nn::GeneralResult<void> compliantVersion(const Type& canonical) { + const auto version = NN_TRY(::android::hardware::neuralnetworks::utils::makeGeneralFailure( + nn::validate(canonical))); + if (version > kVersion) { + return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion; + } + return {}; +} + +template <typename Type> +auto convertFromNonCanonical(const Type& nonCanonicalObject) + -> decltype(convert(nn::convert(nonCanonicalObject).value())) { + return convert(NN_TRY(nn::convert(nonCanonicalObject))); +} + nn::GeneralResult<Memory> clone(const Memory& memory); nn::GeneralResult<Request> clone(const Request& request); nn::GeneralResult<RequestMemoryPool> clone(const RequestMemoryPool& requestPool); diff --git a/neuralnetworks/aidl/utils/src/Burst.cpp b/neuralnetworks/aidl/utils/src/Burst.cpp new file mode 100644 index 0000000000..0b475bcf53 --- /dev/null +++ b/neuralnetworks/aidl/utils/src/Burst.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. + */ + +#include "Burst.h" + +#include "Conversions.h" +#include "Utils.h" + +#include <android-base/logging.h> +#include <android/binder_auto_utils.h> +#include <nnapi/IBurst.h> +#include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/HandleError.h> + +#include <memory> +#include <mutex> +#include <optional> +#include <utility> + +namespace aidl::android::hardware::neuralnetworks::utils { +namespace { + +nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> convertExecutionResults( + const std::vector<OutputShape>& outputShapes, const Timing& timing) { + return std::make_pair(NN_TRY(nn::convert(outputShapes)), NN_TRY(nn::convert(timing))); +} + +} // namespace + +Burst::MemoryCache::MemoryCache(std::shared_ptr<aidl_hal::IBurst> burst) + : kBurst(std::move(burst)) {} + +std::pair<int64_t, Burst::MemoryCache::SharedCleanup> Burst::MemoryCache::getOrCacheMemory( + const nn::SharedMemory& memory) { + std::lock_guard lock(mMutex); + + // Get the cache payload or create it (with default values) if it does not exist. + auto& cachedPayload = mCache[memory]; + { + const auto& [identifier, maybeCleaner] = cachedPayload; + // If cache payload already exists, reuse it. + if (auto cleaner = maybeCleaner.lock()) { + return std::make_pair(identifier, std::move(cleaner)); + } + } + + // If the code reaches this point, the cached payload either did not exist or expired prior to + // this call. + + // Allocate a new identifier. + CHECK_LT(mUnusedIdentifier, std::numeric_limits<int64_t>::max()); + const int64_t identifier = mUnusedIdentifier++; + + // Create reference-counted self-cleaning cache object. + auto self = weak_from_this(); + Task cleanup = [memory, identifier, maybeMemoryCache = std::move(self)] { + if (const auto memoryCache = maybeMemoryCache.lock()) { + memoryCache->tryFreeMemory(memory, identifier); + } + }; + auto cleaner = std::make_shared<const Cleanup>(std::move(cleanup)); + + // Store the result in the cache and return it. + auto result = std::make_pair(identifier, std::move(cleaner)); + cachedPayload = result; + return result; +} + +std::optional<std::pair<int64_t, Burst::MemoryCache::SharedCleanup>> +Burst::MemoryCache::getMemoryIfAvailable(const nn::SharedMemory& memory) { + std::lock_guard lock(mMutex); + + // Get the existing cached entry if it exists. + const auto iter = mCache.find(memory); + if (iter != mCache.end()) { + const auto& [identifier, maybeCleaner] = iter->second; + if (auto cleaner = maybeCleaner.lock()) { + return std::make_pair(identifier, std::move(cleaner)); + } + } + + // If the code reaches this point, the cached payload did not exist or was actively being + // deleted. + return std::nullopt; +} + +void Burst::MemoryCache::tryFreeMemory(const nn::SharedMemory& memory, int64_t identifier) { + { + std::lock_guard guard(mMutex); + // Remove the cached memory and payload if it is present but expired. Note that it may not + // be present or may not be expired because another thread may have removed or cached the + // same memory object before the current thread locked mMutex in tryFreeMemory. + const auto iter = mCache.find(memory); + if (iter != mCache.end()) { + if (std::get<WeakCleanup>(iter->second).expired()) { + mCache.erase(iter); + } + } + } + kBurst->releaseMemoryResource(identifier); +} + +nn::GeneralResult<std::shared_ptr<const Burst>> Burst::create( + std::shared_ptr<aidl_hal::IBurst> burst) { + if (burst == nullptr) { + return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) + << "aidl_hal::utils::Burst::create must have non-null burst"; + } + + return std::make_shared<const Burst>(PrivateConstructorTag{}, std::move(burst)); +} + +Burst::Burst(PrivateConstructorTag /*tag*/, std::shared_ptr<aidl_hal::IBurst> burst) + : kBurst(std::move(burst)), kMemoryCache(std::make_shared<MemoryCache>(kBurst)) { + CHECK(kBurst != nullptr); +} + +Burst::OptionalCacheHold Burst::cacheMemory(const nn::SharedMemory& memory) const { + auto [identifier, hold] = kMemoryCache->getOrCacheMemory(memory); + return hold; +} + +nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> Burst::execute( + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) const { + // Ensure that at most one execution is in flight at any given time. + const bool alreadyInFlight = mExecutionInFlight.test_and_set(); + if (alreadyInFlight) { + return NN_ERROR() << "IBurst already has an execution in flight"; + } + const auto guard = ::android::base::make_scope_guard([this] { mExecutionInFlight.clear(); }); + + // Ensure that request is ready for IPC. + std::optional<nn::Request> maybeRequestInShared; + const nn::Request& requestInShared = NN_TRY(hal::utils::makeExecutionFailure( + hal::utils::flushDataFromPointerToShared(&request, &maybeRequestInShared))); + + const auto aidlRequest = NN_TRY(hal::utils::makeExecutionFailure(convert(requestInShared))); + const auto aidlMeasure = NN_TRY(hal::utils::makeExecutionFailure(convert(measure))); + const auto aidlDeadline = NN_TRY(hal::utils::makeExecutionFailure(convert(deadline))); + const auto aidlLoopTimeoutDuration = + NN_TRY(hal::utils::makeExecutionFailure(convert(loopTimeoutDuration))); + + std::vector<int64_t> memoryIdentifierTokens; + std::vector<OptionalCacheHold> holds; + memoryIdentifierTokens.reserve(request.pools.size()); + holds.reserve(request.pools.size()); + for (const auto& memoryPool : request.pools) { + if (const auto* memory = std::get_if<nn::SharedMemory>(&memoryPool)) { + if (auto cached = kMemoryCache->getMemoryIfAvailable(*memory)) { + auto& [identifier, hold] = *cached; + memoryIdentifierTokens.push_back(identifier); + holds.push_back(std::move(hold)); + continue; + } + } + memoryIdentifierTokens.push_back(-1); + } + CHECK_EQ(request.pools.size(), memoryIdentifierTokens.size()); + + ExecutionResult executionResult; + const auto ret = + kBurst->executeSynchronously(aidlRequest, memoryIdentifierTokens, aidlMeasure, + aidlDeadline, aidlLoopTimeoutDuration, &executionResult); + HANDLE_ASTATUS(ret) << "execute failed"; + if (!executionResult.outputSufficientSize) { + auto canonicalOutputShapes = + nn::convert(executionResult.outputShapes).value_or(std::vector<nn::OutputShape>{}); + return NN_ERROR(nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, std::move(canonicalOutputShapes)) + << "execution failed with " << nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE; + } + auto [outputShapes, timing] = NN_TRY(hal::utils::makeExecutionFailure( + convertExecutionResults(executionResult.outputShapes, executionResult.timing))); + + NN_TRY(hal::utils::makeExecutionFailure( + hal::utils::unflushDataFromSharedToPointer(request, maybeRequestInShared))); + + return std::make_pair(std::move(outputShapes), timing); +} + +} // namespace aidl::android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/aidl/utils/src/Conversions.cpp b/neuralnetworks/aidl/utils/src/Conversions.cpp index 45bc005e9f..d5f7f81663 100644 --- a/neuralnetworks/aidl/utils/src/Conversions.cpp +++ b/neuralnetworks/aidl/utils/src/Conversions.cpp @@ -41,6 +41,8 @@ #include <type_traits> #include <utility> +#include "Utils.h" + #define VERIFY_NON_NEGATIVE(value) \ while (UNLIKELY(value < 0)) return NN_ERROR() @@ -53,7 +55,6 @@ 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; constexpr int64_t kNoTiming = -1; } // namespace @@ -63,32 +64,6 @@ 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())>; @@ -113,14 +88,7 @@ GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvert( 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; - } + NN_TRY(aidl_hal::utils::compliantVersion(canonical)); return canonical; } @@ -185,13 +153,21 @@ static GeneralResult<UniqueNativeHandle> nativeHandleFromAidlHandle(const Native GeneralResult<OperandType> unvalidatedConvert(const aidl_hal::OperandType& operandType) { VERIFY_NON_NEGATIVE(underlyingType(operandType)) << "Negative operand types are not allowed."; - return static_cast<OperandType>(operandType); + const auto canonical = static_cast<OperandType>(operandType); + if (canonical == OperandType::OEM || canonical == OperandType::TENSOR_OEM_BYTE) { + return NN_ERROR() << "Unable to convert invalid OperandType " << canonical; + } + return canonical; } GeneralResult<OperationType> unvalidatedConvert(const aidl_hal::OperationType& operationType) { VERIFY_NON_NEGATIVE(underlyingType(operationType)) << "Negative operation types are not allowed."; - return static_cast<OperationType>(operationType); + const auto canonical = static_cast<OperationType>(operationType); + if (canonical == OperationType::OEM_OPERATION) { + return NN_ERROR() << "Unable to convert invalid OperationType OEM_OPERATION"; + } + return canonical; } GeneralResult<DeviceType> unvalidatedConvert(const aidl_hal::DeviceType& deviceType) { @@ -206,8 +182,7 @@ GeneralResult<Capabilities> unvalidatedConvert(const aidl_hal::Capabilities& cap 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()); + return validatedConvert(operandPerformance.type).has_value(); }); if (!validOperandTypes) { return NN_ERROR() << "Invalid OperandType when unvalidatedConverting OperandPerformance in " @@ -534,6 +509,11 @@ GeneralResult<SharedHandle> unvalidatedConvert(const NativeHandle& aidlNativeHan return std::make_shared<const Handle>(NN_TRY(unvalidatedConvertHelper(aidlNativeHandle))); } +GeneralResult<std::vector<Operation>> unvalidatedConvert( + const std::vector<aidl_hal::Operation>& operations) { + return unvalidatedConvertVec(operations); +} + GeneralResult<SyncFence> unvalidatedConvert(const ndk::ScopedFileDescriptor& syncFence) { auto duplicatedFd = NN_TRY(dupFd(syncFence.get())); return SyncFence::create(std::move(duplicatedFd)); @@ -564,22 +544,14 @@ 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); + return validatedConvert(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); } @@ -589,17 +561,13 @@ GeneralResult<Timing> convert(const aidl_hal::Timing& timing) { } GeneralResult<SyncFence> convert(const ndk::ScopedFileDescriptor& syncFence) { - return unvalidatedConvert(syncFence); + return validatedConvert(syncFence); } GeneralResult<std::vector<Extension>> convert(const std::vector<aidl_hal::Extension>& extension) { return validatedConvert(extension); } -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); } @@ -644,14 +612,7 @@ nn::GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConver 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; - } + NN_TRY(compliantVersion(canonical)); return utils::unvalidatedConvert(canonical); } @@ -797,6 +758,9 @@ nn::GeneralResult<ExecutionPreference> unvalidatedConvert( } nn::GeneralResult<OperandType> unvalidatedConvert(const nn::OperandType& operandType) { + if (operandType == nn::OperandType::OEM || operandType == nn::OperandType::TENSOR_OEM_BYTE) { + return NN_ERROR() << "Unable to convert invalid OperandType " << operandType; + } return static_cast<OperandType>(operandType); } @@ -864,6 +828,9 @@ nn::GeneralResult<Operand> unvalidatedConvert(const nn::Operand& operand) { } nn::GeneralResult<OperationType> unvalidatedConvert(const nn::OperationType& operationType) { + if (operationType == nn::OperationType::OEM_OPERATION) { + return NN_ERROR() << "Unable to convert invalid OperationType OEM_OPERATION"; + } return static_cast<OperationType>(operationType); } @@ -964,11 +931,12 @@ nn::GeneralResult<Timing> unvalidatedConvert(const nn::Timing& timing) { } nn::GeneralResult<int64_t> unvalidatedConvert(const nn::Duration& duration) { - const uint64_t nanoseconds = duration.count(); - if (nanoseconds > std::numeric_limits<int64_t>::max()) { - return std::numeric_limits<int64_t>::max(); + if (duration < nn::Duration::zero()) { + return NN_ERROR() << "Unable to convert invalid (negative) duration"; } - return static_cast<int64_t>(nanoseconds); + constexpr std::chrono::nanoseconds::rep kIntMax = std::numeric_limits<int64_t>::max(); + const auto count = duration.count(); + return static_cast<int64_t>(std::min(count, kIntMax)); } nn::GeneralResult<int64_t> unvalidatedConvert(const nn::OptionalDuration& optionalDuration) { @@ -1004,7 +972,7 @@ nn::GeneralResult<ndk::ScopedFileDescriptor> unvalidatedConvertCache( } nn::GeneralResult<std::vector<uint8_t>> convert(const nn::CacheToken& cacheToken) { - return unvalidatedConvert(cacheToken); + return validatedConvert(cacheToken); } nn::GeneralResult<BufferDesc> convert(const nn::BufferDesc& bufferDesc) { @@ -1076,7 +1044,7 @@ nn::GeneralResult<std::vector<ndk::ScopedFileDescriptor>> convert( nn::GeneralResult<std::vector<ndk::ScopedFileDescriptor>> convert( const std::vector<nn::SyncFence>& syncFences) { - return unvalidatedConvert(syncFences); + return validatedConvert(syncFences); } nn::GeneralResult<std::vector<int32_t>> toSigned(const std::vector<uint32_t>& vec) { diff --git a/neuralnetworks/aidl/utils/src/PreparedModel.cpp b/neuralnetworks/aidl/utils/src/PreparedModel.cpp index aee4d90dab..003965b619 100644 --- a/neuralnetworks/aidl/utils/src/PreparedModel.cpp +++ b/neuralnetworks/aidl/utils/src/PreparedModel.cpp @@ -16,9 +16,9 @@ #include "PreparedModel.h" +#include "Burst.h" #include "Callbacks.h" #include "Conversions.h" -#include "ProtectCallback.h" #include "Utils.h" #include <android/binder_auto_utils.h> @@ -26,7 +26,6 @@ #include <nnapi/Result.h> #include <nnapi/TypeUtils.h> #include <nnapi/Types.h> -#include <nnapi/hal/1.0/Burst.h> #include <nnapi/hal/CommonUtils.h> #include <nnapi/hal/HandleError.h> @@ -161,7 +160,10 @@ PreparedModel::executeFenced(const nn::Request& request, const std::vector<nn::S } nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const { - return hal::V1_0::utils::Burst::create(shared_from_this()); + std::shared_ptr<IBurst> burst; + const auto ret = kPreparedModel->configureExecutionBurst(&burst); + HANDLE_ASTATUS(ret) << "configureExecutionBurst failed"; + return Burst::create(std::move(burst)); } std::any PreparedModel::getUnderlyingResource() const { diff --git a/neuralnetworks/aidl/utils/test/MockBuffer.h b/neuralnetworks/aidl/utils/test/MockBuffer.h index 5746176116..f77fa86953 100644 --- a/neuralnetworks/aidl/utils/test/MockBuffer.h +++ b/neuralnetworks/aidl/utils/test/MockBuffer.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_BUFFER -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_BUFFER +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_BUFFER_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_BUFFER_H #include <aidl/android/hardware/neuralnetworks/BnBuffer.h> #include <android/binder_interface_utils.h> @@ -40,4 +40,4 @@ inline std::shared_ptr<MockBuffer> MockBuffer::create() { } // namespace aidl::android::hardware::neuralnetworks::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_BUFFER +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_BUFFER_H diff --git a/neuralnetworks/aidl/utils/test/MockBurst.h b/neuralnetworks/aidl/utils/test/MockBurst.h new file mode 100644 index 0000000000..5083bbdc86 --- /dev/null +++ b/neuralnetworks/aidl/utils/test/MockBurst.h @@ -0,0 +1,41 @@ +/* + * 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_TEST_MOCK_BURST_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_BURST_H + +#include <aidl/android/hardware/neuralnetworks/BnBurst.h> +#include <android/binder_interface_utils.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/Status.h> + +namespace aidl::android::hardware::neuralnetworks::utils { + +class MockBurst final : public BnBurst { + public: + MOCK_METHOD(ndk::ScopedAStatus, executeSynchronously, + (const Request& request, const std::vector<int64_t>& memoryIdentifierTokens, + bool measureTiming, int64_t deadline, int64_t loopTimeoutDuration, + ExecutionResult* executionResult), + (override)); + MOCK_METHOD(ndk::ScopedAStatus, releaseMemoryResource, (int64_t memoryIdentifierToken), + (override)); +}; + +} // namespace aidl::android::hardware::neuralnetworks::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_BURST_H diff --git a/neuralnetworks/aidl/utils/test/MockDevice.h b/neuralnetworks/aidl/utils/test/MockDevice.h index 9b35bf8f81..3a28d55580 100644 --- a/neuralnetworks/aidl/utils/test/MockDevice.h +++ b/neuralnetworks/aidl/utils/test/MockDevice.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_DEVICE -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_DEVICE +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_DEVICE_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_DEVICE_H #include <aidl/android/hardware/neuralnetworks/BnDevice.h> #include <android/binder_auto_utils.h> @@ -64,4 +64,4 @@ inline std::shared_ptr<MockDevice> MockDevice::create() { } // namespace aidl::android::hardware::neuralnetworks::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_DEVICE +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_DEVICE_H diff --git a/neuralnetworks/aidl/utils/test/MockFencedExecutionCallback.h b/neuralnetworks/aidl/utils/test/MockFencedExecutionCallback.h index 463e1c9700..06f9ea2e41 100644 --- a/neuralnetworks/aidl/utils/test/MockFencedExecutionCallback.h +++ b/neuralnetworks/aidl/utils/test/MockFencedExecutionCallback.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK_H #include <aidl/android/hardware/neuralnetworks/BnFencedExecutionCallback.h> #include <android/binder_auto_utils.h> @@ -42,4 +42,4 @@ inline std::shared_ptr<MockFencedExecutionCallback> MockFencedExecutionCallback: } // namespace aidl::android::hardware::neuralnetworks::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK_H diff --git a/neuralnetworks/aidl/utils/test/MockPreparedModel.h b/neuralnetworks/aidl/utils/test/MockPreparedModel.h index 36e0ec392d..a4ae2b778a 100644 --- a/neuralnetworks/aidl/utils/test/MockPreparedModel.h +++ b/neuralnetworks/aidl/utils/test/MockPreparedModel.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL_H #include <aidl/android/hardware/neuralnetworks/BnPreparedModel.h> #include <android/binder_interface_utils.h> @@ -49,4 +49,4 @@ inline std::shared_ptr<MockPreparedModel> MockPreparedModel::create() { } // namespace aidl::android::hardware::neuralnetworks::utils -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL_H diff --git a/neuralnetworks/aidl/utils/test/PreparedModelTest.cpp b/neuralnetworks/aidl/utils/test/PreparedModelTest.cpp index 7e28861054..630a460cf5 100644 --- a/neuralnetworks/aidl/utils/test/PreparedModelTest.cpp +++ b/neuralnetworks/aidl/utils/test/PreparedModelTest.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "MockBurst.h" #include "MockFencedExecutionCallback.h" #include "MockPreparedModel.h" @@ -252,7 +253,71 @@ TEST(PreparedModelTest, executeFencedDeadObject) { EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); } -// TODO: test burst execution if/when it is added to nn::IPreparedModel. +TEST(PreparedModelTest, configureExecutionBurst) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + const auto mockBurst = ndk::SharedRefBase::make<MockBurst>(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<0>(mockBurst), Invoke(makeStatusOk))); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(PreparedModelTest, configureExecutionBurstError) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralFailure)); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, configureExecutionBurstTransportFailure) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, configureExecutionBurstDeadObject) { + // setup test + const auto mockPreparedModel = MockPreparedModel::create(); + EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + + // run test + const auto result = preparedModel->configureExecutionBurst(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} TEST(PreparedModelTest, getUnderlyingResource) { // setup test diff --git a/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp index 9ace1a9591..e803e38092 100644 --- a/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp +++ b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ +#include <android-base/chrono_utils.h> #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" @@ -61,16 +61,16 @@ static int64_t makeDeadline(DeadlineBoundType deadlineBoundType) { return std::chrono::duration_cast<std::chrono::nanoseconds>(timeSinceEpoch).count(); }; - std::chrono::steady_clock::time_point timePoint; + ::android::base::boot_clock::time_point timePoint; switch (deadlineBoundType) { case DeadlineBoundType::NOW: - timePoint = std::chrono::steady_clock::now(); + timePoint = ::android::base::boot_clock::now(); break; case DeadlineBoundType::UNLIMITED: - timePoint = std::chrono::steady_clock::time_point::max(); + timePoint = ::android::base::boot_clock::time_point::max(); break; case DeadlineBoundType::SHORT: - timePoint = std::chrono::steady_clock::now() + kShortDuration; + timePoint = ::android::base::boot_clock::now() + kShortDuration; break; } diff --git a/neuralnetworks/aidl/vts/functional/ValidateModel.cpp b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp index 6d84e1ed7b..94d3daf6bb 100644 --- a/neuralnetworks/aidl/vts/functional/ValidateModel.cpp +++ b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp @@ -1312,7 +1312,7 @@ static void mutateExecutionPriorityTest(const std::shared_ptr<IDevice>& device, 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()) + nn::unvalidatedConvert(model.main.operations).value()) .value(); mutateExecutionOrderTest(device, model, numberOfConsumers); mutateOperandTypeTest(device, model); diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h index 996858cd55..17b3fd9d2b 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h @@ -32,7 +32,9 @@ class InvalidBurst final : public nn::IBurst { 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; + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) const override; }; } // namespace android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h index 3b87330872..c92cc41dae 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h @@ -47,7 +47,9 @@ class ResilientBurst final : public nn::IBurst, 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; + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) const override; private: const Factory kMakeBurst; diff --git a/neuralnetworks/utils/common/src/InvalidBurst.cpp b/neuralnetworks/utils/common/src/InvalidBurst.cpp index 81ca18d259..0c34f05265 100644 --- a/neuralnetworks/utils/common/src/InvalidBurst.cpp +++ b/neuralnetworks/utils/common/src/InvalidBurst.cpp @@ -32,7 +32,9 @@ InvalidBurst::OptionalCacheHold InvalidBurst::cacheMemory( } nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> InvalidBurst::execute( - const nn::Request& /*request*/, nn::MeasureTiming /*measure*/) const { + const nn::Request& /*request*/, nn::MeasureTiming /*measure*/, + const nn::OptionalTimePoint& /*deadline*/, + const nn::OptionalDuration& /*loopTimeoutDuration*/) const { return NN_ERROR() << "InvalidBurst"; } diff --git a/neuralnetworks/utils/common/src/ResilientBurst.cpp b/neuralnetworks/utils/common/src/ResilientBurst.cpp index 5ca868bc9a..38ccc62156 100644 --- a/neuralnetworks/utils/common/src/ResilientBurst.cpp +++ b/neuralnetworks/utils/common/src/ResilientBurst.cpp @@ -100,9 +100,11 @@ ResilientBurst::OptionalCacheHold ResilientBurst::cacheMemory( } nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> ResilientBurst::execute( - const nn::Request& request, nn::MeasureTiming measure) const { - const auto fn = [&request, measure](const nn::IBurst& burst) { - return burst.execute(request, measure); + const nn::Request& request, nn::MeasureTiming measure, + const nn::OptionalTimePoint& deadline, + const nn::OptionalDuration& loopTimeoutDuration) const { + const auto fn = [&request, measure, deadline, loopTimeoutDuration](const nn::IBurst& burst) { + return burst.execute(request, measure, deadline, loopTimeoutDuration); }; return protect(*this, fn); } diff --git a/neuralnetworks/utils/common/test/MockBuffer.h b/neuralnetworks/utils/common/test/MockBuffer.h index 59d57007ae..3599d0c988 100644 --- a/neuralnetworks/utils/common/test/MockBuffer.h +++ b/neuralnetworks/utils/common/test/MockBuffer.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER_H #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -34,4 +34,4 @@ class MockBuffer final : public IBuffer { } // namespace android::nn -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER_H diff --git a/neuralnetworks/utils/common/test/MockDevice.h b/neuralnetworks/utils/common/test/MockDevice.h index 5566968c68..b274716882 100644 --- a/neuralnetworks/utils/common/test/MockDevice.h +++ b/neuralnetworks/utils/common/test/MockDevice.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE_H #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -55,4 +55,4 @@ class MockDevice final : public IDevice { } // namespace android::nn -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE_H diff --git a/neuralnetworks/utils/common/test/MockPreparedModel.h b/neuralnetworks/utils/common/test/MockPreparedModel.h index 418af61666..c0048615ef 100644 --- a/neuralnetworks/utils/common/test/MockPreparedModel.h +++ b/neuralnetworks/utils/common/test/MockPreparedModel.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL -#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL_H #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -41,4 +41,4 @@ class MockPreparedModel final : public IPreparedModel { } // namespace android::nn -#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL_H diff --git a/power/aidl/Android.bp b/power/aidl/Android.bp index 054fea5467..dbd18fd5ec 100644 --- a/power/aidl/Android.bp +++ b/power/aidl/Android.bp @@ -29,6 +29,9 @@ aidl_interface { ], stability: "vintf", backend: { + cpp: { + enabled: true, + }, java: { platform_apis: true, }, @@ -38,5 +41,7 @@ aidl_interface { }, }, }, - versions: ["1"], + versions: [ + "1", + ], } diff --git a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/Boost.aidl b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/Boost.aidl index aced215bb9..c792d4e0c2 100644 --- a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/Boost.aidl +++ b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/Boost.aidl @@ -1,14 +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. + */ /////////////////////////////////////////////////////////////////////////////// // 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/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPower.aidl b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPower.aidl index 8a06623fca..ae03313f11 100644 --- a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPower.aidl +++ b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPower.aidl @@ -1,14 +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. + */ /////////////////////////////////////////////////////////////////////////////// // 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 @@ -22,4 +38,6 @@ interface IPower { boolean isModeSupported(in android.hardware.power.Mode type); oneway void setBoost(in android.hardware.power.Boost type, in int durationMs); boolean isBoostSupported(in android.hardware.power.Boost type); + android.hardware.power.IPowerHintSession createHintSession(in int tgid, in int uid, in int[] threadIds, in long durationNanos); + long getHintSessionPreferredRate(); } diff --git a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPowerHintSession.aidl b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPowerHintSession.aidl new file mode 100644 index 0000000000..1d3ecb7eee --- /dev/null +++ b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPowerHintSession.aidl @@ -0,0 +1,42 @@ +/* + * 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.power; +@VintfStability +interface IPowerHintSession { + oneway void updateTargetWorkDuration(long targetDurationNanos); + oneway void reportActualWorkDuration(in android.hardware.power.WorkDuration[] durations); + oneway void pause(); + oneway void resume(); + oneway void close(); +} diff --git a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/Mode.aidl b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/Mode.aidl index f7c2552c8d..8920c014e3 100644 --- a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/Mode.aidl +++ b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/Mode.aidl @@ -1,14 +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. + */ /////////////////////////////////////////////////////////////////////////////// // 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/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/WorkDuration.aidl b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/WorkDuration.aidl new file mode 100644 index 0000000000..e86cd40ec2 --- /dev/null +++ b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/WorkDuration.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.power; +@VintfStability +parcelable WorkDuration { + long timeStampNanos; + long durationNanos; +} diff --git a/power/aidl/android/hardware/power/IPower.aidl b/power/aidl/android/hardware/power/IPower.aidl index 2c4bd86014..ee8e5a3a61 100644 --- a/power/aidl/android/hardware/power/IPower.aidl +++ b/power/aidl/android/hardware/power/IPower.aidl @@ -17,6 +17,7 @@ package android.hardware.power; import android.hardware.power.Boost; +import android.hardware.power.IPowerHintSession; import android.hardware.power.Mode; @VintfStability @@ -69,4 +70,37 @@ interface IPower { * @param type Boost to be queried */ boolean isBoostSupported(in Boost type); + + /** + * A Session represents a group of threads with an inter-related workload such that hints for + * their performance should be considered as a unit. The threads in a given session should be + * long-life and not created or destroyed dynamically. + * + * Each session is expected to have a periodic workload with a target duration for each + * cycle. The cycle duration is likely greater than the target work duration to allow other + * parts of the pipeline to run within the available budget. For example, a renderer thread may + * work at 60hz in order to produce frames at the display's frame but have a target work + * duration of only 6ms. + * + * Creates a session for the given set of threads and sets their initial target work + * duration. + * + * @return the new session if it is supported on this device, otherwise return with + * EX_UNSUPPORTED_OPERATION error if hint session is not supported on this device. + * @param tgid The TGID to be associated with this session. + * @param uid The UID to be associated with this session. + * @param threadIds The list of threads to be associated with this session. + * @param durationNanos The desired duration in nanoseconds for this session. + */ + IPowerHintSession createHintSession( + in int tgid, in int uid, in int[] threadIds, in long durationNanos); + + /** + * Get preferred update rate (interval) information for this device. Framework must communicate + * this rate to Apps, and also ensure the session hint sent no faster than the update rate. + * + * @return the preferred update rate in nanoseconds supported by device software. Return with + * EX_UNSUPPORTED_OPERATION if hint session is not supported. + */ + long getHintSessionPreferredRate(); } diff --git a/power/aidl/android/hardware/power/IPowerHintSession.aidl b/power/aidl/android/hardware/power/IPowerHintSession.aidl new file mode 100644 index 0000000000..9a85bee997 --- /dev/null +++ b/power/aidl/android/hardware/power/IPowerHintSession.aidl @@ -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. + */ + +package android.hardware.power; + +import android.hardware.power.WorkDuration; + +@VintfStability +oneway interface IPowerHintSession { + /** + * Updates the desired duration of a previously-created thread group. + * + * See {@link IPowerHintSession#createHintSession} for more information on how + * the desired duration will be used. + * + * @param targetDurationNanos the new desired duration in nanoseconds + */ + void updateTargetWorkDuration(long targetDurationNanos); + + /** + * Reports the actual duration of a thread group. + * + * The system will attempt to adjust the core placement of the threads within + * the thread group and/or the frequency of the core on which they are run to bring + * the actual duration close to the target duration. + * + * @param actualDurationMicros how long the thread group took to complete its + * last task in nanoseconds + */ + void reportActualWorkDuration(in WorkDuration[] durations); + + /** + * Pause the session when the application is not in foreground and above + */ + void pause(); + + /** + * Resume the session when the application is not in foreground and above + */ + void resume(); + + /** + * Close the session to release resources + */ + void close(); +} diff --git a/power/aidl/android/hardware/power/WorkDuration.aidl b/power/aidl/android/hardware/power/WorkDuration.aidl new file mode 100644 index 0000000000..a06a058fb5 --- /dev/null +++ b/power/aidl/android/hardware/power/WorkDuration.aidl @@ -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.power; + +@VintfStability +parcelable WorkDuration { + /** + * Time stamp in nanoseconds based on CLOCK_MONOTONIC when the duration + * sample was measured. + */ + long timeStampNanos; + /** + * Work duration in nanoseconds. + */ + long durationNanos; +} diff --git a/power/aidl/default/Android.bp b/power/aidl/default/Android.bp index 5aa6893e67..c0ba9a079c 100644 --- a/power/aidl/default/Android.bp +++ b/power/aidl/default/Android.bp @@ -30,7 +30,7 @@ cc_binary { shared_libs: [ "libbase", "libbinder_ndk", - "android.hardware.power-V1-ndk_platform", + "android.hardware.power-V2-ndk_platform", ], srcs: [ "main.cpp", diff --git a/power/aidl/default/Power.cpp b/power/aidl/default/Power.cpp index 8610de35b8..7f08f44926 100644 --- a/power/aidl/default/Power.cpp +++ b/power/aidl/default/Power.cpp @@ -25,6 +25,10 @@ namespace power { namespace impl { namespace example { +const std::vector<Boost> BOOST_RANGE{ndk::enum_range<Boost>().begin(), + ndk::enum_range<Boost>().end()}; +const std::vector<Mode> MODE_RANGE{ndk::enum_range<Mode>().begin(), ndk::enum_range<Mode>().end()}; + ndk::ScopedAStatus Power::setMode(Mode type, bool enabled) { LOG(VERBOSE) << "Power setMode: " << static_cast<int32_t>(type) << " to: " << enabled; return ndk::ScopedAStatus::ok(); @@ -32,7 +36,7 @@ ndk::ScopedAStatus Power::setMode(Mode type, bool enabled) { ndk::ScopedAStatus Power::isModeSupported(Mode type, bool* _aidl_return) { LOG(INFO) << "Power isModeSupported: " << static_cast<int32_t>(type); - *_aidl_return = false; + *_aidl_return = type >= MODE_RANGE.front() && type <= MODE_RANGE.back(); return ndk::ScopedAStatus::ok(); } @@ -44,10 +48,21 @@ ndk::ScopedAStatus Power::setBoost(Boost type, int32_t durationMs) { ndk::ScopedAStatus Power::isBoostSupported(Boost type, bool* _aidl_return) { LOG(INFO) << "Power isBoostSupported: " << static_cast<int32_t>(type); - *_aidl_return = false; + *_aidl_return = type >= BOOST_RANGE.front() && type <= BOOST_RANGE.back(); return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus Power::createHintSession(int32_t, int32_t, const std::vector<int32_t>&, int64_t, + std::shared_ptr<IPowerHintSession>* _aidl_return) { + *_aidl_return = nullptr; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + +ndk::ScopedAStatus Power::getHintSessionPreferredRate(int64_t* outNanoseconds) { + *outNanoseconds = -1; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + } // namespace example } // namespace impl } // namespace power diff --git a/power/aidl/default/Power.h b/power/aidl/default/Power.h index f7645aa527..ef6439dde6 100644 --- a/power/aidl/default/Power.h +++ b/power/aidl/default/Power.h @@ -30,6 +30,11 @@ class Power : public BnPower { ndk::ScopedAStatus isModeSupported(Mode type, bool* _aidl_return) override; ndk::ScopedAStatus setBoost(Boost type, int32_t durationMs) override; ndk::ScopedAStatus isBoostSupported(Boost type, bool* _aidl_return) override; + ndk::ScopedAStatus createHintSession(int32_t tgid, int32_t uid, + const std::vector<int32_t>& threadIds, + int64_t durationNanos, + std::shared_ptr<IPowerHintSession>* _aidl_return) override; + ndk::ScopedAStatus getHintSessionPreferredRate(int64_t* outNanoseconds) override; }; } // namespace example diff --git a/power/aidl/default/power-default.xml b/power/aidl/default/power-default.xml index caf6ea2d5d..9f56debdae 100644 --- a/power/aidl/default/power-default.xml +++ b/power/aidl/default/power-default.xml @@ -1,6 +1,7 @@ <manifest version="1.0" type="device"> <hal format="aidl"> <name>android.hardware.power</name> + <version>2</version> <fqname>IPower/default</fqname> </hal> </manifest> diff --git a/power/aidl/vts/Android.bp b/power/aidl/vts/Android.bp index 1051b03823..3036b8202d 100644 --- a/power/aidl/vts/Android.bp +++ b/power/aidl/vts/Android.bp @@ -29,10 +29,10 @@ cc_test { ], srcs: ["VtsHalPowerTargetTest.cpp"], shared_libs: [ - "libbinder", + "libbinder_ndk", ], static_libs: [ - "android.hardware.power-V1-cpp", + "android.hardware.power-V2-ndk_platform", ], test_suites: [ "vts", diff --git a/power/aidl/vts/VtsHalPowerTargetTest.cpp b/power/aidl/vts/VtsHalPowerTargetTest.cpp index d036c9099f..5bb088a121 100644 --- a/power/aidl/vts/VtsHalPowerTargetTest.cpp +++ b/power/aidl/vts/VtsHalPowerTargetTest.cpp @@ -16,29 +16,28 @@ #include <aidl/Gtest.h> #include <aidl/Vintf.h> +#include <aidl/android/hardware/power/BnPower.h> +#include <aidl/android/hardware/power/BnPowerHintSession.h> #include <android-base/properties.h> -#include <android/hardware/power/Boost.h> -#include <android/hardware/power/IPower.h> -#include <android/hardware/power/Mode.h> -#include <binder/IServiceManager.h> -#include <binder/ProcessState.h> - -#include <future> - -using android::ProcessState; -using android::sp; -using android::String16; -using android::base::GetUintProperty; -using android::binder::Status; +#include <android/binder_ibinder.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +#include <unistd.h> + +namespace aidl::android::hardware::power { +namespace { + +using ::android::base::GetUintProperty; using android::hardware::power::Boost; using android::hardware::power::IPower; +using android::hardware::power::IPowerHintSession; using android::hardware::power::Mode; +using android::hardware::power::WorkDuration; -const std::vector<Boost> kBoosts{android::enum_range<Boost>().begin(), - android::enum_range<Boost>().end()}; +const std::vector<Boost> kBoosts{ndk::enum_range<Boost>().begin(), ndk::enum_range<Boost>().end()}; -const std::vector<Mode> kModes{android::enum_range<Mode>().begin(), - android::enum_range<Mode>().end()}; +const std::vector<Mode> kModes{ndk::enum_range<Mode>().begin(), ndk::enum_range<Mode>().end()}; const std::vector<Boost> kInvalidBoosts = { static_cast<Boost>(static_cast<int32_t>(kBoosts.front()) - 1), @@ -50,14 +49,48 @@ const std::vector<Mode> kInvalidModes = { static_cast<Mode>(static_cast<int32_t>(kModes.back()) + 1), }; +class DurationWrapper : public WorkDuration { + public: + DurationWrapper(int64_t dur, int64_t time) { + durationNanos = dur; + timeStampNanos = time; + } +}; + +const std::vector<int32_t> kSelfTids = { + gettid(), +}; + +const std::vector<int32_t> kEmptyTids = {}; + +const std::vector<WorkDuration> kNoDurations = {}; + +const std::vector<WorkDuration> kDurationsWithZero = { + DurationWrapper(1000L, 1L), + DurationWrapper(0L, 2L), +}; + +const std::vector<WorkDuration> kDurationsWithNegative = { + DurationWrapper(1000L, 1L), + DurationWrapper(-1000L, 2L), +}; + +const std::vector<WorkDuration> kDurations = { + DurationWrapper(1L, 1L), + DurationWrapper(1000L, 2L), + DurationWrapper(1000000L, 3L), + DurationWrapper(1000000000L, 4L), +}; + class PowerAidl : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { - power = android::waitForDeclaredService<IPower>(String16(GetParam().c_str())); - ASSERT_NE(power, nullptr); + AIBinder* binder = AServiceManager_waitForService(GetParam().c_str()); + ASSERT_NE(binder, nullptr); + power = IPower::fromBinder(ndk::SpAIBinder(binder)); } - sp<IPower> power; + std::shared_ptr<IPower> power; }; TEST_P(PowerAidl, setMode) { @@ -110,6 +143,56 @@ TEST_P(PowerAidl, isBoostSupported) { } } +TEST_P(PowerAidl, getHintSessionPreferredRate) { + int64_t rate = -1; + auto status = power->getHintSessionPreferredRate(&rate); + if (!status.isOk()) { + ASSERT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode()); + return; + } + + // At least 1ms rate limit from HAL + ASSERT_GE(rate, 1000000); +} + +TEST_P(PowerAidl, createAndCloseHintSession) { + std::shared_ptr<IPowerHintSession> session; + auto status = power->createHintSession(getpid(), getuid(), kSelfTids, 16666666L, &session); + if (!status.isOk()) { + ASSERT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode()); + return; + } + ASSERT_NE(nullptr, session); + ASSERT_TRUE(session->pause().isOk()); + ASSERT_TRUE(session->resume().isOk()); + // Test normal destroy operation + ASSERT_TRUE(session->close().isOk()); + session.reset(); +} +TEST_P(PowerAidl, createHintSessionFailed) { + std::shared_ptr<IPowerHintSession> session; + auto status = power->createHintSession(getpid(), getuid(), kEmptyTids, 16666666L, &session); + ASSERT_FALSE(status.isOk()); + if (EX_UNSUPPORTED_OPERATION == status.getExceptionCode()) { + return; + } + // Test with empty tid list + ASSERT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); +} + +TEST_P(PowerAidl, updateAndReportDurations) { + std::shared_ptr<IPowerHintSession> session; + auto status = power->createHintSession(getpid(), getuid(), kSelfTids, 16666666L, &session); + if (!status.isOk()) { + ASSERT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode()); + return; + } + ASSERT_NE(nullptr, session); + + ASSERT_TRUE(session->updateTargetWorkDuration(16666667LL).isOk()); + ASSERT_TRUE(session->reportActualWorkDuration(kDurations).isOk()); +} + // FIXED_PERFORMANCE mode is required for all devices which ship on Android 11 // or later TEST_P(PowerAidl, hasFixedPerformance) { @@ -128,12 +211,16 @@ TEST_P(PowerAidl, hasFixedPerformance) { GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PowerAidl); INSTANTIATE_TEST_SUITE_P(Power, PowerAidl, - testing::ValuesIn(android::getAidlHalInstanceNames(IPower::descriptor)), - android::PrintInstanceNameToString); + testing::ValuesIn(::android::getAidlHalInstanceNames(IPower::descriptor)), + ::android::PrintInstanceNameToString); + +} // namespace int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); - ProcessState::self()->setThreadPoolMaxThreadCount(1); - ProcessState::self()->startThreadPool(); + ABinderProcess_setThreadPoolMaxThreadCount(1); + ABinderProcess_startThreadPool(); return RUN_ALL_TESTS(); } + +} // namespace aidl::android::hardware::power diff --git a/power/stats/aidl/default/PowerStats.cpp b/power/stats/aidl/default/PowerStats.cpp index 7cf591eb66..4b771a8be0 100644 --- a/power/stats/aidl/default/PowerStats.cpp +++ b/power/stats/aidl/default/PowerStats.cpp @@ -32,15 +32,19 @@ void PowerStats::addStateResidencyDataProvider(std::unique_ptr<IStateResidencyDa } int32_t id = mPowerEntityInfos.size(); + auto info = p->getInfo(); - for (const auto& [entityName, states] : p->getInfo()) { + size_t index = mStateResidencyDataProviders.size(); + mStateResidencyDataProviders.emplace_back(std::move(p)); + + for (const auto& [entityName, states] : info) { PowerEntity i = { .id = id++, .name = entityName, .states = states, }; mPowerEntityInfos.emplace_back(i); - mStateResidencyDataProviders.emplace_back(std::move(p)); + mStateResidencyDataProviderIndex.emplace_back(index); } } @@ -92,7 +96,8 @@ ndk::ScopedAStatus PowerStats::getStateResidency(const std::vector<int32_t>& in_ // Check to see if we already have data for the given id std::string powerEntityName = mPowerEntityInfos[id].name; if (stateResidencies.find(powerEntityName) == stateResidencies.end()) { - mStateResidencyDataProviders[id]->getStateResidencies(&stateResidencies); + mStateResidencyDataProviders.at(mStateResidencyDataProviderIndex.at(id)) + ->getStateResidencies(&stateResidencies); } // Append results if we have them diff --git a/power/stats/aidl/default/PowerStats.h b/power/stats/aidl/default/PowerStats.h index f4c5e69569..91d272d340 100644 --- a/power/stats/aidl/default/PowerStats.h +++ b/power/stats/aidl/default/PowerStats.h @@ -73,6 +73,8 @@ class PowerStats : public BnPowerStats { private: std::vector<std::unique_ptr<IStateResidencyDataProvider>> mStateResidencyDataProviders; std::vector<PowerEntity> mPowerEntityInfos; + /* Index that maps each power entity id to an entry in mStateResidencyDataProviders */ + std::vector<size_t> mStateResidencyDataProviderIndex; std::vector<std::unique_ptr<IEnergyConsumer>> mEnergyConsumers; std::vector<EnergyConsumer> mEnergyConsumerInfos; 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 c21b2fc544..6cf4567782 100644 --- a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp +++ b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp @@ -158,6 +158,9 @@ TEST_P(RadioHidlTest_v1_6, setupDataCall_1_6_osAppId) { {::android::hardware::radio::V1_6::RadioError::NONE, ::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE, ::android::hardware::radio::V1_6::RadioError::OP_NOT_ALLOWED_BEFORE_REG_TO_NW})); + if (radioRsp_v1_6->setupDataCallResult.trafficDescriptors.size() <= 0) { + return; + } EXPECT_EQ(optionalTrafficDescriptor.value().osAppId.value().osAppId, radioRsp_v1_6->setupDataCallResult.trafficDescriptors[0].osAppId.value().osAppId); } diff --git a/security/keymint/aidl/OWNERS b/security/keymint/aidl/OWNERS index 5c79db8eb9..a93b171b00 100644 --- a/security/keymint/aidl/OWNERS +++ b/security/keymint/aidl/OWNERS @@ -1,3 +1,4 @@ +jbires@google.com jdanis@google.com seleneh@google.com swillden@google.com 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 69ec4cefbb..b05a0f3d83 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 @@ -118,6 +118,7 @@ enum ErrorCode { MISSING_ISSUER_SUBJECT = -82, INVALID_ISSUER_SUBJECT = -83, BOOT_LEVEL_EXCEEDED = -84, + HARDWARE_NOT_YET_AVAILABLE = -85, UNIMPLEMENTED = -100, VERSION_MISMATCH = -101, UNKNOWN_ERROR = -1000, diff --git a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl index 0e2c5f29c8..137e6b6387 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl @@ -107,6 +107,7 @@ enum ErrorCode { MISSING_ISSUER_SUBJECT = -82, INVALID_ISSUER_SUBJECT = -83, BOOT_LEVEL_EXCEEDED = -84, + HARDWARE_NOT_YET_AVAILABLE = -85, 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 17aab25a0c..5aa307094c 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl @@ -321,8 +321,8 @@ interface IKeyMintDevice { * 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 ErrorCode::INCOMPATIBLE_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. */ @@ -360,8 +360,8 @@ interface IKeyMintDevice { * 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 ErrorCode::INCOMPATIBLE_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. */ diff --git a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl index 1cb50ba11c..1ae6762420 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl @@ -202,7 +202,7 @@ interface IRemotelyProvisionedComponent { * 2 : bstr // KID : EEK ID * 3 : -25, // Algorithm : ECDH-ES + HKDF-256 * -1 : 4, // Curve : X25519 - * -2 : bstr // Ed25519 public key + * -2 : bstr // X25519 public key * } * * EekSignatureInput = [ @@ -221,7 +221,7 @@ interface IRemotelyProvisionedComponent { * 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 + * that the public key in the root certificate 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. * diff --git a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl index a26094c4b9..62a48e9f55 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl @@ -26,7 +26,7 @@ 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: + * environment. In CDDL, assuming the contained key is a P-256 public key: * * MacedPublicKey = [ // COSE_Mac0 * protected: bstr .cbor { 1 : 5}, // Algorithm : HMAC-256 @@ -36,10 +36,11 @@ parcelable MacedPublicKey { * ] * * PublicKey = { // COSE_Key - * 1 : 1, // Key type : octet key pair - * 3 : -8 // Algorithm : EdDSA - * -1 : 6, // Curve : Ed25519 + * 1 : 2, // Key type : EC2 + * 3 : -8 // Algorithm : ES256 + * -1 : 6, // Curve : P256 * -2 : bstr // X coordinate, little-endian + * -3 : bstr // Y coordinate, little-endian * ? -70000 : nil // Presence indicates this is a test key. If set, K_mac is * // all zeros. * }, @@ -51,7 +52,7 @@ parcelable MacedPublicKey { * payload : bstr .cbor PublicKey * ] * - * if a non-Ed25519 public key were contained, the contents of the PublicKey map would change a + * if a non-P256 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 index 44f316fde7..519906203d 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl @@ -33,7 +33,7 @@ parcelable ProtectedData { * unprotected: { * 5 : bstr .size 12 // IV * }, - * ciphertext: bstr, // AES-GCM-128(K, .cbor ProtectedDataPayload) + * ciphertext: bstr, // AES-GCM-256(K, .cbor ProtectedDataPayload) * recipients : [ * [ // COSE_Recipient * protected : bstr .cbor { diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl index 6243bb9b78..cde1fc0f60 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl @@ -249,8 +249,11 @@ enum Tag { HARDWARE_TYPE = (1 << 28) /* TagType:ENUM */ | 304, /** - * Keys tagged with EARLY_BOOT_ONLY may only be used, or created, during early boot, until - * IKeyMintDevice::earlyBootEnded() is called. + * Keys tagged with EARLY_BOOT_ONLY may only be used during early boot, until + * IKeyMintDevice::earlyBootEnded() is called. Early boot keys may be created after + * early boot. Early boot keys may not be imprted at all, if Tag::EARLY_BOOT_ONLY is + * provided to IKeyMintDevice::importKey, the import must fail with + * ErrorCode::INVALID_ARGUMENT. */ EARLY_BOOT_ONLY = (7 << 28) /* TagType:BOOL */ | 305, diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp index 4dbaa05d54..5b027292fe 100644 --- a/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp +++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp @@ -46,6 +46,14 @@ using namespace keymaster; namespace { +// Hard-coded set of acceptable public keys that can act as roots of EEK chains. +inline const vector<bytevec> kAuthorizedEekRoots = { + // TODO(drysdale): replace this random value with real root pubkey(s). + {0x5c, 0xea, 0x4b, 0xd2, 0x31, 0x27, 0x15, 0x5e, 0x62, 0x94, 0x70, + 0x53, 0x94, 0x43, 0x0f, 0x9a, 0x89, 0xd5, 0xc5, 0x0f, 0x82, 0x9b, + 0xcd, 0x10, 0xe0, 0x79, 0xef, 0xf3, 0xfa, 0x40, 0xeb, 0x0a}, +}; + 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; @@ -135,6 +143,13 @@ StatusOr<std::pair<bytevec /* EEK pub */, bytevec /* EEK ID */>> validateAndExtr "Failed to validate EEK chain: " + cosePubKey.moveMessage()); } lastPubKey = *std::move(cosePubKey); + + // In prod mode the first pubkey should match a well-known Google public key. + if (!testMode && i == 0 && + std::find(kAuthorizedEekRoots.begin(), kAuthorizedEekRoots.end(), lastPubKey) == + kAuthorizedEekRoots.end()) { + return Status(STATUS_INVALID_EEK, "Unrecognized root of EEK chain"); + } } auto eek = CoseKey::parseX25519(lastPubKey, true /* requireKid */); @@ -343,12 +358,13 @@ ScopedAStatus RemotelyProvisionedComponent::generateCertificateRequest( bcc = bcc_.clone(); } - deviceInfo->deviceInfo = createDeviceInfo(); + std::unique_ptr<cppbor::Map> deviceInfoMap = createDeviceInfo(); + deviceInfo->deviceInfo = deviceInfoMap->encode(); auto signedMac = constructCoseSign1(devicePrivKey /* Signing key */, // ephemeralMacKey /* Payload */, cppbor::Array() /* AAD */ .add(challenge) - .add(deviceInfo->deviceInfo) + .add(std::move(deviceInfoMap)) .encode()); if (!signedMac) return Status(signedMac.moveMessage()); @@ -394,8 +410,24 @@ bytevec RemotelyProvisionedComponent::deriveBytesFromHbk(const string& context, return result; } -bytevec RemotelyProvisionedComponent::createDeviceInfo() const { - return cppbor::Map().encode(); +std::unique_ptr<cppbor::Map> RemotelyProvisionedComponent::createDeviceInfo() const { + auto result = std::make_unique<cppbor::Map>(cppbor::Map()); + + // The following placeholders show how the DeviceInfo map would be populated. + // result->add(cppbor::Tstr("brand"), cppbor::Tstr("Google")); + // result->add(cppbor::Tstr("manufacturer"), cppbor::Tstr("Google")); + // result->add(cppbor::Tstr("product"), cppbor::Tstr("Fake")); + // result->add(cppbor::Tstr("model"), cppbor::Tstr("Imaginary")); + // result->add(cppbor::Tstr("board"), cppbor::Tstr("Chess")); + // result->add(cppbor::Tstr("vb_state"), cppbor::Tstr("orange")); + // result->add(cppbor::Tstr("bootloader_state"), cppbor::Tstr("unlocked")); + // result->add(cppbor::Tstr("os_version"), cppbor::Tstr("SC")); + // result->add(cppbor::Tstr("system_patch_level"), cppbor::Uint(20210331)); + // result->add(cppbor::Tstr("boot_patch_level"), cppbor::Uint(20210331)); + // result->add(cppbor::Tstr("vendor_patch_level"), cppbor::Uint(20210331)); + + result->canonicalize(); + return result; } std::pair<bytevec /* privKey */, cppbor::Array /* BCC */> @@ -417,8 +449,8 @@ RemotelyProvisionedComponent::generateBcc() { .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 */) + .add(-4670553 /* Key Usage (little-endian order) */, + std::vector<uint8_t>{0x20} /* keyCertSign = 1<<5 */) .canonicalize() .encode(); auto coseSign1 = constructCoseSign1(privKey, /* signing key */ diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.h b/security/keymint/aidl/default/RemotelyProvisionedComponent.h index 65b1bbc87e..8185e26e1f 100644 --- a/security/keymint/aidl/default/RemotelyProvisionedComponent.h +++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.h @@ -45,7 +45,7 @@ class RemotelyProvisionedComponent : public BnRemotelyProvisionedComponent { 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::unique_ptr<cppbor::Map> createDeviceInfo() const; std::pair<std::vector<uint8_t>, cppbor::Array> generateBcc(); std::vector<uint8_t> macKey_ = deriveBytesFromHbk("Key to MAC public keys", 32); diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp index 991d77a43b..c1affa6f07 100644 --- a/security/keymint/aidl/vts/functional/Android.bp +++ b/security/keymint/aidl/vts/functional/Android.bp @@ -94,11 +94,14 @@ cc_test { ], static_libs: [ "android.hardware.security.keymint-V1-ndk_platform", + "android.hardware.security.secureclock-V1-ndk_platform", "libcppcose", "libgmock_ndk", - "libremote_provisioner", "libkeymint", + "libkeymint_support", "libkeymint_remote_prov_support", + "libkeymint_vts_test_utils", + "libremote_provisioner", ], test_suites: [ "general-tests", diff --git a/security/keymint/aidl/vts/functional/AttestKeyTest.cpp b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp index 7e7a466566..daa3e1871f 100644 --- a/security/keymint/aidl/vts/functional/AttestKeyTest.cpp +++ b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp @@ -26,29 +26,6 @@ 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); @@ -230,6 +207,36 @@ TEST_P(AttestKeyTest, AllEcCurves) { } } +TEST_P(AttestKeyTest, AttestWithNonAttestKey) { + // Create non-attestaton key. + AttestationKey non_attest_key; + vector<KeyCharacteristics> non_attest_key_characteristics; + vector<Certificate> non_attest_key_cert_chain; + ASSERT_EQ( + ErrorCode::OK, + GenerateKey( + AuthorizationSetBuilder().EcdsaSigningKey(EcCurve::P_256).SetDefaultValidity(), + {} /* attestation siging key */, &non_attest_key.keyBlob, + &non_attest_key_characteristics, &non_attest_key_cert_chain)); + + EXPECT_EQ(non_attest_key_cert_chain.size(), 1); + EXPECT_TRUE(IsSelfSigned(non_attest_key_cert_chain)); + + // Attempt to sign attestation with non-attest key. + vector<uint8_t> attested_key_blob; + vector<KeyCharacteristics> attested_key_characteristics; + vector<Certificate> attested_key_cert_chain; + EXPECT_EQ(ErrorCode::INCOMPATIBLE_PURPOSE, + GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(EcCurve::P_256) + .Authorization(TAG_NO_AUTH_REQUIRED) + .AttestationChallenge("foo") + .AttestationApplicationId("bar") + .SetDefaultValidity(), + non_attest_key, &attested_key_blob, &attested_key_characteristics, + &attested_key_cert_chain)); +} + 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 3e87b6b2da..ce6f67a84a 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp @@ -811,30 +811,6 @@ const vector<KeyParameter>& KeyMintAidlTestBase::SecLevelAuthorizations( return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations; } -AuthorizationSet KeyMintAidlTestBase::HwEnforcedAuthorizations( - const vector<KeyCharacteristics>& key_characteristics) { - AuthorizationSet authList; - for (auto& entry : key_characteristics) { - if (entry.securityLevel == SecurityLevel::STRONGBOX || - entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT) { - authList.push_back(AuthorizationSet(entry.authorizations)); - } - } - return authList; -} - -AuthorizationSet KeyMintAidlTestBase::SwEnforcedAuthorizations( - const vector<KeyCharacteristics>& key_characteristics) { - AuthorizationSet authList; - for (auto& entry : key_characteristics) { - if (entry.securityLevel == SecurityLevel::SOFTWARE || - entry.securityLevel == SecurityLevel::KEYSTORE) { - authList.push_back(AuthorizationSet(entry.authorizations)); - } - } - return authList; -} - ErrorCode KeyMintAidlTestBase::UseAesKey(const vector<uint8_t>& aesKeyBlob) { auto [result, ciphertext] = ProcessMessage( aesKeyBlob, KeyPurpose::ENCRYPT, "1234567890123456", @@ -1046,6 +1022,28 @@ string bin2hex(const vector<uint8_t>& data) { return retval; } +AuthorizationSet HwEnforcedAuthorizations(const vector<KeyCharacteristics>& key_characteristics) { + AuthorizationSet authList; + for (auto& entry : key_characteristics) { + if (entry.securityLevel == SecurityLevel::STRONGBOX || + entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT) { + authList.push_back(AuthorizationSet(entry.authorizations)); + } + } + return authList; +} + +AuthorizationSet SwEnforcedAuthorizations(const vector<KeyCharacteristics>& key_characteristics) { + AuthorizationSet authList; + for (auto& entry : key_characteristics) { + if (entry.securityLevel == SecurityLevel::SOFTWARE || + entry.securityLevel == SecurityLevel::KEYSTORE) { + authList.push_back(AuthorizationSet(entry.authorizations)); + } + } + return authList; +} + AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain) { std::stringstream cert_data; @@ -1097,6 +1095,29 @@ X509_Ptr parse_cert_blob(const vector<uint8_t>& blob) { return X509_Ptr(d2i_X509(nullptr /* allocate new */, &p, blob.size())); } +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; +} + } // 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 0aef81bd6f..4d97ea9de3 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h @@ -252,10 +252,6 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { const vector<KeyParameter>& SecLevelAuthorizations( const vector<KeyCharacteristics>& key_characteristics, SecurityLevel securityLevel); - AuthorizationSet HwEnforcedAuthorizations( - 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); @@ -280,12 +276,16 @@ bool verify_attestation_record(const string& challenge, // const vector<uint8_t>& attestation_cert); string bin2hex(const vector<uint8_t>& data); X509_Ptr parse_cert_blob(const vector<uint8_t>& blob); +vector<uint8_t> make_name_from_str(const string& name); +AuthorizationSet HwEnforcedAuthorizations(const vector<KeyCharacteristics>& key_characteristics); +AuthorizationSet SwEnforcedAuthorizations(const vector<KeyCharacteristics>& key_characteristics); ::testing::AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain); #define INSTANTIATE_KEYMINT_AIDL_TEST(name) \ INSTANTIATE_TEST_SUITE_P(PerInstance, name, \ testing::ValuesIn(KeyMintAidlTestBase::build_params()), \ - ::android::PrintInstanceNameToString) + ::android::PrintInstanceNameToString); \ + GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(name); } // namespace test diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp index 9b797de802..57bc27a3f6 100644 --- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp +++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp @@ -17,18 +17,21 @@ #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 <keymint_support/authorization_set.h> +#include <openssl/ec.h> +#include <openssl/ec_key.h> +#include <openssl/x509.h> #include <remote_prov/remote_prov_utils.h> +#include "KeyMintAidlTestBase.h" + namespace aidl::android::hardware::security::keymint::test { using ::std::string; @@ -52,6 +55,214 @@ bytevec string_to_bytevec(const char* s) { return bytevec(p, p + strlen(s)); } +void p256_pub_key(const vector<uint8_t>& coseKeyData, EVP_PKEY_Ptr* signingKey) { + // Extract x and y affine coordinates from the encoded Cose_Key. + auto [parsedPayload, __, payloadParseErr] = cppbor::parse(coseKeyData); + ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr; + auto coseKey = parsedPayload->asMap(); + const std::unique_ptr<cppbor::Item>& xItem = coseKey->get(cppcose::CoseKey::PUBKEY_X); + ASSERT_NE(xItem->asBstr(), nullptr); + vector<uint8_t> x = xItem->asBstr()->value(); + const std::unique_ptr<cppbor::Item>& yItem = coseKey->get(cppcose::CoseKey::PUBKEY_Y); + ASSERT_NE(yItem->asBstr(), nullptr); + vector<uint8_t> y = yItem->asBstr()->value(); + + // Concatenate: 0x04 (uncompressed form marker) | x | y + vector<uint8_t> pubKeyData{0x04}; + pubKeyData.insert(pubKeyData.end(), x.begin(), x.end()); + pubKeyData.insert(pubKeyData.end(), y.begin(), y.end()); + + EC_KEY_Ptr ecKey = EC_KEY_Ptr(EC_KEY_new()); + ASSERT_NE(ecKey, nullptr); + EC_GROUP_Ptr group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + ASSERT_NE(group, nullptr); + ASSERT_EQ(EC_KEY_set_group(ecKey.get(), group.get()), 1); + EC_POINT_Ptr point = EC_POINT_Ptr(EC_POINT_new(group.get())); + ASSERT_NE(point, nullptr); + ASSERT_EQ(EC_POINT_oct2point(group.get(), point.get(), pubKeyData.data(), pubKeyData.size(), + nullptr), + 1); + ASSERT_EQ(EC_KEY_set_public_key(ecKey.get(), point.get()), 1); + + EVP_PKEY_Ptr pubKey = EVP_PKEY_Ptr(EVP_PKEY_new()); + ASSERT_NE(pubKey, nullptr); + EVP_PKEY_assign_EC_KEY(pubKey.get(), ecKey.release()); + *signingKey = std::move(pubKey); +} + +void check_cose_key(const vector<uint8_t>& data, bool testMode) { + auto [parsedPayload, __, payloadParseErr] = cppbor::parse(data); + ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr; + + // The following check assumes that canonical CBOR encoding is used for the COSE_Key. + if (testMode) { + EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()), + MatchesRegex("{\n" + " 1 : 2,\n" // kty: EC2 + " 3 : -7,\n" // alg: ES256 + " -1 : 1,\n" // EC id: P256 + // 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" // pub_x: data + " -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" // pub_y: data + " -70000 : null,\n" // test marker + "}")); + } else { + EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()), + MatchesRegex("{\n" + " 1 : 2,\n" // kty: EC2 + " 3 : -7,\n" // alg: ES256 + " -1 : 1,\n" // EC id: P256 + // 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" // pub_x: data + " -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" // pub_y: data + "}")); + } +} + +void check_maced_pubkey(const MacedPublicKey& macedPubKey, bool testMode, + vector<uint8_t>* payload_value) { + 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); + + // Header label:value of 'alg': HMAC-256 + ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}"); + + auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap(); + ASSERT_NE(unprotParms, nullptr); + ASSERT_EQ(unprotParms->size(), 0); + + // The payload is a bstr holding an encoded COSE_Key + auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr(); + ASSERT_NE(payload, nullptr); + check_cose_key(payload->value(), testMode); + + 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. Should only match in test mode + auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */, + payload->value()); + ASSERT_TRUE(testTag) << "Tag calculation failed: " << testTag.message(); + + if (testMode) { + EXPECT_EQ(*testTag, extractedTag); + } else { + EXPECT_NE(*testTag, extractedTag); + } + if (payload_value != nullptr) { + *payload_value = payload->value(); + } +} + +ErrMsgOr<MacedPublicKey> corrupt_maced_key(const MacedPublicKey& macedPubKey) { + auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey); + if (!coseMac0 || coseMac0->asArray()->size() != kCoseMac0EntryCount) { + return "COSE Mac0 parse failed"; + } + auto protParams = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr(); + auto unprotParams = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap(); + auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr(); + auto tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr(); + if (!protParams || !unprotParams || !payload || !tag) { + return "Invalid COSE_Sign1: missing content"; + } + auto corruptMac0 = cppbor::Array(); + corruptMac0.add(protParams->clone()); + corruptMac0.add(unprotParams->clone()); + corruptMac0.add(payload->clone()); + vector<uint8_t> tagData = tag->value(); + tagData[0] ^= 0x08; + tagData[tagData.size() - 1] ^= 0x80; + corruptMac0.add(cppbor::Bstr(tagData)); + + return MacedPublicKey{corruptMac0.encode()}; +} + +ErrMsgOr<cppbor::Array> corrupt_sig(const cppbor::Array* coseSign1) { + if (coseSign1->size() != kCoseSign1EntryCount) { + return "Invalid COSE_Sign1, wrong entry count"; + } + const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr(); + const cppbor::Map* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asMap(); + 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: missing content"; + } + + auto corruptSig = cppbor::Array(); + corruptSig.add(protectedParams->clone()); + corruptSig.add(unprotectedParams->clone()); + corruptSig.add(payload->clone()); + vector<uint8_t> sigData = signature->value(); + sigData[0] ^= 0x08; + corruptSig.add(cppbor::Bstr(sigData)); + + return std::move(corruptSig); +} + +ErrMsgOr<EekChain> corrupt_sig_chain(const EekChain& eek, int which) { + auto [chain, _, parseErr] = cppbor::parse(eek.chain); + if (!chain || !chain->asArray()) { + return "EekChain parse failed"; + } + + cppbor::Array* eekChain = chain->asArray(); + if (which >= eekChain->size()) { + return "selected sig out of range"; + } + auto corruptChain = cppbor::Array(); + + for (int ii = 0; ii < eekChain->size(); ++ii) { + if (ii == which) { + auto sig = corrupt_sig(eekChain->get(which)->asArray()); + if (!sig) { + return "Failed to build corrupted signature" + sig.moveMessage(); + } + corruptChain.add(sig.moveValue()); + } else { + corruptChain.add(eekChain->get(ii)->clone()); + } + } + return EekChain{corruptChain.encode(), eek.last_pubkey, eek.last_privkey}; +} + +string device_suffix(const string& name) { + size_t pos = name.find('/'); + if (pos == string::npos) { + return name; + } + return name.substr(pos + 1); +} + +bool matching_keymint_device(const string& rp_name, std::shared_ptr<IKeyMintDevice>* keyMint) { + string rp_suffix = device_suffix(rp_name); + + vector<string> km_names = ::android::getAidlHalInstanceNames(IKeyMintDevice::descriptor); + for (const string& km_name : km_names) { + // If the suffix of the KeyMint instance equals the suffix of the + // RemotelyProvisionedComponent instance, assume they match. + if (device_suffix(km_name) == rp_suffix && AServiceManager_isDeclared(km_name.c_str())) { + ::ndk::SpAIBinder binder(AServiceManager_waitForService(km_name.c_str())); + *keyMint = IKeyMintDevice::fromBinder(binder); + return true; + } + } + return false; +} + } // namespace class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::string> { @@ -78,7 +289,8 @@ using GenerateKeyTests = VtsRemotelyProvisionedComponentTests; INSTANTIATE_REM_PROV_AIDL_TEST(GenerateKeyTests); /** - * Generate and validate a production-mode key. MAC tag can't be verified. + * Generate and validate a production-mode key. MAC tag can't be verified, but + * the private key blob should be usable in KeyMint operations. */ TEST_P(GenerateKeyTests, generateEcdsaP256Key_prodMode) { MacedPublicKey macedPubKey; @@ -86,48 +298,72 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_prodMode) { bool testMode = false; auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob); ASSERT_TRUE(status.isOk()); + vector<uint8_t> coseKeyData; + check_maced_pubkey(macedPubKey, testMode, &coseKeyData); +} - 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)->asMap(); - ASSERT_NE(unprotParms, nullptr); - ASSERT_EQ(unprotParms->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(); +/** + * Generate and validate a production-mode key, then use it as a KeyMint attestation key. + */ +TEST_P(GenerateKeyTests, generateAndUseEcdsaP256Key_prodMode) { + // See if there is a matching IKeyMintDevice for this IRemotelyProvisionedComponent. + std::shared_ptr<IKeyMintDevice> keyMint; + if (!matching_keymint_device(GetParam(), &keyMint)) { + // No matching IKeyMintDevice. + GTEST_SKIP() << "Skipping key use test as no matching KeyMint device found"; + return; + } + KeyMintHardwareInfo info; + ASSERT_TRUE(keyMint->getHardwareInfo(&info).isOk()); - EXPECT_NE(*testTag, extractedTag); + MacedPublicKey macedPubKey; + bytevec privateKeyBlob; + bool testMode = false; + auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob); + ASSERT_TRUE(status.isOk()); + vector<uint8_t> coseKeyData; + check_maced_pubkey(macedPubKey, testMode, &coseKeyData); + + AttestationKey attestKey; + attestKey.keyBlob = std::move(privateKeyBlob); + attestKey.issuerSubjectName = make_name_from_str("Android Keystore Key"); + + // Generate an ECDSA key that is attested by the generated P256 keypair. + AuthorizationSet keyDesc = AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .EcdsaSigningKey(256) + .AttestationChallenge("foo") + .AttestationApplicationId("bar") + .Digest(Digest::NONE) + .SetDefaultValidity(); + KeyCreationResult creationResult; + auto result = keyMint->generateKey(keyDesc.vector_data(), attestKey, &creationResult); + ASSERT_TRUE(result.isOk()); + vector<uint8_t> attested_key_blob = std::move(creationResult.keyBlob); + vector<KeyCharacteristics> attested_key_characteristics = + std::move(creationResult.keyCharacteristics); + vector<Certificate> attested_key_cert_chain = std::move(creationResult.certificateChain); + EXPECT_EQ(attested_key_cert_chain.size(), 1); + + 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, + info.securityLevel, + 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)); + + // The signature over the attested key should correspond to the P256 public key. + X509_Ptr key_cert(parse_cert_blob(attested_key_cert_chain[0].encodedCertificate)); + ASSERT_TRUE(key_cert.get()); + EVP_PKEY_Ptr signing_pubkey; + p256_pub_key(coseKeyData, &signing_pubkey); + ASSERT_TRUE(signing_pubkey.get()); + + ASSERT_TRUE(X509_verify(key_cert.get(), signing_pubkey.get())) + << "Verification of attested certificate failed " + << "OpenSSL error string: " << ERR_error_string(ERR_get_error(), NULL); } /** @@ -140,56 +376,20 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_testMode) { 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)->asMap(); - ASSERT_NE(unprotParms, nullptr); - ASSERT_EQ(unprotParms->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); + check_maced_pubkey(macedPubKey, testMode, nullptr); } class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { protected: - CertificateRequestTest() : eekId_(string_to_bytevec("eekid")) { - auto chain = generateEekChain(3, eekId_); + CertificateRequestTest() : eekId_(string_to_bytevec("eekid")), challenge_(randomBytes(32)) { + generateEek(3); + } + + void generateEek(size_t eekLength) { + auto chain = generateEekChain(eekLength, eekId_); EXPECT_TRUE(chain) << chain.message(); if (chain) eekChain_ = chain.moveValue(); + eekLength_ = eekLength; } void generateKeys(bool testMode, size_t numKeys) { @@ -201,21 +401,76 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { 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())); + vector<uint8_t> payload_value; + check_maced_pubkey(key, testMode, &payload_value); + cborKeysToSign_.add(cppbor::EncodedItem(payload_value)); } } + void checkProtectedData(const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign, + const bytevec& keysToSignMac, const ProtectedData& protectedData) { + 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 [deviceInfoMap, __2, deviceInfoErrMsg] = cppbor::parse(deviceInfo.deviceInfo); + ASSERT_TRUE(deviceInfoMap) << "Failed to parse deviceInfo: " << deviceInfoErrMsg; + ASSERT_TRUE(deviceInfoMap->asMap()); + + auto& signingKey = bccContents->back().pubKey; + auto macKey = verifyAndParseCoseSign1(/* ignore_signature = */ false, signedMac->asArray(), + signingKey, + cppbor::Array() // SignedMacAad + .add(challenge_) + .add(std::move(deviceInfoMap)) + .encode()); + ASSERT_TRUE(macKey) << macKey.message(); + + auto coseMac0 = cppbor::Array() + .add(cppbor::Map() // protected + .add(ALGORITHM, HMAC_256) + .canonicalize() + .encode()) + .add(cppbor::Map()) // unprotected + .add(keysToSign.encode()) // payload (keysToSign) + .add(keysToSignMac); // tag + + auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey); + ASSERT_TRUE(macPayload) << macPayload.message(); + } + bytevec eekId_; + size_t eekLength_; EekChain eekChain_; + bytevec challenge_; std::vector<MacedPublicKey> keysToSign_; cppbor::Array cborKeysToSign_; }; @@ -226,66 +481,20 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { */ TEST_P(CertificateRequestTest, EmptyRequest_testMode) { bool testMode = true; - bytevec keysToSignMac; - DeviceInfo deviceInfo; - ProtectedData protectedData; - auto challenge = randomBytes(32); - auto status = provisionable_->generateCertificateRequest( - testMode, {} /* keysToSign */, eekChain_.chain, challenge, &deviceInfo, &protectedData, - &keysToSignMac); - 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::Map()) // unprotected - .add(cppbor::Array().encode()) // payload (keysToSign) - .add(std::move(keysToSignMac)); // tag - - auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey); - ASSERT_TRUE(macPayload) << macPayload.message(); + for (size_t eekLength : {2, 3, 7}) { + SCOPED_TRACE(testing::Message() << "EEK of length " << eekLength); + generateEek(eekLength); + + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + testMode, {} /* keysToSign */, eekChain_.chain, challenge_, &deviceInfo, + &protectedData, &keysToSignMac); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + checkProtectedData(deviceInfo, cppbor::Array(), keysToSignMac, protectedData); + } } /** @@ -297,15 +506,20 @@ TEST_P(CertificateRequestTest, EmptyRequest_testMode) { */ TEST_P(CertificateRequestTest, EmptyRequest_prodMode) { bool testMode = false; - bytevec keysToSignMac; - DeviceInfo deviceInfo; - ProtectedData protectedData; - auto challenge = randomBytes(32); - auto status = provisionable_->generateCertificateRequest( - testMode, {} /* keysToSign */, eekChain_.chain, challenge, &deviceInfo, &protectedData, - &keysToSignMac); - ASSERT_FALSE(status.isOk()); - ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); + for (size_t eekLength : {2, 3, 7}) { + SCOPED_TRACE(testing::Message() << "EEK of length " << eekLength); + generateEek(eekLength); + + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + testMode, {} /* keysToSign */, eekChain_.chain, challenge_, &deviceInfo, + &protectedData, &keysToSignMac); + EXPECT_FALSE(status.isOk()); + EXPECT_EQ(status.getServiceSpecificError(), + BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); + } } /** @@ -315,65 +529,20 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testMode) { bool testMode = true; generateKeys(testMode, 4 /* numKeys */); - bytevec keysToSignMac; - DeviceInfo deviceInfo; - ProtectedData protectedData; - auto challenge = randomBytes(32); - auto status = provisionable_->generateCertificateRequest(testMode, keysToSign_, eekChain_.chain, - challenge, &deviceInfo, &protectedData, - &keysToSignMac); - 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::Map()) // unprotected - .add(cborKeysToSign_.encode()) // payload - .add(std::move(keysToSignMac)); // tag - - auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey); - ASSERT_TRUE(macPayload) << macPayload.message(); + for (size_t eekLength : {2, 3, 7}) { + SCOPED_TRACE(testing::Message() << "EEK of length " << eekLength); + generateEek(eekLength); + + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + testMode, keysToSign_, eekChain_.chain, challenge_, &deviceInfo, &protectedData, + &keysToSignMac); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + checkProtectedData(deviceInfo, cborKeysToSign_, keysToSignMac, protectedData); + } } /** @@ -387,13 +556,117 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_prodMode) { bool testMode = false; generateKeys(testMode, 4 /* numKeys */); + for (size_t eekLength : {2, 3, 7}) { + SCOPED_TRACE(testing::Message() << "EEK of length " << eekLength); + generateEek(eekLength); + + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + testMode, keysToSign_, eekChain_.chain, challenge_, &deviceInfo, &protectedData, + &keysToSignMac); + EXPECT_FALSE(status.isOk()); + EXPECT_EQ(status.getServiceSpecificError(), + BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); + } +} + +/** + * Generate a non-empty certificate request in test mode, but with the MAC corrupted on the keypair. + */ +TEST_P(CertificateRequestTest, NonEmptyRequestCorruptMac_testMode) { + bool testMode = true; + generateKeys(testMode, 1 /* numKeys */); + MacedPublicKey keyWithCorruptMac = corrupt_maced_key(keysToSign_[0]).moveValue(); + bytevec keysToSignMac; DeviceInfo deviceInfo; ProtectedData protectedData; - auto challenge = randomBytes(32); - auto status = provisionable_->generateCertificateRequest(testMode, keysToSign_, eekChain_.chain, - challenge, &deviceInfo, &protectedData, - &keysToSignMac); + auto status = provisionable_->generateCertificateRequest( + testMode, {keyWithCorruptMac}, eekChain_.chain, challenge_, &deviceInfo, &protectedData, + &keysToSignMac); + ASSERT_FALSE(status.isOk()) << status.getMessage(); + EXPECT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); +} + +/** + * Generate a non-empty certificate request in prod mode, but with the MAC corrupted on the keypair. + */ +TEST_P(CertificateRequestTest, NonEmptyRequestCorruptMac_prodMode) { + bool testMode = true; + generateKeys(testMode, 1 /* numKeys */); + MacedPublicKey keyWithCorruptMac = corrupt_maced_key(keysToSign_[0]).moveValue(); + + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + testMode, {keyWithCorruptMac}, eekChain_.chain, challenge_, &deviceInfo, &protectedData, + &keysToSignMac); + ASSERT_FALSE(status.isOk()) << status.getMessage(); + auto rc = status.getServiceSpecificError(); + + // TODO(drysdale): drop the INVALID_EEK potential error code when a real GEEK is available. + EXPECT_TRUE(rc == BnRemotelyProvisionedComponent::STATUS_INVALID_EEK || + rc == BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); +} + +/** + * Generate a non-empty certificate request in prod mode that has a corrupt EEK chain. + * Confirm that the request is rejected. + * + * TODO(drysdale): Update to use a valid GEEK, so that the test actually confirms that the + * implementation is checking signatures. + */ +TEST_P(CertificateRequestTest, NonEmptyCorruptEekRequest_prodMode) { + bool testMode = false; + generateKeys(testMode, 4 /* numKeys */); + + for (size_t ii = 0; ii < eekLength_; ii++) { + auto chain = corrupt_sig_chain(eekChain_, ii); + ASSERT_TRUE(chain) << chain.message(); + EekChain corruptEek = chain.moveValue(); + + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + testMode, keysToSign_, corruptEek.chain, challenge_, &deviceInfo, &protectedData, + &keysToSignMac); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(status.getServiceSpecificError(), + BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); + } +} + +/** + * Generate a non-empty certificate request in prod mode that has an incomplete EEK chain. + * Confirm that the request is rejected. + * + * TODO(drysdale): Update to use a valid GEEK, so that the test actually confirms that the + * implementation is checking signatures. + */ +TEST_P(CertificateRequestTest, NonEmptyIncompleteEekRequest_prodMode) { + bool testMode = false; + generateKeys(testMode, 4 /* numKeys */); + + // Build an EEK chain that omits the first self-signed cert. + auto truncatedChain = cppbor::Array(); + auto [chain, _, parseErr] = cppbor::parse(eekChain_.chain); + ASSERT_TRUE(chain); + auto eekChain = chain->asArray(); + ASSERT_NE(eekChain, nullptr); + for (size_t ii = 1; ii < eekChain->size(); ii++) { + truncatedChain.add(eekChain->get(ii)->clone()); + } + + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + testMode, keysToSign_, truncatedChain.encode(), challenge_, &deviceInfo, &protectedData, + &keysToSignMac); ASSERT_FALSE(status.isOk()); ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); } @@ -408,9 +681,8 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_prodKeyInTestCert) { bytevec keysToSignMac; DeviceInfo deviceInfo; ProtectedData protectedData; - auto challenge = randomBytes(32); auto status = provisionable_->generateCertificateRequest( - true /* testMode */, keysToSign_, eekChain_.chain, challenge, &deviceInfo, + true /* testMode */, keysToSign_, eekChain_.chain, challenge_, &deviceInfo, &protectedData, &keysToSignMac); ASSERT_FALSE(status.isOk()); ASSERT_EQ(status.getServiceSpecificError(), @@ -428,8 +700,8 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testKeyInProdCert) { DeviceInfo deviceInfo; ProtectedData protectedData; auto status = provisionable_->generateCertificateRequest( - false /* testMode */, keysToSign_, eekChain_.chain, randomBytes(32) /* challenge */, - &deviceInfo, &protectedData, &keysToSignMac); + false /* testMode */, keysToSign_, eekChain_.chain, challenge_, &deviceInfo, + &protectedData, &keysToSignMac); ASSERT_FALSE(status.isOk()); ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); diff --git a/security/keymint/support/include/keymint_support/openssl_utils.h b/security/keymint/support/include/keymint_support/openssl_utils.h index a0212aabd4..dee28baf81 100644 --- a/security/keymint/support/include/keymint_support/openssl_utils.h +++ b/security/keymint/support/include/keymint_support/openssl_utils.h @@ -37,6 +37,7 @@ MAKE_OPENSSL_PTR_TYPE(ASN1_OBJECT) MAKE_OPENSSL_PTR_TYPE(BN_CTX) MAKE_OPENSSL_PTR_TYPE(EC_GROUP) MAKE_OPENSSL_PTR_TYPE(EC_KEY) +MAKE_OPENSSL_PTR_TYPE(EC_POINT) MAKE_OPENSSL_PTR_TYPE(EVP_PKEY) MAKE_OPENSSL_PTR_TYPE(EVP_PKEY_CTX) MAKE_OPENSSL_PTR_TYPE(RSA) diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp index 3e4f3f750e..da10eb258d 100644 --- a/security/keymint/support/remote_prov_utils.cpp +++ b/security/keymint/support/remote_prov_utils.cpp @@ -54,6 +54,8 @@ ErrMsgOr<EekChain> generateEekChain(size_t length, const bytevec& eekId) { {} /* AAD */); if (!coseSign1) return coseSign1.moveMessage(); eekChain.add(coseSign1.moveValue()); + + prev_priv_key = priv_key; } bytevec pub_key(X25519_PUBLIC_VALUE_LEN); diff --git a/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp index 9ca1ee8af1..31f4854e8e 100644 --- a/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp +++ b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp @@ -185,9 +185,11 @@ TEST_P(SecureClockAidlTest, MacChangesOnChangingTimestamp) { INSTANTIATE_TEST_SUITE_P(PerInstance, SecureClockAidlTest, testing::ValuesIn(SecureClockAidlTest::build_params()), ::android::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SecureClockAidlTest); + } // 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/tv/cec/1.0/vts/functional/Android.bp b/tv/cec/1.0/vts/functional/Android.bp index 4d82da388b..9a2c71437a 100644 --- a/tv/cec/1.0/vts/functional/Android.bp +++ b/tv/cec/1.0/vts/functional/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + cc_test { name: "VtsHalTvCecV1_0TargetTest", defaults: ["VtsHalTargetTestDefaults"], diff --git a/tv/tuner/1.0/default/Tuner.cpp b/tv/tuner/1.0/default/Tuner.cpp index 9a6ecf704f..c4f610ee23 100644 --- a/tv/tuner/1.0/default/Tuner.cpp +++ b/tv/tuner/1.0/default/Tuner.cpp @@ -139,6 +139,8 @@ Return<void> Tuner::getDemuxCaps(getDemuxCaps_cb _hidl_cb) { // IP filter can be an MMTP filter's data source. caps.linkCaps = {0x00, 0x00, 0x02, 0x00, 0x00}; + // Support time filter testing + caps.bTimeFilter = true; _hidl_cb(Result::SUCCESS, caps); return Void(); } diff --git a/tv/tuner/1.0/vts/functional/Android.bp b/tv/tuner/1.0/vts/functional/Android.bp index c27a935c2e..6187c738ee 100644 --- a/tv/tuner/1.0/vts/functional/Android.bp +++ b/tv/tuner/1.0/vts/functional/Android.bp @@ -35,6 +35,15 @@ cc_test { "DescramblerTests.cpp", "LnbTests.cpp", ], + generated_headers: [ + "tuner_testing_dynamic_configuration_V1_0_enums", + "tuner_testing_dynamic_configuration_V1_0_parser", + ], + generated_sources: [ + "tuner_testing_dynamic_configuration_V1_0_enums", + "tuner_testing_dynamic_configuration_V1_0_parser", + ], + header_libs: ["libxsdc-utils"], static_libs: [ "android.hardware.cas@1.0", "android.hardware.cas@1.1", @@ -49,9 +58,12 @@ cc_test { ], shared_libs: [ "libbinder", + "libxml2", ], data: [ ":tuner_frontend_input_ts", + ":tuner_frontend_input_es", + ":tuner_testing_dynamic_configuration_V1_0", ], test_suites: [ "general-tests", diff --git a/tv/tuner/1.0/vts/functional/FrontendTests.h b/tv/tuner/1.0/vts/functional/FrontendTests.h index 4974ff351e..33ff603b85 100644 --- a/tv/tuner/1.0/vts/functional/FrontendTests.h +++ b/tv/tuner/1.0/vts/functional/FrontendTests.h @@ -132,6 +132,7 @@ class FrontendTests { static AssertionResult failure() { return ::testing::AssertionFailure(); } static AssertionResult success() { return ::testing::AssertionSuccess(); } + // TODO: replace with customized dvr input void getDefaultSoftwareFrontendPlaybackConfig(DvrConfig& dvrConfig) { PlaybackSettings playbackSettings{ .statusMask = 0xf, diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp index 891619a810..92996f9dc7 100644 --- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp +++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp @@ -18,16 +18,15 @@ namespace { -AssertionResult TunerBroadcastHidlTest::filterDataOutputTest(vector<string> /*goldenOutputFiles*/) { +AssertionResult TunerBroadcastHidlTest::filterDataOutputTest() { return filterDataOutputTestBase(mFilterTests); } -AssertionResult TunerPlaybackHidlTest::filterDataOutputTest(vector<string> /*goldenOutputFiles*/) { +AssertionResult TunerPlaybackHidlTest::filterDataOutputTest() { return filterDataOutputTestBase(mFilterTests); } -AssertionResult TunerDescramblerHidlTest::filterDataOutputTest( - vector<string> /*goldenOutputFiles*/) { +AssertionResult TunerDescramblerHidlTest::filterDataOutputTest() { return filterDataOutputTestBase(mFilterTests); } @@ -62,8 +61,14 @@ void TunerFilterHidlTest::testTimeFilter(TimeFilterConfig filterConf) { } uint32_t demuxId; sp<IDemux> demux; + DemuxCapabilities caps; ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId)); + // TODO: add time filter hardware support checker + ASSERT_TRUE(mDemuxTests.getDemuxCaps(caps)); + if (!caps.bTimeFilter) { + return; + } mFilterTests.setDemux(demux); ASSERT_TRUE(mFilterTests.openTimeFilterInDemux()); ASSERT_TRUE(mFilterTests.setTimeStamp(filterConf.timeStamp)); @@ -75,9 +80,6 @@ void TunerFilterHidlTest::testTimeFilter(TimeFilterConfig filterConf) { void TunerBroadcastHidlTest::broadcastSingleFilterTest(FilterConfig filterConf, FrontendConfig frontendConf) { - if (!frontendConf.enable) { - return; - } uint32_t feId; uint32_t demuxId; sp<IDemux> demux; @@ -106,7 +108,7 @@ void TunerBroadcastHidlTest::broadcastSingleFilterTest(FilterConfig filterConf, ASSERT_TRUE(mFilterTests.startFilter(filterId)); // tune test ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/)); - ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles)); + ASSERT_TRUE(filterDataOutputTest()); ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/)); ASSERT_TRUE(mFilterTests.stopFilter(filterId)); ASSERT_TRUE(mFilterTests.closeFilter(filterId)); @@ -119,12 +121,12 @@ void TunerBroadcastHidlTest::broadcastSingleFilterTestWithLnb(FilterConfig filte LnbConfig lnbConf) { vector<uint32_t> ids; ASSERT_TRUE(mLnbTests.getLnbIds(ids)); - if (!lnbConf.usingLnb) { + if (ids.size() == 0) { return; } ASSERT_TRUE(ids.size() > 0); ASSERT_TRUE(mLnbTests.openLnbById(ids[0])); - *mLnbId = ids[0]; + mLnbId = &ids[0]; ASSERT_TRUE(mLnbTests.setLnbCallback()); ASSERT_TRUE(mLnbTests.setVoltage(lnbConf.voltage)); ASSERT_TRUE(mLnbTests.setTone(lnbConf.tone)); @@ -152,7 +154,7 @@ void TunerPlaybackHidlTest::playbackSingleFilterTest(FilterConfig filterConf, Dv mDvrTests.startPlaybackInputThread(dvrConf.playbackInputFile, dvrConf.settings.playback()); ASSERT_TRUE(mDvrTests.startDvrPlayback()); ASSERT_TRUE(mFilterTests.startFilter(filterId)); - ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles)); + ASSERT_TRUE(filterDataOutputTest()); mDvrTests.stopPlaybackThread(); ASSERT_TRUE(mFilterTests.stopFilter(filterId)); ASSERT_TRUE(mDvrTests.stopDvrPlayback()); @@ -163,9 +165,6 @@ void TunerPlaybackHidlTest::playbackSingleFilterTest(FilterConfig filterConf, Dv void TunerRecordHidlTest::recordSingleFilterTest(FilterConfig filterConf, FrontendConfig frontendConf, DvrConfig dvrConf) { - if (!frontendConf.enable) { - return; - } uint32_t feId; uint32_t demuxId; sp<IDemux> demux; @@ -215,12 +214,12 @@ void TunerRecordHidlTest::recordSingleFilterTestWithLnb(FilterConfig filterConf, DvrConfig dvrConf, LnbConfig lnbConf) { vector<uint32_t> ids; ASSERT_TRUE(mLnbTests.getLnbIds(ids)); - if (!lnbConf.usingLnb) { + if (ids.size() == 0) { return; } ASSERT_TRUE(ids.size() > 0); ASSERT_TRUE(mLnbTests.openLnbById(ids[0])); - *mLnbId = ids[0]; + mLnbId = &ids[0]; ASSERT_TRUE(mLnbTests.setLnbCallback()); ASSERT_TRUE(mLnbTests.setVoltage(lnbConf.voltage)); ASSERT_TRUE(mLnbTests.setTone(lnbConf.tone)); @@ -271,9 +270,6 @@ void TunerRecordHidlTest::attachSingleFilterToRecordDvrTest(FilterConfig filterC void TunerDescramblerHidlTest::scrambledBroadcastTest(set<struct FilterConfig> mediaFilterConfs, FrontendConfig frontendConf, DescramblerConfig descConfig) { - if (!frontendConf.enable) { - return; - } uint32_t feId; uint32_t demuxId; sp<IDemux> demux; @@ -319,7 +315,7 @@ void TunerDescramblerHidlTest::scrambledBroadcastTest(set<struct FilterConfig> m } // tune test ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/)); - ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles)); + ASSERT_TRUE(filterDataOutputTest()); ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/)); for (id = filterIds.begin(); id != filterIds.end(); id++) { ASSERT_TRUE(mFilterTests.stopFilter(*id)); @@ -337,21 +333,27 @@ void TunerDescramblerHidlTest::scrambledBroadcastTest(set<struct FilterConfig> m TEST_P(TunerFrontendHidlTest, TuneFrontend) { description("Tune one Frontend with specific setting and check Lock event"); - mFrontendTests.tuneTest(frontendArray[defaultFrontend]); + mFrontendTests.tuneTest(frontendMap[live.frontendId]); } TEST_P(TunerFrontendHidlTest, AutoScanFrontend) { description("Run an auto frontend scan with specific setting and check lock scanMessage"); - mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_AUTO); + mFrontendTests.scanTest(frontendMap[scan.frontendId], FrontendScanType::SCAN_AUTO); } TEST_P(TunerFrontendHidlTest, BlindScanFrontend) { description("Run an blind frontend scan with specific setting and check lock scanMessage"); - mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_BLIND); + mFrontendTests.scanTest(frontendMap[scan.frontendId], FrontendScanType::SCAN_BLIND); } TEST_P(TunerLnbHidlTest, OpenLnbByName) { description("Open and configure an Lnb with name then send a diseqc msg to it."); + // TODO: add lnb hardware support checker + vector<uint32_t> ids; + ASSERT_TRUE(mLnbTests.getLnbIds(ids)); + if (ids.size() == 0) { + return; + } ASSERT_TRUE(mLnbTests.openLnbByName(lnbArray[LNB_EXTERNAL].name)); ASSERT_TRUE(mLnbTests.setLnbCallback()); ASSERT_TRUE(mLnbTests.setVoltage(lnbArray[LNB_EXTERNAL].voltage)); @@ -365,7 +367,7 @@ TEST_P(TunerLnbHidlTest, SendDiseqcMessageToLnb) { description("Open and configure an Lnb with specific settings then send a diseqc msg to it."); vector<uint32_t> ids; ASSERT_TRUE(mLnbTests.getLnbIds(ids)); - if (!lnbArray[LNB0].usingLnb) { + if (ids.size() == 0) { return; } ASSERT_TRUE(ids.size() > 0); @@ -383,7 +385,7 @@ TEST_P(TunerDemuxHidlTest, openDemux) { uint32_t feId; uint32_t demuxId; sp<IDemux> demux; - mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId); + mFrontendTests.getFrontendIdByType(frontendMap[live.frontendId].type, feId); ASSERT_TRUE(feId != INVALID_ID); ASSERT_TRUE(mFrontendTests.openFrontendById(feId)); ASSERT_TRUE(mFrontendTests.setFrontendCallback()); @@ -403,7 +405,7 @@ TEST_P(TunerDemuxHidlTest, getAvSyncTime) { uint32_t avSyncHwId; sp<IFilter> mediaFilter; - mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId); + mFrontendTests.getFrontendIdByType(frontendMap[live.frontendId].type, feId); ASSERT_TRUE(feId != INVALID_ID); ASSERT_TRUE(mFrontendTests.openFrontendById(feId)); ASSERT_TRUE(mFrontendTests.setFrontendCallback()); @@ -431,7 +433,7 @@ TEST_P(TunerDemuxHidlTest, getAvSyncTime) { TEST_P(TunerFilterHidlTest, StartFilterInDemux) { description("Open and start a filter in Demux."); // TODO use paramterized tests - configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]); + configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendMap[live.frontendId]); } TEST_P(TunerFilterHidlTest, SetFilterLinkage) { @@ -472,27 +474,27 @@ TEST_P(TunerFilterHidlTest, testTimeFilter) { TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowVideoFilterTest) { description("Test Video Filter functionality in Broadcast use case."); - broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[defaultFrontend]); + broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendMap[live.frontendId]); } TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowAudioFilterTest) { description("Test Audio Filter functionality in Broadcast use case."); - broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[defaultFrontend]); + broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendMap[live.frontendId]); } TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowSectionFilterTest) { description("Test Section Filter functionality in Broadcast use case."); - broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[defaultFrontend]); + broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendMap[live.frontendId]); } TEST_P(TunerBroadcastHidlTest, IonBufferTest) { description("Test the av filter data bufferring."); - broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]); + broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendMap[live.frontendId]); } TEST_P(TunerBroadcastHidlTest, LnbBroadcastDataFlowVideoFilterTest) { description("Test Video Filter functionality in Broadcast with Lnb use case."); - broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[DVBS]); + broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendMap[live.frontendId]); } TEST_P(TunerBroadcastHidlTest, BroadcastEsDataFlowMediaFiltersTest) { @@ -502,7 +504,7 @@ TEST_P(TunerBroadcastHidlTest, BroadcastEsDataFlowMediaFiltersTest) { sp<IDemux> demux; uint32_t filterId; - mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId); + mFrontendTests.getFrontendIdByType(frontendMap[live.frontendId].type, feId); if (feId == INVALID_ID) { // TODO broadcast test on Cuttlefish needs licensed ts input, // these tests are runnable on vendor device with real frontend module @@ -528,6 +530,7 @@ TEST_P(TunerBroadcastHidlTest, BroadcastEsDataFlowMediaFiltersTest) { ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterArray[TS_VIDEO1].getMqDesc)); ASSERT_TRUE(mFilterTests.startFilter(filterId)); // tune test + // TODO: replace with customized dvr input PlaybackSettings playbackSettings{ .statusMask = 0xf, .lowThreshold = 0x1000, @@ -542,9 +545,8 @@ TEST_P(TunerBroadcastHidlTest, BroadcastEsDataFlowMediaFiltersTest) { }; dvrConfig.settings.playback(playbackSettings); mFrontendTests.setSoftwareFrontendDvrConfig(dvrConfig); - ASSERT_TRUE( - mFrontendTests.tuneFrontend(frontendArray[defaultFrontend], true /*testWithDemux*/)); - ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles)); + ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendMap[live.frontendId], true /*testWithDemux*/)); + ASSERT_TRUE(filterDataOutputTest()); ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/)); ASSERT_TRUE(mFilterTests.stopFilter(filterId)); ASSERT_TRUE(mFilterTests.closeFilter(filterId)); @@ -560,27 +562,40 @@ TEST_P(TunerPlaybackHidlTest, PlaybackDataFlowWithTsSectionFilterTest) { TEST_P(TunerRecordHidlTest, AttachFiltersToRecordTest) { description("Attach a single filter to the record dvr test."); // TODO use paramterized tests - attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend], + if (!record.support) { + return; + } + attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendMap[record.frontendId], dvrArray[DVR_RECORD0]); } TEST_P(TunerRecordHidlTest, RecordDataFlowWithTsRecordFilterTest) { description("Feed ts data from frontend to recording and test with ts record filter"); - recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend], + if (!record.support) { + return; + } + recordSingleFilterTest(filterArray[TS_RECORD0], frontendMap[record.frontendId], dvrArray[DVR_RECORD0]); } TEST_P(TunerRecordHidlTest, LnbRecordDataFlowWithTsRecordFilterTest) { description("Feed ts data from Fe with Lnb to recording and test with ts record filter"); - recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[DVBS], dvrArray[DVR_RECORD0]); + if (record.support) { + return; + } + recordSingleFilterTestWithLnb(filterArray[TS_RECORD0], frontendMap[lnbRecord.frontendId], + dvrArray[DVR_RECORD0], lnbArray[LNB0]); } TEST_P(TunerDescramblerHidlTest, CreateDescrambler) { description("Create Descrambler"); + if (descrambling.support) { + return; + } uint32_t feId; uint32_t demuxId; sp<IDemux> demux; - mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId); + mFrontendTests.getFrontendIdByType(frontendMap[descrambling.frontendId].type, feId); ASSERT_TRUE(feId != INVALID_ID); ASSERT_TRUE(mFrontendTests.openFrontendById(feId)); ASSERT_TRUE(mFrontendTests.setFrontendCallback()); @@ -594,10 +609,14 @@ TEST_P(TunerDescramblerHidlTest, CreateDescrambler) { TEST_P(TunerDescramblerHidlTest, ScrambledBroadcastDataFlowMediaFiltersTest) { description("Test ts audio filter in scrambled broadcast use case"); + if (descrambling.support) { + return; + } set<FilterConfig> filterConfs; filterConfs.insert(filterArray[TS_AUDIO0]); filterConfs.insert(filterArray[TS_VIDEO1]); - scrambledBroadcastTest(filterConfs, frontendArray[defaultFrontend], descramblerArray[DESC_0]); + scrambledBroadcastTest(filterConfs, frontendMap[descrambling.frontendId], + descramblerArray[DESC_0]); } INSTANTIATE_TEST_SUITE_P( diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h index 5a23ca5f4a..d1f6a45366 100644 --- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h +++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h @@ -28,14 +28,24 @@ static AssertionResult success() { namespace { -void initConfiguration() { +bool initConfiguration() { + if (!TunerTestingConfigReader::checkConfigFileExists()) { + return false; + } initFrontendConfig(); - initFrontendScanConfig(); + connectHardwaresToTestCases(); + if (!validateConnections()) { + ALOGW("[vts] failed to validate connections."); + return false; + } + initLnbConfig(); initFilterConfig(); initTimeFilterConfig(); initDvrConfig(); initDescramblerConfig(); + + return true; } AssertionResult filterDataOutputTestBase(FilterTests tests) { @@ -53,7 +63,7 @@ class TunerFrontendHidlTest : public testing::TestWithParam<std::string> { virtual void SetUp() override { mService = ITuner::getService(GetParam()); ASSERT_NE(mService, nullptr); - initConfiguration(); + ASSERT_TRUE(initConfiguration()); mFrontendTests.setService(mService); } @@ -75,7 +85,7 @@ class TunerLnbHidlTest : public testing::TestWithParam<std::string> { virtual void SetUp() override { mService = ITuner::getService(GetParam()); ASSERT_NE(mService, nullptr); - initConfiguration(); + ASSERT_TRUE(initConfiguration()); mLnbTests.setService(mService); } @@ -97,7 +107,7 @@ class TunerDemuxHidlTest : public testing::TestWithParam<std::string> { virtual void SetUp() override { mService = ITuner::getService(GetParam()); ASSERT_NE(mService, nullptr); - initConfiguration(); + ASSERT_TRUE(initConfiguration()); mFrontendTests.setService(mService); mDemuxTests.setService(mService); @@ -123,7 +133,7 @@ class TunerFilterHidlTest : public testing::TestWithParam<std::string> { virtual void SetUp() override { mService = ITuner::getService(GetParam()); ASSERT_NE(mService, nullptr); - initConfiguration(); + ASSERT_TRUE(initConfiguration()); mFrontendTests.setService(mService); mDemuxTests.setService(mService); @@ -152,7 +162,7 @@ class TunerBroadcastHidlTest : public testing::TestWithParam<std::string> { virtual void SetUp() override { mService = ITuner::getService(GetParam()); ASSERT_NE(mService, nullptr); - initConfiguration(); + ASSERT_TRUE(initConfiguration()); mFrontendTests.setService(mService); mDemuxTests.setService(mService); @@ -173,7 +183,7 @@ class TunerBroadcastHidlTest : public testing::TestWithParam<std::string> { LnbTests mLnbTests; DvrTests mDvrTests; - AssertionResult filterDataOutputTest(vector<string> goldenOutputFiles); + AssertionResult filterDataOutputTest(); void broadcastSingleFilterTest(FilterConfig filterConf, FrontendConfig frontendConf); void broadcastSingleFilterTestWithLnb(FilterConfig filterConf, FrontendConfig frontendConf, @@ -191,7 +201,7 @@ class TunerPlaybackHidlTest : public testing::TestWithParam<std::string> { virtual void SetUp() override { mService = ITuner::getService(GetParam()); ASSERT_NE(mService, nullptr); - initConfiguration(); + ASSERT_TRUE(initConfiguration()); mFrontendTests.setService(mService); mDemuxTests.setService(mService); @@ -210,7 +220,7 @@ class TunerPlaybackHidlTest : public testing::TestWithParam<std::string> { FilterTests mFilterTests; DvrTests mDvrTests; - AssertionResult filterDataOutputTest(vector<string> goldenOutputFiles); + AssertionResult filterDataOutputTest(); void playbackSingleFilterTest(FilterConfig filterConf, DvrConfig dvrConf); }; @@ -223,7 +233,7 @@ class TunerRecordHidlTest : public testing::TestWithParam<std::string> { virtual void SetUp() override { mService = ITuner::getService(GetParam()); ASSERT_NE(mService, nullptr); - initConfiguration(); + ASSERT_TRUE(initConfiguration()); mFrontendTests.setService(mService); mDemuxTests.setService(mService); @@ -265,7 +275,7 @@ class TunerDescramblerHidlTest : public testing::TestWithParam<std::string> { mCasService = IMediaCasService::getService(); ASSERT_NE(mService, nullptr); ASSERT_NE(mCasService, nullptr); - initConfiguration(); + ASSERT_TRUE(initConfiguration()); mFrontendTests.setService(mService); mDemuxTests.setService(mService); @@ -281,7 +291,7 @@ class TunerDescramblerHidlTest : public testing::TestWithParam<std::string> { void scrambledBroadcastTest(set<struct FilterConfig> mediaFilterConfs, FrontendConfig frontendConf, DescramblerConfig descConfig); - AssertionResult filterDataOutputTest(vector<string> /*goldenOutputFiles*/); + AssertionResult filterDataOutputTest(); sp<ITuner> mService; sp<IMediaCasService> mCasService; diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h index 834e296872..384455bc74 100644 --- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h +++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h @@ -21,6 +21,9 @@ #include <hidl/Status.h> #include <hidlmemory/FrameworkUtils.h> +#include "../../../config/TunerTestingConfigReader.h" + +// TODO: remove unnecessary imports after config reader refactoring is done. using android::hardware::tv::tuner::V1_0::DataFormat; using android::hardware::tv::tuner::V1_0::DemuxAlpFilterType; using android::hardware::tv::tuner::V1_0::DemuxFilterEvent; @@ -54,7 +57,9 @@ using android::hardware::tv::tuner::V1_0::PlaybackSettings; using android::hardware::tv::tuner::V1_0::RecordSettings; using namespace std; +using namespace android::media::tuner::testing::configuration::V1_0; +// TODO: remove all the constants and structs after config reader refactoring is done. const uint32_t FMQ_SIZE_512K = 0x80000; const uint32_t FMQ_SIZE_1M = 0x100000; const uint32_t FMQ_SIZE_4M = 0x400000; @@ -99,12 +104,6 @@ typedef enum { } Linkage; typedef enum { - DVBT, - DVBS, - FRONTEND_MAX, -} Frontend; - -typedef enum { LNB0, LNB_EXTERNAL, LNB_MAX, @@ -116,11 +115,6 @@ typedef enum { } Diseqc; typedef enum { - SCAN_DVBT, - SCAN_MAX, -} FrontendScan; - -typedef enum { DVR_RECORD0, DVR_PLAYBACK0, DVR_MAX, @@ -145,15 +139,6 @@ struct TimeFilterConfig { uint64_t timeStamp; }; -struct FrontendConfig { - bool enable; - bool isSoftwareFe; - FrontendType type; - FrontendSettings settings; - vector<FrontendStatusType> tuneStatusTypes; - vector<FrontendStatus> expectTuneStatuses; -}; - struct LnbConfig { bool usingLnb; string name; @@ -162,14 +147,6 @@ struct LnbConfig { LnbPosition position; }; -struct ChannelConfig { - int32_t frontendId; - int32_t channelId; - std::string channelName; - DemuxTpid videoPid; - DemuxTpid audioPid; -}; - struct DvrConfig { DvrType type; uint32_t bufferSize; @@ -183,67 +160,78 @@ struct DescramblerConfig { vector<uint8_t> hidlPvtData; }; -static FrontendConfig frontendArray[FILTER_MAX]; -static FrontendConfig frontendScanArray[SCAN_MAX]; +// TODO: remove all the manual config array after the dynamic config refactoring is done. static LnbConfig lnbArray[LNB_MAX]; static vector<uint8_t> diseqcMsgArray[DISEQC_MAX]; -static ChannelConfig channelArray[FRONTEND_MAX]; static FilterConfig filterArray[FILTER_MAX]; static TimeFilterConfig timeFilterArray[TIMER_MAX]; static DemuxFilterType filterLinkageTypes[LINKAGE_DIR][FILTER_MAIN_TYPE_BIT_COUNT]; static DvrConfig dvrArray[DVR_MAX]; static DescramblerConfig descramblerArray[DESC_MAX]; -static vector<string> goldenOutputFiles; -static int defaultFrontend = DVBT; -static int defaultScanFrontend = SCAN_DVBT; + +// Hardware configs +static map<string, FrontendConfig> frontendMap; + +// Hardware and test cases connections +static LiveBroadcastHardwareConnections live; +static ScanHardwareConnections scan; +static DvrRecordHardwareConnections record; +static DescramblingHardwareConnections descrambling; +static LnbLiveHardwareConnections lnbLive; +static LnbRecordHardwareConnections lnbRecord; /** Configuration array for the frontend tune test */ inline void initFrontendConfig() { + // The test will use the internal default fe is default fe is connected to any data flow without + // overriding in the xml config. + string defaultFeId = "FE_DEFAULT"; FrontendDvbtSettings dvbtSettings{ .frequency = 578000, .transmissionMode = FrontendDvbtTransmissionMode::AUTO, .bandwidth = FrontendDvbtBandwidth::BANDWIDTH_8MHZ, - .constellation = FrontendDvbtConstellation::AUTO, - .hierarchy = FrontendDvbtHierarchy::AUTO, - .hpCoderate = FrontendDvbtCoderate::AUTO, - .lpCoderate = FrontendDvbtCoderate::AUTO, - .guardInterval = FrontendDvbtGuardInterval::AUTO, .isHighPriority = true, - .standard = FrontendDvbtStandard::T, }; - frontendArray[DVBT].type = FrontendType::DVBT, frontendArray[DVBT].settings.dvbt(dvbtSettings); + frontendMap[defaultFeId].type = FrontendType::DVBT; + frontendMap[defaultFeId].settings.dvbt(dvbtSettings); + vector<FrontendStatusType> types; types.push_back(FrontendStatusType::DEMOD_LOCK); FrontendStatus status; status.isDemodLocked(true); vector<FrontendStatus> statuses; statuses.push_back(status); - frontendArray[DVBT].tuneStatusTypes = types; - frontendArray[DVBT].expectTuneStatuses = statuses; - frontendArray[DVBT].isSoftwareFe = true; - frontendArray[DVBT].enable = true; - frontendArray[DVBS].type = FrontendType::DVBS; - frontendArray[DVBS].enable = true; - frontendArray[DVBS].isSoftwareFe = true; + frontendMap[defaultFeId].tuneStatusTypes = types; + frontendMap[defaultFeId].expectTuneStatuses = statuses; + frontendMap[defaultFeId].isSoftwareFe = true; + + // Read customized config + TunerTestingConfigReader::readFrontendConfig1_0(frontendMap); }; -/** Configuration array for the frontend scan test */ -inline void initFrontendScanConfig() { - frontendScanArray[SCAN_DVBT].type = FrontendType::DVBT; - frontendScanArray[SCAN_DVBT].settings.dvbt({ - .frequency = 578000, - .transmissionMode = FrontendDvbtTransmissionMode::MODE_8K, - .bandwidth = FrontendDvbtBandwidth::BANDWIDTH_8MHZ, - .constellation = FrontendDvbtConstellation::AUTO, - .hierarchy = FrontendDvbtHierarchy::AUTO, - .hpCoderate = FrontendDvbtCoderate::AUTO, - .lpCoderate = FrontendDvbtCoderate::AUTO, - .guardInterval = FrontendDvbtGuardInterval::AUTO, - .isHighPriority = true, - .standard = FrontendDvbtStandard::T, - }); +/** Read the vendor configurations of which hardware to use for each test cases/data flows */ +inline void connectHardwaresToTestCases() { + TunerTestingConfigReader::connectLiveBroadcast(live); + TunerTestingConfigReader::connectScan(scan); + TunerTestingConfigReader::connectDvrRecord(record); + TunerTestingConfigReader::connectDescrambling(descrambling); + TunerTestingConfigReader::connectLnbLive(lnbLive); + TunerTestingConfigReader::connectLnbRecord(lnbRecord); }; +inline bool validateConnections() { + bool feIsValid = frontendMap.find(live.frontendId) != frontendMap.end() && + frontendMap.find(scan.frontendId) != frontendMap.end(); + feIsValid &= record.support ? frontendMap.find(record.frontendId) != frontendMap.end() : true; + feIsValid &= descrambling.support + ? frontendMap.find(descrambling.frontendId) != frontendMap.end() + : true; + feIsValid &= lnbLive.support ? frontendMap.find(lnbLive.frontendId) != frontendMap.end() : true; + feIsValid &= + lnbRecord.support ? frontendMap.find(lnbRecord.frontendId) != frontendMap.end() : true; + return feIsValid; +} + +// TODO: remove all the manual configs after the dynamic config refactoring is done. /** Configuration array for the Lnb test */ inline void initLnbConfig() { lnbArray[LNB0].usingLnb = true; diff --git a/tv/tuner/1.1/default/Tuner.cpp b/tv/tuner/1.1/default/Tuner.cpp index 38b2a26da0..1e940ba098 100644 --- a/tv/tuner/1.1/default/Tuner.cpp +++ b/tv/tuner/1.1/default/Tuner.cpp @@ -237,6 +237,8 @@ Return<void> Tuner::getDemuxCaps(getDemuxCaps_cb _hidl_cb) { // IP filter can be an MMTP filter's data source. caps.linkCaps = {0x00, 0x00, 0x02, 0x00, 0x00}; + // Support time filter testing + caps.bTimeFilter = true; _hidl_cb(Result::SUCCESS, caps); return Void(); } diff --git a/tv/tuner/config/Android.bp b/tv/tuner/config/Android.bp new file mode 100644 index 0000000000..ddbf3a74a8 --- /dev/null +++ b/tv/tuner/config/Android.bp @@ -0,0 +1,31 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +xsd_config { + name: "tuner_testing_dynamic_configuration_V1_0", + srcs: ["tuner_testing_dynamic_configuration.xsd"], + package_name: "android.media.tuner.testing.configuration.V1_0", + nullability: true, +} + +xsd_config { + name: "tuner_testing_dynamic_configuration_V1_0_enums", + srcs: ["tuner_testing_dynamic_configuration.xsd"], + package_name: "android.media.tuner.testing.configuration.V1_0", + nullability: true, + enums_only: true, +} + +xsd_config { + name: "tuner_testing_dynamic_configuration_V1_0_parser", + srcs: ["tuner_testing_dynamic_configuration.xsd"], + package_name: "android.media.tuner.testing.configuration.V1_0", + nullability: true, + parser_only: true, +} diff --git a/tv/tuner/config/TunerTestingConfigReader.h b/tv/tuner/config/TunerTestingConfigReader.h new file mode 100644 index 0000000000..5c7f5648d3 --- /dev/null +++ b/tv/tuner/config/TunerTestingConfigReader.h @@ -0,0 +1,293 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android-base/logging.h> +#include <android/hardware/tv/tuner/1.0/types.h> +#include <android_media_tuner_testing_configuration_V1_0.h> +#include <android_media_tuner_testing_configuration_V1_0_enums.h> +#include <binder/MemoryDealer.h> +#include <hidl/HidlSupport.h> +#include <hidl/HidlTransportSupport.h> +#include <hidl/Status.h> +#include <hidlmemory/FrameworkUtils.h> + +using namespace std; +using namespace android::media::tuner::testing::configuration::V1_0; + +using android::hardware::tv::tuner::V1_0::DataFormat; +using android::hardware::tv::tuner::V1_0::DemuxAlpFilterType; +using android::hardware::tv::tuner::V1_0::DemuxFilterEvent; +using android::hardware::tv::tuner::V1_0::DemuxFilterMainType; +using android::hardware::tv::tuner::V1_0::DemuxFilterSettings; +using android::hardware::tv::tuner::V1_0::DemuxFilterType; +using android::hardware::tv::tuner::V1_0::DemuxIpFilterType; +using android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType; +using android::hardware::tv::tuner::V1_0::DemuxRecordScIndexType; +using android::hardware::tv::tuner::V1_0::DemuxTlvFilterType; +using android::hardware::tv::tuner::V1_0::DemuxTpid; +using android::hardware::tv::tuner::V1_0::DemuxTsFilterType; +using android::hardware::tv::tuner::V1_0::DvrSettings; +using android::hardware::tv::tuner::V1_0::DvrType; +using android::hardware::tv::tuner::V1_0::FrontendDvbsSettings; +using android::hardware::tv::tuner::V1_0::FrontendDvbtBandwidth; +using android::hardware::tv::tuner::V1_0::FrontendDvbtCoderate; +using android::hardware::tv::tuner::V1_0::FrontendDvbtConstellation; +using android::hardware::tv::tuner::V1_0::FrontendDvbtGuardInterval; +using android::hardware::tv::tuner::V1_0::FrontendDvbtHierarchy; +using android::hardware::tv::tuner::V1_0::FrontendDvbtSettings; +using android::hardware::tv::tuner::V1_0::FrontendDvbtStandard; +using android::hardware::tv::tuner::V1_0::FrontendDvbtTransmissionMode; +using android::hardware::tv::tuner::V1_0::FrontendSettings; +using android::hardware::tv::tuner::V1_0::FrontendStatus; +using android::hardware::tv::tuner::V1_0::FrontendStatusType; +using android::hardware::tv::tuner::V1_0::FrontendType; +using android::hardware::tv::tuner::V1_0::LnbPosition; +using android::hardware::tv::tuner::V1_0::LnbTone; +using android::hardware::tv::tuner::V1_0::LnbVoltage; +using android::hardware::tv::tuner::V1_0::PlaybackSettings; +using android::hardware::tv::tuner::V1_0::RecordSettings; + +const string configFilePath = "/vendor/etc/tuner_vts_config.xml"; + +struct FrontendConfig { + bool isSoftwareFe; + FrontendType type; + FrontendSettings settings; + vector<FrontendStatusType> tuneStatusTypes; + vector<FrontendStatus> expectTuneStatuses; +}; + +struct LiveBroadcastHardwareConnections { + string frontendId; + /* string audioFilterId; + string videoFilterId; + list string of extra filters; */ +}; + +struct ScanHardwareConnections { + string frontendId; +}; + +struct DvrRecordHardwareConnections { + bool support; + string frontendId; + /* string recordFilterId; + string dvrId; */ +}; + +struct DescramblingHardwareConnections { + bool support; + string frontendId; + /* string descramblerId; + string audioFilterId; + string videoFilterId; + list string of extra filters; */ +}; + +struct LnbLiveHardwareConnections { + bool support; + string frontendId; + /* string audioFilterId; + string videoFilterId; + list string of extra filters; + string lnbId; */ +}; + +struct LnbRecordHardwareConnections { + bool support; + string frontendId; + /* string recordFilterId; + list string of extra filters; + string lnbId; */ +}; + +struct TunerTestingConfigReader { + public: + static bool checkConfigFileExists() { + auto res = read(configFilePath.c_str()); + if (res == nullopt) { + ALOGW("[ConfigReader] Couldn't read /vendor/etc/tuner_vts_config.xml." + "Please check tuner_testing_dynamic_configuration.xsd" + "and sample_tuner_vts_config.xml for more details on how to config Tune VTS."); + } + return (res != nullopt); + } + + static void readFrontendConfig1_0(map<string, FrontendConfig>& frontendMap) { + auto hardwareConfig = getHardwareConfig(); + if (hardwareConfig.hasFrontends()) { + // TODO: complete the tune status config + vector<FrontendStatusType> types; + types.push_back(FrontendStatusType::DEMOD_LOCK); + FrontendStatus status; + status.isDemodLocked(true); + vector<FrontendStatus> statuses; + statuses.push_back(status); + + auto frontends = *hardwareConfig.getFirstFrontends(); + for (auto feConfig : frontends.getFrontend()) { + string id = feConfig.getId(); + if (id.compare(string("FE_DEFAULT")) == 0) { + // overrid default + frontendMap.erase(string("FE_DEFAULT")); + } + FrontendType type; + switch (feConfig.getType()) { + case FrontendTypeEnum::UNDEFINED: + type = FrontendType::UNDEFINED; + break; + // TODO: finish all other frontend settings + case FrontendTypeEnum::ANALOG: + type = FrontendType::ANALOG; + break; + case FrontendTypeEnum::ATSC: + type = FrontendType::ATSC; + break; + case FrontendTypeEnum::ATSC3: + type = FrontendType::ATSC3; + break; + case FrontendTypeEnum::DVBC: + type = FrontendType::DVBC; + break; + case FrontendTypeEnum::DVBS: + type = FrontendType::DVBS; + frontendMap[id].settings.dvbs(readDvbsFrontendSettings(feConfig)); + break; + case FrontendTypeEnum::DVBT: { + type = FrontendType::DVBT; + frontendMap[id].settings.dvbt(readDvbtFrontendSettings(feConfig)); + break; + } + case FrontendTypeEnum::ISDBS: + type = FrontendType::ISDBS; + break; + case FrontendTypeEnum::ISDBS3: + type = FrontendType::ISDBS3; + break; + case FrontendTypeEnum::ISDBT: + type = FrontendType::ISDBT; + break; + case FrontendTypeEnum::DTMB: + // dtmb will be handled in readFrontendConfig1_1; + continue; + case FrontendTypeEnum::UNKNOWN: + ALOGW("[ConfigReader] invalid frontend type"); + return; + } + frontendMap[id].type = type; + frontendMap[id].isSoftwareFe = feConfig.getIsSoftwareFrontend(); + // TODO: complete the tune status config + frontendMap[id].tuneStatusTypes = types; + frontendMap[id].expectTuneStatuses = statuses; + } + } + } + + static void connectLiveBroadcast(LiveBroadcastHardwareConnections& live) { + auto liveConfig = getDataFlowConfiguration().getFirstClearLiveBroadcast(); + live.frontendId = liveConfig->getFrontendConnection(); + } + + static void connectScan(ScanHardwareConnections& scan) { + auto scanConfig = getDataFlowConfiguration().getFirstScan(); + scan.frontendId = scanConfig->getFrontendConnection(); + } + + static void connectDvrRecord(DvrRecordHardwareConnections& record) { + auto dataFlow = getDataFlowConfiguration(); + if (!dataFlow.hasDvrRecord()) { + record.support = false; + return; + } + auto recordConfig = dataFlow.getFirstDvrRecord(); + record.frontendId = recordConfig->getFrontendConnection(); + } + + static void connectDescrambling(DescramblingHardwareConnections& descrambling) { + auto dataFlow = getDataFlowConfiguration(); + if (!dataFlow.hasDescrambling()) { + descrambling.support = false; + return; + } + auto descConfig = dataFlow.getFirstDescrambling(); + descrambling.frontendId = descConfig->getFrontendConnection(); + } + + static void connectLnbLive(LnbLiveHardwareConnections& lnbLive) { + auto dataFlow = getDataFlowConfiguration(); + if (!dataFlow.hasLnbLive()) { + lnbLive.support = false; + return; + } + auto lnbLiveConfig = dataFlow.getFirstLnbLive(); + lnbLive.frontendId = lnbLiveConfig->getFrontendConnection(); + } + + static void connectLnbRecord(LnbRecordHardwareConnections& lnbRecord) { + auto dataFlow = getDataFlowConfiguration(); + if (!dataFlow.hasLnbRecord()) { + lnbRecord.support = false; + return; + } + auto lnbRecordConfig = dataFlow.getFirstLnbRecord(); + lnbRecord.frontendId = lnbRecordConfig->getFrontendConnection(); + } + + private: + static FrontendDvbtSettings readDvbtFrontendSettings(Frontend feConfig) { + ALOGW("[ConfigReader] type is dvbt"); + FrontendDvbtSettings dvbtSettings{ + .frequency = (uint32_t)feConfig.getFrequency(), + }; + if (!feConfig.hasDvbtFrontendSettings_optional()) { + ALOGW("[ConfigReader] no more dvbt settings"); + return dvbtSettings; + } + dvbtSettings.transmissionMode = static_cast<FrontendDvbtTransmissionMode>( + feConfig.getFirstDvbtFrontendSettings_optional()->getTransmissionMode()); + dvbtSettings.bandwidth = static_cast<FrontendDvbtBandwidth>( + feConfig.getFirstDvbtFrontendSettings_optional()->getBandwidth()); + dvbtSettings.isHighPriority = + feConfig.getFirstDvbtFrontendSettings_optional()->getIsHighPriority(); + return dvbtSettings; + } + + static FrontendDvbsSettings readDvbsFrontendSettings(Frontend feConfig) { + ALOGW("[ConfigReader] type is dvbs"); + FrontendDvbsSettings dvbsSettings{ + .frequency = (uint32_t)feConfig.getFrequency(), + }; + if (!feConfig.hasDvbsFrontendSettings_optional()) { + ALOGW("[ConfigReader] no more dvbs settings"); + return dvbsSettings; + } + dvbsSettings.symbolRate = static_cast<uint32_t>( + feConfig.getFirstDvbsFrontendSettings_optional()->getSymbolRate()); + dvbsSettings.inputStreamId = static_cast<uint32_t>( + feConfig.getFirstDvbsFrontendSettings_optional()->getInputStreamId()); + return dvbsSettings; + } + + static TunerConfiguration getTunerConfig() { return *read(configFilePath.c_str()); } + + static HardwareConfiguration getHardwareConfig() { + return *getTunerConfig().getFirstHardwareConfiguration(); + } + + static DataFlowConfiguration getDataFlowConfiguration() { + return *getTunerConfig().getFirstDataFlowConfiguration(); + } +}; diff --git a/tv/tuner/config/api/current.txt b/tv/tuner/config/api/current.txt new file mode 100644 index 0000000000..b0f410d25e --- /dev/null +++ b/tv/tuner/config/api/current.txt @@ -0,0 +1,143 @@ +// Signature format: 2.0 +package android.media.tuner.testing.configuration.V1_0 { + + public class DataFlowConfiguration { + ctor public DataFlowConfiguration(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.ClearLiveBroadcast getClearLiveBroadcast(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.Descrambling getDescrambling(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.DvrRecord getDvrRecord(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.LnbLive getLnbLive(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.LnbRecord getLnbRecord(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.Scan getScan(); + method public void setClearLiveBroadcast(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.ClearLiveBroadcast); + method public void setDescrambling(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.Descrambling); + method public void setDvrRecord(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.DvrRecord); + method public void setLnbLive(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.LnbLive); + method public void setLnbRecord(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.LnbRecord); + method public void setScan(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.Scan); + } + + public static class DataFlowConfiguration.ClearLiveBroadcast { + ctor public DataFlowConfiguration.ClearLiveBroadcast(); + method @Nullable public String getFrontendConnection(); + method public void setFrontendConnection(@Nullable String); + } + + public static class DataFlowConfiguration.Descrambling { + ctor public DataFlowConfiguration.Descrambling(); + method @Nullable public String getFrontendConnection(); + method public void setFrontendConnection(@Nullable String); + } + + public static class DataFlowConfiguration.DvrRecord { + ctor public DataFlowConfiguration.DvrRecord(); + method @Nullable public String getFrontendConnection(); + method public void setFrontendConnection(@Nullable String); + } + + public static class DataFlowConfiguration.LnbLive { + ctor public DataFlowConfiguration.LnbLive(); + method @Nullable public String getFrontendConnection(); + method public void setFrontendConnection(@Nullable String); + } + + public static class DataFlowConfiguration.LnbRecord { + ctor public DataFlowConfiguration.LnbRecord(); + method @Nullable public String getFrontendConnection(); + method public void setFrontendConnection(@Nullable String); + } + + public static class DataFlowConfiguration.Scan { + ctor public DataFlowConfiguration.Scan(); + method @Nullable public String getFrontendConnection(); + method public void setFrontendConnection(@Nullable String); + } + + public class DvbsFrontendSettings { + ctor public DvbsFrontendSettings(); + method @Nullable public java.math.BigInteger getInputStreamId(); + method @Nullable public java.math.BigInteger getSymbolRate(); + method public void setInputStreamId(@Nullable java.math.BigInteger); + method public void setSymbolRate(@Nullable java.math.BigInteger); + } + + public class DvbtFrontendSettings { + ctor public DvbtFrontendSettings(); + method @Nullable public java.math.BigInteger getBandwidth(); + method @Nullable public java.math.BigInteger getIsHighPriority(); + method @Nullable public java.math.BigInteger getTransmissionMode(); + method public void setBandwidth(@Nullable java.math.BigInteger); + method public void setIsHighPriority(@Nullable java.math.BigInteger); + method public void setTransmissionMode(@Nullable java.math.BigInteger); + } + + public class Frontend { + ctor public Frontend(); + method @Nullable public java.math.BigInteger getConnectToCicamId(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.DvbsFrontendSettings getDvbsFrontendSettings_optional(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.DvbtFrontendSettings getDvbtFrontendSettings_optional(); + method @Nullable public java.math.BigInteger getEndFrequency(); + method @Nullable public java.math.BigInteger getFrequency(); + method @Nullable public String getId(); + method @Nullable public boolean getIsSoftwareFrontend(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum getType(); + method public void setConnectToCicamId(@Nullable java.math.BigInteger); + method public void setDvbsFrontendSettings_optional(@Nullable android.media.tuner.testing.configuration.V1_0.DvbsFrontendSettings); + method public void setDvbtFrontendSettings_optional(@Nullable android.media.tuner.testing.configuration.V1_0.DvbtFrontendSettings); + method public void setEndFrequency(@Nullable java.math.BigInteger); + method public void setFrequency(@Nullable java.math.BigInteger); + method public void setId(@Nullable String); + method public void setIsSoftwareFrontend(@Nullable boolean); + method public void setType(@Nullable android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum); + } + + public enum FrontendTypeEnum { + method @NonNull public String getRawName(); + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum ANALOG; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum ATSC; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum ATSC3; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum DTMB; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum DVBC; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum DVBS; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum DVBT; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum ISDBS; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum ISDBS3; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum ISDBT; + enum_constant public static final android.media.tuner.testing.configuration.V1_0.FrontendTypeEnum UNDEFINED; + } + + public class HardwareConfiguration { + ctor public HardwareConfiguration(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.HardwareConfiguration.Frontends getFrontends(); + method public void setFrontends(@Nullable android.media.tuner.testing.configuration.V1_0.HardwareConfiguration.Frontends); + } + + public static class HardwareConfiguration.Frontends { + ctor public HardwareConfiguration.Frontends(); + method @Nullable public java.util.List<android.media.tuner.testing.configuration.V1_0.Frontend> getFrontend(); + } + + public class TunerConfiguration { + ctor public TunerConfiguration(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration getDataFlowConfiguration(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.HardwareConfiguration getHardwareConfiguration(); + method @Nullable public android.media.tuner.testing.configuration.V1_0.Version getVersion(); + method public void setDataFlowConfiguration(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration); + method public void setHardwareConfiguration(@Nullable android.media.tuner.testing.configuration.V1_0.HardwareConfiguration); + method public void setVersion(@Nullable android.media.tuner.testing.configuration.V1_0.Version); + } + + public enum Version { + method @NonNull public String getRawName(); + enum_constant public static final android.media.tuner.testing.configuration.V1_0.Version _1_0; + } + + public class XmlParser { + ctor public XmlParser(); + method @Nullable public static android.media.tuner.testing.configuration.V1_0.TunerConfiguration read(@NonNull java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method @Nullable public static String readText(@NonNull org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(@NonNull org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + +} + diff --git a/tv/tuner/config/api/last_current.txt b/tv/tuner/config/api/last_current.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tv/tuner/config/api/last_current.txt diff --git a/tv/tuner/config/api/last_removed.txt b/tv/tuner/config/api/last_removed.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tv/tuner/config/api/last_removed.txt diff --git a/tv/tuner/config/api/removed.txt b/tv/tuner/config/api/removed.txt new file mode 100644 index 0000000000..d802177e24 --- /dev/null +++ b/tv/tuner/config/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/tv/tuner/config/sample_tuner_vts_config.xml b/tv/tuner/config/sample_tuner_vts_config.xml new file mode 100644 index 0000000000..c4080d9ac7 --- /dev/null +++ b/tv/tuner/config/sample_tuner_vts_config.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- 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. +--> + +<!-- The Sample Tuner Testing Configuration. + Name the customized xml with "tuner_vts_config.xml" and push into the device + "/vendor/etc" path. Please use "tuner_testing_dynamic_configuration.xsd" to verify the xml. + The version section contains a “version” tag in the form “major.minor” e.g version=”1.0” + This shows the tuner dynamic configuration version. --> +<TunerConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude"> + <!-- Hardware Configuration section contains the configurations of all the hardwares + that would be used in the tests. In the "dataFlowConfiguration" section, each data flow + under test has its required/optional hardwares. The ids configured in the + "dataFlowConfiguration" would be used to connect the hardware to each data flow test. --> + <hardwareConfiguration> + <!-- Frontends section: + This section contains configurations of all the frontends that would be used + in the tests. + - This section is optional and can be skipped to use the default fe settings. + - The default settings can be found in the sample_tuner_vts_configurations.xml. + - The users can also override the default frontend settings using id="FE_DEFAULT". + - The users can configure 1 or more frontend elements in the frontends sections. + + Each frontend element contain the following attributes: + "id": unique id of the frontend that could be used to connect to the test the + "dataFlowConfiguration" + "type": the frontend type. The enums are defined in the xsd. + "isSoftwareFrontend": if the test environment is using hardware or software + frontend. If using software, a ts input file path needs to be configured. + "softwareFeInputPath": used as the source of the software frontend. + "connectToCicamId": if the device supports frontend connecting to cicam, the target + cicam id needs to be configured here. Supported in Tuner 1.1 or higher. + "frequency": the frequency used to configure tune and scan. + "endFrequency": the end frequency of scan. Supported in Tuner 1.1 or higher. + + Each frontend element also contains one and only one type-related "frontendSettings". + - The settings type should match the frontend "type" attribute. + - For example, when frontend type="DVBT", dvbtFrontendSettings can be configured. + - This is optional and skipping the settings would pass a setting with frequency + config only to the hal. + --> + <frontends> + <frontend id="FE_DEFAULT" type="DVBT" isSoftwareFrontend="true" + connectToCicamId="0" frequency="578000" endFrequency="800000"> + <dvbtFrontendSettings bandwidth="8" transmissionMode="1" isHighPriority="1"/> + </frontend> + <frontend id="FE_DVBS_0" type="DVBS" isSoftwareFrontend="true" + connectToCicamId="0" frequency="578000" endFrequency="800000"> + </frontend> + </frontends> + </hardwareConfiguration> + + <!-- Data flow configuration section connects each data flow under test to the ids of the + hardwares that would be used during the tests. --> + <dataFlowConfiguration> + <clearLiveBroadcast frontendConnection="FE_DEFAULT"/> + <scan frontendConnection="FE_DEFAULT"/> + <descrambling frontendConnection="FE_DEFAULT"/> + <dvrRecord frontendConnection="FE_DEFAULT"/> + <lnbLive frontendConnection="FE_DVBS_0"/> + <lnbRecord frontendConnection="FE_DVBS_0"/> + </dataFlowConfiguration> +</TunerConfiguration> diff --git a/tv/tuner/config/tuner_testing_dynamic_configuration.xsd b/tv/tuner/config/tuner_testing_dynamic_configuration.xsd new file mode 100644 index 0000000000..cd8b061309 --- /dev/null +++ b/tv/tuner/config/tuner_testing_dynamic_configuration.xsd @@ -0,0 +1,188 @@ +<?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. +--> +<xs:schema version="2.0" + elementFormDefault="qualified" + attributeFormDefault="unqualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <!-- List the dynamic config versions supported by tuner testing. --> + <xs:simpleType name="version"> + <xs:restriction base="xs:decimal"> + <xs:enumeration value="1.0"/> + </xs:restriction> + </xs:simpleType> + + <!-- FRONTEND SESSION --> + <xs:simpleType name="frontendId"> + <!-- Frontend id must be either FE_DEFAULT or FE_TYPE_NUM + <frontend id="FE_DVBS_0"/> + --> + <xs:restriction base="xs:string"> + <xs:pattern value="FE_DEFAULT|FE_[A-Z]+_[0-9]+"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="frontendTypeEnum"> + <xs:restriction base="xs:string"> + <xs:enumeration value="UNDEFINED" /> + <xs:enumeration value="ANALOG" /> + <xs:enumeration value="ATSC" /> + <xs:enumeration value="ATSC3"/> + <xs:enumeration value="DVBC"/> + <xs:enumeration value="DVBS"/> + <xs:enumeration value="DVBT"/> + <xs:enumeration value="ISDBS"/> + <xs:enumeration value="ISDBS3"/> + <xs:enumeration value="ISDBT"/> + <xs:enumeration value="DTMB"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="dvbtFrontendSettings"> + <xs:attribute name="bandwidth" type="xs:nonNegativeInteger" use="required"/> + <xs:attribute name="transmissionMode" type="xs:nonNegativeInteger" use="required"/> + <xs:attribute name="isHighPriority" type="xs:nonNegativeInteger" use="required"/> + </xs:complexType> + <xs:complexType name="dvbsFrontendSettings"> + <xs:attribute name="inputStreamId" type="xs:nonNegativeInteger" use="required"/> + <xs:attribute name="symbolRate" type="xs:nonNegativeInteger" use="required"/> + </xs:complexType> + + <xs:complexType name="frontend"> + <xs:annotation> + <xs:documentation> + Each frontend element contain the following attributes: + "id": unique id of the frontend that could be used to connect to the test the + "dataFlowConfiguration" + "type": the frontend type. The enums are defined in the xsd. + "isSoftwareFrontend": if the test environment is using hardware or software + frontend. If using software, a ts input file path needs to be configured. + "softwareFeInputPath": used as the source of the software frontend. + "connectToCicamId": if the device supports frontend connecting to cicam, the + target cicam id needs to be configured here. Supported in Tuner 1.1 or + higher. + "frequency": the frequency used to configure tune and scan. + "endFrequency": the end frequency of scan. Supported in Tuner 1.1 or higher. + + Each frontend element also contains at most one type-related "frontendSettings". + - The settings type should match the frontend "type" attribute. + - For example, when frontend type="DVBT", dvbtFrontendSettings can be + configured. + - This is optional and skipping the settings would pass a setting with frequency + config only to the hal. + </xs:documentation> + </xs:annotation> + <xs:choice minOccurs="0" maxOccurs="1"> + <!-- TODO: finish all the frontend settings structures. --> + <!--xs:element name="analog" type="analogSettings"/> + <xs:element name="atsc" type="atscSettings"/> + <xs:element name="atsc3" type="atsc3Settings"/> + <xs:element name="dvbc" type="dvbcSettings"/--> + <xs:element name="dvbsFrontendSettings" type="dvbsFrontendSettings"/> + <xs:element name="dvbtFrontendSettings" type="dvbtFrontendSettings"/> + <!--xs:element name="isdbs" type="isdbsSettings"/> + <xs:element name="isdbs3" type="isdbs3Settings"/> + <xs:element name="isdbt" type="isdbtSettings"/> + <xs:element name="dtmb" type="dtmbSettings"/--> + </xs:choice> + <xs:attribute name="id" type="frontendId" use="required"/> + <xs:attribute name="type" type="frontendTypeEnum" use="required"/> + <xs:attribute name="isSoftwareFrontend" type="xs:boolean" use="required"/> + <!-- A dvr connection is required in the data flow config section when + "isSoftwareFrontend" is true. --> + <xs:attribute name="frequency" type="xs:nonNegativeInteger" use="required"/> + <xs:attribute name="connectToCicamId" type="xs:nonNegativeInteger" use="optional"/> + <xs:attribute name="endFrequency" type="xs:nonNegativeInteger" use="optional"/> + </xs:complexType> + + <!-- HARDWARE CONFIGURATION SESSION --> + <xs:complexType name="hardwareConfiguration"> + <xs:sequence> + <xs:element name="frontends" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:annotation> + <xs:documentation xml:lang="en"> + This section contains configurations of all the frontends that would be + used in the tests. + - This section is optional and can be skipped to use the default + fe settings. + - The default settings can be found in the + sample_tuner_vts_configurations.xml. + - The users can also override the default frontend settings using + id="FE_DEFAULT". + - The users can configure 1 or more frontend elements in the + frontends sections. + </xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="frontend" type="frontend" minOccurs="1" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + + <!-- DATA FLOW CONFIGURATION SESSION --> + <xs:complexType name="dataFlowConfiguration"> + <xs:sequence> + <xs:element name="clearLiveBroadcast" minOccurs="1" maxOccurs="1"> + <xs:complexType> + <!-- TODO: add optional dvr config for software input --> + <xs:attribute name="frontendConnection" type="frontendId" use="required"/> + </xs:complexType> + </xs:element> + <xs:element name="scan" minOccurs="1" maxOccurs="1"> + <xs:complexType> + <xs:attribute name="frontendConnection" type="frontendId" use="required"/> + </xs:complexType> + </xs:element> + <xs:element name="descrambling" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:attribute name="frontendConnection" type="frontendId" use="required"/> + </xs:complexType> + </xs:element> + <xs:element name="dvrRecord" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:attribute name="frontendConnection" type="frontendId" use="required"/> + </xs:complexType> + </xs:element> + <xs:element name="lnbLive" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:attribute name="frontendConnection" type="frontendId" use="required"/> + </xs:complexType> + </xs:element> + <xs:element name="lnbRecord" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:attribute name="frontendConnection" type="frontendId" use="required"/> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + + <!-- Full Tuner Configuration. This is the root element of the configuration xml. --> + <xs:element name="TunerConfiguration"> + <xs:complexType> + <xs:sequence> + <xs:element name="hardwareConfiguration" type="hardwareConfiguration" minOccurs="1" maxOccurs="1"/> + <xs:element name="dataFlowConfiguration" type="dataFlowConfiguration" minOccurs="1" maxOccurs="1"/> + </xs:sequence> + <xs:attribute name="version" type="version"/> + </xs:complexType> + <xs:key name="frontendIdUniqueness"> + <xs:selector xpath="hardwareConfiguration/frontends/frontend"/> + <xs:field xpath="@id"/> + </xs:key> + </xs:element> +</xs:schema> |