blob: 58262fc82b9d4e3751ba769cfde19ac05e60230a [file] [log] [blame]
// Copyright 2019 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.
#ifndef SRC_DEVICES_SYSMEM_DRIVERS_SYSMEM_LOGICAL_BUFFER_COLLECTION_H_
#define SRC_DEVICES_SYSMEM_DRIVERS_SYSMEM_LOGICAL_BUFFER_COLLECTION_H_
#include <fuchsia/sysmem/c/fidl.h>
#include <fuchsia/sysmem/llcpp/fidl.h>
#include <fuchsia/sysmem2/llcpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl-async-2/fidl_struct.h>
#include <lib/fidl/llcpp/heap_allocator.h>
#include <lib/zx/channel.h>
#include <list>
#include <map>
#include <memory>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include "binding_handle.h"
#include "device.h"
namespace sysmem_driver {
class BufferCollectionToken;
class BufferCollection;
class MemoryAllocator;
// This class can be used to hold an inspect snapshot of one set of constraints taken from a client
// at a particular point in time.
struct ConstraintInfoSnapshot {
inspect::Node node;
inspect::ValueList node_constraints;
};
class LogicalBufferCollection : public fbl::RefCounted<LogicalBufferCollection> {
public:
struct ClientInfo {
std::string name;
zx_koid_t id{};
};
// In sysmem_tests, the max needed was observed to be 12400 bytes, so if we wanted to avoid heap
// for allocating FIDL table fields, 32KiB would likely be enough most of the time. However, the
// difference isn't even reliably measurable sign out of the ~410us +/- ~10us it takes to
// allocate a collection with only 4KiB buffer space total on astro.
//
// The time cost of zeroing VMO buffer space is far higher than anything controlled by this
// number, so it'd be more fruitful to focus there before here.
static constexpr size_t kBufferThenHeapAllocatorSize = 1;
using FidlAllocator = fidl::BufferThenHeapAllocator<kBufferThenHeapAllocatorSize>;
using CollectionMap = std::map<BufferCollection*, std::unique_ptr<BufferCollection>>;
~LogicalBufferCollection();
static void Create(zx::channel buffer_collection_token_request, Device* parent_device);
// |parent_device| the Device* that the calling allocator is part of. The
// tokens_by_koid_ for each Device is separate. If somehow two clients were
// to get connected to two separate sysmem device instances hosted in the
// same devhost, those clients (intentionally) won't be able to share a
// LogicalBufferCollection.
//
// |buffer_collection_token| the client end of the BufferCollectionToken
// being turned in by the client to get a BufferCollection in exchange.
//
// |buffer_collection_request| the server end of a BufferCollection channel
// to be served by the LogicalBufferCollection associated with
// buffer_collection_token.
static void BindSharedCollection(Device* parent_device, zx::channel buffer_collection_token,
zx::channel buffer_collection_request,
const ClientInfo* client_info);
// ZX_OK if the token is known to the server.
// ZX_ERR_NOT_FOUND if the token isn't known to the server.
static zx_status_t ValidateBufferCollectionToken(Device* parent_device,
zx_koid_t token_server_koid);
// This is used to create the initial BufferCollectionToken, and also used
// by BufferCollectionToken::Duplicate().
//
// The |self| parameter exists only because LogicalBufferCollection can't
// hold a std::weak_ptr<> to itself because that requires libc++ (the binary
// not just the headers) which isn't available in Zircon so far.
void CreateBufferCollectionToken(fbl::RefPtr<LogicalBufferCollection> self,
uint32_t rights_attenuation_mask,
zx::channel buffer_collection_token_request,
const ClientInfo* client_info);
void OnSetConstraints();
void SetName(uint32_t priority, std::string name);
void SetDebugTimeoutLogDeadline(int64_t deadline);
void LogClientError(const ClientInfo* client_info, const char* format, ...) __PRINTFLIKE(3, 4);
void VLogClientError(const ClientInfo* client_info, const char* format, va_list args);
struct AllocationResult {
const llcpp::fuchsia::sysmem2::BufferCollectionInfo* buffer_collection_info = nullptr;
const zx_status_t status = ZX_OK;
};
AllocationResult allocation_result();
Device* parent_device() const { return parent_device_; }
const CollectionMap& collection_views() const { return collection_views_; }
// The returned allocator& lasts as long as the LogicalBufferCollection, which is long enough
// for child BufferCollection(s) to use the allocator.
FidlAllocator& fidl_allocator() { return allocator_; }
std::optional<std::string> name() const {
return name_ ? std::make_optional(name_->name) : std::optional<std::string>();
}
inspect::Node& node() { return node_; }
private:
enum class CheckSanitizeStage { kInitial, kNotAggregated, kAggregated };
struct Constraints {
Constraints(const Constraints&) = delete;
Constraints(Constraints&&) = default;
Constraints(llcpp::fuchsia::sysmem2::BufferCollectionConstraints::Builder&& builder,
ClientInfo&& client)
: builder(std::move(builder)), client(std::move(client)) {}
llcpp::fuchsia::sysmem2::BufferCollectionConstraints::Builder builder;
ClientInfo client;
};
struct CollectionName {
uint32_t priority{};
std::string name;
};
using ConstraintsList = std::list<Constraints>;
LogicalBufferCollection(Device* parent_device);
// If |format| is nonnull, will log an error. This also cleans out a lot of
// state that's unnecessary after a failure.
void Fail(const char* format, ...);
static void LogInfo(const char* format, ...);
static void LogErrorStatic(const ClientInfo* client_info, const char* format, ...)
__PRINTFLIKE(2, 3);
// Uses the implicit |current_client_info_| to identify which client has an error.
void LogError(const char* format, ...) __PRINTFLIKE(2, 3);
void VLogError(const char* format, va_list args);
void MaybeAllocate();
void TryAllocate();
void InitializeConstraintSnapshots(const ConstraintsList& constraints_list);
void SetFailedAllocationResult(zx_status_t status);
void SetAllocationResult(llcpp::fuchsia::sysmem2::BufferCollectionInfo&& info);
void SendAllocationResult();
void BindSharedCollectionInternal(BufferCollectionToken* token,
zx::channel buffer_collection_request);
// To be called only by CombineConstraints().
bool IsMinBufferSizeSpecifiedByAnyParticipant();
bool CombineConstraints();
bool CheckSanitizeBufferCollectionConstraints(
CheckSanitizeStage stage,
llcpp::fuchsia::sysmem2::BufferCollectionConstraints::Builder* constraints);
bool CheckSanitizeBufferUsage(CheckSanitizeStage stage,
llcpp::fuchsia::sysmem2::BufferUsage::Builder* buffer_usage);
bool CheckSanitizeBufferMemoryConstraints(
CheckSanitizeStage stage, const llcpp::fuchsia::sysmem2::BufferUsage& buffer_usage,
llcpp::fuchsia::sysmem2::BufferMemoryConstraints::Builder* constraints);
bool CheckSanitizeImageFormatConstraints(
CheckSanitizeStage stage,
llcpp::fuchsia::sysmem2::ImageFormatConstraints::Builder* constraints);
bool AccumulateConstraintBufferCollection(
llcpp::fuchsia::sysmem2::BufferCollectionConstraints::Builder* acc,
llcpp::fuchsia::sysmem2::BufferCollectionConstraints* c);
bool AccumulateConstraintsBufferUsage(llcpp::fuchsia::sysmem2::BufferUsage::Builder* acc,
llcpp::fuchsia::sysmem2::BufferUsage* c);
bool AccumulateConstraintHeapPermitted(fidl::VectorView<llcpp::fuchsia::sysmem2::HeapType>* acc,
fidl::VectorView<llcpp::fuchsia::sysmem2::HeapType>* c);
bool AccumulateConstraintBufferMemory(
llcpp::fuchsia::sysmem2::BufferMemoryConstraints::Builder* acc,
llcpp::fuchsia::sysmem2::BufferMemoryConstraints* c);
bool AccumulateConstraintImageFormats(
fidl::VectorView<llcpp::fuchsia::sysmem2::ImageFormatConstraints::Builder>* acc,
fidl::VectorView<llcpp::fuchsia::sysmem2::ImageFormatConstraints>* c);
bool AccumulateConstraintImageFormat(
llcpp::fuchsia::sysmem2::ImageFormatConstraints::Builder* acc,
llcpp::fuchsia::sysmem2::ImageFormatConstraints* c);
bool AccumulateConstraintColorSpaces(
fidl::VectorView<llcpp::fuchsia::sysmem2::ColorSpace::Builder>* acc,
fidl::VectorView<llcpp::fuchsia::sysmem2::ColorSpace>* c);
size_t InitialCapacityOrZero(CheckSanitizeStage stage, size_t initial_capacity);
bool IsColorSpaceEqual(const llcpp::fuchsia::sysmem2::ColorSpace::Builder& a,
const llcpp::fuchsia::sysmem2::ColorSpace& b);
fit::result<llcpp::fuchsia::sysmem2::BufferCollectionInfo, zx_status_t> Allocate();
fit::result<zx::vmo> AllocateVmo(
MemoryAllocator* allocator,
const llcpp::fuchsia::sysmem2::SingleBufferSettings::Builder& settings, uint32_t index);
int32_t CompareImageFormatConstraintsTieBreaker(
const llcpp::fuchsia::sysmem2::ImageFormatConstraints& a,
const llcpp::fuchsia::sysmem2::ImageFormatConstraints& b);
int32_t CompareImageFormatConstraintsByIndex(uint32_t index_a, uint32_t index_b);
void CreationTimedOut(async_dispatcher_t* dispatcher, async::TaskBase* task, zx_status_t status);
Device* parent_device_ = nullptr;
FidlAllocator allocator_;
using TokenMap = std::map<BufferCollectionToken*, BindingHandle<BufferCollectionToken>>;
TokenMap token_views_;
CollectionMap collection_views_;
ConstraintsList constraints_list_;
std::vector<ConstraintInfoSnapshot> constraints_at_allocation_;
bool is_allocate_attempted_ = false;
std::optional<llcpp::fuchsia::sysmem2::BufferCollectionConstraints::Builder> constraints_;
// Iff true, initial allocation has been attempted and has succeeded or
// failed. Both allocation_result_status_ and allocation_result_info_ are
// not meaningful until has_allocation_result_ is true.
bool has_allocation_result_ = false;
zx_status_t allocation_result_status_ = ZX_OK;
std::optional<llcpp::fuchsia::sysmem2::BufferCollectionInfo> allocation_result_info_;
MemoryAllocator* memory_allocator_ = nullptr;
std::optional<CollectionName> name_;
// Information about the current client - only valid while aggregating state for a particular
// client.
ClientInfo* current_client_info_ = nullptr;
// We keep LogicalBufferCollection alive as long as there are child VMOs
// outstanding (no revoking of child VMOs for now).
//
// This tracking is for the benefit of MemoryAllocator sub-classes that need
// a Delete() call, such as to clean up a slab allocation and/or to inform
// an external allocator of delete.
class TrackedParentVmo {
public:
using DoDelete = fit::callback<void(TrackedParentVmo* parent)>;
// The do_delete callback will be invoked upon the sooner of (A) the client
// code causing ~ParentVmo, or (B) ZX_VMO_ZERO_CHILDREN occurring async
// after StartWait() is called.
TrackedParentVmo(fbl::RefPtr<LogicalBufferCollection> buffer_collection, zx::vmo vmo,
DoDelete do_delete);
~TrackedParentVmo();
// This should only be called after client code has created a child VMO, and
// will begin the wait for ZX_VMO_ZERO_CHILDREN.
zx_status_t StartWait(async_dispatcher_t* dispatcher);
// Cancel the wait. This should only be used by LogicalBufferCollection
zx_status_t CancelWait();
zx::vmo TakeVmo();
[[nodiscard]] const zx::vmo& vmo() const;
void set_child_koid(zx_koid_t koid) { child_koid_ = koid; }
TrackedParentVmo(const TrackedParentVmo&) = delete;
TrackedParentVmo(TrackedParentVmo&&) = delete;
TrackedParentVmo& operator=(const TrackedParentVmo&) = delete;
TrackedParentVmo& operator=(TrackedParentVmo&&) = delete;
private:
void OnZeroChildren(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal);
fbl::RefPtr<LogicalBufferCollection> buffer_collection_;
zx::vmo vmo_;
zx_koid_t child_koid_{};
DoDelete do_delete_;
async::WaitMethod<TrackedParentVmo, &TrackedParentVmo::OnZeroChildren> zero_children_wait_;
// Only for asserts:
bool waiting_ = {};
};
using ParentVmoMap = std::map<zx_handle_t, std::unique_ptr<TrackedParentVmo>>;
ParentVmoMap parent_vmos_;
async::TaskMethod<LogicalBufferCollection, &LogicalBufferCollection::CreationTimedOut>
creation_timer_{this};
inspect::Node node_;
inspect::StringProperty name_property_;
inspect::UintProperty vmo_count_property_;
inspect::ValueList vmo_properties_;
};
} // namespace sysmem_driver
#endif // SRC_DEVICES_SYSMEM_DRIVERS_SYSMEM_LOGICAL_BUFFER_COLLECTION_H_