drm_hwcomposer: Dynamic DrmDisplayPipeline to HwcDisplay bindings
The following use scenarios are now possible:
1. When no display connected, primary HwcDisplay is created in headless
mode.
2. When user connects first display, it binds to primary slot, replacing
headless HwcDisplay.
3. When user connects another display it binds to the new HwcDisplay
slot, creating new display for the framework.
4. When user disconnects first (Primary) display, drm_hwc detaches
second display and attaches it to the Primary slot. In this case
framework is notified as Primary display resolution updated
(Plugged->Plugged transition). And second display is disconnected
(Plugged->Unplugged transition).
DrmDisplayPipeline is now created on demand (after hotplug event).
HwcDisplay class is now destructed on connector unplug, which will give
us ability to destroy any resource caches (will be required for FB
caching logic).
Signed-off-by: Roman Stratiienko <roman.o.stratiienko@globallogic.com>
diff --git a/hwc2_device/DrmHwcTwo.cpp b/hwc2_device/DrmHwcTwo.cpp
index 4cb0fc3..2002b85 100644
--- a/hwc2_device/DrmHwcTwo.cpp
+++ b/hwc2_device/DrmHwcTwo.cpp
@@ -18,51 +18,96 @@
#include "DrmHwcTwo.h"
+#include <cinttypes>
+
#include "backend/Backend.h"
#include "utils/log.h"
namespace android {
-DrmHwcTwo::DrmHwcTwo() = default;
+DrmHwcTwo::DrmHwcTwo() : resource_manager_(this){};
-HWC2::Error DrmHwcTwo::CreateDisplay(hwc2_display_t displ,
- HWC2::DisplayType type) {
- auto *pipe = resource_manager_.GetPipeline(static_cast<int>(displ));
- if (!pipe) {
- ALOGE("Failed to get a valid drmresource");
- return HWC2::Error::NoResources;
- }
- displays_.emplace(std::piecewise_construct, std::forward_as_tuple(displ),
- std::forward_as_tuple(&resource_manager_, pipe, displ, type,
- this));
+/* Must be called after every display attach/detach cycle */
+void DrmHwcTwo::FinalizeDisplayBinding() {
+ if (displays_.count(kPrimaryDisplay) == 0) {
+ /* Create/update new headless display if no other displays exists
+ * or reattach different display to make it primary
+ */
- displays_.at(displ).Init();
- return HWC2::Error::None;
-}
-
-HWC2::Error DrmHwcTwo::Init() {
- int rv = resource_manager_.Init();
- if (rv) {
- ALOGE("Can't initialize the resource manager %d", rv);
- return HWC2::Error::NoResources;
- }
-
- HWC2::Error ret = HWC2::Error::None;
- for (int i = 0; i < resource_manager_.GetDisplayCount(); i++) {
- ret = CreateDisplay(i, HWC2::DisplayType::Physical);
- if (ret != HWC2::Error::None) {
- ALOGE("Failed to create display %d with error %d", i, ret);
- return ret;
+ if (display_handles_.empty()) {
+ /* Enable headless mode */
+ ALOGI("No pipelines available. Creating null-display for headless mode");
+ displays_[kPrimaryDisplay] = std::make_unique<
+ HwcDisplay>(nullptr, kPrimaryDisplay, HWC2::DisplayType::Physical,
+ this);
+ } else {
+ auto *pipe = display_handles_.begin()->first;
+ ALOGI("Primary display was disconnected, reattaching '%s' as new primary",
+ pipe->connector->Get()->GetName().c_str());
+ UnbindDisplay(pipe);
+ BindDisplay(pipe);
+ if (displays_.count(kPrimaryDisplay) == 0) {
+ ALOGE("FIXME!!! Still no primary display after reattaching...");
+ }
}
}
- resource_manager_.GetUEventListener()->RegisterHotplugHandler([this] {
- const std::lock_guard<std::mutex> lock(GetResMan().GetMainLock());
+ // Finally, send hotplug events to the client
+ for (auto &dhe : deferred_hotplug_events_) {
+ SendHotplugEventToClient(dhe.first, dhe.second);
+ }
+ deferred_hotplug_events_.clear();
+}
- HandleHotplugUEvent();
- });
+bool DrmHwcTwo::BindDisplay(DrmDisplayPipeline *pipeline) {
+ if (display_handles_.count(pipeline) != 0) {
+ ALOGE("%s, pipeline is already used by another display, FIXME!!!: %p",
+ __func__, pipeline);
+ return false;
+ }
- return ret;
+ uint32_t disp_handle = kPrimaryDisplay;
+
+ if (displays_.count(kPrimaryDisplay) != 0 &&
+ !displays_[kPrimaryDisplay]->IsInHeadlessMode()) {
+ disp_handle = ++last_display_handle_;
+ }
+
+ auto disp = std::make_unique<HwcDisplay>(pipeline, disp_handle,
+ HWC2::DisplayType::Physical, this);
+
+ if (disp_handle == kPrimaryDisplay) {
+ displays_.erase(disp_handle);
+ }
+
+ ALOGI("Attaching pipeline '%s' to the display #%d%s",
+ pipeline->connector->Get()->GetName().c_str(), (int)disp_handle,
+ disp_handle == kPrimaryDisplay ? " (Primary)" : "");
+
+ displays_[disp_handle] = std::move(disp);
+ display_handles_[pipeline] = disp_handle;
+
+ return true;
+}
+
+bool DrmHwcTwo::UnbindDisplay(DrmDisplayPipeline *pipeline) {
+ if (display_handles_.count(pipeline) == 0) {
+ ALOGE("%s, can't find the display, pipeline: %p", __func__, pipeline);
+ return false;
+ }
+ auto handle = display_handles_[pipeline];
+
+ ALOGI("Detaching the pipeline '%s' from the display #%i%s",
+ pipeline->connector->Get()->GetName().c_str(), (int)handle,
+ handle == kPrimaryDisplay ? " (Primary)" : "");
+
+ display_handles_.erase(pipeline);
+ if (displays_.count(handle) == 0) {
+ ALOGE("%s, can't find the display, handle: %" PRIu64, __func__, handle);
+ return false;
+ }
+ displays_.erase(handle);
+ return true;
}
HWC2::Error DrmHwcTwo::CreateVirtualDisplay(uint32_t /*width*/,
@@ -89,8 +134,8 @@
output << "-- drm_hwcomposer --\n\n";
- for (std::pair<const hwc2_display_t, HwcDisplay> &dp : displays_)
- output << dp.second.Dump();
+ for (auto &disp : displays_)
+ output << disp.second->Dump();
mDumpString = output.str();
*outSize = static_cast<uint32_t>(mDumpString.size());
@@ -107,9 +152,13 @@
switch (static_cast<HWC2::Callback>(descriptor)) {
case HWC2::Callback::Hotplug: {
hotplug_callback_ = std::make_pair(HWC2_PFN_HOTPLUG(function), data);
- const auto &drm_devices = resource_manager_.GetDrmDevices();
- for (const auto &device : drm_devices)
- HandleInitialHotplugState(device.get());
+ if (function != nullptr) {
+ resource_manager_.Init();
+ } else {
+ resource_manager_.DeInit();
+ /* Headless display may still be here, remove it */
+ displays_.erase(kPrimaryDisplay);
+ }
break;
}
case HWC2::Callback::Refresh: {
@@ -132,7 +181,8 @@
return HWC2::Error::None;
}
-void DrmHwcTwo::HandleDisplayHotplug(hwc2_display_t displayid, int state) {
+void DrmHwcTwo::SendHotplugEventToClient(hwc2_display_t displayid,
+ bool connected) {
auto &mutex = GetResMan().GetMainLock();
if (mutex.try_lock()) {
ALOGE("FIXME!!!: Main mutex must be locked in %s", __func__);
@@ -147,50 +197,10 @@
*/
mutex.unlock();
hc.first(hc.second, displayid,
- state == DRM_MODE_CONNECTED ? HWC2_CONNECTION_CONNECTED
- : HWC2_CONNECTION_DISCONNECTED);
+ connected == DRM_MODE_CONNECTED ? HWC2_CONNECTION_CONNECTED
+ : HWC2_CONNECTION_DISCONNECTED);
mutex.lock();
}
}
-void DrmHwcTwo::HandleInitialHotplugState(DrmDevice *drmDevice) {
- for (const auto &conn : drmDevice->GetConnectors()) {
- int display_id = drmDevice->GetDisplayId(conn.get());
- auto &display = displays_.at(display_id);
-
- if (!conn->IsConnected() && !display.IsInHeadlessMode())
- continue;
- HandleDisplayHotplug(display_id, display.IsInHeadlessMode()
- ? 1
- : (conn->IsConnected() ? 1 : 0));
- }
-}
-
-void DrmHwcTwo::HandleHotplugUEvent() {
- for (const auto &drm : resource_manager_.GetDrmDevices()) {
- for (const auto &conn : drm->GetConnectors()) {
- int display_id = drm->GetDisplayId(conn.get());
-
- bool old_state = conn->IsConnected();
- bool cur_state = conn->UpdateModes() ? false : conn->IsConnected();
- if (cur_state == old_state)
- continue;
-
- ALOGI("%s event for connector %u on display %d",
- cur_state == DRM_MODE_CONNECTED ? "Plug" : "Unplug", conn->GetId(),
- display_id);
-
- auto &display = displays_.at(display_id);
- display.ChosePreferredConfig();
- if (cur_state) {
- display.ClearDisplay();
- }
-
- HandleDisplayHotplug(display_id, display.IsInHeadlessMode()
- ? DRM_MODE_CONNECTED
- : (cur_state ? 1 : 0));
- }
- }
-}
-
} // namespace android
diff --git a/hwc2_device/DrmHwcTwo.h b/hwc2_device/DrmHwcTwo.h
index fa13170..83fbd79 100644
--- a/hwc2_device/DrmHwcTwo.h
+++ b/hwc2_device/DrmHwcTwo.h
@@ -24,11 +24,10 @@
namespace android {
-class DrmHwcTwo {
+class DrmHwcTwo : public PipelineToFrontendBindingInterface {
public:
DrmHwcTwo();
-
- HWC2::Error Init();
+ ~DrmHwcTwo() override = default;
std::pair<HWC2_PFN_HOTPLUG, hwc2_callback_data_t> hotplug_callback_{};
std::pair<HWC2_PFN_VSYNC, hwc2_callback_data_t> vsync_callback_{};
@@ -45,27 +44,38 @@
uint32_t GetMaxVirtualDisplayCount();
HWC2::Error RegisterCallback(int32_t descriptor, hwc2_callback_data_t data,
hwc2_function_pointer_t function);
- HWC2::Error CreateDisplay(hwc2_display_t displ, HWC2::DisplayType type);
auto GetDisplay(hwc2_display_t display_handle) {
- return displays_.count(display_handle) != 0 ? &displays_.at(display_handle)
- : nullptr;
+ return displays_.count(display_handle) != 0
+ ? displays_[display_handle].get()
+ : nullptr;
}
auto &GetResMan() {
return resource_manager_;
}
- private:
- void HandleDisplayHotplug(hwc2_display_t displayid, int state);
- void HandleInitialHotplugState(DrmDevice *drmDevice);
+ void ScheduleHotplugEvent(hwc2_display_t displayid, bool connected) {
+ deferred_hotplug_events_[displayid] = connected;
+ }
- void HandleHotplugUEvent();
+ // PipelineToFrontendBindingInterface
+ bool BindDisplay(DrmDisplayPipeline *pipeline) override;
+ bool UnbindDisplay(DrmDisplayPipeline *pipeline) override;
+ void FinalizeDisplayBinding() override;
+
+ private:
+ void SendHotplugEventToClient(hwc2_display_t displayid, bool connected);
ResourceManager resource_manager_;
- std::map<hwc2_display_t, HwcDisplay> displays_;
+ std::map<hwc2_display_t, std::unique_ptr<HwcDisplay>> displays_;
+ std::map<DrmDisplayPipeline *, hwc2_display_t> display_handles_;
std::string mDumpString;
+
+ std::map<hwc2_display_t, bool> deferred_hotplug_events_;
+
+ uint32_t last_display_handle_ = kPrimaryDisplay;
};
} // namespace android
diff --git a/hwc2_device/HwcDisplay.cpp b/hwc2_device/HwcDisplay.cpp
index ac5d196..7ca4549 100644
--- a/hwc2_device/HwcDisplay.cpp
+++ b/hwc2_device/HwcDisplay.cpp
@@ -27,6 +27,9 @@
namespace android {
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+uint32_t HwcDisplay::layer_idx_ = 2; /* Start from 2. See destroyLayer() */
+
std::string HwcDisplay::DumpDelta(HwcDisplay::Stats delta) {
if (delta.total_pixops_ == 0)
return "No stats yet";
@@ -84,11 +87,9 @@
return ss.str();
}
-HwcDisplay::HwcDisplay(ResourceManager *resource_manager,
- DrmDisplayPipeline *pipeline, hwc2_display_t handle,
+HwcDisplay::HwcDisplay(DrmDisplayPipeline *pipeline, hwc2_display_t handle,
HWC2::DisplayType type, DrmHwcTwo *hwc2)
: hwc2_(hwc2),
- resource_manager_(resource_manager),
pipeline_(pipeline),
handle_(handle),
type_(type),
@@ -99,6 +100,26 @@
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0};
// clang-format on
+
+ ChosePreferredConfig();
+ Init();
+
+ hwc2_->ScheduleHotplugEvent(handle_, /*connected = */ true);
+}
+
+HwcDisplay::~HwcDisplay() {
+ if (handle_ != kPrimaryDisplay) {
+ hwc2_->ScheduleHotplugEvent(handle_, /*connected = */ false);
+ }
+
+ auto &main_lock = hwc2_->GetResMan().GetMainLock();
+ /* Unlock to allow pending vsync callbacks to finish */
+ main_lock.unlock();
+ flattening_vsync_worker_.VSyncControl(false);
+ flattening_vsync_worker_.Exit();
+ vsync_worker_.VSyncControl(false);
+ vsync_worker_.Exit();
+ main_lock.lock();
}
void HwcDisplay::ClearDisplay() {
@@ -108,7 +129,7 @@
}
AtomicCommitArgs a_args = {.clear_active_composition = true};
- GetPipe().compositor->ExecuteAtomicCommit(a_args);
+ pipeline_->compositor->ExecuteAtomicCommit(a_args);
}
HWC2::Error HwcDisplay::Init() {
@@ -152,21 +173,29 @@
return HWC2::Error::BadDisplay;
}
- ret = BackendManager::GetInstance().SetBackendForDisplay(this);
- if (ret) {
- ALOGE("Failed to set backend for d=%d %d\n", int(handle_), ret);
- return HWC2::Error::BadDisplay;
+ if (!IsInHeadlessMode()) {
+ ret = BackendManager::GetInstance().SetBackendForDisplay(this);
+ if (ret) {
+ ALOGE("Failed to set backend for d=%d %d\n", int(handle_), ret);
+ return HWC2::Error::BadDisplay;
+ }
}
client_layer_.SetLayerBlendMode(HWC2_BLEND_MODE_PREMULTIPLIED);
- return ChosePreferredConfig();
+ return HWC2::Error::None;
}
HWC2::Error HwcDisplay::ChosePreferredConfig() {
- HWC2::Error err = configs_.Update(*GetPipe().connector->Get());
- if (!IsInHeadlessMode() && err != HWC2::Error::None)
+ HWC2::Error err{};
+ if (!IsInHeadlessMode()) {
+ err = configs_.Update(*pipeline_->connector->Get());
+ } else {
+ configs_.FillHeadless();
+ }
+ if (!IsInHeadlessMode() && err != HWC2::Error::None) {
return HWC2::Error::BadDisplay;
+ }
return SetActiveConfig(configs_.preferred_config_id);
}
@@ -185,8 +214,24 @@
}
HWC2::Error HwcDisplay::DestroyLayer(hwc2_layer_t layer) {
- if (!get_layer(layer))
+ if (!get_layer(layer)) {
+ /* Primary display don't send unplug event, instead it replaces
+ * display to headless or to another one and sends Plug event to the
+ * SF. SF can't distinguish this case from virtualized display size
+ * change case and will destroy previously used layers. If we will return
+ * BadLayer, service will print errors to the logcat.
+ *
+ * Nevertheless VTS is trying to destroy 1st layer without adding any
+ * layers prior to that, than it checks for BadLayer result. So we
+ * numbering the layers starting from 2, and use index 1 to catch VTS client
+ * to return BadLayer, making VTS pass.
+ */
+ if (layers_.empty() && layer != 1) {
+ return HWC2::Error::None;
+ }
+
return HWC2::Error::BadLayer;
+ }
layers_.erase(layer);
return HWC2::Error::None;
@@ -226,12 +271,13 @@
HWC2::Error HwcDisplay::GetClientTargetSupport(uint32_t width, uint32_t height,
int32_t /*format*/,
int32_t dataspace) {
- std::pair<uint32_t, uint32_t> min = GetPipe().device->GetMinResolution();
- std::pair<uint32_t, uint32_t> max = GetPipe().device->GetMaxResolution();
if (IsInHeadlessMode()) {
return HWC2::Error::None;
}
+ std::pair<uint32_t, uint32_t> min = pipeline_->device->GetMinResolution();
+ std::pair<uint32_t, uint32_t> max = pipeline_->device->GetMaxResolution();
+
if (width < min.first || height < min.second)
return HWC2::Error::Unsupported;
@@ -261,7 +307,7 @@
int conf = static_cast<int>(config);
if (configs_.hwc_configs.count(conf) == 0) {
- ALOGE("Could not find active mode for %d", conf);
+ ALOGE("Could not find mode #%d", conf);
return HWC2::Error::BadConfig;
}
@@ -331,7 +377,11 @@
HWC2::Error HwcDisplay::GetDisplayName(uint32_t *size, char *name) {
std::ostringstream stream;
- stream << "display-" << GetPipe().connector->Get()->GetId();
+ if (IsInHeadlessMode()) {
+ stream << "null-display";
+ } else {
+ stream << "display-" << GetPipe().connector->Get()->GetId();
+ }
std::string string = stream.str();
size_t length = string.length();
if (!name) {
@@ -745,11 +795,18 @@
HWC2::Error HwcDisplay::GetDisplayIdentificationData(uint8_t *outPort,
uint32_t *outDataSize,
uint8_t *outData) {
+ if (IsInHeadlessMode()) {
+ return HWC2::Error::None;
+ }
auto blob = GetPipe().connector->Get()->GetEdidBlob();
+ *outPort = handle_ - 1;
+
if (!blob) {
- ALOGE("Failed to get edid property value.");
- return HWC2::Error::Unsupported;
+ if (outData == nullptr) {
+ *outDataSize = 0;
+ }
+ return HWC2::Error::None;
}
if (outData) {
@@ -758,7 +815,6 @@
} else {
*outDataSize = blob->length;
}
- *outPort = GetPipe().connector->Get()->GetId();
return HWC2::Error::None;
}
diff --git a/hwc2_device/HwcDisplay.h b/hwc2_device/HwcDisplay.h
index b73404a..42b000a 100644
--- a/hwc2_device/HwcDisplay.h
+++ b/hwc2_device/HwcDisplay.h
@@ -37,10 +37,10 @@
class HwcDisplay {
public:
- HwcDisplay(ResourceManager *resource_manager, DrmDisplayPipeline *pipeline,
- hwc2_display_t handle, HWC2::DisplayType type, DrmHwcTwo *hwc2);
+ HwcDisplay(DrmDisplayPipeline *pipeline, hwc2_display_t handle,
+ HWC2::DisplayType type, DrmHwcTwo *hwc2);
HwcDisplay(const HwcDisplay &) = delete;
- HWC2::Error Init();
+ ~HwcDisplay();
HWC2::Error CreateComposition(AtomicCommitArgs &a_args);
std::vector<HwcLayer *> GetOrderLayersByZPos();
@@ -144,6 +144,10 @@
const Backend *backend() const;
void set_backend(std::unique_ptr<Backend> backend);
+ auto GetHwc2() {
+ return hwc2_;
+ }
+
std::map<hwc2_layer_t, HwcLayer> &layers() {
return layers_;
}
@@ -152,10 +156,6 @@
return *pipeline_;
}
- ResourceManager *resource_manager() const {
- return resource_manager_;
- }
-
android_color_transform_t &color_transform_hint() {
return color_transform_hint_;
}
@@ -193,8 +193,7 @@
* https://source.android.com/devices/graphics/hotplug#handling-common-scenarios
*/
bool IsInHeadlessMode() {
- return handle_ == kPrimaryDisplay &&
- !GetPipe().connector->Get()->IsConnected();
+ return !pipeline_;
}
private:
@@ -213,20 +212,22 @@
HwcDisplayConfigs configs_;
- DrmHwcTwo *hwc2_;
+ DrmHwcTwo *const hwc2_;
std::optional<DrmMode> staged_mode;
- ResourceManager *resource_manager_;
-
DrmDisplayPipeline *const pipeline_;
std::unique_ptr<Backend> backend_;
VSyncWorker vsync_worker_;
- hwc2_display_t handle_;
+
+ const hwc2_display_t handle_;
HWC2::DisplayType type_;
- uint32_t layer_idx_ = 0;
+
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+ static uint32_t layer_idx_;
+
std::map<hwc2_layer_t, HwcLayer> layers_;
HwcLayer client_layer_;
int32_t color_mode_{};
@@ -237,6 +238,8 @@
Stats total_stats_;
Stats prev_stats_;
std::string DumpDelta(HwcDisplay::Stats delta);
+
+ HWC2::Error Init();
};
} // namespace android
diff --git a/hwc2_device/HwcDisplayConfigs.cpp b/hwc2_device/HwcDisplayConfigs.cpp
index f6ccdb7..c28ec9f 100644
--- a/hwc2_device/HwcDisplayConfigs.cpp
+++ b/hwc2_device/HwcDisplayConfigs.cpp
@@ -31,10 +31,10 @@
namespace android {
-// NOLINTNEXTLINE (readability-function-cognitive-complexity): Fixme
-HWC2::Error HwcDisplayConfigs::Update(DrmConnector &connector) {
- /* In case UpdateModes will fail we will still have one mode for headless
- * mode*/
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+int HwcDisplayConfigs::last_config_id = 1;
+
+void HwcDisplayConfigs::FillHeadless() {
hwc_configs.clear();
last_config_id++;
@@ -53,7 +53,13 @@
mm_width = kHeadlessModeDisplayWidthMm;
mm_height = kHeadlessModeDisplayHeightMm;
+}
+// NOLINTNEXTLINE (readability-function-cognitive-complexity): Fixme
+HWC2::Error HwcDisplayConfigs::Update(DrmConnector &connector) {
+ /* In case UpdateModes will fail we will still have one mode for headless
+ * mode*/
+ FillHeadless();
/* Read real configs */
int ret = connector.UpdateModes();
if (ret != 0) {
@@ -190,8 +196,6 @@
}
}
- /* Set active mode to be valid mode */
- active_config_id = preferred_config_id;
return HWC2::Error::None;
}
diff --git a/hwc2_device/HwcDisplayConfigs.h b/hwc2_device/HwcDisplayConfigs.h
index 5bcf696..75852a6 100644
--- a/hwc2_device/HwcDisplayConfigs.h
+++ b/hwc2_device/HwcDisplayConfigs.h
@@ -40,13 +40,15 @@
struct HwcDisplayConfigs {
HWC2::Error Update(DrmConnector &conn);
+ void FillHeadless();
std::map<int /*config_id*/, struct HwcDisplayConfig> hwc_configs;
int active_config_id = 0;
int preferred_config_id = 0;
- int last_config_id = 1;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+ static int last_config_id;
uint32_t mm_width = 0;
uint32_t mm_height = 0;
diff --git a/hwc2_device/hwc2_device.cpp b/hwc2_device/hwc2_device.cpp
index 57bc205..a6dedb4 100644
--- a/hwc2_device/hwc2_device.cpp
+++ b/hwc2_device/hwc2_device.cpp
@@ -390,12 +390,6 @@
ctx->getCapabilities = HookDevGetCapabilities;
ctx->getFunction = HookDevGetFunction;
- HWC2::Error err = ctx->drmhwctwo.Init();
- if (err != HWC2::Error::None) {
- ALOGE("Failed to initialize DrmHwcTwo err=%d\n", err);
- return -EINVAL;
- }
-
*dev = &ctx.release()->common;
return 0;