blob: 3faf99d3655789954df86e13ac53b92a0190b7ec [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <fuchsia/sysinfo/c/fidl.h>
#include <fuchsia/sysmem/c/fidl.h>
#include <fuchsia/sysmem/llcpp/fidl.h>
#include <inttypes.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/unsafe.h>
#include <lib/fidl-async-2/fidl_struct.h>
#include <lib/zx/channel.h>
#include <lib/zx/clock.h>
#include <lib/zx/event.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <zircon/errors.h>
#include <zircon/limits.h>
#include <zircon/pixelformat.h>
#include <zircon/types.h>
#include <limits>
#include <string>
#include <thread>
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include <hw/arch_ops.h>
#include <zxtest/zxtest.h>
// To dump a corpus file for sysmem_fuzz.cc test, enable SYSMEM_FUZZ_CORPUS. Files can be found
// under /data/cache/r/sys/fuchsia.com:sysmem-test:0#meta:sysmem.cmx/ on the device.
#define SYSMEM_FUZZ_CORPUS 0
// We assume one sysmem since boot, for now.
const char* kSysmemDevicePath = "/dev/class/sysmem/000";
using BufferCollectionConstraints = FidlStruct<fuchsia_sysmem_BufferCollectionConstraints,
llcpp::fuchsia::sysmem::BufferCollectionConstraints>;
using BufferCollectionConstraintsAuxBuffers =
FidlStruct<fuchsia_sysmem_BufferCollectionConstraintsAuxBuffers,
llcpp::fuchsia::sysmem::BufferCollectionConstraintsAuxBuffers>;
using BufferCollectionInfo = FidlStruct<fuchsia_sysmem_BufferCollectionInfo_2,
llcpp::fuchsia::sysmem::BufferCollectionInfo_2>;
namespace {
// This test observer is used to get the name of the current test to send to sysmem to identify the
// client.
std::string current_test_name;
class TestObserver : public zxtest::LifecycleObserver {
public:
void OnTestStart(const zxtest::TestCase& test_case, const zxtest::TestInfo& test_info) final {
current_test_name = std::string(test_case.name()) + "." + std::string(test_info.name());
}
};
TestObserver test_observer;
zx_status_t connect_to_sysmem_driver(zx::channel* allocator2_client_param) {
zx_status_t status;
zx::channel driver_client;
zx::channel driver_server;
status = zx::channel::create(0, &driver_client, &driver_server);
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
status = fdio_service_connect(kSysmemDevicePath, driver_server.release());
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
zx::channel allocator2_client;
zx::channel allocator2_server;
status = zx::channel::create(0, &allocator2_client, &allocator2_server);
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
status = fuchsia_sysmem_DriverConnectorConnect(driver_client.get(), allocator2_server.release());
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
fuchsia_sysmem_AllocatorSetDebugClientInfo(allocator2_client.get(), current_test_name.data(),
current_test_name.size(), 0u);
*allocator2_client_param = std::move(allocator2_client);
return ZX_OK;
}
zx_status_t connect_to_sysmem_service(zx::channel* allocator2_client_param) {
zx_status_t status;
zx::channel allocator2_client;
zx::channel allocator2_server;
status = zx::channel::create(0, &allocator2_client, &allocator2_server);
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator", allocator2_server.release());
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
fuchsia_sysmem_AllocatorSetDebugClientInfo(allocator2_client.get(), current_test_name.data(),
current_test_name.size(), 0u);
*allocator2_client_param = std::move(allocator2_client);
return ZX_OK;
}
zx_koid_t get_koid(zx_handle_t handle) {
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
EXPECT_EQ(status, ZX_OK, "");
ZX_ASSERT(status == ZX_OK);
return info.koid;
}
zx_koid_t get_related_koid(zx_handle_t handle) {
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
EXPECT_EQ(status, ZX_OK, "");
ZX_ASSERT(status == ZX_OK);
return info.related_koid;
}
zx_status_t verify_connectivity(zx::channel& allocator2_client) {
zx_status_t status;
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
status = fuchsia_sysmem_AllocatorAllocateNonSharedCollection(allocator2_client.get(),
collection_server.release());
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
status = fuchsia_sysmem_BufferCollectionSync(collection_client.get());
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
zx_status_t make_single_participant_collection(zx::channel* collection_client_channel) {
// We could use AllocateNonSharedCollection() to implement this function, but we're already
// using AllocateNonSharedCollection() during verify_connectivity(), so instead just set up the
// more general (and more real) way here.
zx_status_t status;
zx::channel allocator2_client;
status = connect_to_sysmem_driver(&allocator2_client);
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator2_client.get(),
token_server.release());
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
EXPECT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client.get(), token_client.release(), collection_server.release());
EXPECT_EQ(status, ZX_OK, "");
if (status != ZX_OK) {
return status;
}
*collection_client_channel = std::move(collection_client);
return ZX_OK;
}
const std::string& GetBoardName() {
static std::string s_board_name;
if (s_board_name.empty()) {
constexpr char kSysInfoPath[] = "/svc/fuchsia.sysinfo.SysInfo";
fbl::unique_fd sysinfo(open(kSysInfoPath, O_RDWR));
ZX_ASSERT(sysinfo);
zx::channel channel;
zx_status_t status =
fdio_get_service_handle(sysinfo.release(), channel.reset_and_get_address());
ZX_ASSERT(status == ZX_OK);
char board_name[fuchsia_sysinfo_BOARD_NAME_LEN + 1];
size_t actual_size;
zx_status_t fidl_status = fuchsia_sysinfo_SysInfoGetBoardName(
channel.get(), &status, board_name, sizeof(board_name), &actual_size);
ZX_ASSERT(fidl_status == ZX_OK);
ZX_ASSERT(status == ZX_OK);
board_name[actual_size] = '\0';
printf("\nFound board %s\n", board_name);
s_board_name = std::string(board_name, actual_size);
}
return s_board_name;
}
bool is_board_astro() { return GetBoardName() == "astro"; }
bool is_board_sherlock() { return GetBoardName() == "sherlock"; }
bool is_board_luis() { return GetBoardName() == "luis"; }
bool is_board_astro_sherlock_or_luis() {
if (is_board_astro()) {
return true;
}
if (is_board_sherlock()) {
return true;
}
if (is_board_luis()) {
return true;
}
return false;
}
bool is_board_with_amlogic_secure() { return is_board_astro_sherlock_or_luis(); }
bool is_board_with_amlogic_secure_vdec() { return is_board_astro_sherlock_or_luis(); }
void nanosleep_duration(zx::duration duration) {
zx_status_t status = zx::nanosleep(zx::deadline_after(duration));
ZX_ASSERT(status == ZX_OK);
}
// Faulting on write to a mapping to the VMO can't be checked currently
// because maybe it goes into CPU cache without faulting because 34580?
class SecureVmoReadTester {
public:
SecureVmoReadTester(zx::vmo secure_vmo);
~SecureVmoReadTester();
bool IsReadFromSecureAThing();
// When we're trying to read from an actual secure VMO, expect_read_success is false.
// When we're trying tor read from an aux VMO, expect_read_success is true.
void AttemptReadFromSecure(bool expect_read_success = false);
private:
zx::vmo secure_vmo_;
zx::vmar child_vmar_;
// volatile only so reads in the code actually read despite the value being
// discarded.
volatile uint8_t* map_addr_ = {};
// This is set to true just before the attempt to read.
std::atomic<bool> is_read_from_secure_attempted_ = false;
std::atomic<bool> is_read_from_secure_a_thing_ = false;
std::thread let_die_thread_;
std::atomic<bool> is_let_die_started_ = false;
};
SecureVmoReadTester::SecureVmoReadTester(zx::vmo secure_vmo) : secure_vmo_(std::move(secure_vmo)) {
// We need a child VMAR so we can clean up robustly without relying on a fault
// to occur at location where a VMO was recently mapped but which
// theoretically something else could be mapped unless we're specific with a
// VMAR that isn't letting something else get mapped there yet.
zx_vaddr_t child_vaddr;
zx_status_t status = zx::vmar::root_self()->allocate2(
ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE | ZX_VM_CAN_MAP_SPECIFIC, 0, ZX_PAGE_SIZE,
&child_vmar_, &child_vaddr);
ZX_ASSERT(status == ZX_OK);
uintptr_t map_addr_raw;
status = child_vmar_.map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_SPECIFIC | ZX_VM_MAP_RANGE, 0,
secure_vmo_, 0, ZX_PAGE_SIZE, &map_addr_raw);
ZX_ASSERT(status == ZX_OK);
map_addr_ = reinterpret_cast<uint8_t*>(map_addr_raw);
ZX_ASSERT(reinterpret_cast<uint8_t*>(child_vaddr) == map_addr_);
status = secure_vmo_.op_range(ZX_VMO_OP_CACHE_INVALIDATE, 0, ZX_PAGE_SIZE, nullptr, 0);
ZX_ASSERT(status == ZX_OK);
// But currently the read doesn't visibily fault while the vaddr is mapped to
// a secure page. Instead the read gets stuck and doesn't complete (perhaps
// internally faulting from kernel's point of view). While that's not ideal,
// we can check that the thread doing the reading doesn't get anything from
// the read while mapped to a secure page, and then let the thread fault
// normally by unmapping the secure VMO.
let_die_thread_ = std::thread([this] {
is_let_die_started_ = true;
// Ensure is_read_from_secure_attempted_ becomes true before we start
// waiting. This just increases the liklihood that we wait long enough
// for the read itself to potentially execute (expected to fault instead).
while (!is_read_from_secure_attempted_) {
nanosleep_duration(zx::msec(10));
}
// Wait 10ms for the read attempt to succed; the read attempt should not
// succeed. The read attempt may fail immediately or may get stuck. It's
// possible we might very occasionally not wait long enough for the read
// to have actually started - if that occurs the test will "pass" without
// having actually attempted the read.
nanosleep_duration(zx::msec(10));
// Let thread running fn die if it hasn't already (if it got stuck, let it
// no longer be stuck).
//
// By removing ZX_VM_PERM_READ, if the read is stuck, the read will cause a
// process-visible fault instead. We don't zx_vmar_unmap() here because the
// syscall docs aren't completely clear on whether zx_vmar_unmap() might
// make the vaddr page available for other uses.
zx_status_t status;
status = child_vmar_.protect2(0, reinterpret_cast<uintptr_t>(map_addr_), ZX_PAGE_SIZE);
ZX_ASSERT(status == ZX_OK);
});
while (!is_let_die_started_) {
nanosleep_duration(zx::msec(10));
}
}
SecureVmoReadTester::~SecureVmoReadTester() {
if (let_die_thread_.joinable()) {
let_die_thread_.join();
}
child_vmar_.destroy();
}
bool SecureVmoReadTester::IsReadFromSecureAThing() {
ZX_ASSERT(is_let_die_started_);
ZX_ASSERT(is_read_from_secure_attempted_);
return is_read_from_secure_a_thing_;
}
void SecureVmoReadTester::AttemptReadFromSecure(bool expect_read_success) {
ZX_ASSERT(is_let_die_started_);
ZX_ASSERT(!is_read_from_secure_attempted_);
is_read_from_secure_attempted_ = true;
// This attempt to read from a vaddr that's mapped to a secure paddr won't
// succeed. For now the read gets stuck while mapped to secure memory, and
// then faults when we've unmapped the VMO. This address is in a child VMAR
// so we know nothing else will be getting mapped to the vaddr.
//
// The loop is mainly for the benefit of debugging/fixing the test should the very first write,
// flush, read not force and fence a fault.
for (uint32_t i = 0; i < ZX_PAGE_SIZE; ++i) {
map_addr_[i] = 0xF0;
zx_status_t status = zx_cache_flush((const void*)&map_addr_[i], 1,
ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE);
ZX_ASSERT(status == ZX_OK);
uint8_t value = map_addr_[i];
// Despite the flush above often causing the fault to be sync, sometimes the fault doesn't
// happen but we read zero. For now, only complain if we read back something other than zero.
if (value != 0) {
is_read_from_secure_a_thing_ = true;
}
if (!expect_read_success) {
if (i % 64 == 0) {
fprintf(stderr, "%08x: ", i);
}
fprintf(stderr, "%02x ", value);
if ((i + 1) % 64 == 0) {
fprintf(stderr, "\n");
}
}
}
if (!expect_read_success) {
fprintf(stderr, "\n");
// If we made it through the whole page without faulting, yet only read zero, consider that
// success in the sense that we weren't able to read anything in secure memory. Cause the thead
// to "die" here on purpose so the test can pass. This is not the typical case, but can happen
// at least on sherlock. Typically we fault during the write, flush, read of byte 0 above.
ZX_PANIC("didn't fault, but also didn't read non-zero, so pretend to fault");
}
}
} // namespace
TEST(Sysmem, DriverConnection) {
zx_status_t status;
zx::channel allocator2_client;
status = connect_to_sysmem_driver(&allocator2_client);
ASSERT_EQ(status, ZX_OK, "");
status = verify_connectivity(allocator2_client);
ASSERT_EQ(status, ZX_OK, "");
}
TEST(Sysmem, ServiceConnection) {
zx_status_t status;
zx::channel allocator2_client;
status = connect_to_sysmem_service(&allocator2_client);
ASSERT_EQ(status, ZX_OK, "");
status = verify_connectivity(allocator2_client);
ASSERT_EQ(status, ZX_OK, "");
}
TEST(Sysmem, VerifyBufferCollectionToken) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel token2_client;
zx::channel token2_server;
status = zx::channel::create(0, &token2_client, &token2_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(fuchsia_sysmem_BufferCollectionTokenDuplicate(token_client.get(), ZX_RIGHT_SAME_RIGHTS,
token2_server.release()),
ZX_OK, "");
zx::channel not_token_client;
zx::channel not_token_server;
status = zx::channel::create(0, &not_token_client, &not_token_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(fuchsia_sysmem_BufferCollectionTokenSync(token_client.get()), ZX_OK, "");
ASSERT_EQ(fuchsia_sysmem_BufferCollectionTokenSync(token2_client.get()), ZX_OK, "");
bool is_token_valid = false;
ASSERT_EQ(fuchsia_sysmem_AllocatorValidateBufferCollectionToken(
allocator_client.get(), get_related_koid(token_client.get()), &is_token_valid),
ZX_OK, "");
ASSERT_EQ(is_token_valid, true, "");
ASSERT_EQ(fuchsia_sysmem_AllocatorValidateBufferCollectionToken(
allocator_client.get(), get_related_koid(token2_client.get()), &is_token_valid),
ZX_OK, "");
ASSERT_EQ(is_token_valid, true, "");
ASSERT_EQ(fuchsia_sysmem_AllocatorValidateBufferCollectionToken(
allocator_client.get(), get_related_koid(not_token_client.get()), &is_token_valid),
ZX_OK, "");
ASSERT_EQ(is_token_valid, false, "");
}
TEST(Sysmem, TokenOneParticipantNoImageConstraints) {
zx::channel collection_client;
zx_status_t status = make_single_participant_collection(&collection_client);
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 3;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 64 * 1024,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, 3, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.size_bytes, 64 * 1024, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_secure, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_CPU, "");
ASSERT_EQ(buffer_collection_info->settings.has_image_format_constraints, false, "");
for (uint32_t i = 0; i < 64; ++i) {
if (i < 3) {
ASSERT_NE(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
uint64_t size_bytes = 0;
status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo, &size_bytes);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(size_bytes, 64 * 1024, "");
} else {
ASSERT_EQ(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
}
}
}
TEST(Sysmem, TokenOneParticipantWithImageConstraints) {
zx_status_t status;
zx::channel allocator2_client;
status = connect_to_sysmem_driver(&allocator2_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator2_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 3;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
// This min_size_bytes is intentionally too small to hold the min_coded_width and
// min_coded_height in NV12
// format.
.min_size_bytes = 64 * 1024,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
constraints->image_format_constraints_count = 1;
fuchsia_sysmem_ImageFormatConstraints& image_constraints =
constraints->image_format_constraints[0];
image_constraints.pixel_format.type = fuchsia_sysmem_PixelFormatType_NV12;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0] = fuchsia_sysmem_ColorSpace{
.type = fuchsia_sysmem_ColorSpaceType_REC709,
};
// The min dimensions intentionally imply a min size that's larger than
// buffer_memory_constraints.min_size_bytes.
image_constraints.min_coded_width = 256;
image_constraints.max_coded_width = std::numeric_limits<uint32_t>::max();
image_constraints.min_coded_height = 256;
image_constraints.max_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.min_bytes_per_row = 256;
image_constraints.max_bytes_per_row = std::numeric_limits<uint32_t>::max();
image_constraints.max_coded_width_times_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.layers = 1;
image_constraints.coded_width_divisor = 2;
image_constraints.coded_height_divisor = 2;
image_constraints.bytes_per_row_divisor = 2;
image_constraints.start_offset_divisor = 2;
image_constraints.display_width_divisor = 1;
image_constraints.display_height_divisor = 1;
#if SYSMEM_FUZZ_CORPUS
FILE* ofp = fopen("/cache/sysmem_fuzz_corpus_buffer_collecton_constraints.dat", "wb");
if (ofp) {
fwrite(constraints.get(), sizeof(fuchsia_sysmem_BufferCollectionConstraints), 1, ofp);
fclose(ofp);
} else {
fprintf(stderr, "Failed to write sysmem BufferCollectionConstraints corpus file.\n");
fflush(stderr);
}
#endif // SYSMEM_FUZZ_CORPUS
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, 3, "");
// The size should be sufficient for the whole NV12 frame, not just min_size_bytes.
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.size_bytes, 64 * 1024 * 3 / 2, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_secure, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_CPU, "");
// We specified image_format_constraints so the result must also have
// image_format_constraints.
ASSERT_EQ(buffer_collection_info->settings.has_image_format_constraints, true, "");
for (uint32_t i = 0; i < 64; ++i) {
if (i < 3) {
ASSERT_NE(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
uint64_t size_bytes = 0;
status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo, &size_bytes);
ASSERT_EQ(status, ZX_OK, "");
// The portion of the VMO the client can use is large enough to hold the min image size,
// despite the min buffer size being smaller.
ASSERT_GE(buffer_collection_info->settings.buffer_settings.size_bytes, 64 * 1024 * 3 / 2, "");
// The vmo has room for the nominal size of the portion of the VMO the client can use.
ASSERT_LE(buffer_collection_info->buffers[i].vmo_usable_start +
buffer_collection_info->settings.buffer_settings.size_bytes,
size_bytes, "");
} else {
ASSERT_EQ(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
}
}
}
TEST(Sysmem, MinBufferCount) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 3;
constraints->min_buffer_count = 5;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 64 * 1024,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, 5, "");
}
TEST(Sysmem, BufferName) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
const char kSysmemName[] = "abcdefghijkl\0mnopqrstuvwxyz";
fuchsia_sysmem_BufferCollectionSetName(collection_client.get(), 10, kSysmemName,
sizeof(kSysmemName));
const char kLowPrioName[] = "low_pri";
fuchsia_sysmem_BufferCollectionSetName(collection_client.get(), 0, kLowPrioName,
sizeof(kLowPrioName));
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count = 1;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 4 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, 1, "");
zx_handle_t vmo = buffer_collection_info->buffers[0].vmo;
char vmo_name[ZX_MAX_NAME_LEN];
ASSERT_EQ(ZX_OK, zx_object_get_property(vmo, ZX_PROP_NAME, vmo_name, sizeof(vmo_name)));
// Should be equal up to the first null, plus an index
EXPECT_EQ(std::string("abcdefghijkl:0"), std::string(vmo_name));
EXPECT_EQ(0u, vmo_name[ZX_MAX_NAME_LEN - 1]);
}
TEST(Sysmem, NoToken) {
zx_status_t status;
zx::channel allocator2_client;
status = connect_to_sysmem_driver(&allocator2_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateNonSharedCollection(allocator2_client.get(),
collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
// Ask for display usage to encourage using the ram coherency domain.
constraints->usage.display = fuchsia_sysmem_displayUsageLayer;
constraints->min_buffer_count_for_camping = 3;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 64 * 1024,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = true,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, 3, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.size_bytes, 64 * 1024, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_secure, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_RAM, "");
ASSERT_EQ(buffer_collection_info->settings.has_image_format_constraints, false, "");
for (uint32_t i = 0; i < 64; ++i) {
if (i < 3) {
ASSERT_NE(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
uint64_t size_bytes = 0;
status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo, &size_bytes);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(size_bytes, 64 * 1024, "");
} else {
ASSERT_EQ(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
}
}
}
TEST(Sysmem, NoSync) {
zx_status_t status;
zx::channel allocator2_client_1;
status = connect_to_sysmem_driver(&allocator2_client_1);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_1;
zx::channel token_server_1;
status = zx::channel::create(0, &token_client_1, &token_server_1);
ASSERT_EQ(status, ZX_OK, "");
// Client 1 creates a token and new LogicalBufferCollection using
// AllocateSharedCollection().
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator2_client_1.get(),
token_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
const char* kAllocatorName = "TestAllocator";
fuchsia_sysmem_AllocatorSetDebugClientInfo(allocator2_client_1.get(), kAllocatorName,
strlen(kAllocatorName), 1u);
const char* kClientName = "TestClient";
fuchsia_sysmem_BufferCollectionTokenSetDebugClientInfo(token_client_1.get(), kClientName,
strlen(kClientName), 2u);
// Make another token so we can bind it and set a name on the collection.
zx::channel collection_client_3;
{
zx::channel token_client_3;
zx::channel token_server_3;
status = zx::channel::create(0, &token_client_3, &token_server_3);
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_server_3;
status = zx::channel::create(0, &collection_client_3, &collection_server_3);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionTokenDuplicate(
token_client_1.get(), ZX_RIGHT_SAME_RIGHTS, token_server_3.release());
EXPECT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionTokenSync(token_client_1.get());
EXPECT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_1.get(), token_client_3.release(), collection_server_3.release());
ASSERT_EQ(status, ZX_OK, "");
const char* kCollectionName = "TestCollection";
fuchsia_sysmem_BufferCollectionSetName(collection_client_3.get(), 1ul, kCollectionName,
strlen(kCollectionName));
}
zx::channel token_client_2;
zx::channel token_server_2;
status = zx::channel::create(0, &token_client_2, &token_server_2);
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
const char* kClient2Name = "TestClient2";
fuchsia_sysmem_BufferCollectionTokenSetDebugClientInfo(token_client_2.get(), kClient2Name,
strlen(kClient2Name), 3u);
// Close to prevent Sync on token_client_1 from failing later due to LogicalBufferCollection
// failure caused by the token handle closing.
fuchsia_sysmem_BufferCollectionTokenClose(token_client_2.get());
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_1.get(), token_client_2.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
// Duplicate has not been sent (or received) so this should fail.
status = fuchsia_sysmem_BufferCollectionSync(collection_client.get());
EXPECT_NE(status, ZX_OK, "");
// The duplicate/sync should print out an errror message but succeed.
status = fuchsia_sysmem_BufferCollectionTokenDuplicate(token_client_1.get(), ZX_RIGHT_SAME_RIGHTS,
token_server_2.release());
EXPECT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionTokenSync(token_client_1.get());
EXPECT_EQ(status, ZX_OK, "");
}
TEST(Sysmem, MultipleParticipants) {
zx_status_t status;
zx::channel allocator2_client_1;
status = connect_to_sysmem_driver(&allocator2_client_1);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_1;
zx::channel token_server_1;
status = zx::channel::create(0, &token_client_1, &token_server_1);
ASSERT_EQ(status, ZX_OK, "");
// Client 1 creates a token and new LogicalBufferCollection using
// AllocateSharedCollection().
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator2_client_1.get(),
token_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_2;
zx::channel token_server_2;
status = zx::channel::create(0, &token_client_2, &token_server_2);
ASSERT_EQ(status, ZX_OK, "");
// Client 1 duplicates its token and gives the duplicate to client 2 (this
// test is single proc, so both clients are coming from this client
// process - normally the two clients would be in separate processes with
// token_client_2 transferred to another participant).
status = fuchsia_sysmem_BufferCollectionTokenDuplicate(token_client_1.get(), ZX_RIGHT_SAME_RIGHTS,
token_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_3;
zx::channel token_server_3;
status = zx::channel::create(0, &token_client_3, &token_server_3);
ASSERT_EQ(status, ZX_OK, "");
// Client 3 is used to test a participant that doesn't set any constraints
// and only wants a notification that the allocation is done.
status = fuchsia_sysmem_BufferCollectionTokenDuplicate(token_client_1.get(), ZX_RIGHT_SAME_RIGHTS,
token_server_3.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_1;
zx::channel collection_server_1;
status = zx::channel::create(0, &collection_client_1, &collection_server_1);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_1.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_1.get(), token_client_1.release(), collection_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints_1(BufferCollectionConstraints::Default);
constraints_1->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints_1->min_buffer_count_for_camping = 3;
constraints_1->has_buffer_memory_constraints = true;
constraints_1->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
// This min_size_bytes is intentionally too small to hold the min_coded_width and
// min_coded_height in NV12
// format.
.min_size_bytes = 64 * 1024,
// Allow a max that's just large enough to accommodate the size implied
// by the min frame size and PixelFormat.
.max_size_bytes = (512 * 512) * 3 / 2,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
constraints_1->image_format_constraints_count = 1;
fuchsia_sysmem_ImageFormatConstraints& image_constraints_1 =
constraints_1->image_format_constraints[0];
image_constraints_1.pixel_format.type = fuchsia_sysmem_PixelFormatType_NV12;
image_constraints_1.color_spaces_count = 1;
image_constraints_1.color_space[0] = fuchsia_sysmem_ColorSpace{
.type = fuchsia_sysmem_ColorSpaceType_REC709,
};
// The min dimensions intentionally imply a min size that's larger than
// buffer_memory_constraints.min_size_bytes.
image_constraints_1.min_coded_width = 256;
image_constraints_1.max_coded_width = std::numeric_limits<uint32_t>::max();
image_constraints_1.min_coded_height = 256;
image_constraints_1.max_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints_1.min_bytes_per_row = 256;
image_constraints_1.max_bytes_per_row = std::numeric_limits<uint32_t>::max();
image_constraints_1.max_coded_width_times_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints_1.layers = 1;
image_constraints_1.coded_width_divisor = 2;
image_constraints_1.coded_height_divisor = 2;
image_constraints_1.bytes_per_row_divisor = 2;
image_constraints_1.start_offset_divisor = 2;
image_constraints_1.display_width_divisor = 1;
image_constraints_1.display_height_divisor = 1;
// Start with constraints_2 a copy of constraints_1. There are no handles
// in the constraints struct so a struct copy instead of clone is fine here.
BufferCollectionConstraints constraints_2(*constraints_1.get());
// Modify constraints_2 to require double the width and height.
constraints_2->image_format_constraints[0].min_coded_width = 512;
constraints_2->image_format_constraints[0].min_coded_height = 512;
#if SYSMEM_FUZZ_CORPUS
FILE* ofp = fopen("/cache/sysmem_fuzz_corpus_multi_buffer_collecton_constraints.dat", "wb");
if (ofp) {
fwrite(constraints_1.get(), sizeof(fuchsia_sysmem_BufferCollectionConstraints), 1, ofp);
fwrite(constraints_2.get(), sizeof(fuchsia_sysmem_BufferCollectionConstraints), 1, ofp);
fclose(ofp);
} else {
fprintf(stderr, "Failed to write sysmem multi BufferCollectionConstraints corpus file.\n");
fflush(stderr);
}
#endif // SYSMEM_FUZZ_CORPUS
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_1.get(), true,
constraints_1.release());
ASSERT_EQ(status, ZX_OK, "");
// Client 2 connects to sysmem separately.
zx::channel allocator2_client_2;
status = connect_to_sysmem_driver(&allocator2_client_2);
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_2;
zx::channel collection_server_2;
status = zx::channel::create(0, &collection_client_2, &collection_server_2);
ASSERT_EQ(status, ZX_OK, "");
// Just because we can, perform this sync as late as possible, just before
// the BindSharedCollection() via allocator2_client_2. Without this Sync(),
// the BindSharedCollection() might arrive at the server before the
// Duplicate() that delivered the server end of token_client_2 to sysmem,
// which would cause sysmem to not recognize the token.
status = fuchsia_sysmem_BufferCollectionSync(collection_client_1.get());
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_2.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_2.get(), token_client_2.release(), collection_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_3;
zx::channel collection_server_3;
status = zx::channel::create(0, &collection_client_3, &collection_server_3);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_3.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_2.get(), token_client_3.release(), collection_server_3.release());
ASSERT_EQ(status, ZX_OK, "");
fuchsia_sysmem_BufferCollectionConstraints empty_constraints = {};
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_3.get(), false,
&empty_constraints);
ASSERT_EQ(status, ZX_OK, "");
// Not all constraints have been input, so the buffers haven't been
// allocated yet.
zx_status_t check_status;
status = fuchsia_sysmem_BufferCollectionCheckBuffersAllocated(collection_client_1.get(),
&check_status);
ASSERT_EQ(status, ZX_OK, "");
EXPECT_EQ(check_status, ZX_ERR_UNAVAILABLE, "");
status = fuchsia_sysmem_BufferCollectionCheckBuffersAllocated(collection_client_2.get(),
&check_status);
ASSERT_EQ(status, ZX_OK, "");
EXPECT_EQ(check_status, ZX_ERR_UNAVAILABLE, "");
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_2.get(), true,
constraints_2.release());
ASSERT_EQ(status, ZX_OK, "");
//
// Only after both participants (both clients) have SetConstraints() will
// the allocation be successful.
//
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info_1(BufferCollectionInfo::Default);
// This helps with a later exact equality check.
memset(buffer_collection_info_1.get(), 0, sizeof(*buffer_collection_info_1.get()));
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client_1.get(), &allocation_status, buffer_collection_info_1.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionCheckBuffersAllocated(collection_client_1.get(),
&check_status);
ASSERT_EQ(status, ZX_OK, "");
EXPECT_EQ(check_status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionCheckBuffersAllocated(collection_client_2.get(),
&check_status);
ASSERT_EQ(status, ZX_OK, "");
EXPECT_EQ(check_status, ZX_OK, "");
BufferCollectionInfo buffer_collection_info_2(BufferCollectionInfo::Default);
// This helps with a later exact equality check.
memset(buffer_collection_info_2.get(), 0, sizeof(*buffer_collection_info_2.get()));
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client_2.get(), &allocation_status, buffer_collection_info_2.get());
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
BufferCollectionInfo buffer_collection_info_3(BufferCollectionInfo::Default);
memset(buffer_collection_info_3.get(), 0, sizeof(*buffer_collection_info_3.get()));
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client_3.get(), &allocation_status, buffer_collection_info_3.get());
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
//
// buffer_collection_info_1 and buffer_collection_info_2 should be exactly
// equal except their non-zero handle values, which should be different. We
// verify the handle values then check that the structs are exactly the same
// with handle values zeroed out.
//
// copy_1 and copy_2 intentionally don't manage their handle values.
// struct copy
fuchsia_sysmem_BufferCollectionInfo_2 copy_1 = *buffer_collection_info_1.get();
// struct copy
fuchsia_sysmem_BufferCollectionInfo_2 copy_2 = *buffer_collection_info_2.get();
for (uint32_t i = 0; i < std::size(buffer_collection_info_1->buffers); ++i) {
ASSERT_EQ(buffer_collection_info_1->buffers[i].vmo != ZX_HANDLE_INVALID,
buffer_collection_info_2->buffers[i].vmo != ZX_HANDLE_INVALID, "");
if (buffer_collection_info_1->buffers[i].vmo != ZX_HANDLE_INVALID) {
// The handle values must be different.
ASSERT_NE(buffer_collection_info_1->buffers[i].vmo, buffer_collection_info_2->buffers[i].vmo,
"");
// For now, the koid(s) are expected to be equal. This is not a
// fundamental check, in that sysmem could legitimately change in
// future to vend separate child VMOs (of the same portion of a
// non-copy-on-write parent VMO) to the two participants and that
// would still be potentially valid overall.
zx_koid_t koid_1 = get_koid(buffer_collection_info_1->buffers[i].vmo);
zx_koid_t koid_2 = get_koid(buffer_collection_info_2->buffers[i].vmo);
ASSERT_EQ(koid_1, koid_2, "");
// Prepare the copies for memcmp().
copy_1.buffers[i].vmo = ZX_HANDLE_INVALID;
copy_2.buffers[i].vmo = ZX_HANDLE_INVALID;
}
// Buffer collection 3 never got a SetConstraints(), so we get no VMOs.
ASSERT_EQ(ZX_HANDLE_INVALID, buffer_collection_info_3->buffers[i].vmo, "");
}
int32_t memcmp_result = memcmp(&copy_1, &copy_2, sizeof(copy_1));
// Check that buffer_collection_info_1 and buffer_collection_info_2 are
// consistent.
ASSERT_EQ(memcmp_result, 0, "");
memcmp_result = memcmp(&copy_1, buffer_collection_info_3.get(), sizeof(copy_1));
// Check that buffer_collection_info_1 and buffer_collection_info_3 are
// consistent, except for the vmos.
ASSERT_EQ(memcmp_result, 0, "");
//
// Verify that buffer_collection_info_1 paid attention to constraints_2, and
// that buffer_collection_info_2 makes sense.
//
// Because each specified min_buffer_count_for_camping 3, and each
// participant camping count adds together since they camp independently.
ASSERT_EQ(buffer_collection_info_1->buffer_count, 6, "");
// The size should be sufficient for the whole NV12 frame, not just
// min_size_bytes. In other words, the portion of the VMO the client can
// use is large enough to hold the min image size, despite the min buffer
// size being smaller.
ASSERT_GE(buffer_collection_info_1->settings.buffer_settings.size_bytes, (512 * 512) * 3 / 2, "");
ASSERT_EQ(buffer_collection_info_1->settings.buffer_settings.is_physically_contiguous, false, "");
ASSERT_EQ(buffer_collection_info_1->settings.buffer_settings.is_secure, false, "");
// We specified image_format_constraints so the result must also have
// image_format_constraints.
ASSERT_EQ(buffer_collection_info_1->settings.has_image_format_constraints, true, "");
for (uint32_t i = 0; i < 64; ++i) {
if (i < 6) {
ASSERT_NE(buffer_collection_info_1->buffers[i].vmo, ZX_HANDLE_INVALID, "");
ASSERT_NE(buffer_collection_info_2->buffers[i].vmo, ZX_HANDLE_INVALID, "");
uint64_t size_bytes_1 = 0;
status = zx_vmo_get_size(buffer_collection_info_1->buffers[i].vmo, &size_bytes_1);
ASSERT_EQ(status, ZX_OK, "");
uint64_t size_bytes_2 = 0;
status = zx_vmo_get_size(buffer_collection_info_2->buffers[i].vmo, &size_bytes_2);
ASSERT_EQ(status, ZX_OK, "");
// The vmo has room for the nominal size of the portion of the VMO
// the client can use. These checks should pass even if sysmem were
// to vend different child VMOs to the two participants.
ASSERT_LE(buffer_collection_info_1->buffers[i].vmo_usable_start +
buffer_collection_info_1->settings.buffer_settings.size_bytes,
size_bytes_1, "");
ASSERT_LE(buffer_collection_info_2->buffers[i].vmo_usable_start +
buffer_collection_info_2->settings.buffer_settings.size_bytes,
size_bytes_2, "");
} else {
ASSERT_EQ(buffer_collection_info_1->buffers[i].vmo, ZX_HANDLE_INVALID, "");
ASSERT_EQ(buffer_collection_info_2->buffers[i].vmo, ZX_HANDLE_INVALID, "");
}
}
// Close to ensure grabbing null constraints from a closed collection
// doesn't crash
zx_status_t close_status = fuchsia_sysmem_BufferCollectionClose(collection_client_3.get());
EXPECT_EQ(close_status, ZX_OK, "");
}
// This test is mainly to have something in the fuzzer corpus using format modifiers.
TEST(Sysmem, ComplicatedFormatModifiers) {
zx_status_t status;
zx::channel allocator2_client_1;
status = connect_to_sysmem_driver(&allocator2_client_1);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_1;
zx::channel token_server_1;
status = zx::channel::create(0, &token_client_1, &token_server_1);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator2_client_1.get(),
token_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_2;
zx::channel token_server_2;
status = zx::channel::create(0, &token_client_2, &token_server_2);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionTokenDuplicate(token_client_1.get(), ZX_RIGHT_SAME_RIGHTS,
token_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_1;
zx::channel collection_server_1;
status = zx::channel::create(0, &collection_client_1, &collection_server_1);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_1.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_1.get(), token_client_1.release(), collection_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints_1(BufferCollectionConstraints::Default);
constraints_1->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints_1->min_buffer_count_for_camping = 1;
constexpr uint64_t kFormatModifiers[] = {
fuchsia_sysmem_FORMAT_MODIFIER_LINEAR, fuchsia_sysmem_FORMAT_MODIFIER_INTEL_I915_X_TILED,
fuchsia_sysmem_FORMAT_MODIFIER_ARM_AFBC_16X16_SPLIT_BLOCK_SPARSE_YUV_TE_TILED_HEADER,
fuchsia_sysmem_FORMAT_MODIFIER_ARM_AFBC_16X16_TE};
constraints_1->image_format_constraints_count = std::size(kFormatModifiers);
for (uint32_t i = 0; i < std::size(kFormatModifiers); i++) {
fuchsia_sysmem_ImageFormatConstraints& image_constraints_1 =
constraints_1->image_format_constraints[i];
image_constraints_1.pixel_format.type =
i < 2 ? fuchsia_sysmem_PixelFormatType_R8G8B8A8 : fuchsia_sysmem_PixelFormatType_BGRA32;
image_constraints_1.pixel_format.has_format_modifier = true;
image_constraints_1.pixel_format.format_modifier.value = kFormatModifiers[i];
image_constraints_1.color_spaces_count = 1;
image_constraints_1.color_space[0] = fuchsia_sysmem_ColorSpace{
.type = fuchsia_sysmem_ColorSpaceType_SRGB,
};
}
// Start with constraints_2 a copy of constraints_1. There are no handles
// in the constraints struct so a struct copy instead of clone is fine here.
BufferCollectionConstraints constraints_2(*constraints_1.get());
for (uint32_t i = 0; i < constraints_2->image_format_constraints_count; i++) {
// Modify constraints_2 to require nonzero image dimensions.
constraints_2->image_format_constraints[i].required_max_coded_height = 512;
constraints_2->image_format_constraints[i].required_max_coded_width = 512;
}
#if SYSMEM_FUZZ_CORPUS
FILE* ofp = fopen("/cache/sysmem_fuzz_corpus_multi_buffer_format_modifier_constraints.dat", "wb");
if (ofp) {
fwrite(constraints_1.get(), sizeof(fuchsia_sysmem_BufferCollectionConstraints), 1, ofp);
fwrite(constraints_2.get(), sizeof(fuchsia_sysmem_BufferCollectionConstraints), 1, ofp);
fclose(ofp);
} else {
fprintf(stderr,
"Failed to write sysmem multi BufferCollectionConstraints corpus file at line %d\n",
__LINE__);
fflush(stderr);
}
#endif // SYSMEM_FUZZ_CORPUS
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_1.get(), true,
constraints_1.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_2;
zx::channel collection_server_2;
status = zx::channel::create(0, &collection_client_2, &collection_server_2);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionSync(collection_client_1.get());
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_2.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_1.get(), token_client_2.release(), collection_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_2.get(), true,
constraints_2.release());
ASSERT_EQ(status, ZX_OK, "");
//
// Only after both participants (both clients) have SetConstraints() will
// the allocation be successful.
//
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info_1(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client_1.get(), &allocation_status, buffer_collection_info_1.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
}
TEST(Sysmem, ConstraintsRetainedBeyondCleanClose) {
zx_status_t status;
zx::channel allocator2_client_1;
status = connect_to_sysmem_driver(&allocator2_client_1);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_1;
zx::channel token_server_1;
status = zx::channel::create(0, &token_client_1, &token_server_1);
ASSERT_EQ(status, ZX_OK, "");
// Client 1 creates a token and new LogicalBufferCollection using
// AllocateSharedCollection().
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator2_client_1.get(),
token_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_2;
zx::channel token_server_2;
status = zx::channel::create(0, &token_client_2, &token_server_2);
ASSERT_EQ(status, ZX_OK, "");
// Client 1 duplicates its token and gives the duplicate to client 2 (this
// test is single proc, so both clients are coming from this client
// process - normally the two clients would be in separate processes with
// token_client_2 transferred to another participant).
status = fuchsia_sysmem_BufferCollectionTokenDuplicate(token_client_1.get(), ZX_RIGHT_SAME_RIGHTS,
token_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_1;
zx::channel collection_server_1;
status = zx::channel::create(0, &collection_client_1, &collection_server_1);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_1.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_1.get(), token_client_1.release(), collection_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints_1(BufferCollectionConstraints::Default);
constraints_1->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints_1->min_buffer_count_for_camping = 2;
constraints_1->has_buffer_memory_constraints = true;
constraints_1->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 64 * 1024,
.max_size_bytes = 64 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
// constraints_2 is just a copy of constraints_1 - since both participants
// specify min_buffer_count_for_camping 2, the total number of allocated
// buffers will be 4. There are no handles in the constraints struct so a
// struct copy instead of clone is fine here.
BufferCollectionConstraints constraints_2(*constraints_1.get());
ASSERT_EQ(constraints_2->min_buffer_count_for_camping, 2, "");
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_1.get(), true,
constraints_1.release());
ASSERT_EQ(status, ZX_OK, "");
// Client 2 connects to sysmem separately.
zx::channel allocator2_client_2;
status = connect_to_sysmem_driver(&allocator2_client_2);
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_2;
zx::channel collection_server_2;
status = zx::channel::create(0, &collection_client_2, &collection_server_2);
ASSERT_EQ(status, ZX_OK, "");
// Just because we can, perform this sync as late as possible, just before
// the BindSharedCollection() via allocator2_client_2. Without this Sync(),
// the BindSharedCollection() might arrive at the server before the
// Duplicate() that delivered the server end of token_client_2 to sysmem,
// which would cause sysmem to not recognize the token.
status = fuchsia_sysmem_BufferCollectionSync(collection_client_1.get());
ASSERT_EQ(status, ZX_OK, "");
// client 1 will now do a clean Close(), but client 1's constraints will be
// retained by the LogicalBufferCollection.
status = fuchsia_sysmem_BufferCollectionClose(collection_client_1.get());
ASSERT_EQ(status, ZX_OK, "");
// close client 1's channel.
collection_client_1.reset();
// Wait briefly so that LogicalBufferCollection will have seen the channel
// closure of client 1 before client 2 sets constraints. If we wanted to
// eliminate this sleep we could add a call to query how many
// BufferCollection views still exist per LogicalBufferCollection, but that
// call wouldn't be meant to be used by normal clients, so it seems best to
// avoid adding such a call.
nanosleep_duration(zx::msec(250));
ASSERT_NE(token_client_2.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_2.get(), token_client_2.release(), collection_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
// Not all constraints have been input (client 2 hasn't SetConstraints()
// yet), so the buffers haven't been allocated yet.
zx_status_t check_status;
status = fuchsia_sysmem_BufferCollectionCheckBuffersAllocated(collection_client_2.get(),
&check_status);
ASSERT_EQ(status, ZX_OK, "");
EXPECT_EQ(check_status, ZX_ERR_UNAVAILABLE, "");
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_2.get(), true,
constraints_2.release());
ASSERT_EQ(status, ZX_OK, "");
//
// Now that client 2 has SetConstraints(), the allocation will proceed, with
// client 1's constraints included despite client 1 having done a clean
// Close().
//
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info_2(BufferCollectionInfo::Default);
// This helps with a later exact equality check.
memset(buffer_collection_info_2.get(), 0, sizeof(*buffer_collection_info_2.get()));
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client_2.get(), &allocation_status, buffer_collection_info_2.get());
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
// The fact that this is 4 instead of 2 proves that client 1's constraints
// were taken into account.
ASSERT_EQ(buffer_collection_info_2->buffer_count, 4, "");
}
TEST(Sysmem, HeapConstraints) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.vulkan = fuchsia_sysmem_vulkanUsageTransferDst;
constraints->min_buffer_count_for_camping = 1;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 4 * 1024,
.max_size_bytes = 4 * 1024,
.physically_contiguous_required = true,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = false,
.inaccessible_domain_supported = true,
.heap_permitted_count = 1,
.heap_permitted = {fuchsia_sysmem_HeapType_SYSTEM_RAM}};
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, 1, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_INACCESSIBLE, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.heap,
fuchsia_sysmem_HeapType_SYSTEM_RAM, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, true, "");
}
TEST(Sysmem, CpuUsageAndInaccessibleDomainFails) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 1;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 4 * 1024,
.max_size_bytes = 4 * 1024,
.physically_contiguous_required = true,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = false,
.inaccessible_domain_supported = true,
.heap_permitted_count = 1,
.heap_permitted = {fuchsia_sysmem_HeapType_SYSTEM_RAM}};
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// usage.cpu != 0 && inaccessible_domain_supported is expected to result in failure to
// allocate.
ASSERT_NE(status, ZX_OK, "");
}
TEST(Sysmem, SystemRamHeapSupportsAllDomains) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
fuchsia_sysmem_CoherencyDomain domains[] = {
fuchsia_sysmem_CoherencyDomain_CPU,
fuchsia_sysmem_CoherencyDomain_RAM,
fuchsia_sysmem_CoherencyDomain_INACCESSIBLE,
};
for (const fuchsia_sysmem_CoherencyDomain domain : domains) {
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.vulkan = fuchsia_sysmem_VULKAN_IMAGE_USAGE_TRANSFER_DST;
constraints->min_buffer_count_for_camping = 1;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 4 * 1024,
.max_size_bytes = 4 * 1024,
.physically_contiguous_required = true,
.secure_required = false,
.ram_domain_supported = (domain == fuchsia_sysmem_CoherencyDomain_RAM),
.cpu_domain_supported = (domain == fuchsia_sysmem_CoherencyDomain_CPU),
.inaccessible_domain_supported = (domain == fuchsia_sysmem_CoherencyDomain_INACCESSIBLE),
.heap_permitted_count = 1,
.heap_permitted = {fuchsia_sysmem_HeapType_SYSTEM_RAM}};
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// usage.cpu != 0 && inaccessible_domain_supported is expected to result in failure to
// allocate.
ASSERT_EQ(status, ZX_OK, "Failed Allocate(): domain supported = %u", domain);
ASSERT_EQ(domain, buffer_collection_info->settings.buffer_settings.coherency_domain,
"Coherency domain doesn't match constraints");
}
}
TEST(Sysmem, RequiredSize) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 1;
constraints->has_buffer_memory_constraints = false;
constraints->image_format_constraints_count = 1;
fuchsia_sysmem_ImageFormatConstraints& image_constraints =
constraints->image_format_constraints[0];
image_constraints.pixel_format.type = fuchsia_sysmem_PixelFormatType_NV12;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0] = fuchsia_sysmem_ColorSpace{
.type = fuchsia_sysmem_ColorSpaceType_REC709,
};
image_constraints.min_coded_width = 256;
image_constraints.max_coded_width = std::numeric_limits<uint32_t>::max();
image_constraints.min_coded_height = 256;
image_constraints.max_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.min_bytes_per_row = 256;
image_constraints.max_bytes_per_row = std::numeric_limits<uint32_t>::max();
image_constraints.max_coded_width_times_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.layers = 1;
image_constraints.coded_width_divisor = 1;
image_constraints.coded_height_divisor = 1;
image_constraints.bytes_per_row_divisor = 1;
image_constraints.start_offset_divisor = 1;
image_constraints.display_width_divisor = 1;
image_constraints.display_height_divisor = 1;
image_constraints.required_max_coded_width = 512;
image_constraints.required_max_coded_height = 1024;
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
ASSERT_EQ(status, ZX_OK, "");
size_t vmo_size;
status = zx_vmo_get_size(buffer_collection_info->buffers[0].vmo, &vmo_size);
ASSERT_EQ(status, ZX_OK, "");
// Image must be at least 512x1024 NV12, due to the required max sizes
// above.
EXPECT_LE(1024 * 512 * 3 / 2, vmo_size);
}
TEST(Sysmem, CpuUsageAndNoBufferMemoryConstraints) {
zx_status_t status;
zx::channel allocator_client_1;
status = connect_to_sysmem_driver(&allocator_client_1);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_1;
zx::channel token_server_1;
status = zx::channel::create(0, &token_client_1, &token_server_1);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client_1.get(),
token_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_2;
zx::channel token_server_2;
status = zx::channel::create(0, &token_client_2, &token_server_2);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionTokenDuplicate(token_client_1.get(), ZX_RIGHT_SAME_RIGHTS,
token_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_1;
zx::channel collection_server_1;
status = zx::channel::create(0, &collection_client_1, &collection_server_1);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_1.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client_1.get(), token_client_1.release(), collection_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
// First client has CPU usage constraints but no buffer memory constraints.
BufferCollectionConstraints constraints_1(BufferCollectionConstraints::Default);
constraints_1->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints_1->min_buffer_count_for_camping = 1;
constraints_1->has_buffer_memory_constraints = false;
BufferCollectionConstraints constraints_2(BufferCollectionConstraints::Default);
constraints_2->usage.display = fuchsia_sysmem_displayUsageLayer;
constraints_2->min_buffer_count_for_camping = 1;
constraints_2->has_buffer_memory_constraints = true;
constraints_2->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 1, // must be at least 1 else no participant has specified min size
.max_size_bytes = 0xffffffff,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = true,
.cpu_domain_supported = true,
.inaccessible_domain_supported = true,
.heap_permitted_count = 0,
.heap_permitted = {},
};
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_1.get(), true,
constraints_1.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel allocator_client_2;
status = connect_to_sysmem_driver(&allocator_client_2);
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_2;
zx::channel collection_server_2;
status = zx::channel::create(0, &collection_client_2, &collection_server_2);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionSync(collection_client_1.get());
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_2.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client_2.get(), token_client_2.release(), collection_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_2.get(), true,
constraints_2.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info_1(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client_1.get(), &allocation_status, buffer_collection_info_1.get());
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info_1->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_CPU, "");
}
TEST(Sysmem, ContiguousSystemRamIsCached) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.vulkan = fuchsia_sysmem_vulkanUsageTransferDst;
constraints->min_buffer_count_for_camping = 1;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 4 * 1024,
.max_size_bytes = 4 * 1024,
.physically_contiguous_required = true,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
// Constraining this to SYSTEM_RAM is redundant for now.
.heap_permitted_count = 1,
.heap_permitted = {fuchsia_sysmem_HeapType_SYSTEM_RAM}};
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, 1, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_CPU, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.heap,
fuchsia_sysmem_HeapType_SYSTEM_RAM, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, true, "");
// We could potentially map and try some non-aligned accesses, but on x64
// that'd just work anyway IIRC, so just directly check if the cache policy
// is cached so that non-aligned accesses will work on aarch64.
//
// We're intentionally only requiring this to be true in a test that
// specifies CoherencyDomain_CPU - intentionally don't care for
// CoherencyDomain_RAM or CoherencyDomain_INACCESSIBLE (when not protected).
// CoherencyDomain_INACCESSIBLE + protected has a separate test (
// test_sysmem_protected_ram_is_uncached).
zx::unowned_vmo the_vmo(buffer_collection_info->buffers[0].vmo);
zx_info_vmo_t vmo_info{};
status = the_vmo->get_info(ZX_INFO_VMO, &vmo_info, sizeof(vmo_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(vmo_info.cache_policy, ZX_CACHE_POLICY_CACHED, "");
}
TEST(Sysmem, ContiguousSystemRamIsRecycled) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
// This needs to be larger than RAM, to know that this test is really checking if the allocations
// are being recycled, regardless of what allocation strategy sysmem might be using.
//
// Unfortunately, at least under QEMU, allocating zx_system_get_physmem() * 2 takes longer than
// the test watchdog, so instead of timing out, we early out with printf and fake "success" if
// that happens.
//
// This test currently relies on timeliness/ordering of the ZX_VMO_ZERO_CHILDREN signal and
// notification to sysmem of that signal vs. allocation of more BufferCollection(s), which to some
// extent could be viewed as an invalid thing to depend on, but on the other hand, if those
// mechanisms _are_ delayed too much, in practice we might have problems, so ... for now the test
// is not ashamed to be relying on that.
uint64_t total_bytes_to_allocate = zx_system_get_physmem() * 2;
uint64_t total_bytes_allocated = 0;
constexpr uint64_t kBytesToAllocatePerPass = 4 * 1024 * 1024;
zx::time deadline_time = zx::deadline_after(zx::sec(10));
int64_t iteration_count = 0;
zx::time start_time = zx::clock::get_monotonic();
while (total_bytes_allocated < total_bytes_to_allocate) {
if (zx::clock::get_monotonic() > deadline_time) {
// Otherwise, we'd potentially trigger the test watchdog. So far we've only seen this happen
// in QEMU environments.
printf(
"\ntest_sysmem_contiguous_system_ram_is_recycled() internal timeout - fake success - "
"total_bytes_allocated so far: %zu\n",
total_bytes_allocated);
break;
}
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.vulkan = fuchsia_sysmem_vulkanUsageTransferDst;
constraints->min_buffer_count_for_camping = 1;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = kBytesToAllocatePerPass,
.max_size_bytes = kBytesToAllocatePerPass,
.physically_contiguous_required = true,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
// Constraining this to SYSTEM_RAM is redundant for now.
.heap_permitted_count = 1,
.heap_permitted = {fuchsia_sysmem_HeapType_SYSTEM_RAM}};
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, 1, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_CPU, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.heap,
fuchsia_sysmem_HeapType_SYSTEM_RAM, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, true, "");
total_bytes_allocated += kBytesToAllocatePerPass;
iteration_count++;
// ~collection_client and ~buffer_collection_info should recycle the space used by the VMOs for
// re-use so that more can be allocated.
}
zx::time end_time = zx::clock::get_monotonic();
zx::duration duration_per_iteration = (end_time - start_time) / iteration_count;
printf("duration_per_iteration: %" PRId64 "us, or %" PRId64 "ms\n",
duration_per_iteration.to_usecs(), duration_per_iteration.to_msecs());
if (total_bytes_allocated >= total_bytes_to_allocate) {
printf("\ntest_sysmem_contiguous_system_ram_is_recycled() real success\n");
}
}
TEST(Sysmem, OnlyNoneUsageFails) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
const char* kCollectionName = "TestCollection";
fuchsia_sysmem_BufferCollectionSetName(collection_client.get(), 1ul, kCollectionName,
strlen(kCollectionName));
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.none = fuchsia_sysmem_noneUsage;
constraints->min_buffer_count_for_camping = 3;
constraints->min_buffer_count = 5;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 64 * 1024,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status{};
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
//
// If the aggregate usage only has "none" usage, allocation should fail.
// Because we weren't waiting at the time that allocation failed, we don't
// necessarily get a response from the wait.
//
// TODO(dustingreen): Once we're able to issue async client requests using
// llcpp, put the wait in flight before the SetConstraints() so we can
// verify that the wait succeeds but the allocation_status is
// ZX_ERR_NOT_SUPPORTED.
ASSERT_TRUE(status == ZX_ERR_PEER_CLOSED || allocation_status == ZX_ERR_NOT_SUPPORTED);
}
TEST(Sysmem, NoneUsageAndOtherUsageFromSingleParticipantFails) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
const char* kClientName = "TestClient";
fuchsia_sysmem_BufferCollectionSetDebugClientInfo(collection_client.get(), kClientName,
strlen(kClientName), 6u);
const char* kCollectionName = "TestCollection";
fuchsia_sysmem_BufferCollectionSetName(collection_client.get(), 1ul, kCollectionName,
strlen(kCollectionName));
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
// Specify both "none" and "cpu" usage from a single participant, which will
// cause allocation failure.
constraints->usage.none = fuchsia_sysmem_noneUsage;
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften;
constraints->min_buffer_count_for_camping = 3;
constraints->min_buffer_count = 5;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = 64 * 1024,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
//
// If the aggregate usage has both "none" usage and "cpu" usage from a
// single participant, allocation should fail.
//
// TODO(dustingreen): Once we're able to issue async client requests using
// llcpp, put the wait in flight before the SetConstraints() so we can
// verify that the wait succeeds but the allocation_status is
// ZX_ERR_NOT_SUPPORTED.
ASSERT_TRUE(status == ZX_ERR_PEER_CLOSED || allocation_status == ZX_ERR_NOT_SUPPORTED);
}
TEST(Sysmem, NoneUsageWithSeparateOtherUsageSucceeds) {
zx_status_t status;
zx::channel allocator2_client_1;
status = connect_to_sysmem_driver(&allocator2_client_1);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_1;
zx::channel token_server_1;
status = zx::channel::create(0, &token_client_1, &token_server_1);
ASSERT_EQ(status, ZX_OK, "");
// Client 1 creates a token and new LogicalBufferCollection using
// AllocateSharedCollection().
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator2_client_1.get(),
token_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client_2;
zx::channel token_server_2;
status = zx::channel::create(0, &token_client_2, &token_server_2);
ASSERT_EQ(status, ZX_OK, "");
// Client 1 duplicates its token and gives the duplicate to client 2 (this
// test is single proc, so both clients are coming from this client
// process - normally the two clients would be in separate processes with
// token_client_2 transferred to another participant).
status = fuchsia_sysmem_BufferCollectionTokenDuplicate(token_client_1.get(), ZX_RIGHT_SAME_RIGHTS,
token_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_1;
zx::channel collection_server_1;
status = zx::channel::create(0, &collection_client_1, &collection_server_1);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_1.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_1.get(), token_client_1.release(), collection_server_1.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints_1(BufferCollectionConstraints::Default);
constraints_1->usage.none = fuchsia_sysmem_noneUsage;
constraints_1->min_buffer_count_for_camping = 3;
constraints_1->has_buffer_memory_constraints = true;
constraints_1->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
// This min_size_bytes is intentionally too small to hold the min_coded_width and
// min_coded_height in NV12
// format.
.min_size_bytes = 64 * 1024,
// Allow a max that's just large enough to accomodate the size implied
// by the min frame size and PixelFormat.
.max_size_bytes = (512 * 512) * 3 / 2,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
// Start with constraints_2 a copy of constraints_1. There are no handles
// in the constraints struct so a struct copy instead of clone is fine here.
BufferCollectionConstraints constraints_2(*constraints_1.get());
// Modify constraints_2 to set non-"none" usage.
constraints_2->usage.none = 0;
constraints_2->usage.vulkan = fuchsia_sysmem_vulkanUsageTransferDst;
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_1.get(), true,
constraints_1.release());
ASSERT_EQ(status, ZX_OK, "");
// Client 2 connects to sysmem separately.
zx::channel allocator2_client_2;
status = connect_to_sysmem_driver(&allocator2_client_2);
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client_2;
zx::channel collection_server_2;
status = zx::channel::create(0, &collection_client_2, &collection_server_2);
ASSERT_EQ(status, ZX_OK, "");
// Just because we can, perform this sync as late as possible, just before
// the BindSharedCollection() via allocator2_client_2. Without this Sync(),
// the BindSharedCollection() might arrive at the server before the
// Duplicate() that delivered the server end of token_client_2 to sysmem,
// which would cause sysmem to not recognize the token.
status = fuchsia_sysmem_BufferCollectionSync(collection_client_1.get());
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client_2.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client_2.get(), token_client_2.release(), collection_server_2.release());
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client_2.get(), true,
constraints_2.release());
ASSERT_EQ(status, ZX_OK, "");
//
// Only after both participants (both clients) have SetConstraints() will
// the allocation be successful.
//
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info_1(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client_1.get(), &allocation_status, buffer_collection_info_1.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
// Success when at least one participant specifies "none" usage and at least
// one participant specifies a usage other than "none".
ASSERT_EQ(allocation_status, ZX_OK, "");
}
TEST(Sysmem, PixelFormatBgr24) {
constexpr uint32_t kWidth = 600;
constexpr uint32_t kHeight = 1;
constexpr uint32_t kStride = kWidth * ZX_PIXEL_FORMAT_BYTES(ZX_PIXEL_FORMAT_RGB_888);
constexpr uint32_t divisor = 32;
constexpr uint32_t kStrideAlign = (kStride + divisor - 1) & ~(divisor - 1);
zx_status_t status;
zx::channel allocator2_client;
status = connect_to_sysmem_driver(&allocator2_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator2_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator2_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 3;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = kStride,
.max_size_bytes = kStrideAlign,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = true,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 1,
.heap_permitted = {fuchsia_sysmem_HeapType_SYSTEM_RAM}};
constraints->image_format_constraints_count = 1;
fuchsia_sysmem_ImageFormatConstraints& image_constraints =
constraints->image_format_constraints[0];
image_constraints.pixel_format.type = fuchsia_sysmem_PixelFormatType_BGR24;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0] = fuchsia_sysmem_ColorSpace{
.type = fuchsia_sysmem_ColorSpaceType_SRGB,
};
// The min dimensions intentionally imply a min size that's larger than
// buffer_memory_constraints.min_size_bytes.
image_constraints.min_coded_width = kWidth;
image_constraints.max_coded_width = std::numeric_limits<uint32_t>::max();
image_constraints.min_coded_height = kHeight;
image_constraints.max_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.min_bytes_per_row = kStride;
image_constraints.max_bytes_per_row = std::numeric_limits<uint32_t>::max();
image_constraints.max_coded_width_times_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.layers = 1;
image_constraints.coded_width_divisor = 1;
image_constraints.coded_height_divisor = 1;
image_constraints.bytes_per_row_divisor = divisor;
image_constraints.start_offset_divisor = divisor;
image_constraints.display_width_divisor = 1;
image_constraints.display_height_divisor = 1;
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, 3, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.size_bytes, kStrideAlign, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_secure, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_CPU, "");
// We specified image_format_constraints so the result must also have
// image_format_constraints.
ASSERT_EQ(buffer_collection_info->settings.has_image_format_constraints, true, "");
ASSERT_EQ(buffer_collection_info->settings.image_format_constraints.pixel_format.type,
fuchsia_sysmem_PixelFormatType_BGR24, "");
for (uint32_t i = 0; i < 64; ++i) {
if (i < 3) {
ASSERT_NE(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
uint64_t size_bytes = 0;
status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo, &size_bytes);
ASSERT_EQ(status, ZX_OK, "");
// The portion of the VMO the client can use is large enough to hold the min image size,
// despite the min buffer size being smaller.
ASSERT_GE(buffer_collection_info->settings.buffer_settings.size_bytes, kStrideAlign, "");
// The vmo has room for the nominal size of the portion of the VMO the client can use.
ASSERT_LE(buffer_collection_info->buffers[i].vmo_usable_start +
buffer_collection_info->settings.buffer_settings.size_bytes,
size_bytes, "");
} else {
ASSERT_EQ(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
}
}
zx_status_t close_status = fuchsia_sysmem_BufferCollectionClose(collection_client.get());
EXPECT_EQ(close_status, ZX_OK, "");
}
// Test that closing a token handle that's had Close() called on it doesn't crash sysmem.
TEST(Sysmem, CloseToken) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel token2_client;
zx::channel token2_server;
status = zx::channel::create(0, &token2_client, &token2_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(fuchsia_sysmem_BufferCollectionTokenDuplicate(token_client.get(), ZX_RIGHT_SAME_RIGHTS,
token2_server.release()),
ZX_OK, "");
ASSERT_EQ(fuchsia_sysmem_BufferCollectionTokenSync(token_client.get()), ZX_OK, "");
ASSERT_EQ(fuchsia_sysmem_BufferCollectionTokenClose(token_client.get()), ZX_OK, "");
token_client.reset();
// Try to ensure sysmem processes the token closure before the sync.
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
EXPECT_EQ(fuchsia_sysmem_BufferCollectionTokenSync(token2_client.get()), ZX_OK, "");
}
TEST(Sysmem, HeapAmlogicSecure) {
if (!is_board_with_amlogic_secure()) {
return;
}
for (uint32_t i = 0; i < 64; ++i) {
bool need_aux = (i % 4 == 0);
bool allow_aux = (i % 2 == 0);
zx::channel collection_client;
zx_status_t status = make_single_participant_collection(&collection_client);
ASSERT_EQ(status, ZX_OK, "");
if (need_aux) {
BufferCollectionConstraintsAuxBuffers aux_constraints(
BufferCollectionConstraintsAuxBuffers::Default);
aux_constraints->need_clear_aux_buffers_for_secure = true;
aux_constraints->allow_clear_aux_buffers_for_secure = true;
status = fuchsia_sysmem_BufferCollectionSetConstraintsAuxBuffers(collection_client.get(),
aux_constraints.release());
ASSERT_EQ(status, ZX_OK, "");
}
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.video = fuchsia_sysmem_videoUsageHwDecoder;
constexpr uint32_t kBufferCount = 4;
constraints->min_buffer_count_for_camping = kBufferCount;
constraints->has_buffer_memory_constraints = true;
constexpr uint32_t kBufferSizeBytes = 64 * 1024;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = kBufferSizeBytes,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = true,
.secure_required = true,
.ram_domain_supported = false,
.cpu_domain_supported = false,
.inaccessible_domain_supported = true,
.heap_permitted_count = 1,
.heap_permitted = {fuchsia_sysmem_HeapType_AMLOGIC_SECURE},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
EXPECT_EQ(buffer_collection_info->buffer_count, kBufferCount, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.size_bytes, kBufferSizeBytes, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, true, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.is_secure, true, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_INACCESSIBLE, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.heap,
fuchsia_sysmem_HeapType_AMLOGIC_SECURE, "");
EXPECT_EQ(buffer_collection_info->settings.has_image_format_constraints, false, "");
BufferCollectionInfo aux_buffer_collection_info(BufferCollectionInfo::Default);
if (need_aux || allow_aux) {
status = fuchsia_sysmem_BufferCollectionGetAuxBuffers(
collection_client.get(), &allocation_status, aux_buffer_collection_info.get());
ASSERT_EQ(status, ZX_OK, "");
EXPECT_EQ(aux_buffer_collection_info->buffer_count, buffer_collection_info->buffer_count, "");
}
for (uint32_t i = 0; i < 64; ++i) {
if (i < kBufferCount) {
EXPECT_NE(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
uint64_t size_bytes = 0;
status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo, &size_bytes);
ASSERT_EQ(status, ZX_OK, "");
EXPECT_EQ(size_bytes, kBufferSizeBytes, "");
if (need_aux) {
EXPECT_NE(aux_buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
uint64_t aux_size_bytes = 0;
status = zx_vmo_get_size(aux_buffer_collection_info->buffers[i].vmo, &aux_size_bytes);
ASSERT_EQ(status, ZX_OK, "");
EXPECT_EQ(aux_size_bytes, kBufferSizeBytes, "");
} else if (allow_aux) {
// This is how v1 indicates that aux buffers weren't allocated.
EXPECT_EQ(aux_buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
}
} else {
EXPECT_EQ(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
if (need_aux) {
EXPECT_EQ(aux_buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
}
}
}
zx::vmo the_vmo = zx::vmo(buffer_collection_info->buffers[0].vmo);
buffer_collection_info->buffers[0].vmo = ZX_HANDLE_INVALID;
SecureVmoReadTester tester(std::move(the_vmo));
ASSERT_DEATH(([&] { tester.AttemptReadFromSecure(); }));
ASSERT_FALSE(tester.IsReadFromSecureAThing());
if (need_aux) {
zx::vmo aux_vmo = zx::vmo(aux_buffer_collection_info->buffers[0].vmo);
aux_buffer_collection_info->buffers[0].vmo = ZX_HANDLE_INVALID;
SecureVmoReadTester aux_tester(std::move(aux_vmo));
// This shouldn't crash for the aux VMO.
aux_tester.AttemptReadFromSecure(/*expect_read_success=*/true);
// Read from aux VMO using REE (rich execution environment, in contrast to the TEE (trusted
// execution evironment)) CPU should work. In actual usage, only the non-encrypted parts of
// the data will be present in the VMO, and the encrypted parts will be all 0xFF. The point
// of the aux VMO is to allow reading and parsing the non-encypted parts using REE CPU.
ASSERT_TRUE(aux_tester.IsReadFromSecureAThing());
}
}
}
TEST(Sysmem, HeapAmlogicSecureOnlySupportsInaccessible) {
if (!is_board_with_amlogic_secure()) {
return;
}
fuchsia_sysmem_CoherencyDomain domains[] = {
fuchsia_sysmem_CoherencyDomain_CPU,
fuchsia_sysmem_CoherencyDomain_RAM,
fuchsia_sysmem_CoherencyDomain_INACCESSIBLE,
};
for (const fuchsia_sysmem_CoherencyDomain domain : domains) {
zx::channel collection_client;
zx_status_t status = make_single_participant_collection(&collection_client);
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.video = fuchsia_sysmem_videoUsageHwDecoder;
constexpr uint32_t kBufferCount = 4;
constraints->min_buffer_count_for_camping = kBufferCount;
constraints->has_buffer_memory_constraints = true;
constexpr uint32_t kBufferSizeBytes = 64 * 1024;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = kBufferSizeBytes,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = true,
.secure_required = true,
.ram_domain_supported = (domain == fuchsia_sysmem_CoherencyDomain_RAM),
.cpu_domain_supported = (domain == fuchsia_sysmem_CoherencyDomain_CPU),
.inaccessible_domain_supported = (domain == fuchsia_sysmem_CoherencyDomain_INACCESSIBLE),
.heap_permitted_count = 1,
.heap_permitted = {fuchsia_sysmem_HeapType_AMLOGIC_SECURE},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
if (domain == fuchsia_sysmem_CoherencyDomain_INACCESSIBLE) {
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
EXPECT_EQ(buffer_collection_info->buffer_count, kBufferCount, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.size_bytes, kBufferSizeBytes, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, true,
"");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.is_secure, true, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_INACCESSIBLE, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.heap,
fuchsia_sysmem_HeapType_AMLOGIC_SECURE, "");
EXPECT_EQ(buffer_collection_info->settings.has_image_format_constraints, false, "");
} else {
ASSERT_TRUE(status != ZX_OK || allocation_status != ZX_OK,
"Sysmem should not allocate memory from secure heap with coherency domain %u",
domain);
}
}
}
TEST(Sysmem, HeapAmlogicSecureVdec) {
if (!is_board_with_amlogic_secure_vdec()) {
return;
}
for (uint32_t i = 0; i < 64; ++i) {
zx::channel collection_client;
zx_status_t status = make_single_participant_collection(&collection_client);
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.video =
fuchsia_sysmem_videoUsageDecryptorOutput | fuchsia_sysmem_videoUsageHwDecoder;
constexpr uint32_t kBufferCount = 4;
constraints->min_buffer_count_for_camping = kBufferCount;
constraints->has_buffer_memory_constraints = true;
constexpr uint32_t kBufferSizeBytes = 64 * 1024 - 1;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = kBufferSizeBytes,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = true,
.secure_required = true,
.ram_domain_supported = false,
.cpu_domain_supported = false,
.inaccessible_domain_supported = true,
.heap_permitted_count = 1,
.heap_permitted = {fuchsia_sysmem_HeapType_AMLOGIC_SECURE_VDEC},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
EXPECT_EQ(buffer_collection_info->buffer_count, kBufferCount, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.size_bytes, kBufferSizeBytes, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, true, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.is_secure, true, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_INACCESSIBLE, "");
EXPECT_EQ(buffer_collection_info->settings.buffer_settings.heap,
fuchsia_sysmem_HeapType_AMLOGIC_SECURE_VDEC, "");
EXPECT_EQ(buffer_collection_info->settings.has_image_format_constraints, false, "");
auto expected_size = fbl::round_up(kBufferSizeBytes, ZX_PAGE_SIZE);
for (uint32_t i = 0; i < 64; ++i) {
if (i < kBufferCount) {
EXPECT_NE(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
uint64_t size_bytes = 0;
status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo, &size_bytes);
ASSERT_EQ(status, ZX_OK, "");
EXPECT_EQ(size_bytes, expected_size, "");
} else {
EXPECT_EQ(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
}
}
zx::vmo the_vmo = zx::vmo(buffer_collection_info->buffers[0].vmo);
buffer_collection_info->buffers[0].vmo = ZX_HANDLE_INVALID;
SecureVmoReadTester tester(std::move(the_vmo));
ASSERT_DEATH(([&] { tester.AttemptReadFromSecure(); }));
ASSERT_FALSE(tester.IsReadFromSecureAThing());
}
}
TEST(Sysmem, CpuUsageAndInaccessibleDomainSupportedSucceeds) {
zx::channel collection_client;
zx_status_t status = make_single_participant_collection(&collection_client);
ASSERT_EQ(status, ZX_OK, "");
constexpr uint32_t kBufferCount = 3;
constexpr uint32_t kBufferSize = 64 * 1024;
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = kBufferCount;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = kBufferSize,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = true,
.heap_permitted_count = 0,
.heap_permitted = {},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
ASSERT_EQ(buffer_collection_info->buffer_count, kBufferCount, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.size_bytes, kBufferSize, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_physically_contiguous, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.is_secure, false, "");
ASSERT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
fuchsia_sysmem_CoherencyDomain_CPU, "");
ASSERT_EQ(buffer_collection_info->settings.has_image_format_constraints, false, "");
for (uint32_t i = 0; i < 64; ++i) {
if (i < kBufferCount) {
ASSERT_NE(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
uint64_t size_bytes = 0;
status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo, &size_bytes);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(size_bytes, kBufferSize, "");
} else {
ASSERT_EQ(buffer_collection_info->buffers[i].vmo, ZX_HANDLE_INVALID, "");
}
}
}
TEST(Sysmem, AllocatedBufferZeroInRam) {
constexpr uint32_t kBufferCount = 1;
// Since we're reading from buffer start to buffer end, let's not allocate too large a buffer,
// since perhaps that'd hide problems if the cache flush is missing in sysmem.
constexpr uint32_t kBufferSize = 64 * 1024;
constexpr uint32_t kIterationCount = 200;
auto zero_buffer = std::make_unique<uint8_t[]>(kBufferSize);
ZX_ASSERT(zero_buffer);
auto tmp_buffer = std::make_unique<uint8_t[]>(kBufferSize);
ZX_ASSERT(tmp_buffer);
for (uint32_t iter = 0; iter < kIterationCount; ++iter) {
zx::channel collection_client;
zx_status_t status = make_single_participant_collection(&collection_client);
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = kBufferCount;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
.min_size_bytes = kBufferSize,
.max_size_bytes = kBufferSize,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(allocation_status, ZX_OK, "");
// We intentionally don't check a bunch of stuff here. We assume that sysmem allocated
// kBufferCount (1) buffer of kBufferSize (64 KiB). That way we're comparing ASAP after buffer
// allocation, in case that helps catch any failure to actually zero in RAM. Ideally we'd read
// using a DMA in this test instead of using CPU reads, but that wouldn't be a portable test.
zx::vmo vmo(buffer_collection_info->buffers[0].vmo);
buffer_collection_info->buffers[0].vmo = ZX_HANDLE_INVALID;
// Before we read from the VMO, we need to invalidate cache for the VMO. We do this via a
// syscall since it seems like mapping would have a greater chance of doing a fence.
// Unfortunately none of these steps are guarnteed not to hide a problem with flushing or fence
// in sysmem...
status = vmo.op_range(ZX_VMO_OP_CACHE_INVALIDATE, /*offset=*/0, kBufferSize, /*buffer=*/nullptr,
/*buffer_size=*/0);
ASSERT_EQ(status, ZX_OK);
// Read using a syscall instead of mapping, just in case mapping would do a bigger fence.
status = vmo.read(tmp_buffer.get(), 0, kBufferSize);
ASSERT_EQ(status, ZX_OK);
// Any non-zero bytes could be a problem with sysmem's zeroing, or cache flushing, or fencing of
// the flush (depending on whether a given architecture is willing to cancel a cache line flush
// on later cache line invalidate, which would seem at least somewhat questionable, and may not
// be a thing). This not catching a problem doesn't mean there are no problems, so that's why
// we loop kIterationCount times to see if we can detect a problem.
EXPECT_EQ(0, memcmp(zero_buffer.get(), tmp_buffer.get(), kBufferSize));
// These should be noticed by sysmem before we've allocated enough space in the loop to cause
// any trouble allocating:
// ~vmo
// ~collection_client
}
}
// Test that most image format constraints don't need to be specified.
TEST(Sysmem, DefaultAttributes) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 1;
constraints->has_buffer_memory_constraints = false;
constraints->image_format_constraints_count = 1;
fuchsia_sysmem_ImageFormatConstraints& image_constraints =
constraints->image_format_constraints[0];
image_constraints.pixel_format.type = fuchsia_sysmem_PixelFormatType_NV12;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0] = fuchsia_sysmem_ColorSpace{
.type = fuchsia_sysmem_ColorSpaceType_REC709,
};
image_constraints.required_max_coded_width = 512;
image_constraints.required_max_coded_height = 1024;
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
ASSERT_EQ(status, ZX_OK, "");
size_t vmo_size;
status = zx_vmo_get_size(buffer_collection_info->buffers[0].vmo, &vmo_size);
ASSERT_EQ(status, ZX_OK, "");
// Image must be at least 512x1024 NV12, due to the required max sizes
// above.
EXPECT_LE(512 * 1024 * 3 / 2, vmo_size);
}
// Perform a sync IPC to ensure the server is still alive.
static void VerifyServerAlive(const zx::channel& allocator_client) {
zx::channel token_client;
zx::channel token_server;
zx_status_t status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
// Ensure server is still alive.
status = fuchsia_sysmem_BufferCollectionTokenSync(token_client.get());
EXPECT_EQ(status, ZX_OK, "");
}
// Check that the server validates how many image format constraints there are.
TEST(Sysmem, TooManyFormats) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 1;
constraints->has_buffer_memory_constraints = false;
constraints->image_format_constraints_count = 100;
for (uint32_t i = 0; i < 32; i++) {
fuchsia_sysmem_ImageFormatConstraints& image_constraints =
constraints->image_format_constraints[i];
image_constraints.pixel_format.type = fuchsia_sysmem_PixelFormatType_NV12;
image_constraints.pixel_format.has_format_modifier = true;
image_constraints.pixel_format.format_modifier.value = fuchsia_sysmem_FORMAT_MODIFIER_LINEAR;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0] = fuchsia_sysmem_ColorSpace{
.type = fuchsia_sysmem_ColorSpaceType_REC709,
};
image_constraints.required_max_coded_width = 512;
image_constraints.required_max_coded_height = 1024;
}
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
EXPECT_NE(status, ZX_OK, "");
VerifyServerAlive(allocator_client);
}
// Check that the server checks for min_buffer_count too large.
TEST(Sysmem, TooManyBuffers) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ASSERT_EQ(status, ZX_OK, "");
status = fuchsia_sysmem_AllocatorAllocateNonSharedCollection(allocator_client.get(),
collection_server.release());
ASSERT_EQ(status, ZX_OK, "");
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints.min_size_bytes = PAGE_SIZE;
constraints->min_buffer_count =
1024 * 1024 * 1024 / constraints->buffer_memory_constraints.min_size_bytes;
constraints->buffer_memory_constraints.cpu_domain_supported = true;
constraints->usage.cpu = fuchsia_sysmem_cpuUsageRead;
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ASSERT_EQ(status, ZX_OK, "");
zx_status_t allocation_status;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
EXPECT_NE(status, ZX_OK, "");
VerifyServerAlive(allocator_client);
}
class EventSinkServer : public llcpp::fuchsia::sysmem::BufferCollectionEvents::Interface {
public:
explicit EventSinkServer(async::Loop& loop) : loop_(loop) {}
void OnDuplicatedTokensKnownByServer(
OnDuplicatedTokensKnownByServerCompleter::Sync& completer) override {
EXPECT_FALSE(got_tokens_known_);
got_tokens_known_ = true;
loop_.Quit();
}
void OnBuffersAllocated(zx_status_t status,
llcpp::fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info,
OnBuffersAllocatedCompleter::Sync& completer) override {
EXPECT_TRUE(!status_);
status_ = status;
buffer_collection_info_ = std::move(buffer_collection_info);
loop_.Quit();
}
void OnAllocateSingleBufferDone(zx_status_t status,
llcpp::fuchsia::sysmem::SingleBufferInfo single_buffer_info,
OnAllocateSingleBufferDoneCompleter::Sync& completer) override {
EXPECT_TRUE(false);
}
bool got_tokens_known() const { return got_tokens_known_; }
zx_status_t status() const {
ZX_DEBUG_ASSERT(status_);
return *status_;
}
const llcpp::fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info() {
return buffer_collection_info_;
}
private:
async::Loop& loop_;
bool got_tokens_known_ = false;
std::optional<zx_status_t> status_;
llcpp::fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info_;
};
TEST(Sysmem, EventSink) {
zx::channel collection_client;
zx_status_t status = make_single_participant_collection(&collection_client);
ASSERT_EQ(status, ZX_OK, "");
zx::channel event_channel_server, event_channel_client;
ASSERT_OK(zx::channel::create(0, &event_channel_client, &event_channel_server));
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
EXPECT_OK(fuchsia_sysmem_BufferCollectionSetEventSink(collection_client.get(),
event_channel_client.release()));
EventSinkServer server(loop);
bool was_unbound = false;
EXPECT_TRUE(fidl::BindServer(loop.dispatcher(), std::move(event_channel_server), &server,
fidl::OnUnboundFn<EventSinkServer>(
[&was_unbound, &loop](EventSinkServer* server,
fidl::UnbindInfo info, zx::channel) {
was_unbound = true;
EXPECT_EQ(info.reason, fidl::UnbindInfo::kPeerClosed);
loop.Quit();
}))
.is_ok());
loop.Run();
EXPECT_TRUE(server.got_tokens_known());
loop.ResetQuit();
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 1;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{};
constraints->buffer_memory_constraints.cpu_domain_supported = true;
constraints->buffer_memory_constraints.min_size_bytes = 1;
EXPECT_OK(fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release()));
loop.Run();
EXPECT_EQ(ZX_OK, server.status());
EXPECT_EQ(1u, server.buffer_collection_info().buffer_count);
loop.ResetQuit();
EXPECT_FALSE(was_unbound);
collection_client.reset();
loop.Run();
EXPECT_TRUE(was_unbound);
loop.Shutdown();
}
bool BasicAllocationSucceeds(
fit::function<void(BufferCollectionConstraints& to_modify)> modify_constraints) {
zx_status_t status;
zx::channel allocator_client;
status = connect_to_sysmem_driver(&allocator_client);
ZX_ASSERT(status == ZX_OK);
zx::channel token_client;
zx::channel token_server;
status = zx::channel::create(0, &token_client, &token_server);
ZX_ASSERT(status == ZX_OK);
status = fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release());
ZX_ASSERT(status == ZX_OK);
zx::channel collection_client;
zx::channel collection_server;
status = zx::channel::create(0, &collection_client, &collection_server);
ZX_ASSERT(status == ZX_OK);
ZX_ASSERT(token_client.get() != ZX_HANDLE_INVALID);
status = fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release());
ZX_ASSERT(status == ZX_OK);
BufferCollectionConstraints constraints(BufferCollectionConstraints::Default);
constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
constraints->min_buffer_count_for_camping = 3;
constraints->has_buffer_memory_constraints = true;
constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{
// This min_size_bytes is intentionally too small to hold the min_coded_width and
// min_coded_height in NV12
// format.
.min_size_bytes = 64 * 1024,
.max_size_bytes = 128 * 1024,
.physically_contiguous_required = false,
.secure_required = false,
.ram_domain_supported = false,
.cpu_domain_supported = true,
.inaccessible_domain_supported = false,
.heap_permitted_count = 0,
.heap_permitted = {},
};
constraints->image_format_constraints_count = 1;
fuchsia_sysmem_ImageFormatConstraints& image_constraints =
constraints->image_format_constraints[0];
image_constraints.pixel_format.type = fuchsia_sysmem_PixelFormatType_NV12;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0] = fuchsia_sysmem_ColorSpace{
.type = fuchsia_sysmem_ColorSpaceType_REC709,
};
// The min dimensions intentionally imply a min size that's larger than
// buffer_memory_constraints.min_size_bytes.
image_constraints.min_coded_width = 256;
image_constraints.max_coded_width = std::numeric_limits<uint32_t>::max();
image_constraints.min_coded_height = 256;
image_constraints.max_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.min_bytes_per_row = 256;
image_constraints.max_bytes_per_row = std::numeric_limits<uint32_t>::max();
image_constraints.max_coded_width_times_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.layers = 1;
image_constraints.coded_width_divisor = 2;
image_constraints.coded_height_divisor = 2;
image_constraints.bytes_per_row_divisor = 2;
image_constraints.start_offset_divisor = 2;
image_constraints.display_width_divisor = 1;
image_constraints.display_height_divisor = 1;
modify_constraints(constraints);
status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
constraints.release());
ZX_ASSERT(status == ZX_OK);
zx_status_t allocation_status = ZX_OK;
BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
collection_client.get(), &allocation_status, buffer_collection_info.get());
// This is the first round-trip to/from sysmem. A failure here can be due
// to any step above failing async.
if (status != ZX_OK || allocation_status != ZX_OK) {
printf("WaitForBuffersAllocated failed - status: %d allocation_status: %d\n", status,
allocation_status);
return false;
}
// Check if the contents in allocated VMOs are already filled with zero.
// If the allocated VMO is readable, then we would expect it could be cleared by sysmem;
// otherwise we just skip this check.
zx::vmo allocated_vmo(buffer_collection_info->buffers[0].vmo);
size_t vmo_size;
status = allocated_vmo.get_size(&vmo_size);
if (status != ZX_OK) {
printf("ERROR: Cannot get size of allocated_vmo: %d\n", status);
return false;
}
size_t size_bytes = std::min(
vmo_size, static_cast<size_t>(buffer_collection_info->settings.buffer_settings.size_bytes));
std::vector<uint8_t> bytes(size_bytes, 0xff);
status = allocated_vmo.read(bytes.data(), 0u, size_bytes);
if (status == ZX_ERR_NOT_SUPPORTED) {
// If the allocated VMO is not readable, then we just skip value check,
// since we do not expect it being cleared by write syscalls.
printf("INFO: allocated_vmo doesn't support zx_vmo_read, skip value check\n");
return true;
}
// Check if all the contents we read from the VMO are filled with zero.
return *std::max_element(bytes.begin(), bytes.end()) == 0u;
}
TEST(Sysmem, BasicAllocationSucceeds) {
EXPECT_TRUE(BasicAllocationSucceeds([](BufferCollectionConstraints& to_modify_nop) {}));
}
TEST(Sysmem, ZeroMinSizeBytesFails) {
EXPECT_FALSE(BasicAllocationSucceeds([](BufferCollectionConstraints& to_modify) {
// Disable image_format_constraints so that the client is not specifying any min size via
// implied by image_format_constraints.
to_modify->image_format_constraints_count = 0;
// Also set 0 min_size_bytes, so that implied minimum overall size is 0.
to_modify->buffer_memory_constraints.min_size_bytes = 0;
}));
}
TEST(Sysmem, ZeroMaxBufferCount_SucceedsOnlyForNow) {
// With sysmem2 this will be expected to fail. With sysmem(1), this succeeds because 0 is
// interpreted as replace with default.
EXPECT_TRUE(BasicAllocationSucceeds(
[](BufferCollectionConstraints& to_modify) { to_modify->max_buffer_count = 0; }));
}
TEST(Sysmem, ZeroRequiredMinCodedWidth_SucceedsOnlyForNow) {
// With sysmem2 this will be expected to fail. With sysmem(1), this succeeds because 0 is
// interpreted as replace with default.
EXPECT_TRUE(BasicAllocationSucceeds([](BufferCollectionConstraints& to_modify) {
to_modify->image_format_constraints[0].required_min_coded_width = 0;
}));
}
TEST(Sysmem, ZeroRequiredMinCodedHeight_SucceedsOnlyForNow) {
// With sysmem2 this will be expected to fail. With sysmem(1), this succeeds because 0 is
// interpreted as replace with default.
EXPECT_TRUE(BasicAllocationSucceeds([](BufferCollectionConstraints& to_modify) {
to_modify->image_format_constraints[0].required_min_coded_height = 0;
}));
}
TEST(Sysmem, ZeroRequiredMinBytesPerRow_SucceedsOnlyForNow) {
// With sysmem2 this will be expected to fail. With sysmem(1), this succeeds because 0 is
// interpreted as replace with default.
EXPECT_TRUE(BasicAllocationSucceeds([](BufferCollectionConstraints& to_modify) {
to_modify->image_format_constraints[0].required_min_bytes_per_row = 0;
}));
}
TEST(Sysmem, DuplicateConstraintsFails) {
EXPECT_FALSE(BasicAllocationSucceeds([](BufferCollectionConstraints& to_modify) {
to_modify->image_format_constraints_count = 2;
to_modify->image_format_constraints[1] = to_modify->image_format_constraints[0];
}));
}
int main(int argc, char** argv) {
setlinebuf(stdout);
zxtest::Runner::GetInstance()->AddObserver(&test_observer);
return RUN_ALL_TESTS(argc, argv);
}