Commit 3dacd47d authored by Roman Stratiienko's avatar Roman Stratiienko
Browse files

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: default avatarRoman Stratiienko <roman.o.stratiienko@globallogic.com>
parent 19c162fe
Pipeline #499089 passed with stages
in 62 minutes and 50 seconds
......@@ -86,7 +86,7 @@ bool Backend::IsClientLayer(HwcDisplay *display, HwcLayer *layer) {
!BufferInfoGetter::GetInstance()->IsHandleUsable(layer->GetBuffer()) ||
display->color_transform_hint() != HAL_COLOR_TRANSFORM_IDENTITY ||
(layer->RequireScalingOrPhasing() &&
display->resource_manager()->ForcedScalingWithGpu());
display->GetHwc2()->GetResMan().ForcedScalingWithGpu());
}
bool Backend::HardwareSupportsLayerType(HWC2::Composition comp_type) {
......
......@@ -22,12 +22,8 @@
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <algorithm>
#include <array>
#include <cerrno>
#include <cinttypes>
#include <cstdint>
#include <sstream>
#include <string>
#include "compositor/DrmDisplayCompositor.h"
......@@ -41,26 +37,25 @@ DrmDevice::DrmDevice() {
drm_fb_importer_ = std::make_unique<DrmFbImporter>(*this);
}
// NOLINTNEXTLINE (readability-function-cognitive-complexity): Fixme
std::tuple<int, int> DrmDevice::Init(const char *path, int num_displays) {
auto DrmDevice::Init(const char *path) -> int {
/* TODO: Use drmOpenControl here instead */
fd_ = UniqueFd(open(path, O_RDWR | O_CLOEXEC));
if (!fd_) {
// NOLINTNEXTLINE(concurrency-mt-unsafe): Fixme
ALOGE("Failed to open dri %s: %s", path, strerror(errno));
return std::make_tuple(-ENODEV, 0);
return -ENODEV;
}
int ret = drmSetClientCap(GetFd(), DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
if (ret != 0) {
ALOGE("Failed to set universal plane cap %d", ret);
return std::make_tuple(ret, 0);
return ret;
}
ret = drmSetClientCap(GetFd(), DRM_CLIENT_CAP_ATOMIC, 1);
if (ret != 0) {
ALOGE("Failed to set atomic cap %d", ret);
return std::make_tuple(ret, 0);
return ret;
}
#ifdef DRM_CLIENT_CAP_WRITEBACK_CONNECTORS
......@@ -80,13 +75,13 @@ std::tuple<int, int> DrmDevice::Init(const char *path, int num_displays) {
drmSetMaster(GetFd());
if (drmIsMaster(GetFd()) == 0) {
ALOGE("DRM/KMS master access required");
return std::make_tuple(-EACCES, 0);
return -EACCES;
}
auto res = MakeDrmModeResUnique(GetFd());
if (!res) {
ALOGE("Failed to get DrmDevice resources");
return std::make_tuple(-ENODEV, 0);
return -ENODEV;
}
min_resolution_ = std::pair<uint32_t, uint32_t>(res->min_width,
......@@ -128,7 +123,7 @@ std::tuple<int, int> DrmDevice::Init(const char *path, int num_displays) {
auto plane_res = MakeDrmModePlaneResUnique(GetFd());
if (!plane_res) {
ALOGE("Failed to get plane resources");
return std::make_tuple(-ENOENT, 0);
return -ENOENT;
}
for (uint32_t i = 0; i < plane_res->count_planes; ++i) {
......@@ -140,42 +135,7 @@ std::tuple<int, int> DrmDevice::Init(const char *path, int num_displays) {
}
}
auto add_displays = [this, &num_displays](bool internal, bool connected) {
for (auto &conn : connectors_) {
bool is_connected = conn->IsConnected();
if ((internal ? conn->IsInternal() : conn->IsExternal()) &&
(connected ? is_connected : !is_connected)) {
auto pipe = DrmDisplayPipeline::CreatePipeline(*conn);
if (pipe) {
pipelines_[num_displays] = std::move(pipe);
++num_displays;
}
}
}
};
/* Put internal first to ensure Primary display will be internal
* in case at least 1 internal is available
*/
add_displays(/*internal = */ true, /*connected = */ true);
add_displays(/*internal = */ false, /*connected = */ true);
add_displays(/*internal = */ true, /*connected = */ false);
add_displays(/*internal = */ false, /*connected = */ false);
return std::make_tuple(0, pipelines_.size());
}
bool DrmDevice::HandlesDisplay(int display) const {
return pipelines_.count(display) != 0;
}
auto DrmDevice::GetDisplayId(DrmConnector *conn) -> int {
for (auto &dpipe : pipelines_) {
if (dpipe.second->connector->Get() == conn) {
return dpipe.first;
}
}
return -1;
return 0;
}
auto DrmDevice::RegisterUserPropertyBlob(void *data, size_t length) const
......
......@@ -37,7 +37,7 @@ class DrmDevice {
DrmDevice();
~DrmDevice() = default;
std::tuple<int, int> Init(const char *path, int num_displays);
auto Init(const char *path) -> int;
auto GetFd() const {
return fd_.Get();
......@@ -56,19 +56,12 @@ class DrmDevice {
return max_resolution_;
}
auto *GetPipelineForDisplay(int display) {
return pipelines_.count(display) != 0 ? pipelines_.at(display).get()
: nullptr;
}
std::string GetName() const;
auto RegisterUserPropertyBlob(void *data, size_t length) const
-> DrmModeUserPropertyBlobUnique;
bool HandlesDisplay(int display) const;
bool HasAddFb2ModifiersSupport() const {
auto HasAddFb2ModifiersSupport() const {
return HasAddFb2ModifiersSupport_;
}
......@@ -98,8 +91,6 @@ class DrmDevice {
return nullptr;
}
auto GetDisplayId(DrmConnector *conn) -> int;
int GetProperty(uint32_t obj_id, uint32_t obj_type, const char *prop_name,
DrmProperty *property) const;
......@@ -115,8 +106,6 @@ class DrmDevice {
std::pair<uint32_t, uint32_t> min_resolution_;
std::pair<uint32_t, uint32_t> max_resolution_;
std::map<int /*display*/, std::unique_ptr<DrmDisplayPipeline>> pipelines_;
bool HasAddFb2ModifiersSupport_{};
std::unique_ptr<DrmFbImporter> drm_fb_importer_;
......
......@@ -33,25 +33,34 @@
namespace android {
ResourceManager::ResourceManager() : num_displays_(0) {
ResourceManager::ResourceManager(
PipelineToFrontendBindingInterface *p2f_bind_interface)
: frontend_interface_(p2f_bind_interface) {
if (uevent_listener_.Init() != 0) {
ALOGE("Can't initialize event listener");
}
}
ResourceManager::~ResourceManager() {
uevent_listener_.Exit();
}
int ResourceManager::Init() {
void ResourceManager::Init() {
if (initialized_) {
ALOGE("Already initialized");
return;
}
char path_pattern[PROPERTY_VALUE_MAX];
// Could be a valid path or it can have at the end of it the wildcard %
// which means that it will try open all devices until an error is met.
int path_len = property_get("vendor.hwc.drm.device", path_pattern,
"/dev/dri/card%");
int ret = 0;
if (path_pattern[path_len - 1] != '%') {
ret = AddDrmDevice(std::string(path_pattern));
AddDrmDevice(std::string(path_pattern));
} else {
path_pattern[path_len - 1] = '\0';
for (int idx = 0; ret == 0; ++idx) {
for (int idx = 0;; ++idx) {
std::ostringstream path;
path << path_pattern << idx;
......@@ -59,51 +68,109 @@ int ResourceManager::Init() {
if (stat(path.str().c_str(), &buf) != 0)
break;
if (DrmDevice::IsKMSDev(path.str().c_str()))
ret = AddDrmDevice(path.str());
if (DrmDevice::IsKMSDev(path.str().c_str())) {
AddDrmDevice(path.str());
}
}
}
if (num_displays_ == 0) {
ALOGE("Failed to initialize any displays");
return ret != 0 ? -EINVAL : ret;
}
char scale_with_gpu[PROPERTY_VALUE_MAX];
property_get("vendor.hwc.drm.scale_with_gpu", scale_with_gpu, "0");
scale_with_gpu_ = bool(strncmp(scale_with_gpu, "0", 1));
if (BufferInfoGetter::GetInstance() == nullptr) {
ALOGE("Failed to initialize BufferInfoGetter");
return -EINVAL;
return;
}
ret = uevent_listener_.Init();
if (ret != 0) {
ALOGE("Can't initialize event listener %d", ret);
return ret;
uevent_listener_.RegisterHotplugHandler([this] {
const std::lock_guard<std::mutex> lock(GetMainLock());
UpdateFrontendDisplays();
});
UpdateFrontendDisplays();
initialized_ = true;
}
void ResourceManager::DeInit() {
if (!initialized_) {
ALOGE("Not initialized");
return;
}
return 0;
uevent_listener_.RegisterHotplugHandler([] {});
DetachAllFrontendDisplays();
drms_.clear();
initialized_ = false;
}
int ResourceManager::AddDrmDevice(const std::string &path) {
auto drm = std::make_unique<DrmDevice>();
int displays_added = 0;
int ret = 0;
std::tie(ret, displays_added) = drm->Init(path.c_str(), num_displays_);
int ret = drm->Init(path.c_str());
drms_.push_back(std::move(drm));
num_displays_ += displays_added;
return ret;
}
DrmDisplayPipeline *ResourceManager::GetPipeline(int display) {
void ResourceManager::UpdateFrontendDisplays() {
auto ordered_connectors = GetOrderedConnectors();
for (auto *conn : ordered_connectors) {
conn->UpdateModes();
bool connected = conn->IsConnected();
bool attached = attached_pipelines_.count(conn) != 0;
if (connected != attached) {
ALOGI("%s connector %s", connected ? "Attaching" : "Detaching",
conn->GetName().c_str());
if (connected) {
auto pipeline = DrmDisplayPipeline::CreatePipeline(*conn);
frontend_interface_->BindDisplay(pipeline.get());
attached_pipelines_[conn] = std::move(pipeline);
} else {
auto &pipeline = attached_pipelines_[conn];
frontend_interface_->UnbindDisplay(pipeline.get());
attached_pipelines_.erase(conn);
}
}
}
frontend_interface_->FinalizeDisplayBinding();
}
void ResourceManager::DetachAllFrontendDisplays() {
for (auto &p : attached_pipelines_) {
frontend_interface_->UnbindDisplay(p.second.get());
}
attached_pipelines_.clear();
frontend_interface_->FinalizeDisplayBinding();
}
auto ResourceManager::GetOrderedConnectors() -> std::vector<DrmConnector *> {
/* Put internal displays first then external to
* ensure Internal will take Primary slot
*/
std::vector<DrmConnector *> ordered_connectors;
for (auto &drm : drms_) {
for (const auto &conn : drm->GetConnectors()) {
if (conn->IsInternal()) {
ordered_connectors.emplace_back(conn.get());
}
}
}
for (auto &drm : drms_) {
auto *pipe = drm->GetPipelineForDisplay(display);
if (pipe != nullptr) {
return pipe;
for (const auto &conn : drm->GetConnectors()) {
if (conn->IsExternal()) {
ordered_connectors.emplace_back(conn.get());
}
}
}
return nullptr;
return ordered_connectors;
}
} // namespace android
......@@ -20,42 +20,48 @@
#include <cstring>
#include "DrmDevice.h"
#include "DrmDisplayPipeline.h"
#include "DrmFbImporter.h"
#include "UEventListener.h"
namespace android {
class PipelineToFrontendBindingInterface {
public:
virtual ~PipelineToFrontendBindingInterface() = default;
virtual bool BindDisplay(DrmDisplayPipeline *);
virtual bool UnbindDisplay(DrmDisplayPipeline *);
virtual void FinalizeDisplayBinding();
};
class ResourceManager {
public:
ResourceManager();
explicit ResourceManager(
PipelineToFrontendBindingInterface *p2f_bind_interface);
ResourceManager(const ResourceManager &) = delete;
ResourceManager &operator=(const ResourceManager &) = delete;
ResourceManager(const ResourceManager &&) = delete;
ResourceManager &&operator=(const ResourceManager &&) = delete;
~ResourceManager();
int Init();
auto GetPipeline(int display) -> DrmDisplayPipeline *;
auto &GetDrmDevices() const {
return drms_;
}
int GetDisplayCount() const {
return num_displays_;
}
void Init();
void DeInit();
bool ForcedScalingWithGpu() const {
return scale_with_gpu_;
}
UEventListener *GetUEventListener() {
return &uevent_listener_;
}
auto &GetMainLock() {
return main_lock_;
}
private:
int AddDrmDevice(std::string const &path);
auto AddDrmDevice(std::string const &path) -> int;
auto GetOrderedConnectors() -> std::vector<DrmConnector *>;
void UpdateFrontendDisplays();
void DetachAllFrontendDisplays();
int num_displays_;
std::vector<std::unique_ptr<DrmDevice>> drms_;
bool scale_with_gpu_{};
......@@ -63,6 +69,13 @@ class ResourceManager {
UEventListener uevent_listener_;
std::mutex main_lock_;
std::map<DrmConnector *, std::unique_ptr<DrmDisplayPipeline>>
attached_pipelines_;
PipelineToFrontendBindingInterface *const frontend_interface_;
bool initialized_{};
};
} // namespace android
......
......@@ -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){};
/* 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
*/
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;
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...");
}
}
}
displays_.emplace(std::piecewise_construct, std::forward_as_tuple(displ),
std::forward_as_tuple(&resource_manager_, pipe, displ, type,
this));
displays_.at(displ).Init();
return HWC2::Error::None;
// Finally, send hotplug events to the client
for (auto &dhe : deferred_hotplug_events_) {
SendHotplugEventToClient(dhe.first, dhe.second);
}
deferred_hotplug_events_.clear();
}
HWC2::Error DrmHwcTwo::Init() {
int rv = resource_manager_.Init();
if (rv) {
ALOGE("Can't initialize the resource manager %d", rv);
return HWC2::Error::NoResources;
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;
}
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;
}
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);
}
resource_manager_.GetUEventListener()->RegisterHotplugHandler([this] {
const std::lock_guard<std::mutex> lock(GetResMan().GetMainLock());
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];
HandleHotplugUEvent();
});
ALOGI("Detaching the pipeline '%s' from the display #%i%s",
pipeline->connector->Get()->GetName().c_str(), (int)handle,
handle == kPrimaryDisplay ? " (Primary)" : "");
return ret;
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 @@ void DrmHwcTwo::Dump(uint32_t *outSize, char *outBuffer) {
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 @@ HWC2::Error DrmHwcTwo::RegisterCallback(int32_t descriptor,
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 @@ HWC2::Error DrmHwcTwo::RegisterCallback(int32_t descriptor,
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 @@ void DrmHwcTwo::HandleDisplayHotplug(hwc2_display_t displayid, int state) {
*/
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();