1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/ozone/platform/drm/gpu/drm_device.h"
11 #include <xf86drmMode.h>
13 #include "base/logging.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/stl_util.h"
16 #include "base/synchronization/waitable_event.h"
17 #include "base/task_runner.h"
18 #include "base/thread_task_runner_handle.h"
19 #include "base/trace_event/trace_event.h"
20 #include "third_party/skia/include/core/SkImageInfo.h"
21 #include "ui/display/types/gamma_ramp_rgb_entry.h"
22 #include "ui/ozone/platform/drm/common/drm_util.h"
23 #include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.h"
25 #if defined(USE_DRM_ATOMIC)
26 #include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.h"
33 typedef base::Callback
<void(uint32_t /* frame */,
34 uint32_t /* seconds */,
35 uint32_t /* useconds */,
36 uint64_t /* id */)> DrmEventHandler
;
38 bool DrmCreateDumbBuffer(int fd
,
39 const SkImageInfo
& info
,
42 struct drm_mode_create_dumb request
;
43 memset(&request
, 0, sizeof(request
));
44 request
.width
= info
.width();
45 request
.height
= info
.height();
46 request
.bpp
= info
.bytesPerPixel() << 3;
49 if (drmIoctl(fd
, DRM_IOCTL_MODE_CREATE_DUMB
, &request
) < 0) {
50 VPLOG(2) << "Cannot create dumb buffer";
54 // The driver may choose to align the last row as well. We don't care about
55 // the last alignment bits since they aren't used for display purposes, so
56 // just check that the expected size is <= to what the driver allocated.
57 DCHECK_LE(info
.getSafeSize(request
.pitch
), request
.size
);
59 *handle
= request
.handle
;
60 *stride
= request
.pitch
;
64 bool DrmDestroyDumbBuffer(int fd
, uint32_t handle
) {
65 struct drm_mode_destroy_dumb destroy_request
;
66 memset(&destroy_request
, 0, sizeof(destroy_request
));
67 destroy_request
.handle
= handle
;
68 return !drmIoctl(fd
, DRM_IOCTL_MODE_DESTROY_DUMB
, &destroy_request
);
71 bool ProcessDrmEvent(int fd
, const DrmEventHandler
& callback
) {
73 int len
= read(fd
, buffer
, sizeof(buffer
));
77 if (len
< static_cast<int>(sizeof(drm_event
))) {
78 PLOG(ERROR
) << "Failed to read DRM event";
84 DCHECK_LE(static_cast<int>(sizeof(drm_event
)), len
- idx
);
86 memcpy(&event
, &buffer
[idx
], sizeof(event
));
88 case DRM_EVENT_FLIP_COMPLETE
: {
89 DCHECK_LE(static_cast<int>(sizeof(drm_event_vblank
)), len
- idx
);
90 drm_event_vblank vblank
;
91 memcpy(&vblank
, &buffer
[idx
], sizeof(vblank
));
92 callback
.Run(vblank
.sequence
, vblank
.tv_sec
, vblank
.tv_usec
,
95 case DRM_EVENT_VBLANK
:
108 bool CanQueryForResources(int fd
) {
109 drm_mode_card_res resources
;
110 memset(&resources
, 0, sizeof(resources
));
111 // If there is no error getting DRM resources then assume this is a
112 // modesetting device.
113 return !drmIoctl(fd
, DRM_IOCTL_MODE_GETRESOURCES
, &resources
);
118 class DrmDevice::PageFlipManager
119 : public base::RefCountedThreadSafe
<DrmDevice::PageFlipManager
> {
121 PageFlipManager() : next_id_(0) {}
123 void OnPageFlip(uint32_t frame
,
128 std::find_if(callbacks_
.begin(), callbacks_
.end(), FindCallback(id
));
129 if (it
== callbacks_
.end()) {
130 LOG(WARNING
) << "Could not find callback for page flip id=" << id
;
134 DrmDevice::PageFlipCallback callback
= it
->callback
;
135 callbacks_
.erase(it
);
136 callback
.Run(frame
, seconds
, useconds
);
139 uint64_t GetNextId() { return next_id_
++; }
141 void RegisterCallback(uint64_t id
,
142 const DrmDevice::PageFlipCallback
& callback
) {
143 callbacks_
.push_back({id
, callback
});
147 friend class base::RefCountedThreadSafe
<DrmDevice::PageFlipManager
>;
148 ~PageFlipManager() {}
152 DrmDevice::PageFlipCallback callback
;
155 struct FindCallback
{
156 FindCallback(uint64_t id
) : id(id
) {}
158 bool operator()(const PageFlip
& flip
) const { return flip
.id
== id
; }
165 std::vector
<PageFlip
> callbacks_
;
167 DISALLOW_COPY_AND_ASSIGN(PageFlipManager
);
170 class DrmDevice::IOWatcher
171 : public base::RefCountedThreadSafe
<DrmDevice::IOWatcher
>,
172 public base::MessagePumpLibevent::Watcher
{
175 const scoped_refptr
<base::SingleThreadTaskRunner
>& io_task_runner
,
176 const scoped_refptr
<DrmDevice::PageFlipManager
>& page_flip_manager
)
177 : main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
178 io_task_runner_(io_task_runner
),
179 page_flip_manager_(page_flip_manager
),
183 void SetPaused(bool paused
) {
184 if (paused_
== paused
)
188 base::WaitableEvent
done(false, false);
189 io_task_runner_
->PostTask(
190 FROM_HERE
, base::Bind(&IOWatcher::SetPausedOnIO
, this, &done
));
196 io_task_runner_
->PostTask(FROM_HERE
,
197 base::Bind(&IOWatcher::UnregisterOnIO
, this));
201 friend class base::RefCountedThreadSafe
<IOWatcher
>;
203 ~IOWatcher() override
{}
205 void RegisterOnIO() {
206 DCHECK(base::MessageLoopForIO::IsCurrent());
207 base::MessageLoopForIO::current()->WatchFileDescriptor(
208 fd_
, true, base::MessageLoopForIO::WATCH_READ
, &controller_
, this);
211 void UnregisterOnIO() {
212 DCHECK(base::MessageLoopForIO::IsCurrent());
213 controller_
.StopWatchingFileDescriptor();
216 void SetPausedOnIO(base::WaitableEvent
* done
) {
217 DCHECK(base::MessageLoopForIO::IsCurrent());
225 void OnPageFlipOnIO(uint32_t frame
,
229 main_task_runner_
->PostTask(
231 base::Bind(&DrmDevice::PageFlipManager::OnPageFlip
, page_flip_manager_
,
232 frame
, seconds
, useconds
, id
));
235 // base::MessagePumpLibevent::Watcher overrides:
236 void OnFileCanReadWithoutBlocking(int fd
) override
{
237 DCHECK(base::MessageLoopForIO::IsCurrent());
238 TRACE_EVENT1("drm", "OnDrmEvent", "socket", fd
);
240 if (!ProcessDrmEvent(
241 fd
, base::Bind(&DrmDevice::IOWatcher::OnPageFlipOnIO
, this)))
245 void OnFileCanWriteWithoutBlocking(int fd
) override
{ NOTREACHED(); }
247 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner_
;
248 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner_
;
250 scoped_refptr
<DrmDevice::PageFlipManager
> page_flip_manager_
;
252 base::MessagePumpLibevent::FileDescriptorWatcher controller_
;
257 DISALLOW_COPY_AND_ASSIGN(IOWatcher
);
260 DrmDevice::DrmDevice(const base::FilePath
& device_path
, base::File file
)
261 : device_path_(device_path
),
263 page_flip_manager_(new PageFlipManager()) {
266 DrmDevice::~DrmDevice() {
268 watcher_
->Shutdown();
271 bool DrmDevice::Initialize(bool use_atomic
) {
272 // Ignore devices that cannot perform modesetting.
273 if (!CanQueryForResources(file_
.GetPlatformFile())) {
274 VLOG(2) << "Cannot query for resources for '" << device_path_
.value()
279 #if defined(USE_DRM_ATOMIC)
280 // Use atomic only if the build, kernel & flags all allow it.
281 if (use_atomic
&& SetCapability(DRM_CLIENT_CAP_ATOMIC
, 1))
282 plane_manager_
.reset(new HardwareDisplayPlaneManagerAtomic());
283 #endif // defined(USE_DRM_ATOMIC)
286 plane_manager_
.reset(new HardwareDisplayPlaneManagerLegacy());
287 if (!plane_manager_
->Initialize(this)) {
288 LOG(ERROR
) << "Failed to initialize the plane manager for "
289 << device_path_
.value();
290 plane_manager_
.reset();
297 void DrmDevice::InitializeTaskRunner(
298 const scoped_refptr
<base::SingleThreadTaskRunner
>& task_runner
) {
299 DCHECK(!task_runner_
);
300 task_runner_
= task_runner
;
302 new IOWatcher(file_
.GetPlatformFile(), task_runner_
, page_flip_manager_
);
305 ScopedDrmCrtcPtr
DrmDevice::GetCrtc(uint32_t crtc_id
) {
306 DCHECK(file_
.IsValid());
307 return ScopedDrmCrtcPtr(drmModeGetCrtc(file_
.GetPlatformFile(), crtc_id
));
310 bool DrmDevice::SetCrtc(uint32_t crtc_id
,
311 uint32_t framebuffer
,
312 std::vector
<uint32_t> connectors
,
313 drmModeModeInfo
* mode
) {
314 DCHECK(file_
.IsValid());
315 DCHECK(!connectors
.empty());
318 TRACE_EVENT2("drm", "DrmDevice::SetCrtc", "crtc", crtc_id
, "size",
319 gfx::Size(mode
->hdisplay
, mode
->vdisplay
).ToString());
320 return !drmModeSetCrtc(file_
.GetPlatformFile(), crtc_id
, framebuffer
, 0, 0,
321 vector_as_array(&connectors
), connectors
.size(), mode
);
324 bool DrmDevice::SetCrtc(drmModeCrtc
* crtc
, std::vector
<uint32_t> connectors
) {
325 DCHECK(file_
.IsValid());
326 // If there's no buffer then the CRTC was disabled.
327 if (!crtc
->buffer_id
)
328 return DisableCrtc(crtc
->crtc_id
);
330 DCHECK(!connectors
.empty());
332 TRACE_EVENT1("drm", "DrmDevice::RestoreCrtc", "crtc", crtc
->crtc_id
);
333 return !drmModeSetCrtc(file_
.GetPlatformFile(), crtc
->crtc_id
,
334 crtc
->buffer_id
, crtc
->x
, crtc
->y
,
335 vector_as_array(&connectors
), connectors
.size(),
339 bool DrmDevice::DisableCrtc(uint32_t crtc_id
) {
340 DCHECK(file_
.IsValid());
341 TRACE_EVENT1("drm", "DrmDevice::DisableCrtc", "crtc", crtc_id
);
342 return !drmModeSetCrtc(file_
.GetPlatformFile(), crtc_id
, 0, 0, 0, NULL
, 0,
346 ScopedDrmConnectorPtr
DrmDevice::GetConnector(uint32_t connector_id
) {
347 DCHECK(file_
.IsValid());
348 TRACE_EVENT1("drm", "DrmDevice::GetConnector", "connector", connector_id
);
349 return ScopedDrmConnectorPtr(
350 drmModeGetConnector(file_
.GetPlatformFile(), connector_id
));
353 bool DrmDevice::AddFramebuffer(uint32_t width
,
359 uint32_t* framebuffer
) {
360 DCHECK(file_
.IsValid());
361 TRACE_EVENT1("drm", "DrmDevice::AddFramebuffer", "handle", handle
);
362 return !drmModeAddFB(file_
.GetPlatformFile(), width
, height
, depth
, bpp
,
363 stride
, handle
, framebuffer
);
366 bool DrmDevice::RemoveFramebuffer(uint32_t framebuffer
) {
367 DCHECK(file_
.IsValid());
368 TRACE_EVENT1("drm", "DrmDevice::RemoveFramebuffer", "framebuffer",
370 return !drmModeRmFB(file_
.GetPlatformFile(), framebuffer
);
373 bool DrmDevice::PageFlip(uint32_t crtc_id
,
374 uint32_t framebuffer
,
376 const PageFlipCallback
& callback
) {
377 DCHECK(file_
.IsValid());
378 TRACE_EVENT2("drm", "DrmDevice::PageFlip", "crtc", crtc_id
, "framebuffer",
382 watcher_
->SetPaused(is_sync
);
384 // NOTE: Calling drmModeSetCrtc will immediately update the state, though
385 // callbacks to already scheduled page flips will be honored by the kernel.
386 uint64_t id
= page_flip_manager_
->GetNextId();
387 if (!drmModePageFlip(file_
.GetPlatformFile(), crtc_id
, framebuffer
,
388 DRM_MODE_PAGE_FLIP_EVENT
, reinterpret_cast<void*>(id
))) {
389 // If successful the payload will be removed by a PageFlip event.
390 page_flip_manager_
->RegisterCallback(id
, callback
);
392 // If the flip was requested synchronous or if no watcher has been installed
393 // yet, then synchronously handle the page flip events.
394 if (is_sync
|| !watcher_
) {
395 TRACE_EVENT1("drm", "OnDrmEvent", "socket", file_
.GetPlatformFile());
398 file_
.GetPlatformFile(),
399 base::Bind(&PageFlipManager::OnPageFlip
, page_flip_manager_
));
408 bool DrmDevice::PageFlipOverlay(uint32_t crtc_id
,
409 uint32_t framebuffer
,
410 const gfx::Rect
& location
,
411 const gfx::Rect
& source
,
413 DCHECK(file_
.IsValid());
414 TRACE_EVENT2("drm", "DrmDevice::PageFlipOverlay", "crtc", crtc_id
,
415 "framebuffer", framebuffer
);
416 return !drmModeSetPlane(file_
.GetPlatformFile(), overlay_plane
, crtc_id
,
417 framebuffer
, 0, location
.x(), location
.y(),
418 location
.width(), location
.height(), source
.x(),
419 source
.y(), source
.width(), source
.height());
422 ScopedDrmFramebufferPtr
DrmDevice::GetFramebuffer(uint32_t framebuffer
) {
423 DCHECK(file_
.IsValid());
424 TRACE_EVENT1("drm", "DrmDevice::GetFramebuffer", "framebuffer", framebuffer
);
425 return ScopedDrmFramebufferPtr(
426 drmModeGetFB(file_
.GetPlatformFile(), framebuffer
));
429 ScopedDrmPropertyPtr
DrmDevice::GetProperty(drmModeConnector
* connector
,
431 TRACE_EVENT2("drm", "DrmDevice::GetProperty", "connector",
432 connector
->connector_id
, "name", name
);
433 for (int i
= 0; i
< connector
->count_props
; ++i
) {
434 ScopedDrmPropertyPtr
property(
435 drmModeGetProperty(file_
.GetPlatformFile(), connector
->props
[i
]));
439 if (strcmp(property
->name
, name
) == 0)
440 return property
.Pass();
443 return ScopedDrmPropertyPtr();
446 bool DrmDevice::SetProperty(uint32_t connector_id
,
447 uint32_t property_id
,
449 DCHECK(file_
.IsValid());
450 return !drmModeConnectorSetProperty(file_
.GetPlatformFile(), connector_id
,
454 bool DrmDevice::GetCapability(uint64_t capability
, uint64_t* value
) {
455 DCHECK(file_
.IsValid());
456 return !drmGetCap(file_
.GetPlatformFile(), capability
, value
);
459 ScopedDrmPropertyBlobPtr
DrmDevice::GetPropertyBlob(drmModeConnector
* connector
,
461 DCHECK(file_
.IsValid());
462 TRACE_EVENT2("drm", "DrmDevice::GetPropertyBlob", "connector",
463 connector
->connector_id
, "name", name
);
464 for (int i
= 0; i
< connector
->count_props
; ++i
) {
465 ScopedDrmPropertyPtr
property(
466 drmModeGetProperty(file_
.GetPlatformFile(), connector
->props
[i
]));
470 if (strcmp(property
->name
, name
) == 0 &&
471 (property
->flags
& DRM_MODE_PROP_BLOB
))
472 return ScopedDrmPropertyBlobPtr(drmModeGetPropertyBlob(
473 file_
.GetPlatformFile(), connector
->prop_values
[i
]));
476 return ScopedDrmPropertyBlobPtr();
479 bool DrmDevice::SetCursor(uint32_t crtc_id
,
481 const gfx::Size
& size
) {
482 DCHECK(file_
.IsValid());
483 TRACE_EVENT1("drm", "DrmDevice::SetCursor", "handle", handle
);
484 return !drmModeSetCursor(file_
.GetPlatformFile(), crtc_id
, handle
,
485 size
.width(), size
.height());
488 bool DrmDevice::MoveCursor(uint32_t crtc_id
, const gfx::Point
& point
) {
489 DCHECK(file_
.IsValid());
490 return !drmModeMoveCursor(file_
.GetPlatformFile(), crtc_id
, point
.x(),
494 bool DrmDevice::CreateDumbBuffer(const SkImageInfo
& info
,
497 DCHECK(file_
.IsValid());
499 TRACE_EVENT0("drm", "DrmDevice::CreateDumbBuffer");
500 return DrmCreateDumbBuffer(file_
.GetPlatformFile(), info
, handle
, stride
);
503 bool DrmDevice::DestroyDumbBuffer(uint32_t handle
) {
504 DCHECK(file_
.IsValid());
505 TRACE_EVENT1("drm", "DrmDevice::DestroyDumbBuffer", "handle", handle
);
506 return DrmDestroyDumbBuffer(file_
.GetPlatformFile(), handle
);
509 bool DrmDevice::MapDumbBuffer(uint32_t handle
, size_t size
, void** pixels
) {
510 struct drm_mode_map_dumb map_request
;
511 memset(&map_request
, 0, sizeof(map_request
));
512 map_request
.handle
= handle
;
513 if (drmIoctl(file_
.GetPlatformFile(), DRM_IOCTL_MODE_MAP_DUMB
,
515 PLOG(ERROR
) << "Cannot prepare dumb buffer for mapping";
519 *pixels
= mmap(0, size
, PROT_READ
| PROT_WRITE
, MAP_SHARED
,
520 file_
.GetPlatformFile(), map_request
.offset
);
521 if (*pixels
== MAP_FAILED
) {
522 PLOG(ERROR
) << "Cannot mmap dumb buffer";
529 bool DrmDevice::UnmapDumbBuffer(void* pixels
, size_t size
) {
530 return !munmap(pixels
, size
);
533 bool DrmDevice::CloseBufferHandle(uint32_t handle
) {
534 struct drm_gem_close close_request
;
535 memset(&close_request
, 0, sizeof(close_request
));
536 close_request
.handle
= handle
;
537 return !drmIoctl(file_
.GetPlatformFile(), DRM_IOCTL_GEM_CLOSE
,
541 bool DrmDevice::CommitProperties(drmModePropertySet
* properties
,
545 const PageFlipCallback
& callback
) {
546 #if defined(USE_DRM_ATOMIC)
548 flags
|= DRM_MODE_ATOMIC_TEST_ONLY
;
550 flags
|= DRM_MODE_PAGE_FLIP_EVENT
;
551 uint64_t id
= page_flip_manager_
->GetNextId();
552 if (!drmModePropertySetCommit(file_
.GetPlatformFile(), flags
,
553 reinterpret_cast<void*>(id
), properties
)) {
556 page_flip_manager_
->RegisterCallback(id
, callback
);
558 // If the flip was requested synchronous or if no watcher has been installed
559 // yet, then synchronously handle the page flip events.
560 if (is_sync
|| !watcher_
) {
561 TRACE_EVENT1("drm", "OnDrmEvent", "socket", file_
.GetPlatformFile());
564 file_
.GetPlatformFile(),
565 base::Bind(&PageFlipManager::OnPageFlip
, page_flip_manager_
));
569 #endif // defined(USE_DRM_ATOMIC)
573 bool DrmDevice::SetCapability(uint64_t capability
, uint64_t value
) {
574 DCHECK(file_
.IsValid());
575 return !drmSetClientCap(file_
.GetPlatformFile(), capability
, value
);
578 bool DrmDevice::SetMaster() {
579 TRACE_EVENT1("drm", "DrmDevice::SetMaster", "path", device_path_
.value());
580 DCHECK(file_
.IsValid());
581 return (drmSetMaster(file_
.GetPlatformFile()) == 0);
584 bool DrmDevice::DropMaster() {
585 TRACE_EVENT1("drm", "DrmDevice::DropMaster", "path", device_path_
.value());
586 DCHECK(file_
.IsValid());
587 return (drmDropMaster(file_
.GetPlatformFile()) == 0);
590 bool DrmDevice::SetGammaRamp(uint32_t crtc_id
,
591 const std::vector
<GammaRampRGBEntry
>& lut
) {
592 ScopedDrmCrtcPtr crtc
= GetCrtc(crtc_id
);
594 // TODO(robert.bradford) resample the incoming ramp to match what the kernel
596 if (static_cast<size_t>(crtc
->gamma_size
) != lut
.size()) {
597 LOG(ERROR
) << "Gamma table size mismatch: supplied " << lut
.size()
598 << " expected " << crtc
->gamma_size
;
601 std::vector
<uint16_t> r
, g
, b
;
602 r
.reserve(lut
.size());
603 g
.reserve(lut
.size());
604 b
.reserve(lut
.size());
606 for (size_t i
= 0; i
< lut
.size(); ++i
) {
607 r
.push_back(lut
[i
].r
);
608 g
.push_back(lut
[i
].g
);
609 b
.push_back(lut
[i
].b
);
612 DCHECK(file_
.IsValid());
613 TRACE_EVENT0("drm", "DrmDevice::SetGamma");
614 return (drmModeCrtcSetGamma(file_
.GetPlatformFile(), crtc_id
, r
.size(), &r
[0],