Merge "Add validation tests for NNAPI Burst serialized format" into qt-dev
diff --git a/automotive/evs/1.0/vts/functional/FormatConvert.cpp b/automotive/evs/1.0/vts/functional/FormatConvert.cpp
index 1e8929d..3d82d32 100644
--- a/automotive/evs/1.0/vts/functional/FormatConvert.cpp
+++ b/automotive/evs/1.0/vts/functional/FormatConvert.cpp
@@ -38,7 +38,8 @@
}
-static uint32_t yuvToRgbx(const unsigned char Y, const unsigned char Uin, const unsigned char Vin) {
+static uint32_t yuvToRgbx(const unsigned char Y, const unsigned char Uin, const unsigned char Vin,
+ bool bgrxFormat = false) {
// Don't use this if you want to see the best performance. :)
// Better to do this in a pixel shader if we really have to, but on actual
// embedded hardware we expect to be able to texture directly from the YUV data
@@ -52,16 +53,24 @@
unsigned char G = (unsigned char)clamp(Gf, 0.0f, 255.0f);
unsigned char B = (unsigned char)clamp(Bf, 0.0f, 255.0f);
- return (R ) |
- (G << 8) |
- (B << 16) |
- 0xFF000000; // Fill the alpha channel with ones
+ if (!bgrxFormat) {
+ return (R ) |
+ (G << 8) |
+ (B << 16) |
+ 0xFF000000; // Fill the alpha channel with ones
+ } else {
+ return (R << 16) |
+ (G << 8) |
+ (B ) |
+ 0xFF000000; // Fill the alpha channel with ones
+ }
}
void copyNV21toRGB32(unsigned width, unsigned height,
uint8_t* src,
- uint32_t* dst, unsigned dstStridePixels)
+ uint32_t* dst, unsigned dstStridePixels,
+ bool bgrxFormat)
{
// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
// U/V array. It assumes an even width and height for the overall image, and a horizontal
@@ -84,7 +93,7 @@
for (unsigned c = 0; c < width; c++) {
unsigned uCol = (c & ~1); // uCol is always even and repeats 1:2 with Y values
unsigned vCol = uCol | 1; // vCol is always odd
- rowDest[c] = yuvToRgbx(rowY[c], rowUV[uCol], rowUV[vCol]);
+ rowDest[c] = yuvToRgbx(rowY[c], rowUV[uCol], rowUV[vCol], bgrxFormat);
}
}
}
@@ -92,7 +101,8 @@
void copyYV12toRGB32(unsigned width, unsigned height,
uint8_t* src,
- uint32_t* dst, unsigned dstStridePixels)
+ uint32_t* dst, unsigned dstStridePixels,
+ bool bgrxFormat)
{
// The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed
// by another 1/2 x 1/2 V array. It assumes an even width and height for the overall image,
@@ -118,7 +128,7 @@
uint32_t* rowDest = dst + r*dstStridePixels;
for (unsigned c = 0; c < width; c++) {
- rowDest[c] = yuvToRgbx(rowY[c], rowU[c], rowV[c]);
+ rowDest[c] = yuvToRgbx(rowY[c], rowU[c], rowV[c], bgrxFormat);
}
}
}
@@ -126,7 +136,8 @@
void copyYUYVtoRGB32(unsigned width, unsigned height,
uint8_t* src, unsigned srcStridePixels,
- uint32_t* dst, unsigned dstStridePixels)
+ uint32_t* dst, unsigned dstStridePixels,
+ bool bgrxFormat)
{
uint32_t* srcWords = (uint32_t*)src;
@@ -144,8 +155,8 @@
uint8_t V = (srcPixel >> 24) & 0xFF;
// On the RGB output, we're writing one pixel at a time
- *(dst+0) = yuvToRgbx(Y1, U, V);
- *(dst+1) = yuvToRgbx(Y2, U, V);
+ *(dst+0) = yuvToRgbx(Y1, U, V, bgrxFormat);
+ *(dst+1) = yuvToRgbx(Y2, U, V, bgrxFormat);
dst += 2;
}
@@ -156,6 +167,30 @@
}
+void copyNV21toBGR32(unsigned width, unsigned height,
+ uint8_t* src,
+ uint32_t* dst, unsigned dstStridePixels)
+{
+ return copyNV21toRGB32(width, height, src, dst, dstStridePixels, true);
+}
+
+
+void copyYV12toBGR32(unsigned width, unsigned height,
+ uint8_t* src,
+ uint32_t* dst, unsigned dstStridePixels)
+{
+ return copyYV12toRGB32(width, height, src, dst, dstStridePixels, true);
+}
+
+
+void copyYUYVtoBGR32(unsigned width, unsigned height,
+ uint8_t* src, unsigned srcStridePixels,
+ uint32_t* dst, unsigned dstStridePixels)
+{
+ return copyYUYVtoRGB32(width, height, src, srcStridePixels, dst, dstStridePixels, true);
+}
+
+
void copyMatchedInterleavedFormats(unsigned width, unsigned height,
void* src, unsigned srcStridePixels,
void* dst, unsigned dstStridePixels,
diff --git a/automotive/evs/1.0/vts/functional/FormatConvert.h b/automotive/evs/1.0/vts/functional/FormatConvert.h
index 3ff1eec..4a94f99 100644
--- a/automotive/evs/1.0/vts/functional/FormatConvert.h
+++ b/automotive/evs/1.0/vts/functional/FormatConvert.h
@@ -21,31 +21,45 @@
#include <stdint.h>
-// Given an image buffer in NV21 format (HAL_PIXEL_FORMAT_YCRCB_420_SP), output 32bit RGBx values.
-// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
+// Given an image buffer in NV21 format (HAL_PIXEL_FORMAT_YCRCB_420_SP), output 32bit RGBx/BGRx
+// values. The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
// U/V array. It assumes an even width and height for the overall image, and a horizontal
// stride that is an even multiple of 16 bytes for both the Y and UV arrays.
void copyNV21toRGB32(unsigned width, unsigned height,
uint8_t* src,
+ uint32_t* dst, unsigned dstStridePixels,
+ bool bgrxFormat = false);
+
+void copyNV21toBGR32(unsigned width, unsigned height,
+ uint8_t* src,
uint32_t* dst, unsigned dstStridePixels);
-// Given an image buffer in YV12 format (HAL_PIXEL_FORMAT_YV12), output 32bit RGBx values.
+// Given an image buffer in YV12 format (HAL_PIXEL_FORMAT_YV12), output 32bit RGBx/BGRx values.
// The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed
// by another 1/2 x 1/2 V array. It assumes an even width and height for the overall image,
// and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U,
// and V arrays.
void copyYV12toRGB32(unsigned width, unsigned height,
uint8_t* src,
+ uint32_t* dst, unsigned dstStridePixels,
+ bool bgrxFormat = false);
+
+void copyYV12toBGR32(unsigned width, unsigned height,
+ uint8_t* src,
uint32_t* dst, unsigned dstStridePixels);
-
-// Given an image buffer in YUYV format (HAL_PIXEL_FORMAT_YCBCR_422_I), output 32bit RGBx values.
-// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
+// Given an image buffer in YUYV format (HAL_PIXEL_FORMAT_YCBCR_422_I), output 32bit RGBx/BGRx
+// values. The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
// U/V array. It assumes an even width and height for the overall image, and a horizontal
// stride that is an even multiple of 16 bytes for both the Y and UV arrays.
void copyYUYVtoRGB32(unsigned width, unsigned height,
uint8_t* src, unsigned srcStrideBytes,
+ uint32_t* dst, unsigned dstStrideBytes,
+ bool bgrxFormat = false);
+
+void copyYUYVtoBGR32(unsigned width, unsigned height,
+ uint8_t* src, unsigned srcStrideBytes,
uint32_t* dst, unsigned dstStrideBytes);
diff --git a/automotive/evs/1.0/vts/functional/FrameHandler.cpp b/automotive/evs/1.0/vts/functional/FrameHandler.cpp
index a69f72b..d44ba41 100644
--- a/automotive/evs/1.0/vts/functional/FrameHandler.cpp
+++ b/automotive/evs/1.0/vts/functional/FrameHandler.cpp
@@ -231,16 +231,12 @@
uint8_t* srcPixels = nullptr;
src->lock(GRALLOC_USAGE_SW_READ_OFTEN, (void**)&srcPixels);
- // Lock our target buffer for writing (should be RGBA8888 format)
+ // Lock our target buffer for writing (should be either RGBA8888 or BGRA8888 format)
uint32_t* tgtPixels = nullptr;
tgt->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)&tgtPixels);
if (srcPixels && tgtPixels) {
- if (tgtBuffer.format != HAL_PIXEL_FORMAT_RGBA_8888) {
- // We always expect 32 bit RGB for the display output for now. Is there a need for 565?
- ALOGE("Diplay buffer is always expected to be 32bit RGBA");
- success = false;
- } else {
+ if (tgtBuffer.format == HAL_PIXEL_FORMAT_RGBA_8888) {
if (srcBuffer.format == HAL_PIXEL_FORMAT_YCRCB_420_SP) { // 420SP == NV21
copyNV21toRGB32(width, height,
srcPixels,
@@ -258,7 +254,36 @@
srcPixels, srcBuffer.stride,
tgtPixels, tgtBuffer.stride,
tgtBuffer.pixelSize);
+ } else {
+ ALOGE("Camera buffer format is not supported");
+ success = false;
}
+ } else if (tgtBuffer.format == HAL_PIXEL_FORMAT_BGRA_8888) {
+ if (srcBuffer.format == HAL_PIXEL_FORMAT_YCRCB_420_SP) { // 420SP == NV21
+ copyNV21toBGR32(width, height,
+ srcPixels,
+ tgtPixels, tgtBuffer.stride);
+ } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12
+ copyYV12toBGR32(width, height,
+ srcPixels,
+ tgtPixels, tgtBuffer.stride);
+ } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV
+ copyYUYVtoBGR32(width, height,
+ srcPixels, srcBuffer.stride,
+ tgtPixels, tgtBuffer.stride);
+ } else if (srcBuffer.format == tgtBuffer.format) { // 32bit RGBA
+ copyMatchedInterleavedFormats(width, height,
+ srcPixels, srcBuffer.stride,
+ tgtPixels, tgtBuffer.stride,
+ tgtBuffer.pixelSize);
+ } else {
+ ALOGE("Camera buffer format is not supported");
+ success = false;
+ }
+ } else {
+ // We always expect 32 bit RGB for the display output for now. Is there a need for 565?
+ ALOGE("Diplay buffer is always expected to be 32bit RGBA");
+ success = false;
}
} else {
ALOGE("Failed to lock buffer contents for contents transfer");
diff --git a/camera/device/3.4/default/ExternalCameraDevice.cpp b/camera/device/3.4/default/ExternalCameraDevice.cpp
index 3f04751..9a2fddf 100644
--- a/camera/device/3.4/default/ExternalCameraDevice.cpp
+++ b/camera/device/3.4/default/ExternalCameraDevice.cpp
@@ -342,8 +342,7 @@
256, 144,
240, 160,
256, 154,
- 240, 240,
- 320, 240};
+ 240, 180};
UPDATE(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES, jpegAvailableThumbnailSizes,
ARRAY_SIZE(jpegAvailableThumbnailSizes));
diff --git a/current.txt b/current.txt
index 3a6241c..9fdab8d 100644
--- a/current.txt
+++ b/current.txt
@@ -401,7 +401,7 @@
65a021fa89085b62fc96b2b6d3bef2f9103cf4d63379c68bc154fd9eef672852 android.hardware.health@1.0::types
b7ecf29927055ec422ec44bf776223f07d79ad9f92ccf9becf167e62c2607e7a android.hardware.keymaster@4.0::IKeymasterDevice
574e8f1499436fb4075894dcae0b36682427956ecb114f17f1fe22d116a83c6b android.hardware.neuralnetworks@1.0::IPreparedModel
-567de4ebb3a224721eabae40c4484fad2cd1608eb0e66ec9214eb88e9b15d3c9 android.hardware.neuralnetworks@1.0::types
+e75759b40a1c5f97b463b30aab91954012c9ea9e454dde308db853a56796e5a6 android.hardware.neuralnetworks@1.0::types
d51937a3567a50f239589e40300264c4b57f2c3582c6fc6df082f45eb74d90e3 android.hardware.neuralnetworks@1.1::types
1d4a5776614c08b5d794a5ec5ab04697260cbd4b3441d5935cd53ee71d19da02 android.hardware.radio@1.0::IRadioResponse
ed9da80ec0c96991fd03f0a46107815d0e50f764656e49dba4980fa5c31d5bc3 android.hardware.radio@1.0::types
@@ -515,7 +515,7 @@
92714960d1a53fc2ec557302b41c7cc93d2636d8364a44bd0f85be0c92927ff8 android.hardware.neuralnetworks@1.2::IExecutionCallback
36e1064c869965dee533c537cefbe87e54db8bd8cd45be7e0e93e00e8a43863a android.hardware.neuralnetworks@1.2::IPreparedModel
e1c734d1545e1a4ae749ff1dd9704a8e594c59aea7c8363159dc258e93e0df3b android.hardware.neuralnetworks@1.2::IPreparedModelCallback
-73e995644b1bb2678ec3ab850feb7a1b4495501953951c22316cefd67b900b3e android.hardware.neuralnetworks@1.2::types
+30f6da776bf909fc139f2bc4c176b5a74e9db0978240efcd7429d04e221bfaee android.hardware.neuralnetworks@1.2::types
cf7a4ba516a638f9b82a249c91fb603042c2d9ca43fd5aad9cf6c0401ed2a5d7 android.hardware.nfc@1.2::INfc
abf98c2ae08bf765db54edc8068e36d52eb558cff6706b6fd7c18c65a1f3fc18 android.hardware.nfc@1.2::types
4cb252dc6372a874aef666b92a6e9529915aa187521a700f0789065c3c702ead android.hardware.power.stats@1.0::IPowerStats
diff --git a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
index fc96724..cf12e2a 100644
--- a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
+++ b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
@@ -414,7 +414,7 @@
EXPECT_NE(strcmp(property_value, "nogood"), 0);
string prop_string(property_value);
EXPECT_EQ(prop_string.size(), 64);
- EXPECT_EQ(0, memcmp(verified_boot_hash.data(), prop_string.data(), verified_boot_hash.size()));
+ EXPECT_EQ(prop_string, bin2hex(verified_boot_hash));
property_get("ro.boot.vbmeta.device_state", property_value, "nogood");
EXPECT_NE(property_value, "nogood");
diff --git a/neuralnetworks/1.0/types.hal b/neuralnetworks/1.0/types.hal
index a358946..b0a1c1a 100644
--- a/neuralnetworks/1.0/types.hal
+++ b/neuralnetworks/1.0/types.hal
@@ -1225,9 +1225,9 @@
* * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
* the input.
* * 1: An {@link OperandType::INT32} scalar, specifying the output
- * height of the output tensor.
- * * 2: An {@link OperandType::INT32} scalar, specifying the output
* width of the output tensor.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
*
* Outputs:
* * 0: The output 4-D tensor, of shape
diff --git a/neuralnetworks/1.2/types.hal b/neuralnetworks/1.2/types.hal
index 087249a..a6abfc3 100644
--- a/neuralnetworks/1.2/types.hal
+++ b/neuralnetworks/1.2/types.hal
@@ -1593,9 +1593,9 @@
* the input. Since API level 29, zero batches is supported for this
* tensor.
* * 1: An {@link OperandType::INT32} scalar, specifying the output
- * height of the output tensor.
- * * 2: An {@link OperandType::INT32} scalar, specifying the output
* width of the output tensor.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
* * 3: An optional {@link OperandType::BOOL} scalar, default to false.
* Set to true to specify NCHW data layout for input0 and output0.
* Available since API level 29.
@@ -1603,15 +1603,15 @@
* Inputs (resizing by scale, since API level 29):
* * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
* the input. Zero batches is supported for this tensor.
- * * 1: A scalar, specifying height_scale, the scaling factor of the height
+ * * 1: A scalar, specifying width_scale, the scaling factor of the width
* dimension from the input tensor to the output tensor. The output
- * height is calculated as new_height = floor(height * height_scale).
+ * width is calculated as new_width = floor(width * width_scale).
* The scalar must be of {@link OperandType::FLOAT16} if input0 is
* of {@link OperandType::TENSOR_FLOAT16} and of
* {@link OperandType::FLOAT32} otherwise.
- * * 2: A scalar, specifying width_scale, the scaling factor of the width
+ * * 2: A scalar, specifying height_scale, the scaling factor of the height
* dimension from the input tensor to the output tensor. The output
- * width is calculated as new_width = floor(width * width_scale).
+ * height is calculated as new_height = floor(height * height_scale).
* The scalar must be of {@link OperandType::FLOAT16} if input0 is
* of {@link OperandType::TENSOR_FLOAT16} and of
* {@link OperandType::FLOAT32} otherwise.
@@ -3531,6 +3531,8 @@
* * {@link OperandType::TENSOR_INT32}
* * {@link OperandType::TENSOR_QUANT8_ASYMM}
*
+ * Supported tensor rank: from 1.
+ *
* Inputs:
* * 0: A tensor.
* * 1: A tensor of the same {@link OperandType} and compatible dimensions
@@ -3552,6 +3554,8 @@
* * {@link OperandType::TENSOR_INT32}
* * {@link OperandType::TENSOR_QUANT8_ASYMM}
*
+ * Supported tensor rank: from 1.
+ *
* Inputs:
* * 0: A tensor.
* * 1: A tensor of the same {@link OperandType} and compatible dimensions
@@ -4659,24 +4663,24 @@
* * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
* the input. Zero batches is supported for this tensor.
* * 1: An {@link OperandType::INT32} scalar, specifying the output
- * height of the output tensor.
- * * 2: An {@link OperandType::INT32} scalar, specifying the output
* width of the output tensor.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
* * 3: An {@link OperandType::BOOL} scalar, default to false.
* Set to true to specify NCHW data layout for input0 and output0.
*
* Inputs (resizing by scale):
* * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
* the input. Zero batches is supported for this tensor.
- * * 1: A scalar, specifying height_scale, the scaling factor of the height
+ * * 1: A scalar, specifying width_scale, the scaling factor of the width
* dimension from the input tensor to the output tensor. The output
- * height is calculated as new_height = floor(height * height_scale).
+ * width is calculated as new_width = floor(width * width_scale).
* The scalar must be of {@link OperandType::FLOAT16} if input0 is
* of {@link OperandType::TENSOR_FLOAT16} and of
* {@link OperandType::FLOAT32} otherwise.
- * * 2: A scalar, specifying width_scale, the scaling factor of the width
+ * * 2: A scalar, specifying height_scale, the scaling factor of the height
* dimension from the input tensor to the output tensor. The output
- * width is calculated as new_width = floor(width * width_scale).
+ * height is calculated as new_height = floor(height * height_scale).
* The scalar must be of {@link OperandType::FLOAT16} if input0 is
* of {@link OperandType::TENSOR_FLOAT16} and of
* {@link OperandType::FLOAT32} otherwise.
diff --git a/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp b/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp
index 167fc09..bf91560 100644
--- a/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp
+++ b/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp
@@ -16,21 +16,22 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
-#include "VtsHalNeuralnetworks.h"
+#include <android-base/logging.h>
+#include <android/hidl/memory/1.0/IMemory.h>
+#include <ftw.h>
+#include <gtest/gtest.h>
+#include <hidlmemory/mapping.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <random>
#include "Callbacks.h"
#include "GeneratedTestHarness.h"
#include "TestHarness.h"
#include "Utils.h"
-
-#include <android-base/logging.h>
-#include <android/hidl/memory/1.0/IMemory.h>
-#include <hidlmemory/mapping.h>
-#include <cstdio>
-#include <cstdlib>
-#include <random>
-
-#include <gtest/gtest.h>
+#include "VtsHalNeuralnetworks.h"
namespace android {
namespace hardware {
@@ -46,7 +47,7 @@
namespace {
-// In frameworks/ml/nn/runtime/tests/generated/, creates a hidl model of mobilenet.
+// In frameworks/ml/nn/runtime/test/generated/, creates a hidl model of mobilenet.
#include "examples/mobilenet_224_gender_basic_fixed.example.cpp"
#include "vts_models/mobilenet_224_gender_basic_fixed.model.cpp"
@@ -89,6 +90,118 @@
createCacheHandles(fileGroups, std::vector<AccessMode>(fileGroups.size(), mode), handles);
}
+// Create a chain of broadcast operations. The second operand is always constant tensor [1].
+// For simplicity, activation scalar is shared. The second operand is not shared
+// in the model to let driver maintain a non-trivial size of constant data and the corresponding
+// data locations in cache.
+//
+// --------- activation --------
+// ↓ ↓ ↓ ↓
+// E.g. input -> ADD -> ADD -> ADD -> ... -> ADD -> output
+// ↑ ↑ ↑ ↑
+// [1] [1] [1] [1]
+//
+Model createLargeTestModel(OperationType op, uint32_t len) {
+ // Model operations and operands.
+ std::vector<Operation> operations(len);
+ std::vector<Operand> operands(len * 2 + 2);
+
+ // The constant buffer pool. This contains the activation scalar, followed by the
+ // per-operation constant operands.
+ std::vector<uint8_t> operandValues(sizeof(int32_t) + len * sizeof(float));
+
+ // The activation scalar, value = 0.
+ operands[0] = {
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = len,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::CONSTANT_COPY,
+ .location = {.poolIndex = 0, .offset = 0, .length = sizeof(int32_t)},
+ };
+ memset(operandValues.data(), 0, sizeof(int32_t));
+
+ const float floatBufferValue = 1.0f;
+ for (uint32_t i = 0; i < len; i++) {
+ const uint32_t firstInputIndex = i * 2 + 1;
+ const uint32_t secondInputIndex = firstInputIndex + 1;
+ const uint32_t outputIndex = secondInputIndex + 1;
+
+ // The first operation input.
+ operands[firstInputIndex] = {
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = (i == 0 ? OperandLifeTime::MODEL_INPUT
+ : OperandLifeTime::TEMPORARY_VARIABLE),
+ .location = {},
+ };
+
+ // The second operation input, value = 1.
+ operands[secondInputIndex] = {
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::CONSTANT_COPY,
+ .location = {.poolIndex = 0,
+ .offset = static_cast<uint32_t>(i * sizeof(float) + sizeof(int32_t)),
+ .length = sizeof(float)},
+ };
+ memcpy(operandValues.data() + sizeof(int32_t) + i * sizeof(float), &floatBufferValue,
+ sizeof(float));
+
+ // The operation. All operations share the same activation scalar.
+ // The output operand is created as an input in the next iteration of the loop, in the case
+ // of all but the last member of the chain; and after the loop as a model output, in the
+ // case of the last member of the chain.
+ operations[i] = {
+ .type = op,
+ .inputs = {firstInputIndex, secondInputIndex, /*activation scalar*/ 0},
+ .outputs = {outputIndex},
+ };
+ }
+
+ // The model output.
+ operands.back() = {
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_OUTPUT,
+ .location = {},
+ };
+
+ const std::vector<uint32_t> inputIndexes = {1};
+ const std::vector<uint32_t> outputIndexes = {len * 2 + 1};
+ const std::vector<hidl_memory> pools = {};
+
+ return {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = inputIndexes,
+ .outputIndexes = outputIndexes,
+ .operandValues = operandValues,
+ .pools = pools,
+ };
+}
+
+// MixedTypedExample is defined in frameworks/ml/nn/tools/test_generator/include/TestHarness.h.
+// This function assumes the operation is always ADD.
+std::vector<MixedTypedExample> getLargeModelExamples(uint32_t len) {
+ float outputValue = 1.0f + static_cast<float>(len);
+ return {{.operands = {
+ // Input
+ {.operandDimensions = {{0, {1}}}, .float32Operands = {{0, {1.0f}}}},
+ // Output
+ {.operandDimensions = {{0, {1}}}, .float32Operands = {{0, {outputValue}}}}}}};
+};
+
} // namespace
// Tag for the compilation caching tests.
@@ -139,21 +252,19 @@
}
void TearDown() override {
- // The tmp directory is only removed when the driver reports caching not supported,
- // otherwise it is kept for debugging purpose.
- if (!mIsCachingSupported) {
- remove(mTmpCache.c_str());
- rmdir(mCacheDir.c_str());
+ // If the test passes, remove the tmp directory. Otherwise, keep it for debugging purposes.
+ if (!::testing::Test::HasFailure()) {
+ // Recursively remove the cache directory specified by mCacheDir.
+ auto callback = [](const char* entry, const struct stat*, int, struct FTW*) {
+ return remove(entry);
+ };
+ nftw(mCacheDir.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
}
NeuralnetworksHidlTest::TearDown();
}
- void saveModelToCache(const V1_2::Model& model, const hidl_vec<hidl_handle>& modelCache,
- const hidl_vec<hidl_handle>& dataCache, bool* supported,
- sp<IPreparedModel>* preparedModel = nullptr) {
- if (preparedModel != nullptr) *preparedModel = nullptr;
-
- // See if service can handle model.
+ // See if the service can handle the model.
+ bool isModelFullySupported(const V1_2::Model& model) {
bool fullySupportsModel = false;
Return<void> supportedCall = device->getSupportedOperations_1_2(
model,
@@ -163,9 +274,14 @@
fullySupportsModel = std::all_of(supported.begin(), supported.end(),
[](bool valid) { return valid; });
});
- ASSERT_TRUE(supportedCall.isOk());
- *supported = fullySupportsModel;
- if (!fullySupportsModel) return;
+ EXPECT_TRUE(supportedCall.isOk());
+ return fullySupportsModel;
+ }
+
+ void saveModelToCache(const V1_2::Model& model, const hidl_vec<hidl_handle>& modelCache,
+ const hidl_vec<hidl_handle>& dataCache,
+ sp<IPreparedModel>* preparedModel = nullptr) {
+ if (preparedModel != nullptr) *preparedModel = nullptr;
// Launch prepare model.
sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
@@ -199,8 +315,8 @@
return false;
}
- bool checkEarlyTermination(bool supported) {
- if (!supported) {
+ bool checkEarlyTermination(const V1_2::Model& model) {
+ if (!isModelFullySupported(model)) {
LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
"prepare model that it does not support.";
std::cout << "[ ] Early termination of test because vendor service cannot "
@@ -254,17 +370,16 @@
TEST_F(CompilationCachingTest, CacheSavingAndRetrieval) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
sp<IPreparedModel> preparedModel = nullptr;
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Retrieve preparedModel from cache.
@@ -296,12 +411,12 @@
TEST_F(CompilationCachingTest, CacheSavingAndRetrievalNonZeroOffset) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
sp<IPreparedModel> preparedModel = nullptr;
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
@@ -318,8 +433,7 @@
write(dataCache[i].getNativeHandle()->data[0], &dummyBytes, sizeof(dummyBytes)),
sizeof(dummyBytes));
}
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Retrieve preparedModel from cache.
@@ -360,11 +474,11 @@
TEST_F(CompilationCachingTest, SaveToCacheInvalidNumCache) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
// Test with number of model cache files greater than mNumModelCache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an additional cache file for model cache.
mModelCache.push_back({mTmpCache});
@@ -372,8 +486,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mModelCache.pop_back();
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -392,7 +505,6 @@
// Test with number of model cache files smaller than mNumModelCache.
if (mModelCache.size() > 0) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pop out the last cache file.
auto tmp = mModelCache.back();
@@ -401,8 +513,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mModelCache.push_back(tmp);
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -421,7 +532,6 @@
// Test with number of data cache files greater than mNumDataCache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an additional cache file for data cache.
mDataCache.push_back({mTmpCache});
@@ -429,8 +539,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mDataCache.pop_back();
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -449,7 +558,6 @@
// Test with number of data cache files smaller than mNumDataCache.
if (mDataCache.size() > 0) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pop out the last cache file.
auto tmp = mDataCache.back();
@@ -458,8 +566,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mDataCache.push_back(tmp);
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -479,16 +586,15 @@
TEST_F(CompilationCachingTest, PrepareModelFromCacheInvalidNumCache) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Test with number of model cache files greater than mNumModelCache.
@@ -560,11 +666,11 @@
TEST_F(CompilationCachingTest, SaveToCacheInvalidNumFd) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
// Go through each handle in model cache, test with NumFd greater than 1.
for (uint32_t i = 0; i < mNumModelCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an invalid number of fds for handle i.
mModelCache[i].push_back(mTmpCache);
@@ -572,8 +678,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mModelCache[i].pop_back();
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -592,7 +697,6 @@
// Go through each handle in model cache, test with NumFd equal to 0.
for (uint32_t i = 0; i < mNumModelCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an invalid number of fds for handle i.
auto tmp = mModelCache[i].back();
@@ -601,8 +705,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mModelCache[i].push_back(tmp);
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -621,7 +724,6 @@
// Go through each handle in data cache, test with NumFd greater than 1.
for (uint32_t i = 0; i < mNumDataCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an invalid number of fds for handle i.
mDataCache[i].push_back(mTmpCache);
@@ -629,8 +731,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mDataCache[i].pop_back();
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -649,7 +750,6 @@
// Go through each handle in data cache, test with NumFd equal to 0.
for (uint32_t i = 0; i < mNumDataCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an invalid number of fds for handle i.
auto tmp = mDataCache[i].back();
@@ -658,8 +758,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mDataCache[i].push_back(tmp);
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -679,16 +778,15 @@
TEST_F(CompilationCachingTest, PrepareModelFromCacheInvalidNumFd) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Go through each handle in model cache, test with NumFd greater than 1.
@@ -760,21 +858,20 @@
TEST_F(CompilationCachingTest, SaveToCacheInvalidAccessMode) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE);
std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE);
// Go through each handle in model cache, test with invalid access mode.
for (uint32_t i = 0; i < mNumModelCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
modelCacheMode[i] = AccessMode::READ_ONLY;
createCacheHandles(mModelCache, modelCacheMode, &modelCache);
createCacheHandles(mDataCache, dataCacheMode, &dataCache);
modelCacheMode[i] = AccessMode::READ_WRITE;
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -793,15 +890,13 @@
// Go through each handle in data cache, test with invalid access mode.
for (uint32_t i = 0; i < mNumDataCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
dataCacheMode[i] = AccessMode::READ_ONLY;
createCacheHandles(mModelCache, modelCacheMode, &modelCache);
createCacheHandles(mDataCache, dataCacheMode, &dataCache);
dataCacheMode[i] = AccessMode::READ_WRITE;
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -821,18 +916,17 @@
TEST_F(CompilationCachingTest, PrepareModelFromCacheInvalidAccessMode) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE);
std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE);
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Go through each handle in model cache, test with invalid access mode.
@@ -864,6 +958,212 @@
}
}
+// Copy file contents between file groups.
+// The outer vector corresponds to handles and the inner vector is for fds held by each handle.
+// The outer vector sizes must match and the inner vectors must have size = 1.
+static void copyCacheFiles(const std::vector<std::vector<std::string>>& from,
+ const std::vector<std::vector<std::string>>& to) {
+ constexpr size_t kBufferSize = 1000000;
+ uint8_t buffer[kBufferSize];
+
+ ASSERT_EQ(from.size(), to.size());
+ for (uint32_t i = 0; i < from.size(); i++) {
+ ASSERT_EQ(from[i].size(), 1u);
+ ASSERT_EQ(to[i].size(), 1u);
+ int fromFd = open(from[i][0].c_str(), O_RDONLY);
+ int toFd = open(to[i][0].c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+ ASSERT_GE(fromFd, 0);
+ ASSERT_GE(toFd, 0);
+
+ ssize_t readBytes;
+ while ((readBytes = read(fromFd, &buffer, kBufferSize)) > 0) {
+ ASSERT_EQ(write(toFd, &buffer, readBytes), readBytes);
+ }
+ ASSERT_GE(readBytes, 0);
+
+ close(fromFd);
+ close(toFd);
+ }
+}
+
+// Number of operations in the large test model.
+constexpr uint32_t kLargeModelSize = 100;
+constexpr uint32_t kNumIterationsTOCTOU = 100;
+
+TEST_F(CompilationCachingTest, SaveToCache_TOCTOU) {
+ if (!mIsCachingSupported) return;
+
+ // Create test models and check if fully supported by the service.
+ const Model testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+ if (checkEarlyTermination(testModelMul)) return;
+ const Model testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+ if (checkEarlyTermination(testModelAdd)) return;
+
+ // Save the testModelMul compilation to cache.
+ auto modelCacheMul = mModelCache;
+ for (auto& cache : modelCacheMul) {
+ cache[0].append("_mul");
+ }
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModelMul, modelCache, dataCache);
+ }
+
+ // Use a different token for testModelAdd.
+ mToken[0]++;
+
+ // This test is probabilistic, so we run it multiple times.
+ for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) {
+ // Save the testModelAdd compilation to cache.
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+
+ // Spawn a thread to copy the cache content concurrently while saving to cache.
+ std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache));
+ saveModelToCache(testModelAdd, modelCache, dataCache);
+ thread.join();
+ }
+
+ // Retrieve preparedModel from cache.
+ {
+ sp<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+
+ // The preparation may fail or succeed, but must not crash. If the preparation succeeds,
+ // the prepared model must be executed with the correct result and not crash.
+ if (status != ErrorStatus::NONE) {
+ ASSERT_EQ(preparedModel, nullptr);
+ } else {
+ ASSERT_NE(preparedModel, nullptr);
+ generated_tests::EvaluatePreparedModel(
+ preparedModel, [](int) { return false; },
+ getLargeModelExamples(kLargeModelSize),
+ testModelAdd.relaxComputationFloat32toFloat16,
+ /*testDynamicOutputShape=*/false);
+ }
+ }
+ }
+}
+
+TEST_F(CompilationCachingTest, PrepareFromCache_TOCTOU) {
+ if (!mIsCachingSupported) return;
+
+ // Create test models and check if fully supported by the service.
+ const Model testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+ if (checkEarlyTermination(testModelMul)) return;
+ const Model testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+ if (checkEarlyTermination(testModelAdd)) return;
+
+ // Save the testModelMul compilation to cache.
+ auto modelCacheMul = mModelCache;
+ for (auto& cache : modelCacheMul) {
+ cache[0].append("_mul");
+ }
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModelMul, modelCache, dataCache);
+ }
+
+ // Use a different token for testModelAdd.
+ mToken[0]++;
+
+ // This test is probabilistic, so we run it multiple times.
+ for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) {
+ // Save the testModelAdd compilation to cache.
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModelAdd, modelCache, dataCache);
+ }
+
+ // Retrieve preparedModel from cache.
+ {
+ sp<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+
+ // Spawn a thread to copy the cache content concurrently while preparing from cache.
+ std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache));
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ thread.join();
+
+ // The preparation may fail or succeed, but must not crash. If the preparation succeeds,
+ // the prepared model must be executed with the correct result and not crash.
+ if (status != ErrorStatus::NONE) {
+ ASSERT_EQ(preparedModel, nullptr);
+ } else {
+ ASSERT_NE(preparedModel, nullptr);
+ generated_tests::EvaluatePreparedModel(
+ preparedModel, [](int) { return false; },
+ getLargeModelExamples(kLargeModelSize),
+ testModelAdd.relaxComputationFloat32toFloat16,
+ /*testDynamicOutputShape=*/false);
+ }
+ }
+ }
+}
+
+TEST_F(CompilationCachingTest, ReplaceSecuritySensitiveCache) {
+ if (!mIsCachingSupported) return;
+
+ // Create test models and check if fully supported by the service.
+ const Model testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+ if (checkEarlyTermination(testModelMul)) return;
+ const Model testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+ if (checkEarlyTermination(testModelAdd)) return;
+
+ // Save the testModelMul compilation to cache.
+ auto modelCacheMul = mModelCache;
+ for (auto& cache : modelCacheMul) {
+ cache[0].append("_mul");
+ }
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModelMul, modelCache, dataCache);
+ }
+
+ // Use a different token for testModelAdd.
+ mToken[0]++;
+
+ // Save the testModelAdd compilation to cache.
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModelAdd, modelCache, dataCache);
+ }
+
+ // Replace the model cache of testModelAdd with testModelMul.
+ copyCacheFiles(modelCacheMul, mModelCache);
+
+ // Retrieve the preparedModel from cache, expect failure.
+ {
+ sp<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+}
+
class CompilationCachingSecurityTest : public CompilationCachingTest,
public ::testing::WithParamInterface<uint32_t> {
protected:
@@ -879,34 +1179,15 @@
return dis(generator);
}
- const uint32_t kSeed = GetParam();
- std::mt19937 generator;
-};
-
-TEST_P(CompilationCachingSecurityTest, CorruptedSecuritySensitiveCache) {
- if (!mIsCachingSupported) return;
-
- // Create test HIDL model and compile.
- Model testModel = createTestModel();
-
- for (uint32_t i = 0; i < mNumModelCache; i++) {
- // Save the compilation to cache.
- {
- bool supported;
- hidl_vec<hidl_handle> modelCache, dataCache;
- createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
- createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
- }
-
- // Randomly flip one single bit of the cache entry.
- FILE* pFile = fopen(mModelCache[i][0].c_str(), "r+");
+ // Randomly flip one single bit of the cache entry.
+ void flipOneBitOfCache(const std::string& filename, bool* skip) {
+ FILE* pFile = fopen(filename.c_str(), "r+");
ASSERT_EQ(fseek(pFile, 0, SEEK_END), 0);
long int fileSize = ftell(pFile);
if (fileSize == 0) {
fclose(pFile);
- continue;
+ *skip = true;
+ return;
}
ASSERT_EQ(fseek(pFile, getRandomInt(0l, fileSize - 1), SEEK_SET), 0);
int readByte = fgetc(pFile);
@@ -914,47 +1195,43 @@
ASSERT_EQ(fseek(pFile, -1, SEEK_CUR), 0);
ASSERT_NE(fputc(static_cast<uint8_t>(readByte) ^ (1U << getRandomInt(0, 7)), pFile), EOF);
fclose(pFile);
-
- // Retrieve preparedModel from cache, expect failure.
- {
- sp<IPreparedModel> preparedModel = nullptr;
- ErrorStatus status;
- hidl_vec<hidl_handle> modelCache, dataCache;
- createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
- createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
- ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
- ASSERT_EQ(preparedModel, nullptr);
- }
+ *skip = false;
}
-}
-TEST_P(CompilationCachingSecurityTest, WrongLengthSecuritySensitiveCache) {
- if (!mIsCachingSupported) return;
-
- // Create test HIDL model and compile.
- Model testModel = createTestModel();
-
- for (uint32_t i = 0; i < mNumModelCache; i++) {
- // Save the compilation to cache.
- {
- bool supported;
- hidl_vec<hidl_handle> modelCache, dataCache;
- createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
- createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
- }
-
- // Randomly append bytes to the cache entry.
- FILE* pFile = fopen(mModelCache[i][0].c_str(), "a");
+ // Randomly append bytes to the cache entry.
+ void appendBytesToCache(const std::string& filename, bool* skip) {
+ FILE* pFile = fopen(filename.c_str(), "a");
uint32_t appendLength = getRandomInt(1, 256);
for (uint32_t i = 0; i < appendLength; i++) {
ASSERT_NE(fputc(getRandomInt<uint8_t>(0, 255), pFile), EOF);
}
fclose(pFile);
+ *skip = false;
+ }
- // Retrieve preparedModel from cache, expect failure.
+ enum class ExpectedResult { GENERAL_FAILURE, NOT_CRASH };
+
+ // Test if the driver behaves as expected when given corrupted cache or token.
+ // The modifier will be invoked after save to cache but before prepare from cache.
+ // The modifier accepts one pointer argument "skip" as the returning value, indicating
+ // whether the test should be skipped or not.
+ void testCorruptedCache(ExpectedResult expected, std::function<void(bool*)> modifier) {
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
+
+ // Save the compilation to cache.
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModel, modelCache, dataCache);
+ }
+
+ bool skip = false;
+ modifier(&skip);
+ if (skip) return;
+
+ // Retrieve preparedModel from cache.
{
sp<IPreparedModel> preparedModel = nullptr;
ErrorStatus status;
@@ -962,43 +1239,66 @@
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
- ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
- ASSERT_EQ(preparedModel, nullptr);
+
+ switch (expected) {
+ case ExpectedResult::GENERAL_FAILURE:
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ ASSERT_EQ(preparedModel, nullptr);
+ break;
+ case ExpectedResult::NOT_CRASH:
+ ASSERT_EQ(preparedModel == nullptr, status != ErrorStatus::NONE);
+ break;
+ default:
+ FAIL();
+ }
}
}
+
+ const uint32_t kSeed = GetParam();
+ std::mt19937 generator;
+};
+
+TEST_P(CompilationCachingSecurityTest, CorruptedModelCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumModelCache; i++) {
+ testCorruptedCache(ExpectedResult::GENERAL_FAILURE,
+ [this, i](bool* skip) { flipOneBitOfCache(mModelCache[i][0], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongLengthModelCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumModelCache; i++) {
+ testCorruptedCache(ExpectedResult::GENERAL_FAILURE,
+ [this, i](bool* skip) { appendBytesToCache(mModelCache[i][0], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, CorruptedDataCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumDataCache; i++) {
+ testCorruptedCache(ExpectedResult::NOT_CRASH,
+ [this, i](bool* skip) { flipOneBitOfCache(mDataCache[i][0], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongLengthDataCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumDataCache; i++) {
+ testCorruptedCache(ExpectedResult::NOT_CRASH,
+ [this, i](bool* skip) { appendBytesToCache(mDataCache[i][0], skip); });
+ }
}
TEST_P(CompilationCachingSecurityTest, WrongToken) {
if (!mIsCachingSupported) return;
-
- // Create test HIDL model and compile.
- Model testModel = createTestModel();
-
- // Save the compilation to cache.
- {
- bool supported;
- hidl_vec<hidl_handle> modelCache, dataCache;
- createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
- createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
- }
-
- // Randomly flip one single bit in mToken.
- uint32_t ind = getRandomInt(0u, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN) - 1);
- mToken[ind] ^= (1U << getRandomInt(0, 7));
-
- // Retrieve the preparedModel from cache, expect failure.
- {
- sp<IPreparedModel> preparedModel = nullptr;
- ErrorStatus status;
- hidl_vec<hidl_handle> modelCache, dataCache;
- createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
- createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
- ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
- ASSERT_EQ(preparedModel, nullptr);
- }
+ testCorruptedCache(ExpectedResult::GENERAL_FAILURE, [this](bool* skip) {
+ // Randomly flip one single bit in mToken.
+ uint32_t ind =
+ getRandomInt(0u, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN) - 1);
+ mToken[ind] ^= (1U << getRandomInt(0, 7));
+ *skip = false;
+ });
}
INSTANTIATE_TEST_CASE_P(TestCompilationCaching, CompilationCachingSecurityTest,
diff --git a/soundtrigger/2.2/vts/functional/Android.bp b/soundtrigger/2.2/vts/functional/Android.bp
new file mode 100644
index 0000000..08ccd7b
--- /dev/null
+++ b/soundtrigger/2.2/vts/functional/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsHalSoundtriggerV2_2TargetTest",
+ defaults: ["VtsHalTargetTestDefaults"],
+ srcs: ["VtsHalSoundtriggerV2_2TargetTest.cpp"],
+ static_libs: [
+ "android.hardware.soundtrigger@2.0",
+ "android.hardware.soundtrigger@2.1",
+ "android.hardware.soundtrigger@2.2",
+ ],
+ test_suites: ["general-tests"],
+}