chromium/ui/ozone/platform/drm/gpu/fake_drm_device.h

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_OZONE_PLATFORM_DRM_GPU_FAKE_DRM_DEVICE_H_
#define UI_OZONE_PLATFORM_DRM_GPU_FAKE_DRM_DEVICE_H_

#include <drm_fourcc.h>
#include <drm_mode.h>
#include <stddef.h>
#include <stdint.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

#include <cstdint>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <tuple>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/containers/queue.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "ui/ozone/platform/drm/gpu/drm_device.h"
#include "ui/ozone/platform/drm/gpu/page_flip_request.h"

// Private types defined in libdrm. Define them here so we can peek at the
// commit and ensure the expected state has been set correctly.
struct drmModeAtomicReqItem {
  uint32_t object_id;
  uint32_t property_id;
  uint64_t value;
  uint32_t cursor;
};

typedef drmModeAtomicReqItem* drmModeAtomicReqItemPtr;

struct _drmModeAtomicReq {
  uint32_t cursor;
  uint32_t size_items;
  drmModeAtomicReqItemPtr items;
};

namespace ui {

using ResolutionAndRefreshRate = std::pair<gfx::Size, uint32_t>;

template <class Object>
Object* DrmAllocator(size_t num_of_objects = 1) {
  return static_cast<Object*>(drmMalloc(num_of_objects * sizeof(Object)));
}

// DRM Object Base IDs:
inline constexpr uint32_t kPlaneOffset = 100;
inline constexpr uint32_t kCrtcIdBase = 200;
inline constexpr uint32_t kConnectorIdBase = 300;
inline constexpr uint32_t kEncoderIdBase = 400;

// Required Connector Property IDs:
inline constexpr uint32_t kCrtcIdPropId = 1000;
inline constexpr uint32_t kLinkStatusPropId = 1001;
inline constexpr uint32_t kEdidBlobPropId = 1002;

// Optional Connector Property IDs:
inline constexpr uint32_t kTileBlobPropId = 1500;
inline constexpr uint32_t kVrrCapablePropId = 1501;

// Required CRTC Property IDs:
inline constexpr uint32_t kActivePropId = 2000;
inline constexpr uint32_t kModePropId = 2001;

// Optional CRTC Property IDs:
inline constexpr uint32_t kBackgroundColorPropId = 3000;
inline constexpr uint32_t kCtmPropId = 3001;
inline constexpr uint32_t kDegammaLutPropId = 3002;
inline constexpr uint32_t kDegammaLutSizePropId = 3003;
inline constexpr uint32_t kGammaLutPropId = 3004;
inline constexpr uint32_t kGammaLutSizePropId = 3005;
inline constexpr uint32_t kInFencePropId = 3006;
inline constexpr uint32_t kOutFencePtrPropId = 3007;
inline constexpr uint32_t kVrrEnabledPropId = 3008;

// Required Plane Property IDs:
inline constexpr uint32_t kCrtcH = 4001;
inline constexpr uint32_t kCrtcW = 4002;
inline constexpr uint32_t kCrtcX = 4003;
inline constexpr uint32_t kCrtcY = 4004;
inline constexpr uint32_t kPlaneCrtcId = 4005;
inline constexpr uint32_t kPlaneFbId = 4006;
inline constexpr uint32_t kSrcH = 4007;
inline constexpr uint32_t kSrcW = 4008;
inline constexpr uint32_t kSrcX = 4009;
inline constexpr uint32_t kSrcY = 4010;

// Optional Plane Property IDs:
inline constexpr uint32_t kTypePropId = 5000;
inline constexpr uint32_t kInFormatsPropId = 5001;
inline constexpr uint32_t kPlaneCtmId = 5002;
inline constexpr uint32_t kRotationPropId = 5003;
inline constexpr uint32_t kColorEncodingPropId = 5004;
inline constexpr uint32_t kColorRangePropId = 5005;
inline constexpr uint32_t kSizeHintsPropId = 5006;

// Blob IDs:
inline constexpr uint32_t kBaseBlobId = 6000;
inline constexpr uint32_t kTileBlobId = 6100;

// The real DrmDevice makes actual DRM calls which we can't use in unit tests.
class FakeDrmDevice : public DrmDevice {
 public:
  struct CrtcProperties {
    CrtcProperties();
    CrtcProperties(const CrtcProperties&);
    ~CrtcProperties();

    uint32_t id;

    std::vector<DrmWrapper::Property> properties;
  };

  struct ConnectorProperties {
    ConnectorProperties();
    ConnectorProperties(const ConnectorProperties&);
    ~ConnectorProperties();

    uint32_t id;
    bool connection;
    std::vector<ResolutionAndRefreshRate> modes;
    std::vector<uint32_t> encoders;
    std::vector<uint8_t> edid_blob;

    std::vector<DrmWrapper::Property> properties;
  };

  struct EncoderProperties {
    EncoderProperties();
    EncoderProperties(const EncoderProperties&);
    ~EncoderProperties();

    uint32_t id;
    uint32_t possible_crtcs;
  };

  struct PlaneProperties {
    PlaneProperties();
    PlaneProperties(const PlaneProperties&);
    ~PlaneProperties();

    uint32_t id;
    uint32_t crtc_mask;

    std::vector<DrmWrapper::Property> properties;
  };

  struct FakeDrmState {
    FakeDrmState();
    FakeDrmState(const FakeDrmState&) = delete;
    FakeDrmState& operator=(const FakeDrmState&) = delete;
    ~FakeDrmState();

    bool HasResources() const;

    std::vector<CrtcProperties> crtc_properties;
    std::vector<ConnectorProperties> connector_properties;
    std::vector<EncoderProperties> encoder_properties;
    std::vector<PlaneProperties> plane_properties;
    std::map<uint32_t, std::string> property_names;
    std::map<uint32_t, std::vector<std::pair<uint64_t, std::string>>>
        enum_values;
  };

  explicit FakeDrmDevice(std::unique_ptr<GbmDevice> gbm_device);
  explicit FakeDrmDevice(const base::FilePath& path,
                         std::unique_ptr<GbmDevice> gbm_device,
                         bool is_primary_device);

  FakeDrmDevice(const FakeDrmDevice&) = delete;
  FakeDrmDevice& operator=(const FakeDrmDevice&) = delete;

  ScopedDrmPropertyBlob CreateInFormatsBlob(
      const std::vector<uint32_t>& supported_formats,
      const std::vector<drm_format_modifier>& supported_format_modifiers);
  ScopedDrmPropertyBlob CreateSizeHintsBlob(
      const std::vector<gfx::Size>& sizes);
  int get_set_crtc_call_count() const { return set_crtc_call_count_; }
  int get_add_framebuffer_call_count() const {
    return add_framebuffer_call_count_;
  }
  int get_remove_framebuffer_call_count() const {
    return remove_framebuffer_call_count_;
  }
  int get_page_flip_call_count() const { return page_flip_call_count_; }
  int get_overlay_clear_call_count() const { return overlay_clear_call_count_; }
  int get_test_modeset_count() const { return test_modeset_count_; }
  int get_commit_modeset_count() const { return commit_modeset_count_; }
  int get_seamless_modeset_count() const { return seamless_modeset_count_; }
  int get_commit_count() const { return commit_count_; }
  int get_set_object_property_count() const {
    return set_object_property_count_;
  }
  int get_set_gamma_ramp_count() const { return set_gamma_ramp_count_; }
  void set_set_crtc_expectation(bool state) { set_crtc_expectation_ = state; }
  void set_page_flip_expectation(bool state) { page_flip_expectation_ = state; }
  void set_add_framebuffer_expectation(bool state) {
    add_framebuffer_expectation_ = state;
  }
  void set_create_dumb_buffer_expectation(bool state) {
    create_dumb_buffer_expectation_ = state;
  }
  void set_legacy_gamma_ramp_expectation(bool state) {
    legacy_gamma_ramp_expectation_ = state;
  }
  void set_commit_expectation(bool state) { commit_expectation_ = state; }
  void set_overlay_modeset_expectation(bool state) {
    modeset_with_overlays_expectation_ = state;
  }

  uint32_t current_framebuffer() const { return current_framebuffer_; }

  const base::flat_map<uint32_t, sk_sp<SkSurface>> buffers() const {
    return buffers_;
  }

  int last_planes_committed_count() const {
    return last_planes_committed_count_;
  }
  int modeset_sequence_id() const override;

  uint32_t get_cursor_handle_for_crtc(uint32_t crtc) const {
    const auto it = crtc_cursor_map_.find(crtc);
    return it != crtc_cursor_map_.end() ? it->second : 0;
  }

  // Resets `drm_state_` to be empty, with no properties configured and no
  // property names set. Resets `plane_manager_` to nullptr (it will not be
  // re-created until InitializeState is called).
  void ResetStateWithNoProperties();

  // Calls `ResetStateWithNoProperties`, then configures `drm_state_` to have
  // all properties registered with their names in `property_names`, but no
  // objects configured.
  void ResetStateWithAllProperties();

  // Calls `ResetStateWithNoProperties`, and then configures `drm_state_`. Will
  // create `crtc_count` different CRTCs and connectors with 1 primary plane, 1
  // cursor plane (since some tests expect them), and `planes_per_crtc` - 1
  // overlay planes for each CRTC. All planes will be allocated with the
  // specified `plane_supported_formats` and `plane_supported_format_modifiers`.
  // Returns a reference to `drm_state_`.
  // TODO(b/335542790): Update tests to not need the returned reference to
  // `drm_state_`.
  FakeDrmDevice::FakeDrmState& ResetStateWithDefaultObjects(
      size_t crtc_count,
      size_t planes_per_crtc,
      size_t movable_planes = 0u,
      std::vector<uint32_t> plane_supported_formats = {DRM_FORMAT_XRGB8888},
      std::vector<drm_format_modifier> plane_supported_format_modifiers = {});

  // Create `plane_manager_`, set the connector link status, and the EDID
  // blob.
  void InitializeState(bool use_atomic);
  bool InitializeStateWithResult(bool use_atomic);

  // Return true if InitializeState has been called yet.
  bool IsInitialized() const { return !!plane_manager_; }

  void RunCallbacks();

  // Add a `property.id` to `object_id`, and set its value to `property.value`.
  // This can only be called before InitializeState.
  void AddProperty(uint32_t object_id, const DrmWrapper::Property& property);

  // Configure the possible enum values for a particular property. This can only
  // be called before InitializeState.
  void SetPossibleValuesForEnumProperty(
      uint32_t property_id,
      std::vector<std::pair<uint64_t /* value */, std::string /* name */>>
          values);

  // Functions to configure the FakeDrmState. Must be called before Initialize
  // is called.
  CrtcProperties& AddCrtc();
  CrtcProperties& AddCrtcWithPrimaryAndCursorPlanes();
  std::pair<CrtcProperties&, ConnectorProperties&> AddCrtcAndConnector();
  PlaneProperties& AddPlane(
      uint32_t crtc_id,
      uint32_t type,
      std::vector<uint32_t> supported_formats = {DRM_FORMAT_XRGB8888},
      std::vector<drm_format_modifier> supported_format_modifiers = {});
  PlaneProperties& AddPlane(
      const std::vector<uint32_t>& crtc_ids,
      uint32_t type,
      std::vector<uint32_t> supported_formats = {DRM_FORMAT_XRGB8888},
      std::vector<drm_format_modifier> supported_format_modifiers = {});
  ConnectorProperties& AddConnector();
  EncoderProperties& AddEncoder();

  void SetModifiersOverhead(base::flat_map<uint64_t, int> modifiers_overhead);
  void SetSystemLimitOfModifiers(uint64_t limit);

  const CrtcProperties& crtc_property(size_t idx) const {
    return drm_state_.crtc_properties[idx];
  }
  const ConnectorProperties& connector_property(size_t idx) const {
    return drm_state_.connector_properties[idx];
  }
  const PlaneProperties& plane_property(size_t idx) const {
    return drm_state_.plane_properties[idx];
  }

  const std::vector<CrtcProperties>& crtc_properties() const {
    return drm_state_.crtc_properties;
  }
  const std::vector<ConnectorProperties>& connector_properties() const {
    return drm_state_.connector_properties;
  }
  const std::vector<PlaneProperties>& plane_properties() const {
    return drm_state_.plane_properties;
  }
  const std::map<uint32_t, std::string>& property_names() const {
    return drm_state_.property_names;
  }

  // DrmDevice:
  ScopedDrmResourcesPtr GetResources() const override;
  ScopedDrmPlaneResPtr GetPlaneResources() const override;
  ScopedDrmObjectPropertyPtr GetObjectProperties(
      uint32_t object_id,
      uint32_t object_type) const override;
  ScopedDrmCrtcPtr GetCrtc(uint32_t crtc_id) const override;
  bool SetCrtc(uint32_t crtc_id,
               uint32_t framebuffer,
               std::vector<uint32_t> connectors,
               const drmModeModeInfo& mode) override;
  bool DisableCrtc(uint32_t crtc_id) override;
  ScopedDrmConnectorPtr GetConnector(uint32_t connector_id) const override;
  ScopedDrmEncoderPtr GetEncoder(uint32_t encoder_id) const override;
  bool AddFramebuffer2(uint32_t width,
                       uint32_t height,
                       uint32_t format,
                       uint32_t handles[4],
                       uint32_t strides[4],
                       uint32_t offsets[4],
                       uint64_t modifiers[4],
                       uint32_t* framebuffer,
                       uint32_t flags) override;
  bool RemoveFramebuffer(uint32_t framebuffer) override;
  ScopedDrmFramebufferPtr GetFramebuffer(uint32_t framebuffer) const override;
  bool PageFlip(uint32_t crtc_id,
                uint32_t framebuffer,
                scoped_refptr<PageFlipRequest> page_flip_request) override;
  ScopedDrmPlanePtr GetPlane(uint32_t plane_id) const override;
  ScopedDrmPropertyPtr GetProperty(drmModeConnector* connector,
                                   const char* name) const override;
  ScopedDrmPropertyPtr GetProperty(uint32_t id) const override;
  bool SetProperty(uint32_t connector_id,
                   uint32_t property_id,
                   uint64_t value) override;
  ScopedDrmPropertyBlob CreatePropertyBlob(const void* blob,
                                           size_t size) override;
  void DestroyPropertyBlob(uint32_t id) override;
  bool GetCapability(uint64_t capability, uint64_t* value) const override;
  ScopedDrmPropertyBlobPtr GetPropertyBlob(uint32_t property_id) const override;
  ScopedDrmPropertyBlobPtr GetPropertyBlob(drmModeConnector* connector,
                                           const char* name) const override;
  bool SetObjectProperty(uint32_t object_id,
                         uint32_t object_type,
                         uint32_t property_id,
                         uint32_t property_value) override;
  bool SetCursor(uint32_t crtc_id,
                 uint32_t handle,
                 const gfx::Size& size) override;
  bool MoveCursor(uint32_t crtc_id, const gfx::Point& point) override;
  bool CommitProperties(drmModeAtomicReq* request,
                        uint32_t flags,
                        uint32_t crtc_count,
                        scoped_refptr<PageFlipRequest> callback) override;
  bool CreateDumbBuffer(const SkImageInfo& info,
                        uint32_t* handle,
                        uint32_t* stride) override;
  bool DestroyDumbBuffer(uint32_t handle) override;
  bool MapDumbBuffer(uint32_t handle, size_t size, void** pixels) override;
  bool UnmapDumbBuffer(void* pixels, size_t size) override;
  bool CloseBufferHandle(uint32_t handle) override;
  bool SetGammaRamp(uint32_t crtc_id,
                    const display::GammaCurve& curve) override;
  bool SetCapability(uint64_t capability, uint64_t value) override;
  std::optional<std::string> GetDriverName() const override;
  void SetDriverName(std::optional<std::string> name);
  uint32_t GetFramebufferForCrtc(uint32_t crtc_id) const;

  // There is a circular reference between DrmDevice and
  // HardwareDisplayPlaneManager, as described in https://crbug.com/40263526.
  // This function can be used to break the cycle in unittests. It must be
  // called after any associated ScreenManager has been destroyed.
  void ResetPlaneManagerForTesting();

  // When CommitProperties has been called with a PageFlipRequest, FakeDrmDevice
  // holds a reference to the PageFlipRequest, resulting in a reference cycle.
  // This reference os released as a part of RunCallbacks. ClearCallbacks breaks
  // the cycle without running the callbacks.
  void ClearCallbacks();

 protected:
  ~FakeDrmDevice() override;

 private:
  // Properties of the plane associated with a fb.
  struct FramebufferProps {
    uint32_t width = 0;
    uint32_t height = 0;
    uint64_t modifier = 0;
  };

  bool UpdateProperty(uint32_t id,
                      uint64_t value,
                      std::vector<DrmWrapper::Property>* properties,
                      bool add_property_if_needed = false);

  bool UpdateProperty(uint32_t object_id,
                      uint32_t property_id,
                      uint64_t value,
                      bool add_property_if_needed = false);

  bool ValidatePropertyValue(uint32_t id, uint64_t value);

  // Returns true iff SetPossibleValuesForEnumProperty() has been called for
  // `prop_id`.
  bool IsPropertyValueEnum(uint32_t prop_id) const;

  // Fills `property->count_enums` and `property->enums` with the possible enum
  // values for the property (provided to a previous call to
  // SetPossibleValuesForEnumProperty()). It's assumed that `property->id` has
  // the correct property ID.
  void FillPossibleValuesForEnumProperty(drmModePropertyRes* property) const;

  int set_crtc_call_count_ = 0;
  int add_framebuffer_call_count_ = 0;
  int remove_framebuffer_call_count_ = 0;
  int page_flip_call_count_ = 0;
  int overlay_clear_call_count_ = 0;
  int allocate_buffer_count_ = 0;
  int test_modeset_count_ = 0;
  int commit_modeset_count_ = 0;
  int seamless_modeset_count_ = 0;
  int commit_count_ = 0;
  int set_object_property_count_ = 0;
  int set_gamma_ramp_count_ = 0;
  int last_planes_committed_count_ = 0;
  int modeset_sequence_id_ = 0;

  bool set_crtc_expectation_ = true;
  bool add_framebuffer_expectation_ = true;
  bool page_flip_expectation_ = true;
  bool create_dumb_buffer_expectation_ = true;
  bool legacy_gamma_ramp_expectation_ = false;
  bool commit_expectation_ = true;
  bool modeset_with_overlays_expectation_ = true;

  uint32_t current_framebuffer_ = 0;

  std::optional<std::string> driver_name_ = "mock";

  base::flat_map<uint32_t /*handle*/, sk_sp<SkSurface>> buffers_;

  std::map<uint32_t, uint32_t> crtc_cursor_map_;

  std::set<uint32_t> framebuffer_ids_;
  std::map<uint32_t, uint32_t> crtc_fb_;
  std::map<uint64_t, uint64_t> capabilities_;

  base::queue<PageFlipRequest::PageFlipCallback> callbacks_;

  FakeDrmState drm_state_;

  struct BlobState {
    BlobState();
    BlobState(const BlobState&);
    ~BlobState();

    // The reference count for this blob. The blob is retained and released
    // with the lifetime of the ScopedDrmPropertyBlob returned by
    // CreatePropertyBlob. It is also retained when its id is set as a value in
    // UpdateProperty and released when that value is overwritten.
    uint32_t ref_count = 0;
    std::vector<uint8_t> data;
  };
  std::map<uint32_t, BlobState> allocated_blobs_;

  // Retain or release the blob with id `id`. Returns true if the blob was found
  // and retained or released. If no blob exists with `id`, return false and
  // do nothing.
  bool RetainBlob(uint32_t id);
  bool ReleaseBlob(uint32_t id);

  // Props of the plane associated with the generated fb_id.
  base::flat_map<uint32_t /*fb_id*/, FramebufferProps> fb_props_;

  uint64_t system_watermark_limitations_ = std::numeric_limits<uint64_t>::max();
  base::flat_map<uint64_t /*modifier*/, int /*overhead*/> modifiers_overhead_;
};

}  // namespace ui

#endif  // UI_OZONE_PLATFORM_DRM_GPU_FAKE_DRM_DEVICE_H_