// 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.
#include "ui/ozone/platform/drm/ozone_platform_drm.h"
#include <gbm.h>
#include <stdlib.h>
#include <xf86drm.h>
#include <memory>
#include <utility>
#include "base/check.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/platform_thread.h"
#include "build/chromeos_buildflags.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "ui/base/buildflags.h"
#include "ui/base/cursor/cursor_factory.h"
#include "ui/events/ozone/device/device_manager.h"
#include "ui/events/ozone/evdev/event_factory_evdev.h"
#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
#include "ui/gfx/linux/client_native_pixmap_dmabuf.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/ozone/common/bitmap_cursor_factory.h"
#include "ui/ozone/platform/drm/common/drm_util.h"
#include "ui/ozone/platform/drm/gpu/drm_device_generator.h"
#include "ui/ozone/platform/drm/gpu/drm_device_manager.h"
#include "ui/ozone/platform/drm/gpu/drm_framebuffer.h"
#include "ui/ozone/platform/drm/gpu/drm_gpu_display_manager.h"
#include "ui/ozone/platform/drm/gpu/drm_overlay_manager_gpu.h"
#include "ui/ozone/platform/drm/gpu/drm_thread_proxy.h"
#include "ui/ozone/platform/drm/gpu/gbm_surface_factory.h"
#include "ui/ozone/platform/drm/gpu/proxy_helpers.h"
#include "ui/ozone/platform/drm/host/drm_cursor.h"
#include "ui/ozone/platform/drm/host/drm_device_connector.h"
#include "ui/ozone/platform/drm/host/drm_display_host_manager.h"
#include "ui/ozone/platform/drm/host/drm_native_display_delegate.h"
#include "ui/ozone/platform/drm/host/drm_window_host.h"
#include "ui/ozone/platform/drm/host/drm_window_host_manager.h"
#include "ui/ozone/platform/drm/host/host_drm_device.h"
#include "ui/ozone/platform/drm/mojom/drm_device.mojom.h"
#include "ui/ozone/public/gpu_platform_support_host.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/platform_screen.h"
#include "ui/platform_window/platform_window_init_properties.h"
#if BUILDFLAG(USE_XKBCOMMON)
#include "ui/events/ozone/layout/xkb/xkb_evdev_codes.h"
#include "ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.h"
#else
#include "ui/events/ozone/layout/stub/stub_keyboard_layout_engine.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ui/base/ime/ash/input_method_ash.h"
#else
#include "ui/base/ime/input_method_minimal.h"
#endif
namespace ui {
namespace {
class OzonePlatformDrm : public OzonePlatform {
public:
OzonePlatformDrm() = default;
~OzonePlatformDrm() override = default;
// OzonePlatform:
ui::SurfaceFactoryOzone* GetSurfaceFactoryOzone() override {
return surface_factory_.get();
}
OverlayManagerOzone* GetOverlayManager() override {
return overlay_manager_.get();
}
CursorFactory* GetCursorFactory() override { return cursor_factory_.get(); }
InputController* GetInputController() override {
return event_factory_ozone_->input_controller();
}
std::unique_ptr<PlatformScreen> CreateScreen() override {
NOTREACHED_IN_MIGRATION();
return nullptr;
}
void InitScreen(PlatformScreen* screen) override {
NOTREACHED_IN_MIGRATION();
}
GpuPlatformSupportHost* GetGpuPlatformSupportHost() override {
return drm_device_connector_.get();
}
std::unique_ptr<SystemInputInjector> CreateSystemInputInjector() override {
return event_factory_ozone_->CreateSystemInputInjector();
}
// In multi-process mode, this function must be executed in Viz as it sets up
// the callbacks needed for Mojo receivers. In single process mode, it may be
// called on any thread. It must follow one of |InitializeUI| or
// |InitializeGPU|.
void AddInterfaces(mojo::BinderMap* binders) override {
if (single_process()) {
// This logic in multi-process mode causes deadlock to happen, where
// |gpu_task_runner_| blocks on drm_thread while drm_thread has not
// received DrmDevice mojo endpoint. Hence, the caller should invoke this
// method after drm_thread is started.
binders->Add<ozone::mojom::DrmDevice>(
base::BindRepeating(
&OzonePlatformDrm::CreateDrmDeviceReceiverOnGpuThread,
weak_factory_.GetWeakPtr()),
gpu_task_runner_);
} else {
// In multi-process mode DRM thread is started right after sandbox entry,
// |AddInterfaces| is invoked from VizMainImpl so DRM thread must have
// been started. |WaitUntilDrmThreadStarted| is not expected to do a real
// wait but helps assuming that the task runner exists.
drm_thread_proxy_->WaitUntilDrmThreadStarted();
// There's no need for binder callback to bounce on |gpu_task_runner_|.
// Binder callbacks should directly run on DRM thread.
binders->Add<ozone::mojom::DrmDevice>(
base::BindRepeating(
&OzonePlatformDrm::CreateDrmDeviceReceiverOnDrmThread,
base::Unretained(this)),
drm_thread_proxy_->GetDrmThreadTaskRunner());
}
}
// Runs on the gpu thread. But the endpoint is always bound on the DRM thread.
void CreateDrmDeviceReceiverOnGpuThread(
mojo::PendingReceiver<ozone::mojom::DrmDevice> receiver) {
CHECK(single_process());
if (drm_thread_started_)
drm_thread_proxy_->AddDrmDeviceReceiver(std::move(receiver));
else
pending_gpu_adapter_receivers_.push_back(std::move(receiver));
}
void CreateDrmDeviceReceiverOnDrmThread(
mojo::PendingReceiver<ozone::mojom::DrmDevice> receiver) {
CHECK(!single_process());
drm_thread_proxy_->AddDrmDeviceReceiver(std::move(receiver));
}
// Runs on the thread that invoked |AddInterfaces| to drain the queue of
// receiver requests that could not be satisfied until the DRM thread is
// available (i.e. if waiting until the sandbox has been entered.)
void DrainReceiverRequests() {
for (auto& receiver : pending_gpu_adapter_receivers_)
drm_thread_proxy_->AddDrmDeviceReceiver(std::move(receiver));
pending_gpu_adapter_receivers_.clear();
drm_thread_started_ = true;
}
std::unique_ptr<PlatformWindow> CreatePlatformWindow(
PlatformWindowDelegate* delegate,
PlatformWindowInitProperties properties) override {
GpuThreadAdapter* adapter = host_drm_device_.get();
auto platform_window = std::make_unique<DrmWindowHost>(
delegate, properties.bounds, adapter, event_factory_ozone_.get(),
cursor_.get(), window_manager_.get(), display_manager_.get());
platform_window->Initialize();
return std::move(platform_window);
}
std::unique_ptr<display::NativeDisplayDelegate> CreateNativeDisplayDelegate()
override {
return std::make_unique<DrmNativeDisplayDelegate>(display_manager_.get());
}
std::unique_ptr<InputMethod> CreateInputMethod(
ImeKeyEventDispatcher* ime_key_event_dispatcher,
gfx::AcceleratedWidget) override {
#if BUILDFLAG(IS_CHROMEOS_ASH)
return std::make_unique<ash::InputMethodAsh>(ime_key_event_dispatcher);
#else
return std::make_unique<InputMethodMinimal>(ime_key_event_dispatcher);
#endif
}
bool IsNativePixmapConfigSupported(gfx::BufferFormat format,
gfx::BufferUsage usage) const override {
return gfx::ClientNativePixmapDmaBuf::IsConfigurationSupported(format,
usage);
}
bool IsWindowCompositingSupported() const override { return true; }
bool InitializeUI(const InitParams& args) override {
// Ozone drm can operate in two modes configured at runtime.
// 1. single-process mode where host and viz components
// communicate via in-process mojo. Single-process mode can be single
// or multi-threaded.
// 2. multi-process mode where host and viz components communicate
// via mojo IPC.
host_thread_ = base::PlatformThread::CurrentRef();
device_manager_ = CreateDeviceManager();
window_manager_ = std::make_unique<DrmWindowHostManager>();
cursor_ = std::make_unique<DrmCursor>(window_manager_.get());
#if BUILDFLAG(USE_XKBCOMMON)
keyboard_layout_engine_ =
std::make_unique<XkbKeyboardLayoutEngine>(xkb_evdev_code_converter_);
#else
keyboard_layout_engine_ = std::make_unique<StubKeyboardLayoutEngine>();
#endif
KeyboardLayoutEngineManager::SetKeyboardLayoutEngine(
keyboard_layout_engine_.get());
event_factory_ozone_ = std::make_unique<EventFactoryEvdev>(
cursor_.get(), device_manager_.get(),
KeyboardLayoutEngineManager::GetKeyboardLayoutEngine());
GpuThreadAdapter* adapter;
host_drm_device_ = base::MakeRefCounted<HostDrmDevice>(cursor_.get());
drm_device_connector_ =
std::make_unique<DrmDeviceConnector>(host_drm_device_);
adapter = host_drm_device_.get();
display_manager_ = std::make_unique<DrmDisplayHostManager>(
adapter, device_manager_.get(), &host_properties_,
event_factory_ozone_->input_controller());
cursor_factory_ = std::make_unique<BitmapCursorFactory>();
host_drm_device_->SetDisplayManager(display_manager_.get());
return true;
}
void InitializeGPU(const InitParams& args) override {
gpu_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
// NOTE: Can't start the thread here since this is called before sandbox
// initialization in multi-process Chrome.
drm_thread_proxy_ = std::make_unique<DrmThreadProxy>();
surface_factory_ =
std::make_unique<GbmSurfaceFactory>(drm_thread_proxy_.get());
// Native pixmaps are always available on ozone/drm.
host_properties_.supports_native_pixmaps = true;
overlay_manager_ = std::make_unique<DrmOverlayManagerGpu>(
drm_thread_proxy_.get(), args.handle_overlays_swap_failure,
args.allow_sync_and_real_buffer_page_flip_testing);
// If gpu is in a separate process, rest of the initialization happens after
// entering the sandbox.
if (!single_process())
return;
// Note we exit this code above when running in multiple processes and so
// following code only executes in single process mode. In single process
// mode we need to make sure DrainReceiverRequest is executed on this thread
// before we start the drm device.
const bool block_for_drm_thread = true;
StartDrmThread(block_for_drm_thread);
if (host_thread_ == base::PlatformThread::CurrentRef()) {
CHECK(has_initialized_ui()) << "Mojo single-thread mode requires "
"InitializeUI to be called first.";
// Connect host and gpu here since OnGpuServiceLaunched() is not called in
// the single-threaded mode.
mojo::PendingRemote<ui::ozone::mojom::DrmDevice> drm_device;
drm_thread_proxy_->AddDrmDeviceReceiver(
drm_device.InitWithNewPipeAndPassReceiver());
drm_device_connector_->ConnectSingleThreaded(std::move(drm_device));
}
}
void PostCreateMainMessageLoop(base::OnceCallback<void()> shutdown_cb,
scoped_refptr<base::SingleThreadTaskRunner>
user_input_task_runner) override {
event_factory_ozone_->SetUserInputTaskRunner(
std::move(user_input_task_runner));
}
const PlatformRuntimeProperties& GetPlatformRuntimeProperties() override {
DCHECK(has_initialized_ui() || has_initialized_gpu());
return host_properties_;
}
// The DRM thread needs to be started late because we need to wait for the
// sandbox to start. This entry point in the Ozone API gives platforms
// flexibility in handing this requirement.
void AfterSandboxEntry() override {
DCHECK(!single_process());
CHECK(has_initialized_gpu()) << "AfterSandboxEntry before InitializeForGPU "
"is invalid startup order.";
const bool block_for_drm_thread = false;
StartDrmThread(block_for_drm_thread);
}
private:
// Starts the DRM thread. |blocking| determines if the call should be blocked
// until the thread is started.
void StartDrmThread(bool blocking) {
if (blocking) {
base::WaitableEvent done_event;
drm_thread_proxy_->StartDrmThread(base::BindOnce(
&base::WaitableEvent::Signal, base::Unretained(&done_event)));
done_event.Wait();
DrainReceiverRequests();
} else {
// OzonePlatformDrm owns |drm_thread_proxy_| which owns the DRM thread
// this callback is invoked on, using base::Unretained pointer here is
// safe.
auto safe_receiver_request_drainer = CreateSafeOnceCallback(
base::BindOnce(&OzonePlatformDrm::DrainReceiverRequests,
base::Unretained(this)));
drm_thread_proxy_->StartDrmThread(
std::move(safe_receiver_request_drainer));
}
}
// Objects in the GPU process.
std::unique_ptr<DrmThreadProxy> drm_thread_proxy_;
std::unique_ptr<GbmSurfaceFactory> surface_factory_;
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;
std::unique_ptr<DrmOverlayManager> overlay_manager_;
// TODO(rjkroege,sadrul): Provide a more elegant solution for this issue when
// running in single process mode.
std::vector<mojo::PendingReceiver<ozone::mojom::DrmDevice>>
pending_gpu_adapter_receivers_;
bool drm_thread_started_ = false;
// host_drm_device_ is the mojo bridge to the Viz process. Only one can be in
// use at any time.
// A raw pointer to |host_drm_device_| is passed to |display_manager_| in
// InitializeUI(). To avoid a use after free, the following two members should
// be declared before the two managers, so that they're deleted after them.
// Objects in the host process.
#if BUILDFLAG(USE_XKBCOMMON)
XkbEvdevCodes xkb_evdev_code_converter_;
#endif
std::unique_ptr<KeyboardLayoutEngine> keyboard_layout_engine_;
std::unique_ptr<DrmDeviceConnector> drm_device_connector_;
scoped_refptr<HostDrmDevice> host_drm_device_;
base::PlatformThreadRef host_thread_;
std::unique_ptr<DeviceManager> device_manager_;
std::unique_ptr<CursorFactory> cursor_factory_;
std::unique_ptr<DrmWindowHostManager> window_manager_;
std::unique_ptr<DrmCursor> cursor_;
std::unique_ptr<EventFactoryEvdev> event_factory_ozone_;
std::unique_ptr<DrmDisplayHostManager> display_manager_;
PlatformRuntimeProperties host_properties_;
base::WeakPtrFactory<OzonePlatformDrm> weak_factory_{this};
OzonePlatformDrm(const OzonePlatformDrm&) = delete;
OzonePlatformDrm& operator=(const OzonePlatformDrm&) = delete;
};
} // namespace
OzonePlatform* CreateOzonePlatformDrm() {
return new OzonePlatformDrm;
}
} // namespace ui