| // 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 <fidl/fuchsia.hardware.sysmem/cpp/fidl.h> |
| #include <fidl/fuchsia.sysinfo/cpp/fidl.h> |
| #include <fidl/fuchsia.sysmem/cpp/fidl.h> |
| #include <fidl/fuchsia.sysmem2/cpp/fidl.h> |
| #include <inttypes.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/ddk/hw/arch_ops.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/unsafe.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/fidl/cpp/wire/arena.h> |
| #include <lib/fidl/cpp/wire/channel.h> |
| #include <lib/fidl/cpp/wire/connect_service.h> |
| #include <lib/fit/defer.h> |
| #include <lib/sysmem-version/sysmem-version.h> |
| #include <lib/zbi-format/graphics.h> |
| #include <lib/zbitl/items/graphics.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/event.h> |
| #include <lib/zx/eventpair.h> |
| #include <lib/zx/object.h> |
| #include <lib/zx/time.h> |
| #include <lib/zx/vmar.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/errors.h> |
| #include <zircon/limits.h> |
| #include <zircon/rights.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/object.h> |
| #include <zircon/system/public/zircon/syscalls.h> |
| #include <zircon/time.h> |
| #include <zircon/types.h> |
| |
| #include <cstdint> |
| #include <limits> |
| #include <map> |
| #include <memory> |
| #include <random> |
| #include <string> |
| #include <thread> |
| #include <variant> |
| #include <vector> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/unique_fd.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "common.h" |
| #include "secure_vmo_read_tester.h" |
| #include "test_observer.h" |
| |
| namespace { |
| |
| namespace v1 = fuchsia_sysmem; |
| |
| using TokenV1 = fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken>; |
| using CollectionV1 = fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>; |
| using GroupV1 = fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionTokenGroup>; |
| using SharedTokenV1 = std::shared_ptr<TokenV1>; |
| using SharedCollectionV1 = std::shared_ptr<CollectionV1>; |
| using SharedGroupV1 = std::shared_ptr<GroupV1>; |
| |
| zx::result<fidl::WireSyncClient<fuchsia_sysmem::Allocator>> connect_to_sysmem_service_v1(); |
| zx_status_t verify_connectivity_v1(fidl::WireSyncClient<fuchsia_sysmem::Allocator>& allocator); |
| |
| zx::result<fidl::WireSyncClient<fuchsia_sysmem::Allocator>> connect_to_sysmem_driver_v1() { |
| fbl::unique_fd sysmem_dir(open(SYSMEM_CLASS_PATH, O_RDONLY)); |
| |
| zx::result<fidl::ClientEnd<fuchsia_hardware_sysmem::DriverConnector>> client_end; |
| zx_status_t status = fdio_watch_directory( |
| sysmem_dir.get(), |
| [](int dirfd, int event, const char* fn, void* cookie) { |
| if (std::string_view{fn} == ".") { |
| return ZX_OK; |
| } |
| if (event != WATCH_EVENT_ADD_FILE) { |
| return ZX_OK; |
| } |
| fdio_cpp::UnownedFdioCaller caller(dirfd); |
| *reinterpret_cast<zx::result<fidl::ClientEnd<fuchsia_hardware_sysmem::DriverConnector>>*>( |
| cookie) = |
| component::ConnectAt<fuchsia_hardware_sysmem::DriverConnector>(caller.directory(), fn); |
| return ZX_ERR_STOP; |
| }, |
| ZX_TIME_INFINITE, &client_end); |
| EXPECT_STATUS(status, ZX_ERR_STOP); |
| if (status != ZX_ERR_STOP) { |
| if (status == ZX_OK) { |
| status = ZX_ERR_BAD_STATE; |
| } |
| return zx::error(status); |
| } |
| EXPECT_OK(client_end); |
| if (!client_end.is_ok()) { |
| return zx::error(client_end.status_value()); |
| } |
| fidl::WireSyncClient connector{std::move(client_end.value())}; |
| |
| auto allocator_endpoints = fidl::CreateEndpoints<fuchsia_sysmem::Allocator>(); |
| EXPECT_OK(allocator_endpoints); |
| if (!allocator_endpoints.is_ok()) { |
| return zx::error(allocator_endpoints.status_value()); |
| } |
| |
| auto connect_result = connector->ConnectV1(std::move(allocator_endpoints->server)); |
| EXPECT_OK(connect_result); |
| if (!connect_result.ok()) { |
| return zx::error(connect_result.status()); |
| } |
| |
| fidl::WireSyncClient allocator{std::move(allocator_endpoints->client)}; |
| const fidl::Status result = |
| allocator->SetDebugClientInfo(fidl::StringView::FromExternal(current_test_name), 0u); |
| EXPECT_OK(result.status()); |
| return zx::ok(std::move(allocator)); |
| } |
| |
| zx::result<fidl::WireSyncClient<fuchsia_sysmem::Allocator>> connect_to_sysmem_service_v1() { |
| auto client_end = component::Connect<fuchsia_sysmem::Allocator>(); |
| EXPECT_OK(client_end); |
| if (!client_end.is_ok()) { |
| return zx::error(client_end.status_value()); |
| } |
| fidl::WireSyncClient allocator{std::move(client_end.value())}; |
| const fidl::Status result = |
| allocator->SetDebugClientInfo(fidl::StringView::FromExternal(current_test_name), 0u); |
| EXPECT_OK(result.status()); |
| return zx::ok(std::move(allocator)); |
| } |
| |
| zx_status_t verify_connectivity_v1(fidl::WireSyncClient<fuchsia_sysmem::Allocator>& allocator) { |
| auto [collection_client_end, collection_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| |
| auto result = allocator->AllocateNonSharedCollection(std::move(collection_server_end)); |
| EXPECT_OK(result); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| |
| fidl::WireSyncClient collection{std::move(collection_client_end)}; |
| auto sync_result = collection->Sync(); |
| EXPECT_OK(sync_result); |
| if (!sync_result.ok()) { |
| return sync_result.status(); |
| } |
| return ZX_OK; |
| } |
| |
| static void SetDefaultCollectionNameV1( |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>& collection, fbl::String suffix = "") { |
| constexpr uint32_t kPriority = 1000000; |
| fbl::String name = "sysmem-test-v1"; |
| if (!suffix.empty()) { |
| name = fbl::String::Concat({name, "-", suffix}); |
| } |
| EXPECT_OK(collection->SetName(kPriority, fidl::StringView::FromExternal(name.c_str())).status()); |
| } |
| |
| zx::result<fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>> |
| make_single_participant_collection_v1() { |
| // We could use AllocateNonSharedCollection() to implement this function, but we're already |
| // using AllocateNonSharedCollection() during verify_connectivity_v1(), so instead just set up the |
| // more general (and more real) way here. |
| |
| auto allocator = connect_to_sysmem_driver_v1(); |
| EXPECT_OK(allocator.status_value()); |
| if (!allocator.is_ok()) { |
| return zx::error(allocator.status_value()); |
| } |
| |
| auto [token_client_end, token_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| auto new_collection_result = allocator->AllocateSharedCollection(std::move(token_server_end)); |
| EXPECT_OK(new_collection_result); |
| if (!new_collection_result.ok()) { |
| return zx::error(new_collection_result.status()); |
| } |
| |
| auto [collection_client_end, collection_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| |
| EXPECT_NE(token_client_end.channel().get(), ZX_HANDLE_INVALID); |
| auto bind_result = allocator->BindSharedCollection(std::move(token_client_end), |
| std::move(collection_server_end)); |
| EXPECT_OK(bind_result); |
| if (!bind_result.ok()) { |
| return zx::error(bind_result.status()); |
| } |
| |
| fidl::WireSyncClient collection{std::move(collection_client_end)}; |
| |
| SetDefaultCollectionNameV1(collection); |
| |
| return zx::ok(std::move(collection)); |
| } |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> create_initial_token_v1() { |
| zx::result allocator = connect_to_sysmem_service_v1(); |
| EXPECT_OK(allocator.status_value()); |
| auto [token_client_0, token_server_0] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| EXPECT_OK(allocator->AllocateSharedCollection(std::move(token_server_0)).status()); |
| fidl::WireSyncClient token{std::move(token_client_0)}; |
| EXPECT_OK(token->Sync().status()); |
| return token; |
| } |
| |
| std::vector<fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>> create_clients_v1( |
| uint32_t client_count) { |
| std::vector<fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>> result; |
| auto next_token = create_initial_token_v1(); |
| auto allocator = connect_to_sysmem_service_v1(); |
| for (uint32_t i = 0; i < client_count; ++i) { |
| auto cur_token = std::move(next_token); |
| if (i < client_count - 1) { |
| auto [token_client_endpoint, token_server_endpoint] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| EXPECT_OK( |
| cur_token->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_endpoint)).status()); |
| next_token = fidl::WireSyncClient(std::move(token_client_endpoint)); |
| } |
| auto [collection_client_endpoint, collection_server_endpoint] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| EXPECT_OK( |
| allocator |
| ->BindSharedCollection(cur_token.TakeClientEnd(), std::move(collection_server_endpoint)) |
| .status()); |
| fidl::WireSyncClient collection_client{std::move(collection_client_endpoint)}; |
| SetDefaultCollectionNameV1(collection_client, fbl::StringPrintf("%u", i)); |
| if (i < client_count - 1) { |
| // Ensure next_token is usable. |
| EXPECT_OK(collection_client->Sync().status()); |
| } |
| result.emplace_back(std::move(collection_client)); |
| } |
| return result; |
| } |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> create_token_under_token_v1( |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken>& token_a) { |
| auto [token_b_client, token_b_server] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| EXPECT_OK(token_a->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_b_server)).status()); |
| fidl::WireSyncClient token_b{std::move(token_b_client)}; |
| EXPECT_OK(token_b->Sync().status()); |
| return token_b; |
| } |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionTokenGroup> create_group_under_token_v1( |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken>& token) { |
| auto [group_client, group_server] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionTokenGroup>::Create(); |
| EXPECT_OK(token->CreateBufferCollectionTokenGroup(std::move(group_server)).status()); |
| auto group = fidl::WireSyncClient(std::move(group_client)); |
| EXPECT_OK(group->Sync().status()); |
| return group; |
| } |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> create_token_under_group_v1( |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionTokenGroup>& group, |
| uint32_t rights_attenuation_mask = ZX_RIGHT_SAME_RIGHTS) { |
| auto [token_client, token_server] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::Arena arena; |
| auto request_builder = |
| fuchsia_sysmem::wire::BufferCollectionTokenGroupCreateChildRequest::Builder(arena); |
| request_builder.token_request(std::move(token_server)); |
| if (rights_attenuation_mask != ZX_RIGHT_SAME_RIGHTS) { |
| request_builder.rights_attenuation_mask(rights_attenuation_mask); |
| } |
| EXPECT_OK(group->CreateChild(request_builder.Build()).status()); |
| auto token = fidl::WireSyncClient(std::move(token_client)); |
| EXPECT_OK(token->Sync().status()); |
| return token; |
| } |
| |
| void check_token_alive_v1(fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken>& token) { |
| constexpr uint32_t kIterations = 1; |
| for (uint32_t i = 0; i < kIterations; ++i) { |
| zx::nanosleep(zx::deadline_after(zx::usec(500))); |
| EXPECT_OK(token->Sync().status()); |
| } |
| } |
| |
| void check_group_alive_v1(fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionTokenGroup>& group) { |
| constexpr uint32_t kIterations = 1; |
| for (uint32_t i = 0; i < kIterations; ++i) { |
| zx::nanosleep(zx::deadline_after(zx::usec(500))); |
| EXPECT_OK(group->Sync().status()); |
| } |
| } |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection> convert_token_to_collection_v1( |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> token) { |
| auto allocator = connect_to_sysmem_service_v1(); |
| auto [collection_client, collection_server] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| EXPECT_OK(allocator->BindSharedCollection(token.TakeClientEnd(), std::move(collection_server)) |
| .status()); |
| return fidl::WireSyncClient(std::move(collection_client)); |
| } |
| |
| void set_picky_constraints_v1(fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>& collection, |
| uint32_t exact_buffer_size) { |
| EXPECT_EQ(exact_buffer_size % zx_system_get_page_size(), 0); |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::BufferMemoryConstraints{ |
| .min_size_bytes = exact_buffer_size, |
| // Allow a max that's just large enough to accommodate the size implied |
| // by the min frame size and PixelFormat. |
| .max_size_bytes = exact_buffer_size, |
| .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 = 0; |
| EXPECT_OK(collection->SetConstraints(true, std::move(constraints)).status()); |
| } |
| |
| void set_min_camping_constraints_v1( |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>& collection, |
| uint32_t min_buffer_count_for_camping, uint32_t max_buffer_count = 0) { |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = min_buffer_count_for_camping; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::BufferMemoryConstraints{ |
| .min_size_bytes = zx_system_get_page_size(), |
| // Allow a max that's just large enough to accommodate the size implied |
| // by the min frame size and PixelFormat. |
| .max_size_bytes = zx_system_get_page_size(), |
| .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 = {}, |
| }; |
| if (max_buffer_count) { |
| constraints.max_buffer_count = max_buffer_count; |
| } |
| constraints.image_format_constraints_count = 0; |
| EXPECT_OK(collection->SetConstraints(true, std::move(constraints)).status()); |
| } |
| |
| template <typename T, size_t S> |
| bool ArrayEqual(const ::fidl::Array<T, S>& a, const ::fidl::Array<T, S>& b); |
| |
| bool Equal(const fuchsia_sysmem::wire::VmoBuffer& a, const fuchsia_sysmem::wire::VmoBuffer& b) { |
| return a.vmo == b.vmo && a.vmo_usable_start == b.vmo_usable_start; |
| } |
| |
| bool Equal(const fuchsia_sysmem::wire::PixelFormat& a, const fuchsia_sysmem::wire::PixelFormat& b) { |
| return a.type == b.type && a.has_format_modifier == b.has_format_modifier && |
| a.format_modifier.value == b.format_modifier.value; |
| } |
| |
| bool Equal(const fuchsia_sysmem::wire::ImageFormatConstraints& a, |
| const fuchsia_sysmem::wire::ImageFormatConstraints& b) { |
| return Equal(a.pixel_format, b.pixel_format) && a.color_spaces_count == b.color_spaces_count && |
| ArrayEqual(a.color_space, b.color_space) && a.min_coded_width == b.min_coded_width && |
| a.max_coded_width == b.max_coded_width && a.min_coded_height == b.min_coded_height && |
| a.max_coded_height == b.max_coded_height && a.min_bytes_per_row == b.min_bytes_per_row && |
| a.max_bytes_per_row == b.max_bytes_per_row && |
| a.max_coded_width_times_coded_height == b.max_coded_width_times_coded_height && |
| a.layers == b.layers && a.coded_width_divisor == b.coded_width_divisor && |
| a.coded_height_divisor == b.coded_height_divisor && |
| a.bytes_per_row_divisor == b.bytes_per_row_divisor && |
| a.start_offset_divisor == b.start_offset_divisor && |
| a.display_width_divisor == b.display_width_divisor && |
| a.display_height_divisor == b.display_height_divisor && |
| a.required_min_coded_width == b.required_min_coded_width && |
| a.required_max_coded_width == b.required_max_coded_width && |
| a.required_min_coded_height == b.required_min_coded_height && |
| a.required_max_coded_height == b.required_max_coded_height && |
| a.required_min_bytes_per_row == b.required_min_bytes_per_row && |
| a.required_max_bytes_per_row == b.required_max_bytes_per_row; |
| } |
| |
| bool Equal(const fuchsia_sysmem::wire::BufferMemorySettings& a, |
| const fuchsia_sysmem::wire::BufferMemorySettings& b) { |
| return a.size_bytes == b.size_bytes && a.is_physically_contiguous == b.is_physically_contiguous && |
| a.is_secure == b.is_secure && a.coherency_domain == b.coherency_domain && a.heap == b.heap; |
| } |
| |
| bool Equal(const fuchsia_sysmem::wire::SingleBufferSettings& a, |
| const fuchsia_sysmem::wire::SingleBufferSettings& b) { |
| return Equal(a.buffer_settings, b.buffer_settings) && |
| a.has_image_format_constraints == b.has_image_format_constraints && |
| Equal(a.image_format_constraints, b.image_format_constraints); |
| } |
| |
| bool Equal(const fuchsia_sysmem::wire::BufferCollectionInfo2& a, |
| const fuchsia_sysmem::wire::BufferCollectionInfo2& b) { |
| return a.buffer_count == b.buffer_count && Equal(a.settings, b.settings) && |
| ArrayEqual(a.buffers, b.buffers); |
| } |
| |
| bool Equal(const fuchsia_sysmem::wire::ColorSpace& a, const fuchsia_sysmem::wire::ColorSpace& b) { |
| return a.type == b.type; |
| } |
| |
| // Some helpers to test equality of buffer collection infos and related types. |
| template <typename T, size_t S> |
| bool ArrayEqual(const ::fidl::Array<T, S>& a, const ::fidl::Array<T, S>& b) { |
| for (size_t i = 0; i < S; i++) { |
| if (!Equal(a[i], b[i])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool AttachTokenSucceedsV1( |
| bool attach_before_also, bool fail_attached_early, |
| fit::function<void(fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify)> |
| modify_constraints_initiator, |
| fit::function<void(fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify)> |
| modify_constraints_participant, |
| fit::function<void(fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify)> |
| modify_constraints_attached, |
| fit::function<void(fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify)> verify_info, |
| uint32_t expected_buffer_count = 6) { |
| ZX_DEBUG_ASSERT(!fail_attached_early || attach_before_also); |
| auto allocator = connect_to_sysmem_driver_v1(); |
| EXPECT_OK(allocator); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| // Client 1 creates a token and new LogicalBufferCollection using |
| // AllocateSharedCollection(). |
| EXPECT_OK(allocator->AllocateSharedCollection(std::move(token_server_1))); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| // 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). |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| EXPECT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| // Client 3 is attached later. |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| EXPECT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| EXPECT_OK( |
| allocator->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_1.min_buffer_count_for_camping = 3; |
| constraints_1.has_buffer_memory_constraints = true; |
| constraints_1.buffer_memory_constraints = fuchsia_sysmem::wire::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::wire::ImageFormatConstraints& image_constraints_1 = |
| constraints_1.image_format_constraints[0]; |
| image_constraints_1.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints_1.color_spaces_count = 1; |
| image_constraints_1.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| // 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. |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_2(constraints_1); |
| // 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, sizeof(fuchsia_sysmem::wire::BufferCollectionConstraints), 1, ofp); |
| fwrite(&constraints_2, sizeof(fuchsia_sysmem::wire::BufferCollectionConstraints), 1, ofp); |
| fclose(ofp); |
| } else { |
| printf("Failed to write sysmem multi BufferCollectionConstraints corpus file.\n"); |
| fflush(stderr); |
| } |
| #endif // SYSMEM_FUZZ_CORPUS |
| |
| modify_constraints_initiator(constraints_1); |
| |
| EXPECT_OK(collection_1->SetConstraints(true, std::move(constraints_1))); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| // Client 2 connects to sysmem separately. |
| auto allocator_2 = connect_to_sysmem_driver_v1(); |
| EXPECT_OK(allocator_2); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| auto [collection_client_2, collection_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_client_2)}; |
| |
| // 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. |
| EXPECT_OK(collection_1->Sync()); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| EXPECT_NE(token_client_2.channel().get(), ZX_HANDLE_INVALID); |
| EXPECT_OK( |
| allocator_2->BindSharedCollection(std::move(token_client_2), std::move(collection_server_2))); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| // Not all constraints have been input, so the buffers haven't been |
| // allocated yet. |
| auto check_1_result = collection_1->CheckBuffersAllocated(); |
| EXPECT_OK(check_1_result); |
| EXPECT_EQ(check_1_result.value().status, ZX_ERR_UNAVAILABLE); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| auto check_2_result = collection_2->CheckBuffersAllocated(); |
| EXPECT_OK(check_2_result); |
| EXPECT_EQ(check_2_result.value().status, ZX_ERR_UNAVAILABLE); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| fidl::ClientEnd<fuchsia_sysmem::BufferCollectionToken> token_client_3; |
| fidl::ServerEnd<fuchsia_sysmem::BufferCollectionToken> token_server_3; |
| |
| auto use_collection_2_to_attach_token_3 = [&collection_2, &token_client_3, &token_server_3] { |
| token_client_3.reset(); |
| ZX_DEBUG_ASSERT(!token_server_3); |
| zx::result token_endpoints_3 = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>(); |
| EXPECT_OK(token_endpoints_3); |
| IF_FAILURES_RETURN(); |
| token_client_3 = std::move(token_endpoints_3->client); |
| token_server_3 = std::move(token_endpoints_3->server); |
| |
| EXPECT_OK(collection_2->AttachToken(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_3))); |
| IF_FAILURES_RETURN(); |
| // Since we're not doing any Duplicate()s first or anything like that (which could allow us to |
| // share the round trip), go ahead and Sync() the token creation to sysmem now. |
| EXPECT_OK(collection_2->Sync()); |
| IF_FAILURES_RETURN(); |
| }; |
| |
| if (attach_before_also) { |
| use_collection_2_to_attach_token_3(); |
| } |
| IF_FAILURES_RETURN_FALSE(); |
| |
| // The AttachToken() participant needs to set constraints also, but it will never hold up initial |
| // allocation. |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_3; |
| constraints_3.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_3.has_buffer_memory_constraints = true; |
| constraints_3.buffer_memory_constraints.cpu_domain_supported = true; |
| modify_constraints_attached(constraints_3); |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection> collection_3; |
| |
| auto collection_client_3_set_constraints = [&allocator_2, &token_client_3, &constraints_3, |
| &collection_3] { |
| EXPECT_NE(allocator_2.value().client_end().channel().get(), ZX_HANDLE_INVALID); |
| EXPECT_NE(token_client_3.channel().get(), ZX_HANDLE_INVALID); |
| IF_FAILURES_RETURN(); |
| collection_3 = {}; |
| ZX_DEBUG_ASSERT(!collection_3.is_valid()); |
| |
| auto [collection_client_3, collection_server_3] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| collection_3 = fidl::WireSyncClient(std::move(collection_client_3)); |
| |
| EXPECT_OK(allocator_2->BindSharedCollection(std::move(token_client_3), |
| std::move(collection_server_3))); |
| IF_FAILURES_RETURN(); |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_3_copy(constraints_3); |
| |
| EXPECT_OK(collection_3->SetConstraints(true, constraints_3_copy)); |
| IF_FAILURES_RETURN(); |
| }; |
| |
| if (attach_before_also) { |
| collection_client_3_set_constraints(); |
| IF_FAILURES_RETURN_FALSE(); |
| if (fail_attached_early) { |
| // Also close the channel to simulate early client 3 failure before allocation. |
| collection_3 = {}; |
| } |
| } |
| |
| // |
| // Only after all non-AttachToken() participants have SetConstraints() will the initial allocation |
| // be successful. The initial allocation will always succeed regardless of how uncooperative the |
| // AttachToken() client 3 is being with its constraints. |
| // |
| |
| modify_constraints_participant(constraints_2); |
| EXPECT_OK(collection_2->SetConstraints(true, std::move(constraints_2))); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| auto allocate_result_1 = collection_1->WaitForBuffersAllocated(); |
| |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| EXPECT_OK(allocate_result_1); |
| EXPECT_OK(allocate_result_1->status); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| auto check_result_good_1 = collection_1->CheckBuffersAllocated(); |
| EXPECT_OK(check_result_good_1); |
| EXPECT_OK(check_result_good_1->status); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| auto check_result_good_2 = collection_2->CheckBuffersAllocated(); |
| EXPECT_OK(check_result_good_2); |
| EXPECT_OK(check_result_good_2->status); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| auto allocate_result_2 = collection_2->WaitForBuffersAllocated(); |
| EXPECT_OK(allocate_result_2); |
| EXPECT_OK(allocate_result_2->status); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| // |
| // 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. |
| // |
| fuchsia_sysmem::wire::BufferCollectionInfo2* buffer_collection_info_1 = |
| &allocate_result_1->buffer_collection_info; |
| fuchsia_sysmem::wire::BufferCollectionInfo2* buffer_collection_info_2 = |
| &allocate_result_2->buffer_collection_info; |
| fuchsia_sysmem::wire::BufferCollectionInfo2 buffer_collection_info_3; |
| |
| for (uint32_t i = 0; i < std::size(buffer_collection_info_1->buffers); ++i) { |
| EXPECT_EQ(buffer_collection_info_1->buffers[i].vmo.get() != ZX_HANDLE_INVALID, |
| buffer_collection_info_2->buffers[i].vmo.get() != ZX_HANDLE_INVALID); |
| IF_FAILURES_RETURN_FALSE(); |
| if (buffer_collection_info_1->buffers[i].vmo.get() != ZX_HANDLE_INVALID) { |
| // The handle values must be different. |
| EXPECT_NE(buffer_collection_info_1->buffers[i].vmo.get(), |
| buffer_collection_info_2->buffers[i].vmo.get()); |
| IF_FAILURES_RETURN_FALSE(); |
| // 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.get()); |
| zx_koid_t koid_2 = get_koid(buffer_collection_info_2->buffers[i].vmo.get()); |
| EXPECT_EQ(koid_1, koid_2); |
| IF_FAILURES_RETURN_FALSE(); |
| } |
| } |
| |
| // |
| // Verify that buffer_collection_info_1 paid attention to constraints_2, and |
| // that buffer_collection_info_2 makes sense. This also indirectly confirms |
| // that buffer_collection_info_3 paid attention to constraints_2. |
| // |
| |
| EXPECT_EQ(buffer_collection_info_1->buffer_count, expected_buffer_count); |
| // 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. |
| EXPECT_GE(buffer_collection_info_1->settings.buffer_settings.size_bytes, (512 * 512) * 3 / 2); |
| EXPECT_EQ(buffer_collection_info_1->settings.buffer_settings.is_physically_contiguous, false); |
| EXPECT_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. |
| EXPECT_EQ(buffer_collection_info_1->settings.has_image_format_constraints, true); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| for (uint32_t i = 0; i < 64; ++i) { |
| if (i < expected_buffer_count) { |
| EXPECT_NE(buffer_collection_info_1->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| EXPECT_NE(buffer_collection_info_2->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| uint64_t size_bytes_1 = 0; |
| EXPECT_OK(buffer_collection_info_1->buffers[i].vmo.get_size(&size_bytes_1)); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| uint64_t size_bytes_2 = 0; |
| EXPECT_OK(buffer_collection_info_2->buffers[i].vmo.get_size(&size_bytes_2)); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| // 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. |
| EXPECT_LE(buffer_collection_info_1->buffers[i].vmo_usable_start + |
| buffer_collection_info_1->settings.buffer_settings.size_bytes, |
| size_bytes_1); |
| EXPECT_LE(buffer_collection_info_2->buffers[i].vmo_usable_start + |
| buffer_collection_info_2->settings.buffer_settings.size_bytes, |
| size_bytes_2); |
| IF_FAILURES_RETURN_FALSE(); |
| } else { |
| EXPECT_EQ(buffer_collection_info_1->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| EXPECT_EQ(buffer_collection_info_2->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| IF_FAILURES_RETURN_FALSE(); |
| } |
| } |
| |
| if (attach_before_also && !collection_3.is_valid()) { |
| // We already failed collection_client_3 early, so AttachToken() can't succeed, but we've |
| // checked that initial allocation did succeed despite the pre-allocation |
| // failure of client 3. |
| return false; |
| } |
| |
| if (attach_before_also) { |
| auto allocate_result_3 = collection_3->WaitForBuffersAllocated(); |
| if (!allocate_result_3.ok() || allocate_result_3->status != ZX_OK) { |
| return false; |
| } |
| } |
| |
| const uint32_t kIterationCount = 3; |
| for (uint32_t i = 0; i < kIterationCount; ++i) { |
| if (i != 0 || !attach_before_also) { |
| use_collection_2_to_attach_token_3(); |
| collection_client_3_set_constraints(); |
| } |
| |
| // The collection_client_3_set_constraints() above closed the old collection_client_3, which the |
| // sysmem server treats as a client 3 failure, but because client 3 was created via |
| // AttachToken(), the failure of client 3 doesn't cause failure of the LogicalBufferCollection. |
| // |
| // Give some time to fail if it were going to (but it shouldn't). |
| nanosleep_duration(zx::msec(250)); |
| EXPECT_OK(collection_1->Sync()); |
| // LogicalBufferCollection still ok. |
| IF_FAILURES_RETURN_FALSE(); |
| |
| auto allocate_result_3 = collection_3->WaitForBuffersAllocated(); |
| if (!allocate_result_3.ok() || allocate_result_3->status != ZX_OK) { |
| return false; |
| } |
| |
| buffer_collection_info_3 = std::move(allocate_result_3->buffer_collection_info); |
| |
| for (uint32_t i = 0; i < std::size(buffer_collection_info_1->buffers); ++i) { |
| EXPECT_EQ(buffer_collection_info_1->buffers[i].vmo.get() != ZX_HANDLE_INVALID, |
| buffer_collection_info_3.buffers[i].vmo.get() != ZX_HANDLE_INVALID); |
| IF_FAILURES_RETURN_FALSE(); |
| if (buffer_collection_info_1->buffers[i].vmo.get() != ZX_HANDLE_INVALID) { |
| // The handle values must be different. |
| EXPECT_NE(buffer_collection_info_1->buffers[i].vmo.get(), |
| buffer_collection_info_3.buffers[i].vmo.get()); |
| EXPECT_NE(buffer_collection_info_2->buffers[i].vmo.get(), |
| buffer_collection_info_3.buffers[i].vmo.get()); |
| IF_FAILURES_RETURN_FALSE(); |
| // 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.get()); |
| zx_koid_t koid_3 = get_koid(buffer_collection_info_3.buffers[i].vmo.get()); |
| EXPECT_EQ(koid_1, koid_3); |
| IF_FAILURES_RETURN_FALSE(); |
| } |
| } |
| } |
| |
| // Clear out vmo handles and compare all three collections |
| for (uint32_t i = 0; i < std::size(buffer_collection_info_1->buffers); ++i) { |
| buffer_collection_info_1->buffers[i].vmo.reset(); |
| buffer_collection_info_2->buffers[i].vmo.reset(); |
| buffer_collection_info_3.buffers[i].vmo.reset(); |
| } |
| |
| // Check that buffer_collection_info_1 and buffer_collection_info_2 are |
| // consistent. |
| EXPECT_TRUE(Equal(*buffer_collection_info_1, *buffer_collection_info_2)); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| // Check that buffer_collection_info_1 and buffer_collection_info_3 are |
| // consistent. |
| EXPECT_TRUE(Equal(*buffer_collection_info_1, buffer_collection_info_3)); |
| IF_FAILURES_RETURN_FALSE(); |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| TEST(Sysmem, DriverConnectionV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator); |
| ASSERT_OK(verify_connectivity_v1(allocator.value())); |
| } |
| |
| TEST(Sysmem, ServiceConnectionV1) { |
| auto allocator = connect_to_sysmem_service_v1(); |
| ASSERT_OK(allocator); |
| ASSERT_OK(verify_connectivity_v1(allocator.value())); |
| } |
| |
| TEST(Sysmem, VerifyBufferCollectionTokenV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator); |
| |
| auto [token_client, token_server] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token{std::move(token_client)}; |
| |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server))); |
| |
| auto [token2_client, token2_server] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token2{std::move(token2_client)}; |
| |
| ASSERT_OK(token->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token2_server))); |
| |
| auto [not_token_client, not_token_server] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| ASSERT_OK(token->Sync()); |
| ASSERT_OK(token2->Sync()); |
| |
| auto validate_result_1 = allocator->ValidateBufferCollectionToken( |
| get_related_koid(token.client_end().channel().get())); |
| ASSERT_OK(validate_result_1); |
| ASSERT_TRUE(validate_result_1->is_known); |
| |
| auto validate_result_2 = allocator->ValidateBufferCollectionToken( |
| get_related_koid(token2.client_end().channel().get())); |
| ASSERT_OK(validate_result_2); |
| ASSERT_TRUE(validate_result_2->is_known); |
| |
| auto validate_result_not_known = |
| allocator->ValidateBufferCollectionToken(get_related_koid(not_token_client.channel().get())); |
| ASSERT_OK(validate_result_not_known); |
| ASSERT_FALSE(validate_result_not_known->is_known); |
| } |
| |
| TEST(Sysmem, TokenOneParticipantNoImageConstraintsV1) { |
| auto collection = make_single_participant_collection_v1(); |
| ASSERT_OK(collection); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 3; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocation_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocation_result); |
| ASSERT_OK(allocation_result.value().status); |
| |
| auto buffer_collection_info = &allocation_result.value().buffer_collection_info; |
| 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::wire::CoherencyDomain::kCpu); |
| 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.get(), ZX_HANDLE_INVALID); |
| uint64_t size_bytes = 0; |
| auto status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo.get(), &size_bytes); |
| ASSERT_EQ(status, ZX_OK); |
| ASSERT_EQ(size_bytes, 64 * 1024); |
| } else { |
| ASSERT_EQ(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| } |
| } |
| } |
| |
| TEST(Sysmem, TokenOneParticipantColorspaceRankingV1) { |
| auto collection = make_single_participant_collection_v1(); |
| ASSERT_OK(collection); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::BufferMemoryConstraints{ |
| .min_size_bytes = 64 * 1024, |
| .max_size_bytes = 128 * 1024, |
| .cpu_domain_supported = true, |
| }; |
| constraints.image_format_constraints_count = 1; |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[0]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints.color_spaces_count = 3; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec601Pal, |
| }; |
| image_constraints.color_space[1] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec601PalFullRange, |
| }; |
| image_constraints.color_space[2] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocation_result = collection->WaitForBuffersAllocated(); |
| ASSERT_OK(allocation_result); |
| ASSERT_OK(allocation_result.value().status); |
| |
| auto buffer_collection_info = &allocation_result.value().buffer_collection_info; |
| ASSERT_EQ(buffer_collection_info->buffer_count, 1); |
| 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::wire::PixelFormatType::kNv12); |
| ASSERT_EQ(buffer_collection_info->settings.image_format_constraints.color_spaces_count, 1); |
| ASSERT_EQ(buffer_collection_info->settings.image_format_constraints.color_space[0].type, |
| fuchsia_sysmem::wire::ColorSpaceType::kRec709); |
| } |
| |
| TEST(Sysmem, AttachLifetimeTrackingV1) { |
| auto collection = make_single_participant_collection_v1(); |
| ASSERT_OK(collection); |
| |
| ASSERT_OK(collection->Sync()); |
| |
| constexpr uint32_t kNumBuffers = 3; |
| constexpr uint32_t kNumEventpairs = kNumBuffers + 3; |
| zx::eventpair client[kNumEventpairs]; |
| zx::eventpair server[kNumEventpairs]; |
| for (uint32_t i = 0; i < kNumEventpairs; ++i) { |
| ASSERT_OK(zx::eventpair::create(/*options=*/0, &client[i], &server[i])); |
| ASSERT_OK(collection->AttachLifetimeTracking(std::move(server[i]), i).status()); |
| } |
| |
| nanosleep_duration(zx::msec(500)); |
| |
| for (uint32_t i = 0; i < kNumEventpairs; ++i) { |
| zx_signals_t pending_signals; |
| auto status = |
| client[i].wait_one(ZX_EVENTPAIR_PEER_CLOSED, zx::time::infinite_past(), &pending_signals); |
| ASSERT_EQ(status, ZX_ERR_TIMED_OUT); |
| // Buffers are not allocated yet, so lifetime tracking is pending, since we don't yet know how |
| // many buffers there will be. |
| ASSERT_EQ(pending_signals & ZX_EVENTPAIR_PEER_CLOSED, 0); |
| } |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = kNumBuffers; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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); |
| auto set_constraints_result = collection->SetConstraints(true, std::move(constraints)); |
| ASSERT_TRUE(set_constraints_result.ok()); |
| |
| // Enough time to typically notice if server accidentally closes server 0..kNumEventpairs-1. |
| nanosleep_duration(zx::msec(200)); |
| |
| // Now that we've set constraints, allocation can happen, and ZX_EVENTPAIR_PEER_CLOSED should be |
| // seen for eventpair(s) >= kNumBuffers. |
| for (uint32_t i = 0; i < kNumEventpairs; ++i) { |
| zx_signals_t pending_signals; |
| auto status = client[i].wait_one( |
| ZX_EVENTPAIR_PEER_CLOSED, |
| i >= kNumBuffers ? zx::time::infinite() : zx::time::infinite_past(), &pending_signals); |
| ASSERT_TRUE(status == (i >= kNumBuffers) ? ZX_OK : ZX_ERR_TIMED_OUT); |
| ASSERT_TRUE(!!(pending_signals & ZX_EVENTPAIR_PEER_CLOSED) == (i >= kNumBuffers)); |
| } |
| |
| auto wait_for_buffers_allocated_result = collection->WaitForBuffersAllocated(); |
| ASSERT_OK(wait_for_buffers_allocated_result.status()); |
| auto& response = wait_for_buffers_allocated_result.value(); |
| ASSERT_OK(response.status); |
| auto& info = response.buffer_collection_info; |
| ASSERT_EQ(info.buffer_count, kNumBuffers); |
| |
| // ZX_EVENTPAIR_PEER_CLOSED should be seen for eventpair(s) >= kNumBuffers. |
| for (uint32_t i = 0; i < kNumEventpairs; ++i) { |
| zx_signals_t pending_signals; |
| auto status = client[i].wait_one( |
| ZX_EVENTPAIR_PEER_CLOSED, |
| i >= kNumBuffers ? zx::time::infinite() : zx::time::infinite_past(), &pending_signals); |
| ASSERT_TRUE(status == (i >= kNumBuffers) ? ZX_OK : ZX_ERR_TIMED_OUT); |
| ASSERT_TRUE(!!(pending_signals & ZX_EVENTPAIR_PEER_CLOSED) == (i >= kNumBuffers)); |
| } |
| |
| auto [attached_token_client, attached_token_server] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| ASSERT_OK(collection->AttachToken(std::numeric_limits<uint32_t>::max(), |
| std::move(attached_token_server))); |
| |
| ASSERT_OK(collection->Sync()); |
| |
| zx::result attached_collection_endpoints = |
| fidl::CreateEndpoints<fuchsia_sysmem::BufferCollection>(); |
| ASSERT_OK(attached_collection_endpoints); |
| auto [attached_collection_client, attached_collection_server] = |
| std::move(*attached_collection_endpoints); |
| fidl::WireSyncClient attached_collection{std::move(attached_collection_client)}; |
| |
| auto allocator = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator); |
| auto bind_result = allocator->BindSharedCollection(std::move(attached_token_client), |
| std::move(attached_collection_server)); |
| ASSERT_OK(bind_result); |
| auto sync_result_3 = attached_collection->Sync(); |
| ASSERT_OK(sync_result_3); |
| |
| zx::eventpair attached_lifetime_client, attached_lifetime_server; |
| zx::eventpair::create(/*options=*/0, &attached_lifetime_client, &attached_lifetime_server); |
| // With a buffers_remaining of 0, normally this would require 0 buffers remaining in the |
| // LogicalBuffercollection to close attached_lifetime_server, but because we're about to force |
| // logical allocation failure, it'll close as soon as we hit logical allocation failure for the |
| // attached token. The logical allocation failure of the attached token doesn't impact collection |
| // in any way. |
| ASSERT_OK(attached_collection |
| ->AttachLifetimeTracking(std::move(attached_lifetime_server), |
| /*buffers_remaining=*/0) |
| .status()); |
| fuchsia_sysmem::wire::BufferCollectionConstraints attached_constraints; |
| attached_constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| // We won't be able to logically allocate, because original allocation didn't make room for this |
| // buffer. |
| attached_constraints.min_buffer_count_for_camping = 1; |
| attached_constraints.has_buffer_memory_constraints = true; |
| attached_constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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(attached_constraints.image_format_constraints_count == 0); |
| auto attached_set_constraints_result = |
| attached_collection->SetConstraints(true, std::move(attached_constraints)); |
| ASSERT_TRUE(attached_set_constraints_result.ok()); |
| zx_signals_t attached_pending_signals; |
| zx_status_t status = attached_lifetime_client.wait_one( |
| ZX_EVENTPAIR_PEER_CLOSED, zx::time::infinite(), &attached_pending_signals); |
| ASSERT_OK(status); |
| ASSERT_TRUE(attached_pending_signals & ZX_EVENTPAIR_PEER_CLOSED); |
| |
| collection.value() = {}; |
| |
| // ZX_EVENTPAIR_PEER_CLOSED should be seen for eventpair(s) >= kNumBuffers. |
| for (uint32_t i = 0; i < kNumEventpairs; ++i) { |
| zx_signals_t pending_signals; |
| status = client[i].wait_one(ZX_EVENTPAIR_PEER_CLOSED, |
| i >= kNumBuffers ? zx::time::infinite() : zx::time::infinite_past(), |
| &pending_signals); |
| ASSERT_TRUE(status == (i >= kNumBuffers) ? ZX_OK : ZX_ERR_TIMED_OUT); |
| ASSERT_TRUE(!!(pending_signals & ZX_EVENTPAIR_PEER_CLOSED) == (i >= kNumBuffers)); |
| } |
| |
| for (uint32_t j = 0; j < kNumBuffers; ++j) { |
| info.buffers[j].vmo.reset(); |
| for (uint32_t i = 0; i < kNumBuffers; ++i) { |
| zx_signals_t pending_signals; |
| status = client[i].wait_one( |
| ZX_EVENTPAIR_PEER_CLOSED, |
| i >= kNumBuffers - (j + 1) ? zx::time::infinite() : zx::time::infinite_past(), |
| &pending_signals); |
| ASSERT_TRUE(status == (i >= kNumBuffers - (j + 1)) ? ZX_OK : ZX_ERR_TIMED_OUT); |
| ASSERT_TRUE(!!(pending_signals & ZX_EVENTPAIR_PEER_CLOSED) == (i >= kNumBuffers - (j + 1)), |
| ""); |
| } |
| } |
| } |
| |
| TEST(Sysmem, TokenOneParticipantWithImageConstraintsV1) { |
| auto collection = make_single_participant_collection_v1(); |
| ASSERT_OK(collection); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 3; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[0]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| // 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, sizeof(fuchsia_sysmem::wire::BufferCollectionConstraints), 1, ofp); |
| fclose(ofp); |
| } else { |
| printf("Failed to write sysmem BufferCollectionConstraints corpus file.\n"); |
| fflush(stderr); |
| } |
| #endif // SYSMEM_FUZZ_CORPUS |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocation_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocation_result); |
| ASSERT_OK(allocation_result.value().status); |
| |
| auto buffer_collection_info = &allocation_result.value().buffer_collection_info; |
| |
| 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::wire::CoherencyDomain::kCpu); |
| // 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.get(), ZX_HANDLE_INVALID); |
| uint64_t size_bytes = 0; |
| auto status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo.get(), &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.get(), ZX_HANDLE_INVALID); |
| } |
| } |
| } |
| |
| TEST(Sysmem, MinBufferCountV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 3; |
| constraints.min_buffer_count = 5; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocation_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocation_result); |
| ASSERT_OK(allocation_result.value().status); |
| ASSERT_EQ(allocation_result.value().buffer_collection_info.buffer_count, 5); |
| } |
| |
| TEST(Sysmem, BufferNameV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| const char kSysmemName[] = "abcdefghijkl\0mnopqrstuvwxyz"; |
| const char kLowPrioName[] = "low_pri"; |
| // Override default set in make_single_participant_collection_v1) |
| ASSERT_OK(collection->SetName(2000000, kSysmemName).status()); |
| ASSERT_OK(collection->SetName(0, kLowPrioName).status()); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocation_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocation_result); |
| ASSERT_OK(allocation_result.value().status); |
| |
| ASSERT_EQ(allocation_result.value().buffer_collection_info.buffer_count, 1); |
| zx_handle_t vmo = allocation_result.value().buffer_collection_info.buffers[0].vmo.get(); |
| 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, NoTokenV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| auto [collection_client_end, collection_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection{std::move(collection_client_end)}; |
| |
| ASSERT_OK(allocator->AllocateNonSharedCollection(std::move(collection_server_end))); |
| |
| SetDefaultCollectionNameV1(collection); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| // Ask for display usage to encourage using the ram coherency domain. |
| constraints.usage.display = fuchsia_sysmem::wire::kDisplayUsageLayer; |
| constraints.min_buffer_count_for_camping = 3; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocation_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocation_result); |
| ASSERT_OK(allocation_result.value().status); |
| |
| auto buffer_collection_info = &allocation_result.value().buffer_collection_info; |
| 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::wire::CoherencyDomain::kRam); |
| 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.get(), ZX_HANDLE_INVALID); |
| uint64_t size_bytes = 0; |
| auto status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo.get(), &size_bytes); |
| ASSERT_EQ(status, ZX_OK); |
| ASSERT_EQ(size_bytes, 64 * 1024); |
| } else { |
| ASSERT_EQ(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| } |
| } |
| } |
| |
| TEST(Sysmem, NoSyncV1) { |
| auto allocator_1 = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator_1); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| ASSERT_OK(allocator_1->AllocateSharedCollection(std::move(token_server_1))); |
| |
| const char* kAllocatorName = "TestAllocator"; |
| ASSERT_OK( |
| allocator_1->SetDebugClientInfo(fidl::StringView::FromExternal(kAllocatorName), 1u).status()); |
| |
| const char* kClientName = "TestClient"; |
| ASSERT_OK(token_1->SetDebugClientInfo(fidl::StringView::FromExternal(kClientName), 2u).status()); |
| |
| // Make another token so we can bind it and set a name on the collection. |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection> collection_3; |
| |
| { |
| auto [token_client_3, token_server_3] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| auto [collection_client_3, collection_server_3] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_3))); |
| ASSERT_OK(token_1->Sync()); |
| |
| ASSERT_OK(allocator_1->BindSharedCollection(std::move(token_client_3), |
| std::move(collection_server_3))); |
| collection_3 = fidl::WireSyncClient(std::move(collection_client_3)); |
| |
| const char* kCollectionName = "TestCollection"; |
| ASSERT_OK(collection_3->SetName(1u, fidl::StringView::FromExternal(kCollectionName)).status()); |
| } |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_2{std::move(token_client_2)}; |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| const char* kClient2Name = "TestClient2"; |
| ASSERT_OK(token_2->SetDebugClientInfo(fidl::StringView::FromExternal(kClient2Name), 3u).status()); |
| |
| // Close to prevent Sync on token_client_1 from failing later due to LogicalBufferCollection |
| // failure caused by the token handle closing. |
| ASSERT_OK(token_2->Close().status()); |
| |
| ASSERT_OK( |
| allocator_1->BindSharedCollection(token_2.TakeClientEnd(), std::move(collection_server_1))); |
| |
| // Duplicate has not been sent (or received) so this should fail. |
| auto sync_result = collection_1->Sync(); |
| EXPECT_STATUS(sync_result.status(), ZX_ERR_PEER_CLOSED); |
| |
| // The duplicate/sync should print out an error message but succeed. |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| ASSERT_OK(token_1->Sync()); |
| } |
| |
| TEST(Sysmem, MultipleParticipantsV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| // Client 1 creates a token and new LogicalBufferCollection using |
| // AllocateSharedCollection(). |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server_1))); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| // 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). |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| |
| auto [token_client_3, token_server_3] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| // Client 3 is used to test a participant that doesn't set any constraints |
| // and only wants a notification that the allocation is done. |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_3))); |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| ASSERT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| |
| SetDefaultCollectionNameV1(collection_1); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_1.min_buffer_count_for_camping = 3; |
| constraints_1.has_buffer_memory_constraints = true; |
| constraints_1.buffer_memory_constraints = fuchsia_sysmem::wire::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::wire::ImageFormatConstraints& image_constraints_1 = |
| constraints_1.image_format_constraints[0]; |
| image_constraints_1.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints_1.color_spaces_count = 1; |
| image_constraints_1.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| // 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. |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_2(constraints_1); |
| // 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, sizeof(fuchsia_sysmem::wire::BufferCollectionConstraints), 1, ofp); |
| fwrite(&constraints_2, sizeof(fuchsia_sysmem::wire::BufferCollectionConstraints), 1, ofp); |
| fclose(ofp); |
| } else { |
| printf("Failed to write sysmem multi BufferCollectionConstraints corpus file.\n"); |
| fflush(stderr); |
| } |
| #endif // SYSMEM_FUZZ_CORPUS |
| |
| ASSERT_OK(collection_1->SetConstraints(true, std::move(constraints_1))); |
| |
| // Client 2 connects to sysmem separately. |
| auto allocator_2 = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator_2); |
| |
| auto [collection_client_2, collection_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_client_2)}; |
| |
| // 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. |
| ASSERT_OK(collection_1->Sync()); |
| |
| // For the moment, cause the server to count some fake churn, enough times to cause the server |
| // to re-alloc all the server's held FIDL tables 4 times before we continue. These are |
| // synchronous calls, so the 4 re-allocs are done by the time this loop completes. |
| // |
| // TODO(https://fxbug.dev/42108892): Switch to creating real churn instead, once we have new |
| // messages that can create real churn. |
| constexpr uint32_t kChurnCount = 256 * 2; // 256 * 4; |
| for (uint32_t i = 0; i < kChurnCount; ++i) { |
| ASSERT_OK(collection_1->Sync()); |
| } |
| |
| ASSERT_NE(token_client_2.channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator_2->BindSharedCollection(std::move(token_client_2), std::move(collection_server_2))); |
| |
| auto [collection_client_3, collection_server_3] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_3{std::move(collection_client_3)}; |
| |
| ASSERT_NE(token_client_3.channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator_2->BindSharedCollection(std::move(token_client_3), std::move(collection_server_3))); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints empty_constraints; |
| ASSERT_OK(collection_3->SetConstraints(false, std::move(empty_constraints))); |
| |
| // Not all constraints have been input, so the buffers haven't been |
| // allocated yet. |
| auto check_result_1 = collection_1->CheckBuffersAllocated(); |
| ASSERT_OK(check_result_1); |
| EXPECT_EQ(check_result_1->status, ZX_ERR_UNAVAILABLE); |
| auto check_result_2 = collection_2->CheckBuffersAllocated(); |
| ASSERT_OK(check_result_2); |
| EXPECT_EQ(check_result_2->status, ZX_ERR_UNAVAILABLE); |
| |
| ASSERT_OK(collection_2->SetConstraints(true, std::move(constraints_2))); |
| |
| // |
| // Only after both participants (both clients) have SetConstraints() will |
| // the allocation be successful. |
| // |
| |
| auto allocate_result_1 = collection_1->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result_1); |
| ASSERT_OK(allocate_result_1->status); |
| |
| auto check_result_allocated_1 = collection_1->CheckBuffersAllocated(); |
| ASSERT_OK(check_result_allocated_1); |
| EXPECT_OK(check_result_allocated_1->status); |
| auto check_result_allocated_2 = collection_2->CheckBuffersAllocated(); |
| ASSERT_OK(check_result_allocated_2); |
| EXPECT_OK(check_result_allocated_2->status); |
| |
| auto allocate_result_2 = collection_2->WaitForBuffersAllocated(); |
| ASSERT_OK(allocate_result_2); |
| ASSERT_OK(allocate_result_2->status); |
| |
| auto allocate_result_3 = collection_3->WaitForBuffersAllocated(); |
| ASSERT_OK(allocate_result_3); |
| ASSERT_OK(allocate_result_3->status); |
| |
| // |
| // 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. |
| // |
| auto buffer_collection_info_1 = &allocate_result_1->buffer_collection_info; |
| auto buffer_collection_info_2 = &allocate_result_2->buffer_collection_info; |
| auto buffer_collection_info_3 = &allocate_result_3->buffer_collection_info; |
| |
| for (uint32_t i = 0; i < std::size(buffer_collection_info_1->buffers); ++i) { |
| ASSERT_EQ(buffer_collection_info_1->buffers[i].vmo.get() != ZX_HANDLE_INVALID, |
| buffer_collection_info_2->buffers[i].vmo.get() != ZX_HANDLE_INVALID); |
| if (buffer_collection_info_1->buffers[i].vmo.get() != ZX_HANDLE_INVALID) { |
| // The handle values must be different. |
| ASSERT_NE(buffer_collection_info_1->buffers[i].vmo.get(), |
| buffer_collection_info_2->buffers[i].vmo.get()); |
| // 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.get()); |
| zx_koid_t koid_2 = get_koid(buffer_collection_info_2->buffers[i].vmo.get()); |
| ASSERT_EQ(koid_1, koid_2); |
| } |
| |
| // Buffer collection 3 passed false to SetConstraints(), so we get no VMOs. |
| ASSERT_EQ(ZX_HANDLE_INVALID, buffer_collection_info_3->buffers[i].vmo.get()); |
| } |
| |
| // |
| // 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.get(), ZX_HANDLE_INVALID); |
| ASSERT_NE(buffer_collection_info_2->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| |
| uint64_t size_bytes_1 = 0; |
| ASSERT_OK(buffer_collection_info_1->buffers[i].vmo.get_size(&size_bytes_1)); |
| |
| uint64_t size_bytes_2 = 0; |
| ASSERT_OK(zx_vmo_get_size(buffer_collection_info_2->buffers[i].vmo.get(), &size_bytes_2)); |
| |
| // 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); |
| |
| // Clear out vmo handles for memcmp below |
| buffer_collection_info_1->buffers[i].vmo.reset(); |
| buffer_collection_info_2->buffers[i].vmo.reset(); |
| } else { |
| ASSERT_EQ(buffer_collection_info_1->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| ASSERT_EQ(buffer_collection_info_2->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| } |
| } |
| |
| int32_t memcmp_result = memcmp(buffer_collection_info_1, buffer_collection_info_2, |
| sizeof(fuchsia_sysmem::wire::BufferCollectionInfo2)); |
| // Check that buffer_collection_info_1 and buffer_collection_info_2 are |
| // consistent. |
| ASSERT_EQ(memcmp_result, 0); |
| |
| memcmp_result = memcmp(buffer_collection_info_1, buffer_collection_info_3, |
| sizeof(fuchsia_sysmem::wire::BufferCollectionInfo2)); |
| // Check that buffer_collection_info_1 and buffer_collection_info_3 are |
| // consistent, except for the vmos. |
| ASSERT_EQ(memcmp_result, 0); |
| |
| // Close to ensure grabbing null constraints from a closed collection |
| // doesn't crash |
| EXPECT_OK(collection_3->Close()); |
| } |
| |
| // This test is mainly to have something in the fuzzer corpus using format modifiers. |
| TEST(Sysmem, ComplicatedFormatModifiersV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server_1))); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| ASSERT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| |
| SetDefaultCollectionNameV1(collection_1); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_1.min_buffer_count_for_camping = 1; |
| |
| constexpr uint64_t kFormatModifiers[] = { |
| fuchsia_sysmem::wire::kFormatModifierLinear, |
| fuchsia_sysmem::wire::kFormatModifierIntelI915XTiled, |
| fuchsia_sysmem::wire::kFormatModifierArmAfbc16X16SplitBlockSparseYuvTeTiledHeader, |
| fuchsia_sysmem::wire::kFormatModifierArmAfbc16X16Te}; |
| constraints_1.image_format_constraints_count = std::size(kFormatModifiers); |
| |
| for (uint32_t i = 0; i < std::size(kFormatModifiers); i++) { |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints_1 = |
| constraints_1.image_format_constraints[i]; |
| |
| image_constraints_1.pixel_format.type = i < 2 ? fuchsia_sysmem::wire::PixelFormatType::kR8G8B8A8 |
| : fuchsia_sysmem::wire::PixelFormatType::kBgra32; |
| 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::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kSrgb, |
| }; |
| } |
| |
| // 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. |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_2(constraints_1); |
| |
| 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, sizeof(fuchsia_sysmem::wire::BufferCollectionConstraints), 1, ofp); |
| fwrite(&constraints_2, sizeof(fuchsia_sysmem::wire::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 |
| |
| ASSERT_OK(collection_1->SetConstraints(true, std::move(constraints_1))); |
| |
| auto [collection_client_2, collection_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_client_2)}; |
| |
| ASSERT_OK(collection_1->Sync()); |
| |
| ASSERT_NE(token_client_2.channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator->BindSharedCollection(std::move(token_client_2), std::move(collection_server_2))); |
| |
| ASSERT_OK(collection_2->SetConstraints(true, std::move(constraints_2))); |
| |
| // |
| // Only after both participants (both clients) have SetConstraints() will |
| // the allocation be successful. |
| // |
| auto allocate_result_1 = collection_1->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result_1); |
| ASSERT_OK(allocate_result_1->status); |
| } |
| |
| TEST(Sysmem, MultipleParticipantsColorspaceRankingV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server_1))); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| ASSERT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| |
| SetDefaultCollectionNameV1(collection_1); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_1.min_buffer_count_for_camping = 1; |
| constraints_1.has_buffer_memory_constraints = true; |
| constraints_1.buffer_memory_constraints = fuchsia_sysmem::wire::BufferMemoryConstraints{ |
| .min_size_bytes = 64 * 1024, |
| .max_size_bytes = 128 * 1024, |
| .cpu_domain_supported = true, |
| }; |
| constraints_1.image_format_constraints_count = 1; |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints_1 = |
| constraints_1.image_format_constraints[0]; |
| image_constraints_1.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints_1.color_spaces_count = 3; |
| image_constraints_1.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec601Pal, |
| }; |
| image_constraints_1.color_space[1] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec601PalFullRange, |
| }; |
| image_constraints_1.color_space[2] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| |
| // 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. |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_2(constraints_1); |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints_2 = |
| constraints_2.image_format_constraints[0]; |
| image_constraints_2.color_spaces_count = 2; |
| image_constraints_2.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec601PalFullRange, |
| }; |
| image_constraints_2.color_space[1] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| |
| ASSERT_OK(collection_1->SetConstraints(true, std::move(constraints_1))); |
| |
| auto [collection_client_2, collection_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_client_2)}; |
| |
| ASSERT_OK(collection_1->Sync()); |
| |
| ASSERT_NE(token_client_2.channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator->BindSharedCollection(std::move(token_client_2), std::move(collection_server_2))); |
| |
| ASSERT_OK(collection_2->SetConstraints(true, std::move(constraints_2))); |
| |
| // Both collections should yield the same results |
| auto check_allocation_results = |
| [](const ::fidl::WireResult<::fuchsia_sysmem::BufferCollection::WaitForBuffersAllocated> |
| allocation_result) { |
| ASSERT_OK(allocation_result); |
| ASSERT_OK(allocation_result.value().status); |
| |
| auto buffer_collection_info = &allocation_result.value().buffer_collection_info; |
| ASSERT_EQ(buffer_collection_info->buffer_count, 2); |
| 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::wire::PixelFormatType::kNv12); |
| ASSERT_EQ(buffer_collection_info->settings.image_format_constraints.color_spaces_count, 1, |
| ""); |
| ASSERT_EQ(buffer_collection_info->settings.image_format_constraints.color_space[0].type, |
| fuchsia_sysmem::wire::ColorSpaceType::kRec709); |
| }; |
| |
| check_allocation_results(collection_1->WaitForBuffersAllocated()); |
| check_allocation_results(collection_2->WaitForBuffersAllocated()); |
| } |
| |
| // Regression-avoidance test for https://fxbug.dev/42139125: |
| // * One client with two NV12 ImageFormatConstraints, one with rec601, one with rec709 |
| // * One client with NV12 ImageFormatConstraints with rec709. |
| // * Scrambled ordering of which constraints get processed first, but deterministically check each |
| // client going first in the first couple iterations. |
| TEST(Sysmem, |
| MultipleParticipants_TwoImageFormatConstraintsSamePixelFormat_CompatibleColorspacesV1) { |
| // Multiple iterations to try to repro https://fxbug.dev/42139125, in case it comes back. This |
| // should be at least 2 to check both orderings with two clients. |
| std::atomic<uint32_t> clean_failure_seen_count = 0; |
| const uint32_t kCleanFailureSeenGoal = 15; |
| const uint32_t kMaxIterations = 50; |
| uint32_t iteration; |
| for (iteration = 0; |
| iteration < kMaxIterations && clean_failure_seen_count.load() < kCleanFailureSeenGoal; |
| ++iteration) { |
| if (iteration % 100 == 0) { |
| printf("starting iteration: %u clean_failure_seen_count: %u\n", iteration, |
| clean_failure_seen_count.load()); |
| } |
| const uint32_t kNumClients = 2; |
| std::vector<fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>> clients = |
| create_clients_v1(kNumClients); |
| |
| auto build_constraints = |
| [](bool include_rec601) -> fuchsia_sysmem::wire::BufferCollectionConstraints { |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::BufferMemoryConstraints{ |
| .min_size_bytes = zx_system_get_page_size(), |
| .max_size_bytes = 0, // logically not set |
| .cpu_domain_supported = true, |
| }; |
| constraints.image_format_constraints_count = 1 + (include_rec601 ? 1 : 0); |
| uint32_t image_constraints_index = 0; |
| if (include_rec601) { |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[image_constraints_index]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec601Ntsc, |
| }; |
| ++image_constraints_index; |
| } |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[image_constraints_index]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| return constraints; |
| }; |
| |
| // Both collections should yield the same results |
| auto check_allocation_results = |
| [&](const ::fidl::WireResult<::fuchsia_sysmem::BufferCollection::WaitForBuffersAllocated>& |
| allocation_result) { |
| EXPECT_STATUS(allocation_result.value().status, ZX_ERR_NOT_SUPPORTED); |
| ++clean_failure_seen_count; |
| }; |
| |
| std::vector<std::thread> client_threads; |
| std::atomic<uint32_t> ready_thread_count = 0; |
| std::atomic<bool> step_0 = false; |
| for (uint32_t i = 0; i < kNumClients; ++i) { |
| client_threads.emplace_back([&, which_client = i] { |
| std::random_device random_device; |
| std::uint_fast64_t seed{random_device()}; |
| std::mt19937_64 prng{seed}; |
| std::uniform_int_distribution<uint32_t> uint32_distribution( |
| 0, std::numeric_limits<uint32_t>::max()); |
| ++ready_thread_count; |
| while (!step_0.load()) { |
| // spin-wait |
| } |
| // randomize the continuation timing |
| zx::nanosleep(zx::deadline_after(zx::usec(uint32_distribution(prng) % 50))); |
| auto set_constraints_result = |
| clients[which_client]->SetConstraints(true, build_constraints(which_client % 2 == 0)); |
| if (!set_constraints_result.ok()) { |
| EXPECT_STATUS(set_constraints_result.status(), ZX_ERR_PEER_CLOSED); |
| return; |
| } |
| // randomize the continuation timing |
| zx::nanosleep(zx::deadline_after(zx::usec(uint32_distribution(prng) % 50))); |
| auto wait_result = clients[which_client]->WaitForBuffersAllocated(); |
| if (!wait_result.ok()) { |
| EXPECT_STATUS(wait_result.status(), ZX_ERR_PEER_CLOSED); |
| return; |
| } |
| check_allocation_results(wait_result); |
| }); |
| } |
| |
| while (ready_thread_count.load() != kNumClients) { |
| // spin wait |
| } |
| |
| step_0.store(true); |
| |
| for (auto& thread : client_threads) { |
| thread.join(); |
| } |
| } |
| |
| printf("iterations: %u clean_failure_seen_count: %u\n", iteration, |
| clean_failure_seen_count.load()); |
| } |
| |
| TEST(Sysmem, DuplicateSyncV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server_1))); |
| |
| zx_rights_t rights_attenuation_masks[] = {ZX_RIGHT_SAME_RIGHTS}; |
| auto duplicate_result = |
| token_1->DuplicateSync(fidl::VectorView<zx_rights_t>::FromExternal(rights_attenuation_masks)); |
| ASSERT_OK(duplicate_result); |
| ASSERT_EQ(duplicate_result.value().tokens.count(), 1); |
| auto token_client_2 = std::move(duplicate_result.value().tokens[0]); |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| ASSERT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| |
| SetDefaultCollectionNameV1(collection_1); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_1.min_buffer_count_for_camping = 1; |
| constraints_1.has_buffer_memory_constraints = true; |
| constraints_1.buffer_memory_constraints = fuchsia_sysmem::wire::BufferMemoryConstraints{ |
| .min_size_bytes = 64 * 1024, |
| .cpu_domain_supported = true, |
| }; |
| |
| // 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. |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_2(constraints_1); |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_3(constraints_1); |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_4(constraints_1); |
| |
| ASSERT_OK(collection_1->SetConstraints(true, std::move(constraints_1))); |
| |
| auto collection_endpoints_2 = fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_endpoints_2.client)}; |
| |
| fidl::WireSyncClient token_2{std::move(token_client_2)}; |
| // Remove write from last token |
| zx_rights_t rights_attenuation_masks_2[] = {ZX_RIGHT_SAME_RIGHTS, |
| ZX_DEFAULT_VMO_RIGHTS & ~ZX_RIGHT_WRITE}; |
| auto duplicate_result_2 = token_2->DuplicateSync( |
| fidl::VectorView<zx_rights_t>::FromExternal(rights_attenuation_masks_2)); |
| ASSERT_OK(duplicate_result_2); |
| ASSERT_EQ(duplicate_result_2->tokens.count(), 2); |
| |
| ASSERT_NE(token_2.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK(allocator->BindSharedCollection(token_2.TakeClientEnd(), |
| std::move(collection_endpoints_2.server))); |
| |
| ASSERT_OK(collection_2->SetConstraints(true, std::move(constraints_2))); |
| |
| auto collection_endpoints_3 = fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_3{std::move(collection_endpoints_3.client)}; |
| |
| auto collection_endpoints_4 = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollection>(); |
| ASSERT_OK(collection_endpoints_4); |
| fidl::WireSyncClient collection_4{std::move(collection_endpoints_4->client)}; |
| |
| ASSERT_NE(duplicate_result_2->tokens[0].channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK(allocator->BindSharedCollection(std::move(duplicate_result_2->tokens[0]), |
| std::move(collection_endpoints_3.server))); |
| |
| ASSERT_NE(duplicate_result_2->tokens[1].channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK(allocator->BindSharedCollection(std::move(duplicate_result_2->tokens[1]), |
| std::move(collection_endpoints_4->server))); |
| |
| ASSERT_OK(collection_3->SetConstraints(true, constraints_3)); |
| ASSERT_OK(collection_4->SetConstraints(true, constraints_4)); |
| |
| // |
| // Only after all participants have SetConstraints() will |
| // the allocation be successful. |
| // |
| auto allocate_result_1 = collection_1->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result_1); |
| ASSERT_OK(allocate_result_1->status); |
| |
| auto allocate_result_3 = collection_3->WaitForBuffersAllocated(); |
| ASSERT_OK(allocate_result_3); |
| ASSERT_OK(allocate_result_3->status); |
| |
| auto allocate_result_4 = collection_4->WaitForBuffersAllocated(); |
| ASSERT_OK(allocate_result_4); |
| ASSERT_OK(allocate_result_4->status); |
| |
| // Check rights |
| zx_info_handle_basic_t info = {}; |
| ASSERT_OK(allocate_result_3->buffer_collection_info.buffers[0].vmo.get_info( |
| ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr)); |
| ASSERT_EQ(info.rights & ZX_RIGHT_WRITE, ZX_RIGHT_WRITE); |
| |
| ASSERT_OK(allocate_result_4->buffer_collection_info.buffers[0].vmo.get_info( |
| ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr)); |
| ASSERT_EQ(info.rights & ZX_RIGHT_WRITE, 0); |
| } |
| |
| TEST(Sysmem, CloseWithOutstandingWaitV1) { |
| auto allocator_1 = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator_1); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| ASSERT_OK(allocator_1->AllocateSharedCollection(std::move(token_server_1))); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| ASSERT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator_1->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_1.min_buffer_count_for_camping = 1; |
| |
| constraints_1.image_format_constraints_count = 1; |
| |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints_1 = |
| constraints_1.image_format_constraints[0]; |
| |
| image_constraints_1.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kR8G8B8A8; |
| image_constraints_1.pixel_format.has_format_modifier = true; |
| image_constraints_1.pixel_format.format_modifier.value = |
| fuchsia_sysmem::wire::kFormatModifierLinear; |
| image_constraints_1.color_spaces_count = 1; |
| image_constraints_1.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kSrgb, |
| }; |
| |
| ASSERT_OK(collection_1->SetConstraints(true, std::move(constraints_1))); |
| |
| std::thread wait_thread([&collection_1]() { |
| auto allocate_result_1 = collection_1->WaitForBuffersAllocated(); |
| EXPECT_EQ(allocate_result_1.status(), ZX_ERR_PEER_CLOSED); |
| }); |
| |
| // Try to wait until the wait has been processed by the server. |
| zx_nanosleep(zx_deadline_after(ZX_SEC(5))); |
| |
| auto [collection_client_2, collection_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_client_2)}; |
| |
| ASSERT_OK(collection_1->Sync()); |
| |
| ASSERT_NE(token_client_2.channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator_1->BindSharedCollection(std::move(token_client_2), std::move(collection_server_2))); |
| |
| collection_2 = {}; |
| |
| wait_thread.join(); |
| |
| ASSERT_OK(verify_connectivity_v1(*allocator_1)); |
| } |
| |
| TEST(Sysmem, ConstraintsRetainedBeyondCleanCloseV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| // Client 1 creates a token and new LogicalBufferCollection using |
| // AllocateSharedCollection(). |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server_1))); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| // 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). |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| ASSERT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| |
| SetDefaultCollectionNameV1(collection_1); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_1.min_buffer_count_for_camping = 2; |
| constraints_1.has_buffer_memory_constraints = true; |
| constraints_1.buffer_memory_constraints = fuchsia_sysmem::wire::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. |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_2(constraints_1); |
| ASSERT_EQ(constraints_2.min_buffer_count_for_camping, 2); |
| |
| ASSERT_OK(collection_1->SetConstraints(true, constraints_1)); |
| |
| // Client 2 connects to sysmem separately. |
| auto allocator_2 = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator_2); |
| |
| auto [collection_client_2, collection_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_client_2)}; |
| |
| // 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. |
| ASSERT_OK(collection_1->Sync()); |
| |
| // client 1 will now do a clean Close(), but client 1's constraints will be |
| // retained by the LogicalBufferCollection. |
| ASSERT_OK(collection_1->Close()); |
| // close client 1's channel. |
| collection_1 = {}; |
| |
| // 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.channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator_2->BindSharedCollection(std::move(token_client_2), std::move(collection_server_2))); |
| |
| SetDefaultCollectionNameV1(collection_2); |
| |
| // Not all constraints have been input (client 2 hasn't SetConstraints() |
| // yet), so the buffers haven't been allocated yet. |
| auto check_result_2 = collection_2->CheckBuffersAllocated(); |
| ASSERT_OK(check_result_2); |
| EXPECT_EQ(check_result_2->status, ZX_ERR_UNAVAILABLE); |
| |
| ASSERT_OK(collection_2->SetConstraints(true, std::move(constraints_2))); |
| |
| // |
| // Now that client 2 has SetConstraints(), the allocation will proceed, with |
| // client 1's constraints included despite client 1 having done a clean |
| // Close(). |
| // |
| auto allocate_result_2 = collection_2->WaitForBuffersAllocated(); |
| ASSERT_OK(allocate_result_2); |
| ASSERT_OK(allocate_result_2->status); |
| |
| // The fact that this is 4 instead of 2 proves that client 1's constraints |
| // were taken into account. |
| ASSERT_EQ(allocate_result_2->buffer_collection_info.buffer_count, 4); |
| } |
| |
| TEST(Sysmem, HeapConstraintsV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.vulkan = fuchsia_sysmem::wire::kVulkanUsageTransferDst; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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::wire::HeapType::kSystemRam}}; |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| ASSERT_EQ(allocate_result.value().buffer_collection_info.buffer_count, 1); |
| ASSERT_EQ( |
| allocate_result.value().buffer_collection_info.settings.buffer_settings.coherency_domain, |
| fuchsia_sysmem::wire::CoherencyDomain::kInaccessible); |
| ASSERT_EQ(allocate_result.value().buffer_collection_info.settings.buffer_settings.heap, |
| fuchsia_sysmem::wire::HeapType::kSystemRam); |
| ASSERT_EQ(allocate_result.value() |
| .buffer_collection_info.settings.buffer_settings.is_physically_contiguous, |
| true); |
| } |
| |
| TEST(Sysmem, CpuUsageAndInaccessibleDomainFailsV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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::wire::HeapType::kSystemRam}}; |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // usage.cpu != 0 && inaccessible_domain_supported is expected to result in failure to |
| // allocate. |
| ASSERT_NE(allocate_result.status(), ZX_OK); |
| } |
| |
| TEST(Sysmem, SystemRamHeapSupportsAllDomainsV1) { |
| fuchsia_sysmem::wire::CoherencyDomain domains[] = { |
| fuchsia_sysmem::wire::CoherencyDomain::kCpu, |
| fuchsia_sysmem::wire::CoherencyDomain::kRam, |
| fuchsia_sysmem::wire::CoherencyDomain::kInaccessible, |
| }; |
| |
| for (const fuchsia_sysmem::wire::CoherencyDomain domain : domains) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.vulkan = fuchsia_sysmem::wire::kVulkanImageUsageTransferDst; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::BufferMemoryConstraints{ |
| .min_size_bytes = 4 * 1024, |
| .max_size_bytes = 4 * 1024, |
| .physically_contiguous_required = true, |
| .secure_required = false, |
| .ram_domain_supported = (domain == fuchsia_sysmem::wire::CoherencyDomain::kRam), |
| .cpu_domain_supported = (domain == fuchsia_sysmem::wire::CoherencyDomain::kCpu), |
| .inaccessible_domain_supported = |
| (domain == fuchsia_sysmem::wire::CoherencyDomain::kInaccessible), |
| .heap_permitted_count = 1, |
| .heap_permitted = {fuchsia_sysmem::wire::HeapType::kSystemRam}}; |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| ASSERT_OK(allocate_result, "Failed Allocate(): domain supported = %u", |
| static_cast<unsigned int>(domain)); |
| |
| ASSERT_EQ( |
| domain, |
| allocate_result.value().buffer_collection_info.settings.buffer_settings.coherency_domain, |
| "Coherency domain doesn't match constraints"); |
| } |
| } |
| |
| TEST(Sysmem, RequiredSizeV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = false; |
| constraints.image_format_constraints_count = 1; |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[0]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| 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; |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| |
| size_t vmo_size; |
| zx_status_t status = zx_vmo_get_size( |
| allocate_result.value().buffer_collection_info.buffers[0].vmo.get(), &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); |
| } |
| |
| // min_bytes_per_row should account for the bytes_per_row_divisor. |
| TEST(Sysmem, BytesPerRowMinRowV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = false; |
| constraints.image_format_constraints_count = 1; |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[0]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kR8G8B8A8; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kSrgb, |
| }; |
| image_constraints.min_coded_width = 254; |
| 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; |
| constexpr uint32_t kBytesPerRowDivisor = 64; |
| image_constraints.bytes_per_row_divisor = kBytesPerRowDivisor; |
| 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; |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| |
| auto& out_constraints = allocate_result->buffer_collection_info.settings.image_format_constraints; |
| EXPECT_EQ(out_constraints.min_bytes_per_row % kBytesPerRowDivisor, 0, "%u", |
| out_constraints.min_bytes_per_row); |
| } |
| |
| TEST(Sysmem, CpuUsageAndNoBufferMemoryConstraintsV1) { |
| auto allocator_1 = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator_1); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| ASSERT_OK(allocator_1->AllocateSharedCollection(std::move(token_server_1))); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| ASSERT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator_1->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| |
| SetDefaultCollectionNameV1(collection_1); |
| |
| // First client has CPU usage constraints but no buffer memory constraints. |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_1.min_buffer_count_for_camping = 1; |
| constraints_1.has_buffer_memory_constraints = false; |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_2; |
| constraints_2.usage.display = fuchsia_sysmem::wire::kDisplayUsageLayer; |
| constraints_2.min_buffer_count_for_camping = 1; |
| constraints_2.has_buffer_memory_constraints = true; |
| constraints_2.buffer_memory_constraints = fuchsia_sysmem::wire::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 = {}, |
| }; |
| |
| ASSERT_OK(collection_1->SetConstraints(true, std::move(constraints_1))); |
| |
| auto allocator_2 = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator_2); |
| |
| auto [collection_client_2, collection_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_client_2)}; |
| |
| ASSERT_OK(collection_1->Sync()); |
| |
| ASSERT_NE(token_client_2.channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator_2->BindSharedCollection(std::move(token_client_2), std::move(collection_server_2))); |
| |
| ASSERT_OK(collection_2->SetConstraints(true, std::move(constraints_2))); |
| |
| auto allocate_result_1 = collection_1->WaitForBuffersAllocated(); |
| ASSERT_OK(allocate_result_1); |
| ASSERT_OK(allocate_result_1->status); |
| ASSERT_EQ(allocate_result_1->buffer_collection_info.settings.buffer_settings.coherency_domain, |
| fuchsia_sysmem::wire::CoherencyDomain::kCpu); |
| } |
| |
| TEST(Sysmem, ContiguousSystemRamIsCachedV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.vulkan = fuchsia_sysmem::wire::kVulkanUsageTransferDst; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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::wire::HeapType::kSystemRam}}; |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| ASSERT_EQ(allocate_result.value().buffer_collection_info.buffer_count, 1); |
| ASSERT_EQ( |
| allocate_result.value().buffer_collection_info.settings.buffer_settings.coherency_domain, |
| fuchsia_sysmem::wire::CoherencyDomain::kCpu); |
| ASSERT_EQ(allocate_result.value().buffer_collection_info.settings.buffer_settings.heap, |
| fuchsia_sysmem::wire::HeapType::kSystemRam); |
| ASSERT_EQ(allocate_result.value() |
| .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::kCpu - intentionally don't care for |
| // CoherencyDomain::kRam or CoherencyDomain::kInaccessible (when not protected). |
| // CoherencyDomain::kInaccessible + protected has a separate test ( |
| // test_sysmem_protected_ram_is_uncached). |
| zx_info_vmo_t vmo_info{}; |
| zx_status_t status = allocate_result.value().buffer_collection_info.buffers[0].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, ContiguousSystemRamIsRecycledV1) { |
| // 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 = 2 * 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; |
| } |
| |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.vulkan = fuchsia_sysmem::wire::kVulkanUsageTransferDst; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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::wire::HeapType::kSystemRam}}; |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| |
| ASSERT_EQ(allocate_result.value().buffer_collection_info.buffer_count, 1); |
| ASSERT_EQ( |
| allocate_result.value().buffer_collection_info.settings.buffer_settings.coherency_domain, |
| fuchsia_sysmem::wire::CoherencyDomain::kCpu); |
| ASSERT_EQ(allocate_result.value().buffer_collection_info.settings.buffer_settings.heap, |
| fuchsia_sysmem::wire::HeapType::kSystemRam); |
| ASSERT_EQ(allocate_result.value() |
| .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, OnlyNoneUsageFailsV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.none = fuchsia_sysmem::wire::kNoneUsage; |
| constraints.min_buffer_count_for_camping = 3; |
| constraints.min_buffer_count = 5; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // 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): Issue async client request to 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(allocate_result.status() == ZX_ERR_PEER_CLOSED || |
| allocate_result.value().status == ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST(Sysmem, NoneUsageAndOtherUsageFromSingleParticipantFailsV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| const char* kClientName = "TestClient"; |
| ASSERT_OK( |
| collection->SetDebugClientInfo(fidl::StringView::FromExternal(kClientName), 6u).status()); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| // Specify both "none" and "cpu" usage from a single participant, which will |
| // cause allocation failure. |
| constraints.usage.none = fuchsia_sysmem::wire::kNoneUsage; |
| constraints.usage.cpu = fuchsia_sysmem::wire::kCpuUsageReadOften; |
| constraints.min_buffer_count_for_camping = 3; |
| constraints.min_buffer_count = 5; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // 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): Issue async client request to 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(allocate_result.status() == ZX_ERR_PEER_CLOSED || |
| allocate_result.value().status == ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST(Sysmem, NoneUsageWithSeparateOtherUsageSucceedsV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| // Client 1 creates a token and new LogicalBufferCollection using |
| // AllocateSharedCollection(). |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server_1))); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| // 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). |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| ASSERT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| |
| SetDefaultCollectionNameV1(collection_1); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.none = fuchsia_sysmem::wire::kNoneUsage; |
| constraints_1.min_buffer_count_for_camping = 3; |
| constraints_1.has_buffer_memory_constraints = true; |
| constraints_1.buffer_memory_constraints = fuchsia_sysmem::wire::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 = {}, |
| }; |
| |
| // 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. |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_2(constraints_1); |
| // Modify constraints_2 to set non-"none" usage. |
| constraints_2.usage.none = 0; |
| constraints_2.usage.vulkan = fuchsia_sysmem::wire::kVulkanUsageTransferDst; |
| |
| ASSERT_OK(collection_1->SetConstraints(true, std::move(constraints_1))); |
| |
| // Client 2 connects to sysmem separately. |
| auto allocator_2 = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator_2); |
| |
| auto [collection_client_2, collection_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_client_2)}; |
| |
| // 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. |
| ASSERT_OK(collection_1->Sync()); |
| |
| ASSERT_NE(token_client_2.channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator_2->BindSharedCollection(std::move(token_client_2), std::move(collection_server_2))); |
| |
| ASSERT_OK(collection_2->SetConstraints(true, std::move(constraints_2))); |
| |
| // |
| // Only after both participants (both clients) have SetConstraints() will |
| // the allocation be successful. |
| // |
| auto allocate_result_1 = collection_1->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result_1); |
| |
| // Success when at least one participant specifies "none" usage and at least |
| // one participant specifies a usage other than "none". |
| ASSERT_OK(allocate_result_1->status); |
| } |
| |
| TEST(Sysmem, PixelFormatBgr24V1) { |
| constexpr uint32_t kWidth = 600; |
| constexpr uint32_t kHeight = 1; |
| constexpr uint32_t kStride = kWidth * zbitl::BytesPerPixel(ZBI_PIXEL_FORMAT_RGB_888); |
| constexpr uint32_t divisor = 32; |
| constexpr uint32_t kStrideAlign = (kStride + divisor - 1) & ~(divisor - 1); |
| |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 3; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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::wire::HeapType::kSystemRam}}; |
| constraints.image_format_constraints_count = 1; |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[0]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kBgr24; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kSrgb, |
| }; |
| // 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; |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| 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::wire::CoherencyDomain::kCpu); |
| // 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::wire::PixelFormatType::kBgr24); |
| |
| for (uint32_t i = 0; i < 64; ++i) { |
| if (i < 3) { |
| ASSERT_NE(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| uint64_t size_bytes = 0; |
| auto status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo.get(), &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.get(), ZX_HANDLE_INVALID); |
| } |
| } |
| |
| EXPECT_OK(collection->Close()); |
| } |
| |
| // Test that closing a token handle that's had Close() called on it doesn't crash sysmem. |
| TEST(Sysmem, CloseTokenV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server_1))); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_2{std::move(token_client_2)}; |
| |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| |
| ASSERT_OK(token_1->Sync()); |
| ASSERT_OK(token_1->Close()); |
| token_1 = {}; |
| |
| // Try to ensure sysmem processes the token closure before the sync. |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(5))); |
| |
| EXPECT_OK(token_2->Sync()); |
| } |
| |
| TEST(Sysmem, HeapAmlogicSecureV1) { |
| if (!is_board_with_amlogic_secure()) { |
| return; |
| } |
| |
| for (uint32_t i = 0; i < 64; ++i) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.video = fuchsia_sysmem::wire::kVideoUsageHwDecoder; |
| 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::wire::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::wire::HeapType::kAmlogicSecure}, |
| }; |
| ZX_DEBUG_ASSERT(constraints.image_format_constraints_count == 0); |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| 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::wire::CoherencyDomain::kInaccessible); |
| EXPECT_EQ(buffer_collection_info->settings.buffer_settings.heap, |
| fuchsia_sysmem::wire::HeapType::kAmlogicSecure); |
| EXPECT_EQ(buffer_collection_info->settings.has_image_format_constraints, false); |
| |
| for (uint32_t i = 0; i < 64; ++i) { |
| if (i < kBufferCount) { |
| EXPECT_NE(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| uint64_t size_bytes = 0; |
| auto status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo.get(), &size_bytes); |
| ASSERT_EQ(status, ZX_OK); |
| EXPECT_EQ(size_bytes, kBufferSizeBytes); |
| } else { |
| EXPECT_EQ(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| } |
| } |
| |
| zx::vmo the_vmo = std::move(buffer_collection_info->buffers[0].vmo); |
| buffer_collection_info->buffers[0].vmo = zx::vmo(); |
| SecureVmoReadTester tester(std::move(the_vmo)); |
| ASSERT_DEATH(([&] { tester.AttemptReadFromSecure(); })); |
| ASSERT_FALSE(tester.IsReadFromSecureAThing()); |
| } |
| } |
| |
| TEST(Sysmem, HeapAmlogicSecureMiniStressV1) { |
| if (!is_board_with_amlogic_secure()) { |
| return; |
| } |
| |
| // 256 64 KiB chunks, and well below protected_memory_size, even accounting for fragmentation. |
| const uint32_t kBlockSize = 64 * 1024; |
| const uint32_t kTotalSizeThreshold = 256 * kBlockSize; |
| |
| // For generating different sequences of random ranges, but still being able to easily repro any |
| // failure by putting the uint64_t seed inside the {} here. |
| static constexpr std::optional<uint64_t> kForcedSeed{}; |
| std::random_device random_device; |
| std::uint_fast64_t seed{kForcedSeed ? *kForcedSeed : random_device()}; |
| std::mt19937_64 prng{seed}; |
| std::uniform_int_distribution<uint32_t> op_distribution(0, 104); |
| std::uniform_int_distribution<uint32_t> key_distribution(0, std::numeric_limits<uint32_t>::max()); |
| // Buffers aren't required to be block aligned. |
| const uint32_t max_buffer_size = 4 * kBlockSize; |
| std::uniform_int_distribution<uint32_t> size_distribution(1, max_buffer_size); |
| |
| struct Vmo { |
| uint32_t size; |
| zx::vmo vmo; |
| }; |
| using BufferMap = std::multimap<uint32_t, Vmo>; |
| BufferMap buffers; |
| // random enough for this test; buffers at the end of a big gap are more likely to be selected, |
| // but that's fine since which buffers those are is also random due to random key when the buffer |
| // is added; still, not claiming this is rigorously random |
| auto get_random_buffer = [&key_distribution, &prng, &buffers]() -> BufferMap::iterator { |
| if (buffers.empty()) { |
| return buffers.end(); |
| } |
| uint32_t key = key_distribution(prng); |
| auto iter = buffers.upper_bound(key); |
| if (iter == buffers.end()) { |
| iter = buffers.begin(); |
| } |
| return iter; |
| }; |
| |
| uint32_t total_size = 0; |
| auto add = [&key_distribution, &size_distribution, &prng, &total_size, &buffers] { |
| uint32_t size = fbl::round_up(size_distribution(prng), zx_system_get_page_size()); |
| total_size += size; |
| |
| auto collection = make_single_participant_collection_v1(); |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.video = fuchsia_sysmem::wire::kVideoUsageHwDecoder; |
| constexpr uint32_t kBufferCount = 1; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::BufferMemoryConstraints{ |
| .min_size_bytes = size, |
| .max_size_bytes = size, |
| .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::wire::HeapType::kAmlogicSecure}, |
| }; |
| ZX_DEBUG_ASSERT(constraints.image_format_constraints_count == 0); |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| EXPECT_EQ(buffer_collection_info->buffer_count, kBufferCount); |
| EXPECT_EQ(buffer_collection_info->settings.buffer_settings.size_bytes, size); |
| 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::wire::CoherencyDomain::kInaccessible); |
| EXPECT_EQ(buffer_collection_info->settings.buffer_settings.heap, |
| fuchsia_sysmem::wire::HeapType::kAmlogicSecure); |
| EXPECT_EQ(buffer_collection_info->settings.has_image_format_constraints, false); |
| EXPECT_EQ(buffer_collection_info->buffers[0].vmo_usable_start, 0); |
| |
| zx::vmo buffer = std::move(buffer_collection_info->buffers[0].vmo); |
| |
| buffers.emplace( |
| std::make_pair(key_distribution(prng), Vmo{.size = size, .vmo = std::move(buffer)})); |
| }; |
| auto remove = [&get_random_buffer, &buffers, &total_size] { |
| auto random_buffer = get_random_buffer(); |
| EXPECT_NE(random_buffer, buffers.end()); |
| total_size -= random_buffer->second.size; |
| buffers.erase(random_buffer); |
| }; |
| auto check_random_buffer_page = [&get_random_buffer] { |
| auto random_buffer_iter = get_random_buffer(); |
| SecureVmoReadTester tester(zx::unowned_vmo(random_buffer_iter->second.vmo)); |
| ASSERT_DEATH(([&] { tester.AttemptReadFromSecure(); })); |
| ASSERT_FALSE(tester.IsReadFromSecureAThing()); |
| }; |
| |
| const uint32_t kIterations = 1000; |
| for (uint32_t i = 0; i < kIterations; ++i) { |
| if (i % 100 == 0) { |
| printf("iteration: %u\n", i); |
| } |
| while (total_size > kTotalSizeThreshold) { |
| remove(); |
| } |
| uint32_t roll = op_distribution(prng); |
| switch (roll) { |
| // add |
| case 0 ... 48: |
| if (total_size < kTotalSizeThreshold) { |
| add(); |
| } |
| break; |
| // fill |
| case 49: |
| while (total_size < kTotalSizeThreshold) { |
| add(); |
| } |
| break; |
| // remove |
| case 50 ... 98: |
| if (total_size) { |
| remove(); |
| } |
| break; |
| // clear |
| case 99: |
| while (total_size) { |
| remove(); |
| } |
| break; |
| case 100 ... 104: |
| if (total_size) { |
| check_random_buffer_page(); |
| } |
| break; |
| } |
| } |
| |
| for (uint32_t i = 0; i < 64; ++i) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.video = fuchsia_sysmem::wire::kVideoUsageHwDecoder; |
| 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::wire::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::wire::HeapType::kAmlogicSecure}, |
| }; |
| ZX_DEBUG_ASSERT(constraints.image_format_constraints_count == 0); |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| 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::wire::CoherencyDomain::kInaccessible); |
| EXPECT_EQ(buffer_collection_info->settings.buffer_settings.heap, |
| fuchsia_sysmem::wire::HeapType::kAmlogicSecure); |
| EXPECT_EQ(buffer_collection_info->settings.has_image_format_constraints, false); |
| |
| for (uint32_t i = 0; i < 64; ++i) { |
| if (i < kBufferCount) { |
| EXPECT_NE(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| uint64_t size_bytes = 0; |
| auto status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo.get(), &size_bytes); |
| ASSERT_EQ(status, ZX_OK); |
| EXPECT_EQ(size_bytes, kBufferSizeBytes); |
| } else { |
| EXPECT_EQ(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| } |
| } |
| |
| zx::vmo the_vmo = std::move(buffer_collection_info->buffers[0].vmo); |
| buffer_collection_info->buffers[0].vmo = zx::vmo(); |
| SecureVmoReadTester tester(std::move(the_vmo)); |
| ASSERT_DEATH(([&] { tester.AttemptReadFromSecure(); })); |
| ASSERT_FALSE(tester.IsReadFromSecureAThing()); |
| } |
| } |
| |
| TEST(Sysmem, HeapAmlogicSecureOnlySupportsInaccessibleV1) { |
| if (!is_board_with_amlogic_secure()) { |
| return; |
| } |
| |
| fuchsia_sysmem::wire::CoherencyDomain domains[] = { |
| fuchsia_sysmem::wire::CoherencyDomain::kCpu, |
| fuchsia_sysmem::wire::CoherencyDomain::kRam, |
| fuchsia_sysmem::wire::CoherencyDomain::kInaccessible, |
| }; |
| |
| for (const fuchsia_sysmem::wire::CoherencyDomain domain : domains) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.video = fuchsia_sysmem::wire::kVideoUsageHwDecoder; |
| 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::wire::BufferMemoryConstraints{ |
| .min_size_bytes = kBufferSizeBytes, |
| .max_size_bytes = 128 * 1024, |
| .physically_contiguous_required = true, |
| .secure_required = true, |
| .ram_domain_supported = (domain == fuchsia_sysmem::wire::CoherencyDomain::kRam), |
| .cpu_domain_supported = (domain == fuchsia_sysmem::wire::CoherencyDomain::kCpu), |
| .inaccessible_domain_supported = |
| (domain == fuchsia_sysmem::wire::CoherencyDomain::kInaccessible), |
| .heap_permitted_count = 1, |
| .heap_permitted = {fuchsia_sysmem::wire::HeapType::kAmlogicSecure}, |
| }; |
| ZX_DEBUG_ASSERT(constraints.image_format_constraints_count == 0); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| |
| if (domain == fuchsia_sysmem::wire::CoherencyDomain::kInaccessible) { |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| |
| 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::wire::CoherencyDomain::kInaccessible); |
| EXPECT_EQ(buffer_collection_info->settings.buffer_settings.heap, |
| fuchsia_sysmem::wire::HeapType::kAmlogicSecure); |
| EXPECT_EQ(buffer_collection_info->settings.has_image_format_constraints, false); |
| } else { |
| ASSERT_TRUE(allocate_result.status() != ZX_OK || allocate_result.value().status != ZX_OK, |
| "Sysmem should not allocate memory from secure heap with coherency domain %u", |
| static_cast<unsigned int>(domain)); |
| } |
| } |
| } |
| |
| TEST(Sysmem, HeapAmlogicSecureVdecV1) { |
| if (!is_board_with_amlogic_secure_vdec()) { |
| return; |
| } |
| |
| for (uint32_t i = 0; i < 64; ++i) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.video = fuchsia_sysmem::wire::kVideoUsageDecryptorOutput | |
| fuchsia_sysmem::wire::kVideoUsageHwDecoder; |
| 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::wire::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::wire::HeapType::kAmlogicSecureVdec}, |
| }; |
| ZX_DEBUG_ASSERT(constraints.image_format_constraints_count == 0); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| |
| 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::wire::CoherencyDomain::kInaccessible); |
| EXPECT_EQ(buffer_collection_info->settings.buffer_settings.heap, |
| fuchsia_sysmem::wire::HeapType::kAmlogicSecureVdec); |
| EXPECT_EQ(buffer_collection_info->settings.has_image_format_constraints, false); |
| |
| auto expected_size = fbl::round_up(kBufferSizeBytes, zx_system_get_page_size()); |
| for (uint32_t i = 0; i < 64; ++i) { |
| if (i < kBufferCount) { |
| EXPECT_NE(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| uint64_t size_bytes = 0; |
| auto status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo.get(), &size_bytes); |
| ASSERT_EQ(status, ZX_OK); |
| EXPECT_EQ(size_bytes, expected_size); |
| } else { |
| EXPECT_EQ(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| } |
| } |
| |
| zx::vmo the_vmo = std::move(buffer_collection_info->buffers[0].vmo); |
| buffer_collection_info->buffers[0].vmo = zx::vmo(); |
| SecureVmoReadTester tester(std::move(the_vmo)); |
| ASSERT_DEATH(([&] { tester.AttemptReadFromSecure(); })); |
| ASSERT_FALSE(tester.IsReadFromSecureAThing()); |
| } |
| } |
| |
| TEST(Sysmem, CpuUsageAndInaccessibleDomainSupportedSucceedsV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| constexpr uint32_t kBufferCount = 3; |
| constexpr uint32_t kBufferSize = 64 * 1024; |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = kBufferCount; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| |
| 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::wire::CoherencyDomain::kCpu); |
| 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.get(), ZX_HANDLE_INVALID); |
| uint64_t size_bytes = 0; |
| auto status = zx_vmo_get_size(buffer_collection_info->buffers[i].vmo.get(), &size_bytes); |
| ASSERT_EQ(status, ZX_OK); |
| ASSERT_EQ(size_bytes, kBufferSize); |
| } else { |
| ASSERT_EQ(buffer_collection_info->buffers[i].vmo.get(), ZX_HANDLE_INVALID); |
| } |
| } |
| } |
| |
| TEST(Sysmem, AllocatedBufferZeroInRamV1) { |
| 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); |
| ASSERT_TRUE(zero_buffer); |
| auto tmp_buffer = std::make_unique<uint8_t[]>(kBufferSize); |
| ASSERT_TRUE(tmp_buffer); |
| for (uint32_t iter = 0; iter < kIterationCount; ++iter) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = kBufferCount; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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); |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| |
| // 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. |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| zx::vmo vmo = std::move(buffer_collection_info->buffers[0].vmo); |
| buffer_collection_info->buffers[0].vmo = zx::vmo(); |
| |
| // 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 guaranteed not to hide a problem with flushing or fence |
| // in sysmem... |
| auto 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, DefaultAttributesV1) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = false; |
| constraints.image_format_constraints_count = 1; |
| fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[0]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| image_constraints.required_max_coded_width = 512; |
| image_constraints.required_max_coded_height = 1024; |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| ASSERT_OK(allocate_result); |
| ASSERT_OK(allocate_result.value().status); |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| |
| size_t vmo_size; |
| auto status = zx_vmo_get_size(buffer_collection_info->buffers[0].vmo.get(), &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); |
| } |
| |
| // Check that the server validates how many image format constraints there are. |
| TEST(Sysmem, TooManyFormatsV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator); |
| |
| auto [token_client_end, token_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server_end))); |
| |
| auto [collection_client_end, collection_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| |
| EXPECT_NE(token_client_end.channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK(allocator->BindSharedCollection(std::move(token_client_end), |
| std::move(collection_server_end))); |
| |
| fidl::WireSyncClient collection{std::move(collection_client_end)}; |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| 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::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[i]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints.pixel_format.has_format_modifier = true; |
| image_constraints.pixel_format.format_modifier.value = |
| fuchsia_sysmem::wire::kFormatModifierLinear; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| image_constraints.required_max_coded_width = 512; |
| image_constraints.required_max_coded_height = 1024; |
| } |
| |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| EXPECT_NE(allocate_result.status(), ZX_OK); |
| |
| verify_connectivity_v1(*allocator); |
| } |
| |
| // Check that the server checks for min_buffer_count too large. |
| TEST(Sysmem, TooManyBuffersV1) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| |
| auto [collection_client_end, collection_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection{std::move(collection_client_end)}; |
| |
| ASSERT_OK(allocator->AllocateNonSharedCollection(std::move(collection_server_end))); |
| |
| SetDefaultCollectionNameV1(collection); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints.min_size_bytes = zx_system_get_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::wire::kCpuUsageRead; |
| ASSERT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocation_result = collection->WaitForBuffersAllocated(); |
| EXPECT_NE(allocation_result.status(), ZX_OK); |
| |
| verify_connectivity_v1(*allocator); |
| } |
| |
| bool BasicAllocationSucceedsV1( |
| fit::function<void(fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify)> |
| modify_constraints) { |
| auto collection = make_single_participant_collection_v1(); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 3; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints = fuchsia_sysmem::wire::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::wire::ImageFormatConstraints& image_constraints = |
| constraints.image_format_constraints[0]; |
| image_constraints.pixel_format.type = fuchsia_sysmem::wire::PixelFormatType::kNv12; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::wire::ColorSpaceType::kRec709, |
| }; |
| // 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); |
| EXPECT_OK(collection->SetConstraints(true, std::move(constraints))); |
| |
| auto allocate_result = collection->WaitForBuffersAllocated(); |
| |
| // This is the first round-trip to/from sysmem. A failure here can be due |
| // to any step above failing async. |
| if (allocate_result.status() != ZX_OK) { |
| printf("WaitForBuffersAllocated failed - status: %d\n", allocate_result.status()); |
| return false; |
| } |
| |
| if (allocate_result.value().status != ZX_OK) { |
| printf("WaitForBuffersAllocated failed - allocation_status: %d\n", |
| allocate_result.value().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. |
| auto buffer_collection_info = &allocate_result.value().buffer_collection_info; |
| |
| zx::vmo allocated_vmo = std::move(buffer_collection_info->buffers[0].vmo); |
| size_t vmo_size; |
| auto 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, BasicAllocationSucceedsV1) { |
| EXPECT_TRUE(BasicAllocationSucceedsV1( |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify_nop) {})); |
| } |
| |
| TEST(Sysmem, ZeroMinSizeBytesFailsV1) { |
| EXPECT_FALSE( |
| BasicAllocationSucceedsV1([](fuchsia_sysmem::wire::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_SucceedsOnlyForNowV1) { |
| // With sysmem2 this will be expected to fail. With sysmem(1), this succeeds because 0 is |
| // interpreted as replace with default. |
| EXPECT_TRUE( |
| BasicAllocationSucceedsV1([](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.max_buffer_count = 0; |
| })); |
| } |
| |
| TEST(Sysmem, ZeroRequiredMinCodedWidth_SucceedsOnlyForNowV1) { |
| // With sysmem2 this will be expected to fail. With sysmem(1), this succeeds because 0 is |
| // interpreted as replace with default. |
| EXPECT_TRUE( |
| BasicAllocationSucceedsV1([](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.image_format_constraints[0].required_min_coded_width = 0; |
| })); |
| } |
| |
| TEST(Sysmem, ZeroRequiredMinCodedHeight_SucceedsOnlyForNowV1) { |
| // With sysmem2 this will be expected to fail. With sysmem(1), this succeeds because 0 is |
| // interpreted as replace with default. |
| EXPECT_TRUE( |
| BasicAllocationSucceedsV1([](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.image_format_constraints[0].required_min_coded_height = 0; |
| })); |
| } |
| |
| TEST(Sysmem, ZeroRequiredMinBytesPerRow_SucceedsOnlyForNowV1) { |
| // With sysmem2 this will be expected to fail. With sysmem(1), this succeeds because 0 is |
| // interpreted as replace with default. |
| EXPECT_TRUE( |
| BasicAllocationSucceedsV1([](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.image_format_constraints[0].required_min_bytes_per_row = 0; |
| })); |
| } |
| |
| TEST(Sysmem, DuplicateConstraintsFailsV1) { |
| EXPECT_FALSE( |
| BasicAllocationSucceedsV1([](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.image_format_constraints_count = 2; |
| to_modify.image_format_constraints[1] = to_modify.image_format_constraints[0]; |
| })); |
| } |
| |
| TEST(Sysmem, AttachToken_BeforeAllocate_SuccessV1) { |
| EXPECT_TRUE(AttachTokenSucceedsV1( |
| true, false, [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) {})); |
| IF_FAILURES_RETURN(); |
| } |
| |
| TEST(Sysmem, AttachToken_AfterAllocate_SuccessV1) { |
| EXPECT_TRUE(AttachTokenSucceedsV1( |
| false, false, [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) {})); |
| } |
| |
| TEST(Sysmem, AttachToken_BeforeAllocate_AttachedFailedEarly_FailureV1) { |
| // Despite the attached token failing early, this still verifies that the non-attached tokens |
| // are still ok and the LogicalBufferCollection is still ok. |
| EXPECT_FALSE(AttachTokenSucceedsV1( |
| true, true, [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) {})); |
| } |
| |
| TEST(Sysmem, AttachToken_BeforeAllocate_Failure_BufferSizesV1) { |
| EXPECT_FALSE(AttachTokenSucceedsV1( |
| true, false, [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.buffer_memory_constraints.max_size_bytes = (512 * 512) * 3 / 2 - 1; |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) {})); |
| } |
| |
| TEST(Sysmem, AttachToken_AfterAllocate_Failure_BufferSizesV1) { |
| EXPECT_FALSE(AttachTokenSucceedsV1( |
| false, false, [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.buffer_memory_constraints.max_size_bytes = (512 * 512) * 3 / 2 - 1; |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) {})); |
| } |
| |
| TEST(Sysmem, AttachToken_BeforeAllocate_Success_BufferCountsV1) { |
| const uint32_t kAttachTokenBufferCount = 2; |
| uint32_t buffers_needed = 0; |
| EXPECT_TRUE(AttachTokenSucceedsV1( |
| true, false, |
| [&buffers_needed](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| // 3 |
| buffers_needed += to_modify.min_buffer_count_for_camping; |
| }, |
| [&buffers_needed](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| // 3 |
| buffers_needed += to_modify.min_buffer_count_for_camping; |
| // 8 |
| to_modify.min_buffer_count = buffers_needed + kAttachTokenBufferCount; |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| // 2 |
| to_modify.min_buffer_count_for_camping = kAttachTokenBufferCount; |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) {}, |
| 8 // max(8, 3 + 3 + 2) |
| )); |
| } |
| |
| TEST(Sysmem, AttachToken_AfterAllocate_Success_BufferCountsV1) { |
| const uint32_t kAttachTokenBufferCount = 2; |
| uint32_t buffers_needed = 0; |
| EXPECT_TRUE(AttachTokenSucceedsV1( |
| false, false, |
| [&buffers_needed](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| // 3 |
| buffers_needed += to_modify.min_buffer_count_for_camping; |
| }, |
| [&buffers_needed](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| // 3 |
| buffers_needed += to_modify.min_buffer_count_for_camping; |
| // 8 |
| to_modify.min_buffer_count = buffers_needed + kAttachTokenBufferCount; |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| // 2 |
| to_modify.min_buffer_count_for_camping = kAttachTokenBufferCount; |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) {}, |
| 8 // max(8, 3 + 3) |
| )); |
| } |
| |
| TEST(Sysmem, AttachToken_BeforeAllocate_Failure_BufferCountsV1) { |
| EXPECT_FALSE(AttachTokenSucceedsV1( |
| true, false, [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.min_buffer_count_for_camping = 1; |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) {}, |
| // Only 6 get allocated, despite AttachToken() before allocation, because we intentionally |
| // want AttachToken() before vs. after initial allocation to behave as close to the same as |
| // possible. |
| 6)); |
| } |
| |
| TEST(Sysmem, AttachToken_AfterAllocate_Failure_BufferCountsV1) { |
| EXPECT_FALSE(AttachTokenSucceedsV1( |
| false, false, [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) {}, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.min_buffer_count_for_camping = 1; |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) {}, |
| // Only 6 get allocated at first, then AttachToken() sequence started after initial allocation |
| // fails (it would have failed even if it had started before initial allocation though). |
| 6)); |
| } |
| |
| TEST(Sysmem, AttachToken_SelectsSameDomainAsInitialAllocationV1) { |
| // The first part is mostly to verify that we have a way of influencing an initial allocation |
| // to pick RAM coherency domain, for the benefit of the second AttachTokenSucceeds() call below. |
| EXPECT_TRUE(AttachTokenSucceedsV1( |
| false, false, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.buffer_memory_constraints.cpu_domain_supported = true; |
| to_modify.buffer_memory_constraints.ram_domain_supported = true; |
| to_modify.buffer_memory_constraints.inaccessible_domain_supported = false; |
| // This will influence the initial allocation to pick RAM. |
| to_modify.usage.display = fuchsia_sysmem::wire::kDisplayUsageLayer; |
| EXPECT_TRUE(to_modify.usage.cpu != 0); |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.buffer_memory_constraints.cpu_domain_supported = true; |
| to_modify.buffer_memory_constraints.ram_domain_supported = true; |
| to_modify.buffer_memory_constraints.inaccessible_domain_supported = false; |
| EXPECT_TRUE(to_modify.usage.cpu != 0); |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.buffer_memory_constraints.cpu_domain_supported = true; |
| to_modify.buffer_memory_constraints.ram_domain_supported = true; |
| to_modify.buffer_memory_constraints.inaccessible_domain_supported = false; |
| EXPECT_TRUE(to_modify.usage.cpu != 0); |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) { |
| EXPECT_EQ(fuchsia_sysmem::wire::CoherencyDomain::kRam, |
| to_verify.settings.buffer_settings.coherency_domain); |
| }, |
| 6)); |
| // Now verify that if the initial allocation is CPU coherency domain, an attached token that would |
| // normally prefer RAM domain can succeed but will get CPU because the initial allocation already |
| // picked CPU. |
| EXPECT_TRUE(AttachTokenSucceedsV1( |
| false, false, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.buffer_memory_constraints.cpu_domain_supported = true; |
| to_modify.buffer_memory_constraints.ram_domain_supported = true; |
| to_modify.buffer_memory_constraints.inaccessible_domain_supported = false; |
| EXPECT_TRUE(to_modify.usage.cpu != 0); |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.buffer_memory_constraints.cpu_domain_supported = true; |
| to_modify.buffer_memory_constraints.ram_domain_supported = true; |
| to_modify.buffer_memory_constraints.inaccessible_domain_supported = false; |
| EXPECT_TRUE(to_modify.usage.cpu != 0); |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionConstraints& to_modify) { |
| to_modify.buffer_memory_constraints.cpu_domain_supported = true; |
| to_modify.buffer_memory_constraints.ram_domain_supported = true; |
| to_modify.buffer_memory_constraints.inaccessible_domain_supported = false; |
| // This would normally influence to pick RAM coherency domain, but because the existing |
| // BufferCollectionInfo says CPU, the attached participant will get CPU instead of its |
| // normally-preferred RAM. |
| to_modify.usage.display = fuchsia_sysmem::wire::kDisplayUsageLayer; |
| EXPECT_TRUE(to_modify.usage.cpu != 0); |
| }, |
| [](fuchsia_sysmem::wire::BufferCollectionInfo2& to_verify) { |
| EXPECT_EQ(fuchsia_sysmem::wire::CoherencyDomain::kCpu, |
| to_verify.settings.buffer_settings.coherency_domain); |
| }, |
| 6)); |
| } |
| |
| TEST(Sysmem, SetDispensableV1) { |
| enum class Variant { kDispensableFailureBeforeAllocation, kDispensableFailureAfterAllocation }; |
| constexpr Variant variants[] = {Variant::kDispensableFailureBeforeAllocation, |
| Variant::kDispensableFailureAfterAllocation}; |
| for (Variant variant : variants) { |
| auto allocator = connect_to_sysmem_driver_v1(); |
| |
| auto [token_client_1, token_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_1{std::move(token_client_1)}; |
| |
| // Client 1 creates a token and new LogicalBufferCollection using |
| // AllocateSharedCollection(). |
| ASSERT_OK(allocator->AllocateSharedCollection(std::move(token_server_1))); |
| |
| auto [token_client_2, token_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| fidl::WireSyncClient token_2{std::move(token_client_2)}; |
| |
| // 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). |
| ASSERT_OK(token_1->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_server_2))); |
| |
| // Client 1 calls SetDispensable() on token 2. Client 2's constraints will be part of the |
| // initial allocation, but post-allocation, client 2 failure won't cause failure of the |
| // LogicalBufferCollection. |
| ASSERT_OK(token_2->SetDispensable()); |
| |
| auto [collection_client_1, collection_server_1] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_1{std::move(collection_client_1)}; |
| |
| ASSERT_NE(token_1.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator->BindSharedCollection(token_1.TakeClientEnd(), std::move(collection_server_1))); |
| |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_1; |
| constraints_1.usage.cpu = |
| fuchsia_sysmem::wire::kCpuUsageReadOften | fuchsia_sysmem::wire::kCpuUsageWriteOften; |
| constraints_1.min_buffer_count_for_camping = 2; |
| constraints_1.has_buffer_memory_constraints = true; |
| constraints_1.buffer_memory_constraints = fuchsia_sysmem::wire::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. |
| fuchsia_sysmem::wire::BufferCollectionConstraints constraints_2(constraints_1); |
| ASSERT_EQ(constraints_2.min_buffer_count_for_camping, 2); |
| |
| // Client 2 connects to sysmem separately. |
| auto allocator_2 = connect_to_sysmem_driver_v1(); |
| ASSERT_OK(allocator_2); |
| |
| auto [collection_client_2, collection_server_2] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::WireSyncClient collection_2{std::move(collection_client_2)}; |
| |
| // 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. |
| ASSERT_OK(collection_1->Sync()); |
| |
| ASSERT_NE(token_2.client_end().channel().get(), ZX_HANDLE_INVALID); |
| ASSERT_OK( |
| allocator_2->BindSharedCollection(token_2.TakeClientEnd(), std::move(collection_server_2))); |
| |
| ASSERT_OK(collection_1->SetConstraints(true, constraints_1)); |
| |
| if (variant == Variant::kDispensableFailureBeforeAllocation) { |
| // Client 2 will now abruptly close its channel. Since client 2 hasn't provided constraints |
| // yet, the LogicalBufferCollection will fail. |
| collection_2 = {}; |
| } else { |
| // Client 2 SetConstraints(). |
| |
| ASSERT_OK(collection_2->SetConstraints(true, constraints_2)); |
| } |
| |
| // |
| // kDispensableFailureAfterAllocation - The LogicalBufferCollection won't fail. |
| auto allocate_result_1 = collection_1->WaitForBuffersAllocated(); |
| |
| if (variant == Variant::kDispensableFailureBeforeAllocation) { |
| // The LogicalBufferCollection will be failed, because client 2 failed before providing |
| // constraints. |
| ASSERT_EQ(allocate_result_1.status(), ZX_ERR_PEER_CLOSED); |
| // next variant, if any |
| continue; |
| } |
| ZX_DEBUG_ASSERT(variant == Variant::kDispensableFailureAfterAllocation); |
| |
| // The LogicalBufferCollection will not be failed, because client 2 didn't fail before |
| // allocation. |
| ASSERT_EQ(allocate_result_1.status(), ZX_OK); |
| ASSERT_EQ(allocate_result_1->status, ZX_OK); |
| // This being 4 instead of 2 proves that client 2's constraints were used. |
| ASSERT_EQ(allocate_result_1->buffer_collection_info.buffer_count, 4); |
| |
| // Now that we know allocation is done, client 2 will abruptly close its channel, which the |
| // server treats as client 2 failure. Since client 2 has already provided constraints, this |
| // won't fail the LogicalBufferCollection. |
| collection_2 = {}; |
| |
| // Give the LogicalBufferCollection time to fail if it were going to fail, which it isn't. |
| nanosleep_duration(zx::msec(250)); |
| |
| // Verify LogicalBufferCollection still ok. |
| ASSERT_OK(collection_1->Sync()); |
| |
| // next variant, if any |
| } |
| } |
| |
| TEST(Sysmem, IsAlternateFor_FalseV1) { |
| auto root_token = create_initial_token_v1(); |
| auto token_a = create_token_under_token_v1(root_token); |
| auto token_b = create_token_under_token_v1(root_token); |
| auto maybe_response = token_b->GetNodeRef(); |
| ASSERT_TRUE(maybe_response.ok()); |
| auto token_b_node_ref = std::move(maybe_response->node_ref); |
| auto result = token_a->IsAlternateFor(std::move(token_b_node_ref)); |
| ASSERT_TRUE(result.ok()); |
| EXPECT_FALSE(result->value()->is_alternate); |
| } |
| |
| TEST(Sysmem, IsAlternateFor_TrueV1) { |
| auto root_token = create_initial_token_v1(); |
| auto group = create_group_under_token_v1(root_token); |
| auto token_a = create_token_under_group_v1(group); |
| auto token_b = create_token_under_group_v1(group); |
| auto maybe_response = token_b->GetNodeRef(); |
| ASSERT_TRUE(maybe_response.ok()); |
| auto token_b_node_ref = std::move(maybe_response->node_ref); |
| auto result = token_a->IsAlternateFor(std::move(token_b_node_ref)); |
| ASSERT_TRUE(result.ok()); |
| EXPECT_TRUE(result->value()->is_alternate); |
| } |
| |
| TEST(Sysmem, IsAlternateFor_MiniStressV1) { |
| std::random_device random_device; |
| std::uint_fast64_t seed{random_device()}; |
| std::mt19937_64 prng{seed}; |
| std::uniform_int_distribution<uint32_t> uint32_distribution(0, |
| std::numeric_limits<uint32_t>::max()); |
| |
| // We use shared_ptr<> in this test to make it easier to share code between |
| // cases below. |
| std::vector<std::shared_ptr<TokenV1>> tokens; |
| std::vector<std::shared_ptr<GroupV1>> groups; |
| |
| auto create_extra_group = [&](std::shared_ptr<TokenV1> token) { |
| auto group = std::make_shared<GroupV1>(create_group_under_token_v1(*token)); |
| groups.emplace_back(group); |
| |
| auto child_0 = std::make_shared<TokenV1>(create_token_under_group_v1(*group)); |
| tokens.emplace_back(child_0); |
| |
| auto child_1 = std::make_shared<TokenV1>(create_token_under_group_v1(*group)); |
| tokens.emplace_back(token); |
| token = child_1; |
| |
| return token; |
| }; |
| |
| auto create_child_chain = [&](std::shared_ptr<TokenV1> token, uint32_t additional_tokens) { |
| for (uint32_t i = 0; i < additional_tokens; ++i) { |
| if (i == additional_tokens / 2 && uint32_distribution(prng) % 2 == 0) { |
| token = create_extra_group(token); |
| } else { |
| auto child_token = std::make_shared<TokenV1>(create_token_under_token_v1(*token)); |
| check_token_alive_v1(*child_token); |
| tokens.emplace_back(token); |
| token = child_token; |
| } |
| } |
| return token; |
| }; |
| |
| constexpr uint32_t kIterations = 50; |
| for (uint32_t i = 0; i < kIterations; ++i) { |
| if ((i + 1) % 100 == 0) { |
| printf("iteration cardinal: %u\n", i + 1); |
| } |
| |
| tokens.clear(); |
| groups.clear(); |
| auto root_token = std::make_shared<TokenV1>(create_initial_token_v1()); |
| tokens.emplace_back(root_token); |
| auto branch_token = create_child_chain(root_token, uint32_distribution(prng) % 5); |
| std::shared_ptr<TokenV1> child_a; |
| std::shared_ptr<TokenV1> child_b; |
| bool is_alternate_for = !!(uint32_distribution(prng) % 2); |
| if (is_alternate_for) { |
| auto group = std::make_shared<GroupV1>(create_group_under_token_v1(*branch_token)); |
| groups.emplace_back(group); |
| check_group_alive_v1(*group); |
| // Both can remain direct children of group, or can become indirect children of group. |
| child_a = std::make_shared<TokenV1>(create_token_under_group_v1(*group)); |
| child_b = std::make_shared<TokenV1>(create_token_under_group_v1(*group)); |
| } else { |
| // Both can remain branch_token, either can be parent of the other, or both can be children |
| // (direct or indirect) of branch_token. |
| child_a = branch_token; |
| child_b = branch_token; |
| } |
| child_a = create_child_chain(child_a, uint32_distribution(prng) % 5); |
| child_b = create_child_chain(child_b, uint32_distribution(prng) % 5); |
| auto maybe_node_ref_b = (*child_b)->GetNodeRef(); |
| ASSERT_TRUE(maybe_node_ref_b.ok()); |
| auto node_ref_b = std::move(maybe_node_ref_b->node_ref); |
| auto result = (*child_a)->IsAlternateFor(std::move(node_ref_b)); |
| ASSERT_TRUE(result.ok()); |
| EXPECT_EQ(is_alternate_for, result->value()->is_alternate); |
| } |
| } |
| |
| TEST(Sysmem, GroupPrefersFirstChildV1) { |
| const uint32_t kFirstChildSize = zx_system_get_page_size() * 16; |
| const uint32_t kSecondChildSize = zx_system_get_page_size() * 32; |
| |
| auto root_token = create_initial_token_v1(); |
| auto group = create_group_under_token_v1(root_token); |
| auto child_0_token = create_token_under_group_v1(group); |
| auto child_1_token = create_token_under_group_v1(group); |
| |
| auto root = convert_token_to_collection_v1(std::move(root_token)); |
| auto child_0 = convert_token_to_collection_v1(std::move(child_0_token)); |
| auto child_1 = convert_token_to_collection_v1(std::move(child_1_token)); |
| auto set_result = |
| root->SetConstraints(false, ::fuchsia_sysmem::wire::BufferCollectionConstraints{}); |
| ASSERT_TRUE(set_result.ok()); |
| set_picky_constraints_v1(child_0, kFirstChildSize); |
| set_picky_constraints_v1(child_1, kSecondChildSize); |
| |
| auto all_children_present_result = group->AllChildrenPresent(); |
| ASSERT_TRUE(all_children_present_result.ok()); |
| |
| auto wait_result1 = root->WaitForBuffersAllocated(); |
| ASSERT_TRUE(wait_result1.ok() && wait_result1->status == ZX_OK); |
| ASSERT_OK(wait_result1->status); |
| auto info = std::move(wait_result1->buffer_collection_info); |
| ASSERT_EQ(info.settings.buffer_settings.size_bytes, kFirstChildSize); |
| |
| auto wait_result2 = child_0->WaitForBuffersAllocated(); |
| ASSERT_TRUE(wait_result2.ok() && wait_result2->status == ZX_OK); |
| ASSERT_OK(wait_result2->status); |
| info = std::move(wait_result2->buffer_collection_info); |
| ASSERT_EQ(info.settings.buffer_settings.size_bytes, kFirstChildSize); |
| |
| auto wait_result3 = child_1->WaitForBuffersAllocated(); |
| // Most clients calling this way won't get the epitaph; this test doesn't either. |
| ASSERT_TRUE(!wait_result3.ok() && wait_result3.error().status() == ZX_ERR_PEER_CLOSED || |
| wait_result3->status == ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST(Sysmem, Group_CloseBeforeChildrenSetConstraints) { |
| const uint32_t kFirstChildSize = zx_system_get_page_size() * 16; |
| const uint32_t kSecondChildSize = zx_system_get_page_size() * 32; |
| |
| auto root_token = create_initial_token_v1(); |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> child_0_token; |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> child_1_token; |
| { |
| auto group = create_group_under_token_v1(root_token); |
| child_0_token = create_token_under_group_v1(group); |
| child_1_token = create_token_under_group_v1(group); |
| |
| auto all_children_present_result = group->AllChildrenPresent(); |
| ASSERT_TRUE(all_children_present_result.ok()); |
| |
| auto group_close_result = group->Close(); |
| ASSERT_TRUE(group_close_result.ok()); |
| } |
| |
| auto root = convert_token_to_collection_v1(std::move(root_token)); |
| auto child_0 = convert_token_to_collection_v1(std::move(child_0_token)); |
| auto child_1 = convert_token_to_collection_v1(std::move(child_1_token)); |
| auto set_result = |
| root->SetConstraints(false, ::fuchsia_sysmem::wire::BufferCollectionConstraints{}); |
| ASSERT_TRUE(set_result.ok()); |
| set_picky_constraints_v1(child_0, kFirstChildSize); |
| set_picky_constraints_v1(child_1, kSecondChildSize); |
| |
| auto wait_result1 = root->WaitForBuffersAllocated(); |
| ASSERT_TRUE(wait_result1.ok() && wait_result1->status == ZX_OK); |
| ASSERT_OK(wait_result1->status); |
| auto info = std::move(wait_result1->buffer_collection_info); |
| ASSERT_EQ(info.settings.buffer_settings.size_bytes, kFirstChildSize); |
| |
| auto wait_result2 = child_0->WaitForBuffersAllocated(); |
| ASSERT_TRUE(wait_result2.ok() && wait_result2->status == ZX_OK); |
| ASSERT_OK(wait_result2->status); |
| info = std::move(wait_result2->buffer_collection_info); |
| ASSERT_EQ(info.settings.buffer_settings.size_bytes, kFirstChildSize); |
| |
| auto wait_result3 = child_1->WaitForBuffersAllocated(); |
| // Most clients calling this way won't get the epitaph; this test doesn't either. |
| ASSERT_TRUE(!wait_result3.ok() && wait_result3.error().status() == ZX_ERR_PEER_CLOSED || |
| wait_result3->status == ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST(Sysmem, GroupPriorityV1) { |
| constexpr uint32_t kIterations = 20; |
| for (uint32_t i = 0; i < kIterations; ++i) { |
| if ((i + 1) % 100 == 0) { |
| printf("iteration cardinal: %u\n", i + 1); |
| } |
| |
| // We determine which group is lowest priority according to sysmem by noticing which group |
| // selects its ordinal 1 child instead of its ordinal 0 child. We force one group to pick the |
| // ordinal 1 child by setting max_buffer_count to 1 buffer less than can be satisfied with all |
| // ordinal 0 children which each specify min_buffer_count_for_camping |
| // 1. In contrast, the ordinal 1 children specify min_buffer_count_for_camping 0, so selecting |
| // a single ordinal 1 child is enough to succeed aggregation. |
| |
| uint32_t constraints_count = 0; |
| |
| std::random_device random_device; |
| std::uint_fast64_t seed{random_device()}; |
| std::mt19937_64 prng{seed}; |
| std::uniform_int_distribution<uint32_t> uint32_distribution( |
| 0, std::numeric_limits<uint32_t>::max()); |
| |
| constexpr uint32_t kGroupCount = 4; |
| constexpr uint32_t kNonGroupCount = 12; |
| constexpr uint32_t kPickCount = 2; |
| |
| using Item = std::variant<std::monostate, SharedTokenV1, SharedCollectionV1, SharedGroupV1>; |
| struct Node; |
| using SharedNode = std::shared_ptr<Node>; |
| struct Node { |
| bool is_group() { return item.index() == 3; } |
| Node* parent = nullptr; |
| std::vector<SharedNode> children; |
| // For a group, true means this group's direct child tokens all have is_camper true, and false |
| // means the second (ordinal 1) child has is_camper false. |
| // |
| // For a token, true means this token will specify min_buffer_count_for_camping 1, and false |
| // means this token will specify min_buffer_count_for_camping 0. |
| bool is_camper = true; |
| Item item; |
| std::optional<uint32_t> which_child; |
| uint32_t picked_group_dfs_preorder_ordinal; |
| }; |
| std::vector<SharedNode> all_nodes; |
| std::vector<SharedNode> group_nodes; |
| std::vector<SharedNode> un_picked_group_nodes; |
| std::vector<SharedNode> picked_group_nodes; |
| std::vector<SharedNode> non_group_nodes; |
| std::vector<SharedNode> pre_groups_non_group_nodes; |
| |
| SharedNode root_node; |
| |
| auto create_node = [&](Node* parent, Item item) { |
| auto node = std::make_shared<Node>(); |
| node->parent = parent; |
| bool is_parent_a_group = false; |
| if (parent) { |
| parent->children.emplace_back(node); |
| is_parent_a_group = parent->is_group(); |
| } |
| node->item = std::move(item); |
| all_nodes.emplace_back(node); |
| if (node->is_group()) { |
| group_nodes.emplace_back(node); |
| un_picked_group_nodes.emplace_back(node); |
| } else { |
| non_group_nodes.emplace_back(node); |
| // this condition works for tallying constraints count only because we don't have nested |
| // groups in this test |
| if (!is_parent_a_group || parent->children.size() == 1) { |
| ++constraints_count; |
| } |
| } |
| if (group_nodes.empty() && !node->is_group()) { |
| pre_groups_non_group_nodes.emplace_back(node); |
| } |
| return node; |
| }; |
| |
| // This has a bias toward having more nodes directly under nodes that existed for longer, but |
| // that's fine. |
| auto add_random_nodes = [&]() { |
| for (uint32_t i = 0; i < kNonGroupCount; ++i) { |
| auto parent_node = all_nodes[uint32_distribution(prng) % all_nodes.size()]; |
| auto node = |
| create_node(parent_node.get(), std::make_shared<TokenV1>(create_token_under_token_v1( |
| *std::get<SharedTokenV1>(parent_node->item)))); |
| } |
| for (uint32_t i = 0; i < kGroupCount; ++i) { |
| auto parent_node = pre_groups_non_group_nodes[uint32_distribution(prng) % |
| pre_groups_non_group_nodes.size()]; |
| auto group = |
| create_node(parent_node.get(), std::make_shared<GroupV1>(create_group_under_token_v1( |
| *std::get<SharedTokenV1>(parent_node->item)))); |
| // a group must have at least one child so go ahead and add that child when we add the group |
| create_node(group.get(), std::make_shared<TokenV1>(create_token_under_group_v1( |
| *std::get<SharedGroupV1>(group->item)))); |
| } |
| }; |
| |
| auto pick_groups = [&]() { |
| for (uint32_t i = 0; i < kPickCount; ++i) { |
| uint32_t which = uint32_distribution(prng) % un_picked_group_nodes.size(); |
| auto group_node = un_picked_group_nodes[which]; |
| group_node->is_camper = false; |
| un_picked_group_nodes.erase(un_picked_group_nodes.begin() + which); |
| picked_group_nodes.emplace_back(group_node); |
| auto group = std::get<SharedGroupV1>(group_node->item); |
| auto token = create_node(group_node.get(), |
| std::make_shared<TokenV1>(create_token_under_group_v1(*group))); |
| token->is_camper = false; |
| } |
| }; |
| |
| auto set_picked_group_dfs_preorder_ordinals = [&] { |
| struct StackLevel { |
| SharedNode node; |
| uint32_t next_child = 0; |
| }; |
| uint32_t ordinal = 0; |
| std::vector<StackLevel> stack; |
| stack.emplace_back(StackLevel{.node = root_node, .next_child = 0}); |
| while (!stack.empty()) { |
| auto& cur = stack.back(); |
| if (cur.next_child == 0) { |
| // visit |
| if (cur.node->is_group() && !cur.node->is_camper) { |
| cur.node->picked_group_dfs_preorder_ordinal = ordinal; |
| ++ordinal; |
| } |
| } |
| if (cur.next_child == cur.node->children.size()) { |
| stack.pop_back(); |
| continue; |
| } |
| auto child = cur.node->children[cur.next_child]; |
| ++cur.next_child; |
| stack.push_back(StackLevel{.node = child, .next_child = 0}); |
| } |
| }; |
| |
| auto finalize_nodes = [&] { |
| for (auto& node : all_nodes) { |
| if (node->is_group()) { |
| auto group = std::get<SharedGroupV1>(node->item); |
| EXPECT_OK((*group)->AllChildrenPresent().status()); |
| } else { |
| auto token = std::get<SharedTokenV1>(node->item); |
| auto collection = |
| std::make_shared<CollectionV1>(convert_token_to_collection_v1(std::move(*token))); |
| node->item.emplace<SharedCollectionV1>(collection); |
| if (node.get() == root_node.get()) { |
| set_min_camping_constraints_v1(*collection, node->is_camper ? 1 : 0, |
| constraints_count - 1); |
| } else { |
| set_min_camping_constraints_v1(*collection, node->is_camper ? 1 : 0); |
| } |
| } |
| } |
| }; |
| |
| auto check_nodes = [&] { |
| for (auto& node : all_nodes) { |
| if (node->is_group()) { |
| continue; |
| } |
| auto collection = std::get<SharedCollectionV1>(node->item); |
| auto wait_result = (*collection)->WaitForBuffersAllocated(); |
| if (!wait_result.ok()) { |
| auto* group = node->parent; |
| ASSERT_FALSE(group->is_camper); |
| // Only the picked group enumerated last among the picked groups (in DFS pre-order) gives |
| // up on camping on a buffer by failing its camping child 0 and using it's non-camping |
| // child 1 instead. The picked groups with higher priority (more important) instaed keep |
| // their camping child 0 and fail their non-camping child 1. |
| ASSERT_EQ((group->picked_group_dfs_preorder_ordinal < kPickCount - 1), !node->is_camper); |
| } |
| } |
| }; |
| |
| root_node = create_node(nullptr, std::make_shared<TokenV1>(create_initial_token_v1())); |
| add_random_nodes(); |
| pick_groups(); |
| set_picked_group_dfs_preorder_ordinals(); |
| finalize_nodes(); |
| check_nodes(); |
| } |
| } |
| |
| TEST(Sysmem, Group_MiniStressV1) { |
| // In addition to some degree of stress, this tests whether sysmem can find the group child |
| // selections that work, given various arrangements of tokens, groups, and constraints |
| // compatibility / incompatibility. |
| constexpr uint32_t kIterations = 50; |
| for (uint32_t i = 0; i < kIterations; ++i) { |
| if ((i + 1) % 100 == 0) { |
| printf("iteration cardinal: %u\n", i + 1); |
| } |
| const uint32_t kCompatibleSize = zx_system_get_page_size(); |
| const uint32_t kIncompatibleSize = 2 * zx_system_get_page_size(); |
| // We can't go too high here, since we allow groups, and all the groups could end up as sibling |
| // groups whose child selections don't hide any of the other groups, leading to a high number of |
| // group child selection combinations. |
| // |
| // Child 0 of a group doesn't count as a "random" child since every group must have at least one |
| // child. |
| const uint32_t kRandomChildrenCount = 6; |
| |
| using Item = std::variant<SharedTokenV1, SharedCollectionV1, SharedGroupV1>; |
| |
| std::random_device random_device; |
| std::uint_fast64_t seed{random_device()}; |
| std::mt19937_64 prng{seed}; |
| std::uniform_int_distribution<uint32_t> uint32_distribution( |
| 0, std::numeric_limits<uint32_t>::max()); |
| |
| struct Node; |
| using SharedNode = std::shared_ptr<Node>; |
| struct Node { |
| bool is_group() { return item.index() == 2; } |
| Node* parent; |
| std::vector<SharedNode> children; |
| bool is_compatible; |
| Item item; |
| std::optional<uint32_t> which_child; |
| }; |
| std::vector<SharedNode> all_nodes; |
| |
| auto create_node = [&](Node* parent, Item item) { |
| auto node = std::make_shared<Node>(); |
| node->parent = parent; |
| if (parent) { |
| parent->children.emplace_back(node); |
| } |
| node->is_compatible = false; |
| node->item = std::move(item); |
| all_nodes.emplace_back(node); |
| return node; |
| }; |
| |
| // This has a bias toward having more nodes directly under nodes that existed for longer, but |
| // that's fine. |
| auto add_random_nodes = [&]() { |
| for (uint32_t i = 0; i < kRandomChildrenCount; ++i) { |
| auto parent_node = all_nodes[uint32_distribution(prng) % all_nodes.size()]; |
| SharedNode node; |
| if (parent_node->is_group()) { |
| node = |
| create_node(parent_node.get(), std::make_shared<TokenV1>(create_token_under_group_v1( |
| *std::get<SharedGroupV1>(parent_node->item)))); |
| } else if (uint32_distribution(prng) % 2 == 0) { |
| node = |
| create_node(parent_node.get(), std::make_shared<TokenV1>(create_token_under_token_v1( |
| *std::get<SharedTokenV1>(parent_node->item)))); |
| } else { |
| node = |
| create_node(parent_node.get(), std::make_shared<GroupV1>(create_group_under_token_v1( |
| *std::get<SharedTokenV1>(parent_node->item)))); |
| // a group must have at least one child so go ahead and add that child when we add the |
| // group |
| create_node(node.get(), std::make_shared<TokenV1>(create_token_under_group_v1( |
| *std::get<SharedGroupV1>(node->item)))); |
| } |
| } |
| }; |
| |
| auto select_group_children = [&] { |
| for (auto& node : all_nodes) { |
| if (!node->is_group()) { |
| continue; |
| } |
| node->which_child = {uint32_distribution(prng) % node->children.size()}; |
| } |
| }; |
| |
| auto is_visible = [&](SharedNode node) { |
| for (Node* iter = node.get(); iter; iter = iter->parent) { |
| if (!iter->parent) { |
| return true; |
| } |
| Node* parent = iter->parent; |
| if (!parent->is_group()) { |
| continue; |
| } |
| EXPECT_TRUE(parent->which_child.has_value()); |
| if (parent->children[*parent->which_child].get() != iter) { |
| return false; |
| } |
| } |
| ZX_PANIC("impossible"); |
| }; |
| |
| auto find_visible_nodes_and_mark_compatible = [&] { |
| for (auto& node : all_nodes) { |
| if (!is_visible(node)) { |
| continue; |
| } |
| if (node->is_group()) { |
| continue; |
| } |
| node->is_compatible = true; |
| } |
| }; |
| |
| auto finalize_nodes = [&] { |
| std::atomic<uint32_t> ready_threads = 0; |
| std::atomic<bool> threads_go = false; |
| std::vector<std::thread> threads; |
| threads.reserve(all_nodes.size()); |
| for (auto& node : all_nodes) { |
| threads.emplace_back(std::thread([&] { |
| ++ready_threads; |
| while (!threads_go) { |
| std::this_thread::yield(); |
| } |
| if (node->is_group()) { |
| auto group = std::get<SharedGroupV1>(node->item); |
| EXPECT_OK((*group)->AllChildrenPresent().status()); |
| } else { |
| auto token = std::get<SharedTokenV1>(node->item); |
| auto collection = |
| std::make_shared<CollectionV1>(convert_token_to_collection_v1(std::move(*token))); |
| node->item.emplace<SharedCollectionV1>(collection); |
| uint32_t size = node->is_compatible ? kCompatibleSize : kIncompatibleSize; |
| set_picky_constraints_v1(*collection, size); |
| } |
| })); |
| } |
| while (ready_threads != threads.size()) { |
| std::this_thread::yield(); |
| } |
| threads_go = true; |
| for (auto& thread : threads) { |
| thread.join(); |
| } |
| }; |
| |
| auto check_nodes = [&] { |
| for (auto& node : all_nodes) { |
| if (node->is_group()) { |
| continue; |
| } |
| auto collection = std::get<SharedCollectionV1>(node->item); |
| auto wait_result = (*collection)->WaitForBuffersAllocated(); |
| if (!node->is_compatible) { |
| if (wait_result.ok()) { |
| EXPECT_STATUS(wait_result.value().status, ZX_ERR_NOT_SUPPORTED); |
| } else { |
| EXPECT_STATUS(wait_result.status(), ZX_ERR_PEER_CLOSED); |
| } |
| continue; |
| } |
| EXPECT_OK(wait_result.status()); |
| EXPECT_OK(wait_result.value().status); |
| EXPECT_EQ(wait_result->buffer_collection_info.settings.buffer_settings.size_bytes, |
| kCompatibleSize); |
| } |
| }; |
| |
| auto root_node = create_node(nullptr, std::make_shared<TokenV1>(create_initial_token_v1())); |
| add_random_nodes(); |
| select_group_children(); |
| find_visible_nodes_and_mark_compatible(); |
| finalize_nodes(); |
| check_nodes(); |
| } |
| } |
| |
| TEST(Sysmem, SkipUnreachableChildSelectionsV1) { |
| const uint32_t kCompatibleSize = zx_system_get_page_size(); |
| const uint32_t kIncompatibleSize = 2 * zx_system_get_page_size(); |
| std::vector<std::shared_ptr<TokenV1>> incompatible_tokens; |
| std::vector<std::shared_ptr<TokenV1>> compatible_tokens; |
| std::vector<std::shared_ptr<GroupV1>> groups; |
| std::vector<std::shared_ptr<CollectionV1>> collections; |
| auto root_token = std::make_shared<TokenV1>(create_initial_token_v1()); |
| compatible_tokens.emplace_back(root_token); |
| auto cur_token = root_token; |
| // Essentially counting to 2^63 would take too long and cause the test to time out. The fact |
| // that the test doesn't time out shows that sysmem isn't trying all the unreachable child |
| // selections. |
| // |
| // We use 63 instead of 64 to avoid hitting kMaxGroupChildCombinations. |
| // |
| // While this sysmem behavior can help cut down on the amount of unnecessary work as sysmem is |
| // enumerating through all potentially useful combinations of group child selections, this |
| // behavior doesn't prevent constructing a bunch of sibling groups (unlike this test which uses |
| // child groups) each with two options (for example) where only the right children can all |
| // succeed (for example). In that case (and others with many reachable child selection |
| // combinations) sysmem would be forced to give up after trying several group child combinations |
| // instead of ever finding the highest priority combination that can succeed aggregation. |
| for (uint32_t i = 0; i < 63; ++i) { |
| auto group = std::make_shared<GroupV1>(create_group_under_token_v1(*cur_token)); |
| groups.emplace_back(group); |
| // We create the next_token first, because we want to prefer the deeper sub-tree. |
| auto next_token = std::make_shared<TokenV1>(create_token_under_group_v1(*group)); |
| incompatible_tokens.emplace_back(next_token); |
| auto shorter_child = std::make_shared<TokenV1>(create_token_under_group_v1(*group)); |
| compatible_tokens.emplace_back(shorter_child); |
| cur_token = next_token; |
| } |
| for (auto& token : compatible_tokens) { |
| auto collection = |
| std::make_shared<CollectionV1>(convert_token_to_collection_v1(std::move(*token))); |
| collections.emplace_back(collection); |
| set_picky_constraints_v1(*collection, kCompatibleSize); |
| } |
| for (auto& token : incompatible_tokens) { |
| auto collection = |
| std::make_shared<CollectionV1>(convert_token_to_collection_v1(std::move(*token))); |
| collections.emplace_back(collection); |
| set_picky_constraints_v1(*collection, kIncompatibleSize); |
| } |
| for (auto& group : groups) { |
| auto result = (*group)->AllChildrenPresent(); |
| ASSERT_TRUE(result.ok()); |
| } |
| // Only two collections will succeed - the root and the right child of the group direclty under |
| // the root. |
| std::vector<std::shared_ptr<CollectionV1>> success_collections; |
| auto root_collection = collections[0]; |
| auto right_child_under_root_collection = collections[1]; |
| success_collections.emplace_back(root_collection); |
| success_collections.emplace_back(right_child_under_root_collection); |
| |
| // Remove the success collections so we can conveniently iterate over the failed collections. |
| // |
| // This is not efficient, but it's a test, and it's not super slow. |
| collections.erase(collections.begin()); |
| collections.erase(collections.begin()); |
| |
| for (auto& collection : success_collections) { |
| auto wait_result = (*collection)->WaitForBuffersAllocated(); |
| ASSERT_TRUE(wait_result.ok() && wait_result->status == ZX_OK); |
| auto info = std::move(wait_result->buffer_collection_info); |
| ASSERT_EQ(info.settings.buffer_settings.size_bytes, kCompatibleSize); |
| } |
| |
| for (auto& collection : collections) { |
| auto wait_result = (*collection)->WaitForBuffersAllocated(); |
| ASSERT_TRUE(!wait_result.ok() && wait_result.error().status() == ZX_ERR_PEER_CLOSED || |
| wait_result.ok() && wait_result->status == ZX_ERR_NOT_SUPPORTED); |
| } |
| } |
| |
| TEST(Sysmem, GroupChildSelectionCombinationCountLimitV1) { |
| const uint32_t kCompatibleSize = zx_system_get_page_size(); |
| const uint32_t kIncompatibleSize = 2 * zx_system_get_page_size(); |
| std::vector<std::shared_ptr<TokenV1>> incompatible_tokens; |
| std::vector<std::shared_ptr<TokenV1>> compatible_tokens; |
| std::vector<std::shared_ptr<GroupV1>> groups; |
| std::vector<std::shared_ptr<CollectionV1>> collections; |
| auto root_token = std::make_shared<TokenV1>(create_initial_token_v1()); |
| incompatible_tokens.emplace_back(root_token); |
| // Essentially counting to 2^64 would take too long and cause the test to time out. The fact |
| // that the test doesn't time out (and instead the logical allocation fails) shows that sysmem |
| // isn't trying all the group child selections, by design. |
| for (uint32_t i = 0; i < 64; ++i) { |
| auto group = std::make_shared<GroupV1>(create_group_under_token_v1(*root_token)); |
| groups.emplace_back(group); |
| auto child_token_0 = std::make_shared<TokenV1>(create_token_under_group_v1(*group)); |
| compatible_tokens.emplace_back(child_token_0); |
| auto child_token_1 = std::make_shared<TokenV1>(create_token_under_group_v1(*group)); |
| compatible_tokens.emplace_back(child_token_1); |
| } |
| for (auto& token : compatible_tokens) { |
| auto collection = |
| std::make_shared<CollectionV1>(convert_token_to_collection_v1(std::move(*token))); |
| collections.emplace_back(collection); |
| set_picky_constraints_v1(*collection, kCompatibleSize); |
| } |
| for (auto& token : incompatible_tokens) { |
| auto collection = |
| std::make_shared<CollectionV1>(convert_token_to_collection_v1(std::move(*token))); |
| collections.emplace_back(collection); |
| set_picky_constraints_v1(*collection, kIncompatibleSize); |
| } |
| for (auto& group : groups) { |
| auto result = (*group)->AllChildrenPresent(); |
| ASSERT_TRUE(result.ok()); |
| } |
| // Because the root has constraints that are incompatible with any group child token, the logical |
| // allocation will fail (eventually). The fact that it fails in a duration that avoids the test |
| // timing out is passing the test. |
| for (auto& collection : collections) { |
| // If we get stuck here, it means the test will time out, which is effectively a failure. |
| auto wait_result = (*collection)->WaitForBuffersAllocated(); |
| ASSERT_TRUE(!wait_result.ok() && wait_result.error().status() == ZX_ERR_PEER_CLOSED || |
| wait_result.ok() && wait_result->status == ZX_ERR_OUT_OF_RANGE); |
| } |
| } |
| |
| TEST(Sysmem, GroupCreateChildrenSyncV1) { |
| for (uint32_t is_oddball_writable = 0; is_oddball_writable < 2; ++is_oddball_writable) { |
| const uint32_t kCompatibleSize = zx_system_get_page_size(); |
| const uint32_t kIncompatibleSize = 2 * zx_system_get_page_size(); |
| auto root_token = create_initial_token_v1(); |
| auto group = create_group_under_token_v1(root_token); |
| check_group_alive_v1(group); |
| constexpr uint32_t kTokenCount = 16; |
| zx_rights_t rights_attenuation_masks[kTokenCount]; |
| for (auto& rights : rights_attenuation_masks) { |
| rights = ZX_RIGHT_SAME_RIGHTS; |
| } |
| constexpr uint32_t kOddballIndex = kTokenCount / 2; |
| if (!is_oddball_writable) { |
| rights_attenuation_masks[kOddballIndex] = (0xFFFFFFFF & ~ZX_RIGHT_WRITE); |
| EXPECT_EQ((rights_attenuation_masks[kOddballIndex] & ZX_RIGHT_WRITE), 0); |
| } |
| auto result = group->CreateChildrenSync( |
| fidl::VectorView<zx_rights_t>::FromExternal(rights_attenuation_masks)); |
| ASSERT_TRUE(result.ok()); |
| std::vector<fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken>> tokens; |
| for (auto& token_client : result->tokens) { |
| tokens.emplace_back(fidl::WireSyncClient(std::move(token_client))); |
| } |
| tokens.emplace_back(std::move(root_token)); |
| auto is_root = [&](uint32_t index) { return index == tokens.size() - 1; }; |
| auto is_oddball = [&](uint32_t index) { return index == kOddballIndex; }; |
| auto is_compatible = [&](uint32_t index) { return is_oddball(index) || is_root(index); }; |
| std::vector<fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>> collections; |
| collections.reserve(tokens.size()); |
| for (auto& token : tokens) { |
| collections.emplace_back(convert_token_to_collection_v1(std::move(token))); |
| } |
| uint32_t index = 0; |
| for (auto& collection : collections) { |
| uint32_t size = is_compatible(index) ? kCompatibleSize : kIncompatibleSize; |
| set_picky_constraints_v1(collection, size); |
| ++index; |
| } |
| auto group_done_result = group->AllChildrenPresent(); |
| ASSERT_TRUE(group_done_result.ok()); |
| index = 0; |
| for (auto& collection : collections) { |
| auto inc_index = fit::defer([&] { ++index; }); |
| auto wait_result = collection->WaitForBuffersAllocated(); |
| if (!is_compatible(index)) { |
| ASSERT_TRUE(!wait_result.ok() && wait_result.error().status() == ZX_ERR_PEER_CLOSED || |
| wait_result.ok() && wait_result->status == ZX_ERR_NOT_SUPPORTED); |
| continue; |
| } |
| ASSERT_TRUE(wait_result.ok() && wait_result->status == ZX_OK); |
| ASSERT_OK(wait_result->status); |
| auto info = std::move(wait_result->buffer_collection_info); |
| // root and oddball both said min_buffer_count_for_camping 1 |
| ASSERT_EQ(info.buffer_count, 2); |
| ASSERT_EQ(info.settings.buffer_settings.size_bytes, kCompatibleSize); |
| zx::unowned_vmo vmo(info.buffers[0].vmo); |
| zx_info_handle_basic_t basic_info{}; |
| EXPECT_OK( |
| vmo->get_info(ZX_INFO_HANDLE_BASIC, &basic_info, sizeof(basic_info), nullptr, nullptr)); |
| uint8_t data = 42; |
| zx_status_t write_status = vmo->write(&data, 0, sizeof(data)); |
| if (is_root(index)) { |
| ASSERT_OK(write_status); |
| } else { |
| EXPECT_TRUE(is_oddball(index)); |
| if (is_oddball_writable) { |
| ASSERT_OK(write_status); |
| } else { |
| ASSERT_EQ(write_status, ZX_ERR_ACCESS_DENIED); |
| } |
| } |
| } |
| } |
| } |
| |
| TEST(Sysmem, SetVerboseLoggingV1) { |
| // Verbose logging shouldn't have any observable effect via the sysmem protocols, so this test |
| // mainly just checks that having verbose logging enabled for a failed allocation doesn't crash |
| // sysmem. In addition, the log output during this test can be manually checked to make sure it's |
| // significantly more verbose as intended. |
| |
| auto root_token = create_initial_token_v1(); |
| auto set_verbose_result = root_token->SetVerboseLogging(); |
| ASSERT_TRUE(set_verbose_result.ok()); |
| auto child_token = create_token_under_token_v1(root_token); |
| auto child2_token = create_token_under_token_v1(child_token); |
| check_token_alive_v1(child_token); |
| check_token_alive_v1(child2_token); |
| const uint32_t kCompatibleSize = zx_system_get_page_size(); |
| const uint32_t kIncompatibleSize = 2 * zx_system_get_page_size(); |
| auto root = convert_token_to_collection_v1(std::move(root_token)); |
| auto child = convert_token_to_collection_v1(std::move(child_token)); |
| auto child2 = convert_token_to_collection_v1(std::move(child2_token)); |
| set_picky_constraints_v1(root, kCompatibleSize); |
| set_picky_constraints_v1(child, kIncompatibleSize); |
| auto set_result = |
| child2->SetConstraints(false, fuchsia_sysmem::wire::BufferCollectionConstraints{}); |
| ASSERT_TRUE(set_result.ok()); |
| auto root_result = root->WaitForBuffersAllocated(); |
| ASSERT_TRUE(!root_result.ok() && root_result.error().status() == ZX_ERR_PEER_CLOSED || |
| root_result.ok() && root_result->status == ZX_ERR_NOT_SUPPORTED); |
| auto child_result = child->WaitForBuffersAllocated(); |
| ASSERT_TRUE(!child_result.ok() && child_result.error().status() == ZX_ERR_PEER_CLOSED || |
| child_result.ok() && child_result->status == ZX_ERR_NOT_SUPPORTED); |
| auto child2_result = child2->WaitForBuffersAllocated(); |
| ASSERT_TRUE(!child2_result.ok() && child2_result.error().status() == ZX_ERR_PEER_CLOSED || |
| child2_result.ok() && child2_result->status == ZX_ERR_NOT_SUPPORTED); |
| |
| // Make sure sysmem is still alive. |
| auto check_token = create_initial_token_v1(); |
| auto sync_result = check_token->Sync(); |
| ASSERT_TRUE(sync_result.ok()); |
| } |
| |
| TEST(Sysmem, PixelFormatDoNotCare_SuccessV1) { |
| auto token1 = create_initial_token_v1(); |
| auto token2 = create_token_under_token_v1(token1); |
| |
| auto collection1 = convert_token_to_collection_v1(std::move(token1)); |
| auto collection2 = convert_token_to_collection_v1(std::move(token2)); |
| |
| fuchsia_sysmem::BufferCollectionConstraints c2; |
| c2.usage().cpu() = fuchsia_sysmem::kCpuUsageWriteOften; |
| c2.min_buffer_count_for_camping() = 1; |
| c2.has_buffer_memory_constraints() = true; |
| c2.buffer_memory_constraints().min_size_bytes() = 1; |
| c2.buffer_memory_constraints().max_size_bytes() = 512 * 1024 * 1024; |
| c2.image_format_constraints_count() = 1; |
| c2.image_format_constraints()[0] = {}; |
| c2.image_format_constraints()[0].color_spaces_count() = 1; |
| c2.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kRec709; |
| c2.image_format_constraints()[0].min_coded_width() = 32; |
| c2.image_format_constraints()[0].min_coded_height() = 32; |
| |
| // clone / copy |
| auto c1 = c2; |
| |
| c1.image_format_constraints()[0].pixel_format().type() = |
| fuchsia_sysmem::PixelFormatType::kDoNotCare; |
| c2.image_format_constraints()[0].pixel_format().type() = fuchsia_sysmem::PixelFormatType::kNv12; |
| |
| EXPECT_FALSE(c1.image_format_constraints()[0].pixel_format().has_format_modifier()); |
| EXPECT_FALSE(c2.image_format_constraints()[0].pixel_format().has_format_modifier()); |
| |
| fidl::Arena arena; |
| auto c1w = fidl::ToWire(arena, std::move(c1)); |
| auto c2w = fidl::ToWire(arena, std::move(c2)); |
| |
| auto set_verbose_result = collection1->SetVerboseLogging(); |
| ASSERT_TRUE(set_verbose_result.ok()); |
| |
| auto set_result1 = collection1->SetConstraints(true, std::move(c1w)); |
| ASSERT_TRUE(set_result1.ok()); |
| |
| auto set_result2 = collection2->SetConstraints(true, std::move(c2w)); |
| ASSERT_TRUE(set_result2.ok()); |
| |
| auto wait_result1 = collection1->WaitForBuffersAllocated(); |
| auto wait_result2 = collection2->WaitForBuffersAllocated(); |
| |
| ASSERT_TRUE(wait_result1.ok() && wait_result1->status == ZX_OK); |
| ASSERT_TRUE(wait_result2.ok() && wait_result2->status == ZX_OK); |
| |
| auto info1_w = std::move(wait_result1.value()); |
| auto info2_w = std::move(wait_result2.value()); |
| |
| auto info1 = fidl::ToNatural(std::move(info1_w)); |
| auto info2 = fidl::ToNatural(std::move(info2_w)); |
| |
| ASSERT_OK(info1.status()); |
| ASSERT_OK(info2.status()); |
| |
| ASSERT_EQ(2, info1.buffer_collection_info().buffer_count()); |
| ASSERT_EQ( |
| fuchsia_sysmem::PixelFormatType::kNv12, |
| info1.buffer_collection_info().settings().image_format_constraints().pixel_format().type()); |
| } |
| |
| TEST(Sysmem, PixelFormatDoNotCare_MultiplePixelFormatsFailsV1) { |
| // pass 0 - verify success when single pixel format |
| // pass 1 - verify failure when multiple pixel formats |
| // pass 1 - verify failure when multiple pixel formats (kInvalid variant) |
| for (uint32_t pass = 0; pass < 3; ++pass) { |
| auto token1 = create_initial_token_v1(); |
| auto token2 = create_token_under_token_v1(token1); |
| |
| auto collection1 = convert_token_to_collection_v1(std::move(token1)); |
| auto collection2 = convert_token_to_collection_v1(std::move(token2)); |
| |
| fuchsia_sysmem::BufferCollectionConstraints c2; |
| c2.usage().cpu() = fuchsia_sysmem::kCpuUsageWriteOften; |
| c2.min_buffer_count_for_camping() = 1; |
| c2.image_format_constraints_count() = 1; |
| c2.image_format_constraints()[0].color_spaces_count() = 1; |
| c2.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kRec709; |
| c2.image_format_constraints()[0].min_coded_width() = 32; |
| c2.image_format_constraints()[0].min_coded_height() = 32; |
| |
| // clone / copy |
| auto c1 = c2; |
| |
| // Setting two pixel_format values will intentionally cause failure. |
| c1.image_format_constraints()[0].pixel_format().type() = |
| fuchsia_sysmem::PixelFormatType::kDoNotCare; |
| if (pass != 0) { |
| c1.image_format_constraints_count() = 2; |
| c1.image_format_constraints()[1] = c1.image_format_constraints()[0]; |
| switch (pass) { |
| case 1: |
| c1.image_format_constraints()[1].pixel_format().type() = |
| fuchsia_sysmem::PixelFormatType::kNv12; |
| break; |
| case 2: |
| c1.image_format_constraints()[1].pixel_format().type() = |
| fuchsia_sysmem::PixelFormatType::kInvalid; |
| break; |
| } |
| } |
| |
| c2.image_format_constraints()[0].pixel_format().type() = fuchsia_sysmem::PixelFormatType::kNv12; |
| |
| fidl::Arena arena; |
| auto c1w = fidl::ToWire(arena, std::move(c1)); |
| auto c2w = fidl::ToWire(arena, std::move(c2)); |
| |
| auto set_result2 = collection2->SetConstraints(true, std::move(c2w)); |
| ASSERT_TRUE(set_result2.ok()); |
| auto set_result1 = collection1->SetConstraints(true, std::move(c1w)); |
| ASSERT_TRUE(set_result1.ok()); |
| |
| auto wait_result1 = collection1->WaitForBuffersAllocated(); |
| auto wait_result2 = collection2->WaitForBuffersAllocated(); |
| |
| if (pass == 0) { |
| ASSERT_TRUE(wait_result1.ok() && wait_result1->status == ZX_OK); |
| ASSERT_TRUE(wait_result2.ok() && wait_result2->status == ZX_OK); |
| ASSERT_EQ( |
| fuchsia_sysmem::PixelFormatType::kNv12, |
| wait_result1->buffer_collection_info.settings.image_format_constraints.pixel_format.type); |
| } else { |
| ASSERT_TRUE(!wait_result1.ok() || wait_result1->status != ZX_OK); |
| ASSERT_TRUE(!wait_result2.ok() || wait_result2->status != ZX_OK); |
| } |
| } |
| } |
| |
| TEST(Sysmem, PixelFormatDoNotCare_UnconstrainedFailsV1) { |
| // pass 0 - verify success when not all participants are unconstrained pixel format |
| // pass 1 - verify fialure when all participants are unconstrained pixel format |
| for (uint32_t pass = 0; pass < 2; ++pass) { |
| auto token1 = create_initial_token_v1(); |
| auto token2 = create_token_under_token_v1(token1); |
| |
| auto collection1 = convert_token_to_collection_v1(std::move(token1)); |
| auto collection2 = convert_token_to_collection_v1(std::move(token2)); |
| |
| fuchsia_sysmem::BufferCollectionConstraints c2; |
| c2.usage().cpu() = fuchsia_sysmem::kCpuUsageWriteOften; |
| c2.min_buffer_count_for_camping() = 1; |
| c2.image_format_constraints_count() = 1; |
| c2.image_format_constraints()[0].color_spaces_count() = 1; |
| c2.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kRec709; |
| c2.image_format_constraints()[0].min_coded_width() = 32; |
| c2.image_format_constraints()[0].min_coded_height() = 32; |
| |
| // clone / copy |
| auto c1 = c2; |
| |
| c1.image_format_constraints()[0].pixel_format().type() = |
| fuchsia_sysmem::PixelFormatType::kDoNotCare; |
| switch (pass) { |
| case 0: |
| c2.image_format_constraints()[0].pixel_format().type() = |
| fuchsia_sysmem::PixelFormatType::kNv12; |
| break; |
| case 1: |
| // Not constraining the pixel format overall will intentionally cause failure. |
| c2.image_format_constraints()[0].pixel_format().type() = |
| fuchsia_sysmem::PixelFormatType::kDoNotCare; |
| break; |
| } |
| |
| fidl::Arena arena; |
| auto c1w = fidl::ToWire(arena, std::move(c1)); |
| auto c2w = fidl::ToWire(arena, std::move(c2)); |
| |
| auto set_result1 = collection1->SetConstraints(true, std::move(c1w)); |
| ASSERT_TRUE(set_result1.ok()); |
| auto set_result2 = collection2->SetConstraints(true, std::move(c2w)); |
| ASSERT_TRUE(set_result2.ok()); |
| |
| auto wait_result1 = collection1->WaitForBuffersAllocated(); |
| auto wait_result2 = collection2->WaitForBuffersAllocated(); |
| |
| if (pass == 0) { |
| ASSERT_TRUE(wait_result1.ok() && wait_result1->status == ZX_OK); |
| ASSERT_TRUE(wait_result2.ok() && wait_result2->status == ZX_OK); |
| ASSERT_EQ( |
| fuchsia_sysmem::PixelFormatType::kNv12, |
| wait_result1->buffer_collection_info.settings.image_format_constraints.pixel_format.type); |
| } else { |
| ASSERT_TRUE(!wait_result1.ok() || wait_result1->status != ZX_OK); |
| ASSERT_TRUE(!wait_result2.ok() || wait_result2->status != ZX_OK); |
| } |
| } |
| } |
| |
| TEST(Sysmem, ColorSpaceDoNotCare_SuccessV1) { |
| auto token1 = create_initial_token_v1(); |
| auto token2 = create_token_under_token_v1(token1); |
| |
| auto collection1 = convert_token_to_collection_v1(std::move(token1)); |
| auto collection2 = convert_token_to_collection_v1(std::move(token2)); |
| |
| fuchsia_sysmem::BufferCollectionConstraints c2; |
| c2.usage().cpu() = fuchsia_sysmem::kCpuUsageWriteOften; |
| c2.min_buffer_count_for_camping() = 1; |
| c2.image_format_constraints_count() = 1; |
| c2.image_format_constraints()[0].pixel_format().type() = fuchsia_sysmem::PixelFormatType::kNv12; |
| c2.image_format_constraints()[0].min_coded_width() = 32; |
| c2.image_format_constraints()[0].min_coded_height() = 32; |
| |
| // clone / copy |
| auto c1 = c2; |
| |
| c1.image_format_constraints()[0].color_spaces_count() = 1; |
| c1.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kDoNotCare; |
| |
| c2.image_format_constraints()[0].color_spaces_count() = 1; |
| c2.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kRec709; |
| |
| fidl::Arena arena; |
| auto c1w = fidl::ToWire(arena, std::move(c1)); |
| auto c2w = fidl::ToWire(arena, std::move(c2)); |
| |
| auto set_result1 = collection1->SetConstraints(true, std::move(c1w)); |
| ASSERT_TRUE(set_result1.ok()); |
| |
| auto set_result2 = collection2->SetConstraints(true, std::move(c2w)); |
| ASSERT_TRUE(set_result2.ok()); |
| |
| auto wait_result1 = collection1->WaitForBuffersAllocated(); |
| auto wait_result2 = collection2->WaitForBuffersAllocated(); |
| |
| ASSERT_TRUE(wait_result1.ok() && wait_result1->status == ZX_OK); |
| ASSERT_TRUE(wait_result2.ok() && wait_result2->status == ZX_OK); |
| |
| auto info1_w = std::move(wait_result1.value()); |
| auto info2_w = std::move(wait_result2.value()); |
| |
| auto info1 = fidl::ToNatural(std::move(info1_w)); |
| auto info2 = fidl::ToNatural(std::move(info2_w)); |
| |
| ASSERT_OK(info1.status()); |
| ASSERT_OK(info2.status()); |
| |
| ASSERT_EQ(2, info1.buffer_collection_info().buffer_count()); |
| ASSERT_EQ( |
| fuchsia_sysmem::PixelFormatType::kNv12, |
| info1.buffer_collection_info().settings().image_format_constraints().pixel_format().type()); |
| ASSERT_EQ( |
| fuchsia_sysmem::ColorSpaceType::kRec709, |
| info1.buffer_collection_info().settings().image_format_constraints().color_space()[0].type()); |
| } |
| |
| TEST(Sysmem, PixelFormatDoNotCare_OneColorSpaceElseFailsV1) { |
| // In pass 0, c1 sets kDoNotCare (via ForceUnsetColorSpaceConstraint) and correctly sets a single |
| // kInvalid color space - in this pass we expect success. |
| // |
| // In pass 1, c1 sets kDoNotCare (via ForceUnsetColorSpaceConstraints) but incorrectly sets more |
| // than 1 color space - in this pass we expect failure. |
| // |
| // Pass 2 is a variant of pass 1 that sets the 2nd color space to kInvalid instead. |
| for (uint32_t pass = 0; pass < 3; ++pass) { |
| auto token1 = create_initial_token_v1(); |
| auto token2 = create_token_under_token_v1(token1); |
| |
| auto collection1 = convert_token_to_collection_v1(std::move(token1)); |
| auto collection2 = convert_token_to_collection_v1(std::move(token2)); |
| |
| fuchsia_sysmem::BufferCollectionConstraints c2; |
| c2.usage().cpu() = fuchsia_sysmem::kCpuUsageWriteOften; |
| c2.min_buffer_count_for_camping() = 1; |
| c2.image_format_constraints_count() = 1; |
| c2.image_format_constraints()[0].pixel_format().type() = fuchsia_sysmem::PixelFormatType::kNv12; |
| c2.image_format_constraints()[0].min_coded_width() = 32; |
| c2.image_format_constraints()[0].min_coded_height() = 32; |
| |
| // clone / copy |
| auto c1 = c2; |
| |
| // Setting >= 2 color spaces where at least one is kDoNotCare will trigger failure, regardless |
| // of what the specific additional color space(s) are. |
| switch (pass) { |
| case 0: |
| c1.image_format_constraints()[0].color_spaces_count() = 1; |
| c1.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kDoNotCare; |
| break; |
| case 1: |
| c1.image_format_constraints()[0].color_spaces_count() = 2; |
| c1.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kDoNotCare; |
| c1.image_format_constraints()[0].color_space()[1].type() = |
| fuchsia_sysmem::ColorSpaceType::kRec709; |
| break; |
| case 2: |
| c1.image_format_constraints()[0].color_spaces_count() = 2; |
| c1.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kInvalid; |
| c1.image_format_constraints()[0].color_space()[1].type() = |
| fuchsia_sysmem::ColorSpaceType::kDoNotCare; |
| break; |
| } |
| |
| c2.image_format_constraints()[0].color_spaces_count() = 1; |
| c2.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kRec709; |
| |
| fidl::Arena arena; |
| auto c1w = fidl::ToWire(arena, std::move(c1)); |
| auto c2w = fidl::ToWire(arena, std::move(c2)); |
| |
| auto set_result1 = collection1->SetConstraints(true, std::move(c1w)); |
| ASSERT_TRUE(set_result1.ok()); |
| |
| auto set_result2 = collection2->SetConstraints(true, std::move(c2w)); |
| ASSERT_TRUE(set_result2.ok()); |
| |
| auto wait_result1 = collection1->WaitForBuffersAllocated(); |
| auto wait_result2 = collection2->WaitForBuffersAllocated(); |
| |
| if (pass == 0) { |
| ASSERT_TRUE(wait_result1.ok() && wait_result1->status == ZX_OK); |
| ASSERT_TRUE(wait_result2.ok() && wait_result2->status == ZX_OK); |
| ASSERT_EQ( |
| fuchsia_sysmem::ColorSpaceType::kRec709, |
| wait_result1->buffer_collection_info.settings.image_format_constraints.color_space[0] |
| .type); |
| } else { |
| ASSERT_TRUE(!wait_result1.ok() || wait_result1->status != ZX_OK); |
| ASSERT_TRUE(!wait_result2.ok() || wait_result2->status != ZX_OK); |
| } |
| } |
| } |
| |
| TEST(Sysmem, ColorSpaceDoNotCare_UnconstrainedColorSpaceRemovesPixelFormatV1) { |
| // In pass 0, we verify that with a participant (2) that does constrain the color space, success. |
| // |
| // In pass 1, we verify that essentially "removing" the constraining participant (2) is enough to |
| // cause a failure, despite participant 1 setting the same constraints it did in pass 0 (including |
| // sending ForceUnsetColorSpaceConstraint()). |
| // |
| // This way, we know that the reason for the failure seen by participant 1 in pass 1 is the lack |
| // of any participant that constrains the color space, not just a problem with constraints set by |
| // participant 1 (in both passes). |
| for (uint32_t pass = 0; pass < 2; ++pass) { |
| // All the "decltype(1)" stuff is defined in both passes but only actually used in pass 0, not |
| // pass 1. |
| |
| auto token1 = create_initial_token_v1(); |
| decltype(token1) token2; |
| if (pass == 0) { |
| token2 = create_token_under_token_v1(token1); |
| } |
| |
| auto collection1 = convert_token_to_collection_v1(std::move(token1)); |
| decltype(collection1) collection2; |
| if (pass == 0) { |
| collection2 = convert_token_to_collection_v1(std::move(token2)); |
| } |
| |
| fuchsia_sysmem::BufferCollectionConstraints c1; |
| c1.usage().cpu() = fuchsia_sysmem::kCpuUsageWriteOften; |
| c1.min_buffer_count_for_camping() = 1; |
| c1.image_format_constraints_count() = 1; |
| c1.image_format_constraints()[0].pixel_format().type() = fuchsia_sysmem::PixelFormatType::kNv12; |
| // c1 is logically kDoNotCare, via ForceUnsetColorSpaceConstraint() sent below. This field is |
| // copied but then overridden for c2 (pass 0 only). |
| c1.image_format_constraints()[0].min_coded_width() = 32; |
| c1.image_format_constraints()[0].min_coded_height() = 32; |
| |
| // clone / copy |
| auto c2 = c1; |
| c1.image_format_constraints()[0].color_spaces_count() = 1; |
| c1.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kDoNotCare; |
| if (pass == 0) { |
| // c2 will constrain the pixel format (by setting a valid one here and not sending |
| // ForceUnsetColorSpaceConstraint() below). |
| c2.image_format_constraints()[0].color_spaces_count() = 1; |
| c2.image_format_constraints()[0].color_space()[0].type() = |
| fuchsia_sysmem::ColorSpaceType::kRec709; |
| } |
| |
| fidl::Arena arena; |
| auto c1w = fidl::ToWire(arena, std::move(c1)); |
| decltype(c1w) c2w; |
| if (pass == 0) { |
| c2w = fidl::ToWire(arena, std::move(c2)); |
| } |
| |
| auto set_result1 = collection1->SetConstraints(true, std::move(c1w)); |
| ASSERT_TRUE(set_result1.ok()); |
| if (pass == 0) { |
| auto set_result2 = collection2->SetConstraints(true, std::move(c2w)); |
| ASSERT_TRUE(set_result2.ok()); |
| } |
| |
| auto wait_result1 = collection1->WaitForBuffersAllocated(); |
| if (pass == 0) { |
| // Expected success because c2's constraining of the color space allows for c1's lack of |
| // constraining of the color space. |
| ASSERT_TRUE(wait_result1.ok() && wait_result1->status == ZX_OK); |
| auto wait_result2 = collection2->WaitForBuffersAllocated(); |
| ASSERT_TRUE(wait_result2.ok() && wait_result2->status == ZX_OK); |
| // may as well check that the color space is indeed kRec709, but this is also checked by the |
| // _Success test above. |
| ASSERT_EQ( |
| fuchsia_sysmem::ColorSpaceType::kRec709, |
| wait_result1->buffer_collection_info.settings.image_format_constraints.color_space[0] |
| .type); |
| } else { |
| // Expect failed because c2's constraining of the color space is missing in pass 1, leaving |
| // the color space completely unconstrained. By design, sysmem doesn't arbitrarily select a |
| // color space when the color space is completely unconstrained (at least for now). |
| ASSERT_TRUE(!wait_result1.ok() || wait_result1->status != ZX_OK); |
| } |
| } |
| } |
| |
| TEST(Sysmem, DuplicateSyncRightsAttenuationMaskZeroFails) { |
| auto parent = create_initial_token_v1(); |
| std::vector<zx_rights_t> rights_masks{0, 0}; |
| fidl::Arena arena; |
| auto duplicate_sync_result = parent->DuplicateSync(fidl::VectorView(arena, rights_masks)); |
| ASSERT_FALSE(duplicate_sync_result.ok()); |
| } |
| |
| TEST(Sysmem, BufferCollectionTokenGroupCreateChildZeroAttenuationMaskFails) { |
| auto parent = create_initial_token_v1(); |
| auto group = create_group_under_token_v1(parent); |
| auto child_endpoints = |
| std::move(fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>().value()); |
| fidl::Arena arena; |
| auto request = fuchsia_sysmem::wire::BufferCollectionTokenGroupCreateChildRequest::Builder(arena) |
| .rights_attenuation_mask(0) |
| .token_request(std::move(child_endpoints.server)) |
| .Build(); |
| auto create_child_result = group->CreateChild(std::move(request)); |
| // one-way, so no failure yet |
| ASSERT_TRUE(create_child_result.ok()); |
| // We shouldn't have to wait anywhere near this long, but to avoid flakes we |
| // won't fail the test until it's very clear that sysmem hasn't failed the |
| // buffer collection despite zero attenuation mask. |
| constexpr zx::duration kWaitDuration = zx::sec(10); |
| const zx::time start_wait = zx::clock::get_monotonic(); |
| while (true) { |
| // give up after kWaitDuration |
| ASSERT_TRUE(zx::clock::get_monotonic() < start_wait + kWaitDuration); |
| if (parent->Sync().ok()) { |
| // failure due to Close before AllChildrenPresent takes effect async; try again |
| zx::nanosleep(zx::deadline_after(zx::msec(10))); |
| continue; |
| } else { |
| // expected failure seen - pass |
| break; |
| } |
| } |
| } |
| |
| TEST(Sysmem, BufferCollectionTokenGroupCreateChildrenZeroAttenuationMaskFails) { |
| auto parent = create_initial_token_v1(); |
| auto group = create_group_under_token_v1(parent); |
| std::vector<zx_rights_t> rights_masks{0, 0}; |
| fidl::Arena arena; |
| auto create_sync_result = group->CreateChildrenSync(fidl::VectorView(arena, rights_masks)); |
| ASSERT_FALSE(create_sync_result.ok()); |
| } |
| |
| TEST(Sysmem, BufferCollectionTokenGroupCloseBeforeAllChildrenPresentFails) { |
| auto parent = create_initial_token_v1(); |
| auto group = create_group_under_token_v1(parent); |
| auto child1 = create_token_under_group_v1(group); |
| |
| // sending Close before AllChildrenPresent expected to cause buffer collection failure |
| auto close_result = group->Close(); |
| // one-way message; no visible error yet |
| ASSERT_TRUE(close_result.ok()); |
| |
| // We shouldn't have to wait anywhere near this long, but to avoid flakes we |
| // won't fail the test until it's very clear that sysmem hasn't failed the |
| // buffer collection despite Close before AllChildrenPresent. |
| constexpr zx::duration kWaitDuration = zx::sec(10); |
| const zx::time start_wait = zx::clock::get_monotonic(); |
| while (true) { |
| // give up after kWaitDuration |
| ASSERT_TRUE(zx::clock::get_monotonic() < start_wait + kWaitDuration); |
| if (parent->Sync().ok()) { |
| // failure due to Close before AllChildrenPresent takes effect async; try again |
| zx::nanosleep(zx::deadline_after(zx::msec(10))); |
| continue; |
| } else { |
| // expected failure seen - pass |
| break; |
| } |
| } |
| } |