1 // Copyright 2015 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.
4
5 #include "ui/ozone/platform/drm/gpu/drm_display.h"
6
7 #include <xf86drmMode.h>
8 #include <memory>
9
10 #include "base/logging.h"
11 #include "base/stl_util.h"
12 #include "base/trace_event/trace_event.h"
13 #include "ui/display/display_features.h"
14 #include "ui/display/types/display_snapshot.h"
15 #include "ui/display/types/gamma_ramp_rgb_entry.h"
16 #include "ui/gfx/color_space.h"
17 #include "ui/ozone/platform/drm/common/drm_util.h"
18 #include "ui/ozone/platform/drm/gpu/drm_device.h"
19 #include "ui/ozone/platform/drm/gpu/screen_manager.h"
20
21 namespace ui {
22
23 namespace {
24
25 const char kContentProtection[] = "Content Protection";
26 const char kHdcpContentType[] = "HDCP Content Type";
27
28 const char kPrivacyScreen[] = "privacy-screen";
29
30 struct ContentProtectionMapping {
31 const char* name;
32 display::HDCPState state;
33 };
34
35 struct HdcpContentTypeMapping {
36 const char* name;
37 display::ContentProtectionMethod content_type;
38 };
39
40 const ContentProtectionMapping kContentProtectionStates[] = {
41 {"Undesired", display::HDCP_STATE_UNDESIRED},
42 {"Desired", display::HDCP_STATE_DESIRED},
43 {"Enabled", display::HDCP_STATE_ENABLED}};
44
45 const HdcpContentTypeMapping kHdcpContentTypeStates[] = {
46 {"HDCP Type0", display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_0},
47 {"HDCP Type1", display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_1}};
48
49 // Converts |state| to the DRM value associated with the it.
GetContentProtectionValue(drmModePropertyRes * property,display::HDCPState state)50 uint32_t GetContentProtectionValue(drmModePropertyRes* property,
51 display::HDCPState state) {
52 std::string name;
53 for (size_t i = 0; i < base::size(kContentProtectionStates); ++i) {
54 if (kContentProtectionStates[i].state == state) {
55 name = kContentProtectionStates[i].name;
56 break;
57 }
58 }
59
60 for (int i = 0; i < property->count_enums; ++i) {
61 if (name == property->enums[i].name)
62 return i;
63 }
64
65 NOTREACHED();
66 return 0;
67 }
68
69 // Converts |content_type| to the DRM value associated with the it.
GetHdcpContentTypeValue(drmModePropertyRes * property,display::ContentProtectionMethod content_type)70 uint32_t GetHdcpContentTypeValue(
71 drmModePropertyRes* property,
72 display::ContentProtectionMethod content_type) {
73 std::string name;
74 for (size_t i = 0; i < base::size(kHdcpContentTypeStates); ++i) {
75 if (kHdcpContentTypeStates[i].content_type == content_type) {
76 name = kHdcpContentTypeStates[i].name;
77 break;
78 }
79 }
80
81 for (int i = 0; i < property->count_enums; ++i) {
82 if (name == property->enums[i].name)
83 return i;
84 }
85
86 NOTREACHED();
87 return 0;
88 }
89
GetEnumNameForProperty(drmModeObjectProperties * property_values,drmModePropertyRes * property)90 std::string GetEnumNameForProperty(drmModeObjectProperties* property_values,
91 drmModePropertyRes* property) {
92 for (uint32_t prop_idx = 0; prop_idx < property_values->count_props;
93 ++prop_idx) {
94 if (property_values->props[prop_idx] != property->prop_id)
95 continue;
96
97 for (int enum_idx = 0; enum_idx < property->count_enums; ++enum_idx) {
98 const drm_mode_property_enum& property_enum = property->enums[enum_idx];
99 if (property_enum.value == property_values->prop_values[prop_idx])
100 return property_enum.name;
101 }
102 }
103
104 NOTREACHED();
105 return std::string();
106 }
107
GetDrmModeVector(drmModeConnector * connector)108 std::vector<drmModeModeInfo> GetDrmModeVector(drmModeConnector* connector) {
109 std::vector<drmModeModeInfo> modes;
110 for (int i = 0; i < connector->count_modes; ++i)
111 modes.push_back(connector->modes[i]);
112
113 return modes;
114 }
115
FillPowerFunctionValues(std::vector<display::GammaRampRGBEntry> * table,size_t table_size,float max_value,float exponent)116 void FillPowerFunctionValues(std::vector<display::GammaRampRGBEntry>* table,
117 size_t table_size,
118 float max_value,
119 float exponent) {
120 for (size_t i = 0; i < table_size; i++) {
121 const uint16_t v = max_value * std::numeric_limits<uint16_t>::max() *
122 pow((static_cast<float>(i) + 1) / table_size, exponent);
123 struct display::GammaRampRGBEntry gamma_entry = {v, v, v};
124 table->push_back(gamma_entry);
125 }
126 }
127
128 } // namespace
129
DrmDisplay(const scoped_refptr<DrmDevice> & drm)130 DrmDisplay::DrmDisplay(const scoped_refptr<DrmDevice>& drm)
131 : drm_(drm), current_color_space_(gfx::ColorSpace::CreateSRGB()) {}
132
133 DrmDisplay::~DrmDisplay() = default;
134
connector() const135 uint32_t DrmDisplay::connector() const {
136 DCHECK(connector_);
137 return connector_->connector_id;
138 }
139
Update(HardwareDisplayControllerInfo * info,size_t device_index)140 std::unique_ptr<display::DisplaySnapshot> DrmDisplay::Update(
141 HardwareDisplayControllerInfo* info,
142 size_t device_index) {
143 std::unique_ptr<display::DisplaySnapshot> params = CreateDisplaySnapshot(
144 info, drm_->get_fd(), drm_->device_path(), device_index, origin_);
145 crtc_ = info->crtc()->crtc_id;
146 // TODO(crbug.com/1119499): consider taking ownership of |info->connector()|
147 connector_ = ScopedDrmConnectorPtr(
148 drm_->GetConnector(info->connector()->connector_id));
149 if (!connector_) {
150 PLOG(ERROR) << "Failed to get connector "
151 << info->connector()->connector_id;
152 return nullptr;
153 }
154
155 display_id_ = params->display_id();
156 modes_ = GetDrmModeVector(info->connector());
157 is_hdr_capable_ =
158 params->bits_per_channel() > 8 && params->color_space().IsHDR();
159 #if defined(OS_CHROMEOS)
160 is_hdr_capable_ =
161 is_hdr_capable_ &&
162 base::FeatureList::IsEnabled(display::features::kUseHDRTransferFunction);
163 #endif
164
165 return params;
166 }
167
GetHDCPState(display::HDCPState * state,display::ContentProtectionMethod * protection_method)168 bool DrmDisplay::GetHDCPState(
169 display::HDCPState* state,
170 display::ContentProtectionMethod* protection_method) {
171 if (!connector_)
172 return false;
173
174 TRACE_EVENT1("drm", "DrmDisplay::GetHDCPState", "connector",
175 connector_->connector_id);
176 ScopedDrmPropertyPtr hdcp_property(
177 drm_->GetProperty(connector_.get(), kContentProtection));
178 if (!hdcp_property) {
179 PLOG(INFO) << "'" << kContentProtection << "' property doesn't exist.";
180 return false;
181 }
182
183 ScopedDrmObjectPropertyPtr property_values(drm_->GetObjectProperties(
184 connector_->connector_id, DRM_MODE_OBJECT_CONNECTOR));
185 std::string name =
186 GetEnumNameForProperty(property_values.get(), hdcp_property.get());
187 size_t i;
188 for (i = 0; i < base::size(kContentProtectionStates); ++i) {
189 if (name == kContentProtectionStates[i].name) {
190 *state = kContentProtectionStates[i].state;
191 VLOG(3) << "HDCP state: " << *state << " (" << name << ")";
192 break;
193 }
194 }
195
196 if (i == base::size(kContentProtectionStates)) {
197 LOG(ERROR) << "Unknown content protection value '" << name << "'";
198 return false;
199 }
200
201 if (*state == display::HDCP_STATE_UNDESIRED) {
202 // ProtectionMethod doesn't matter if we don't have it desired/enabled.
203 *protection_method = display::CONTENT_PROTECTION_METHOD_NONE;
204 return true;
205 }
206
207 ScopedDrmPropertyPtr content_type_property(
208 drm_->GetProperty(connector_.get(), kHdcpContentType));
209 if (!content_type_property) {
210 // This won't exist if the driver doesn't support HDCP 2.2, so default it in
211 // that case.
212 VLOG(3) << "HDCP Content Type not supported, default to Type 0";
213 *protection_method = display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_0;
214 return true;
215 }
216 name = GetEnumNameForProperty(property_values.get(),
217 content_type_property.get());
218 for (i = 0; i < base::size(kHdcpContentTypeStates); ++i) {
219 if (name == kHdcpContentTypeStates[i].name) {
220 *protection_method = kHdcpContentTypeStates[i].content_type;
221 VLOG(3) << "Content Protection Method: " << *protection_method << " ("
222 << name << ")";
223 break;
224 }
225 }
226
227 if (i == base::size(kHdcpContentTypeStates)) {
228 LOG(ERROR) << "Unknown HDCP content type value '" << name << "'";
229 return false;
230 }
231 return true;
232 }
233
SetHDCPState(display::HDCPState state,display::ContentProtectionMethod protection_method)234 bool DrmDisplay::SetHDCPState(
235 display::HDCPState state,
236 display::ContentProtectionMethod protection_method) {
237 if (!connector_) {
238 return false;
239 }
240
241 if (protection_method != display::CONTENT_PROTECTION_METHOD_NONE) {
242 ScopedDrmPropertyPtr content_type_property(
243 drm_->GetProperty(connector_.get(), kHdcpContentType));
244 if (!content_type_property) {
245 // If the driver doesn't support HDCP 2.2, this won't exist.
246 if (protection_method & display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_1) {
247 // We can't do this, since we can't specify the content type.
248 VLOG(3)
249 << "Cannot set HDCP Content Type 1 since driver doesn't support it";
250 return false;
251 }
252 VLOG(3) << "HDCP Content Type not supported, default to Type 0";
253 } else if (!drm_->SetProperty(
254 connector_->connector_id, content_type_property->prop_id,
255 GetHdcpContentTypeValue(content_type_property.get(),
256 protection_method))) {
257 // Failed setting HDCP Content Type.
258 return false;
259 }
260 }
261
262 ScopedDrmPropertyPtr hdcp_property(
263 drm_->GetProperty(connector_.get(), kContentProtection));
264 if (!hdcp_property) {
265 PLOG(INFO) << "'" << kContentProtection << "' property doesn't exist.";
266 return false;
267 }
268
269 return drm_->SetProperty(
270 connector_->connector_id, hdcp_property->prop_id,
271 GetContentProtectionValue(hdcp_property.get(), state));
272 }
273
SetColorMatrix(const std::vector<float> & color_matrix)274 void DrmDisplay::SetColorMatrix(const std::vector<float>& color_matrix) {
275 if (!drm_->plane_manager()->SetColorMatrix(crtc_, color_matrix)) {
276 LOG(ERROR) << "Failed to set color matrix for display: crtc_id = " << crtc_;
277 }
278 }
279
SetBackgroundColor(const uint64_t background_color)280 void DrmDisplay::SetBackgroundColor(const uint64_t background_color) {
281 drm_->plane_manager()->SetBackgroundColor(crtc_, background_color);
282 }
283
SetGammaCorrection(const std::vector<display::GammaRampRGBEntry> & degamma_lut,const std::vector<display::GammaRampRGBEntry> & gamma_lut)284 void DrmDisplay::SetGammaCorrection(
285 const std::vector<display::GammaRampRGBEntry>& degamma_lut,
286 const std::vector<display::GammaRampRGBEntry>& gamma_lut) {
287 // When both |degamma_lut| and |gamma_lut| are empty they are interpreted as
288 // "linear/pass-thru" [1]. If the display |is_hdr_capable_| we have to make
289 // sure the |current_color_space_| is considered properly.
290 // [1]
291 // https://www.kernel.org/doc/html/v4.19/gpu/drm-kms.html#color-management-properties
292 if (degamma_lut.empty() && gamma_lut.empty() && is_hdr_capable_)
293 SetColorSpace(current_color_space_);
294 else
295 CommitGammaCorrection(degamma_lut, gamma_lut);
296 }
297
298 // TODO(gildekel): consider reformatting this to use the new DRM API or cache
299 // |privacy_screen_property| after crrev.com/c/1715751 lands.
SetPrivacyScreen(bool enabled)300 void DrmDisplay::SetPrivacyScreen(bool enabled) {
301 if (!connector_)
302 return;
303
304 ScopedDrmPropertyPtr privacy_screen_property(
305 drm_->GetProperty(connector_.get(), kPrivacyScreen));
306
307 if (!privacy_screen_property) {
308 LOG(ERROR) << "'" << kPrivacyScreen << "' property doesn't exist.";
309 return;
310 }
311
312 if (!drm_->SetProperty(connector_->connector_id,
313 privacy_screen_property->prop_id, enabled)) {
314 LOG(ERROR) << (enabled ? "Enabling" : "Disabling") << " property '"
315 << kPrivacyScreen << "' failed!";
316 }
317 }
318
SetColorSpace(const gfx::ColorSpace & color_space)319 void DrmDisplay::SetColorSpace(const gfx::ColorSpace& color_space) {
320 // There's only something to do if the display supports HDR.
321 if (!is_hdr_capable_)
322 return;
323 current_color_space_ = color_space;
324
325 // When |color_space| is HDR we can simply leave the gamma tables empty, which
326 // is interpreted as "linear/pass-thru", see [1]. However when we have an SDR
327 // |color_space|, we need to write a scaled down |gamma| function to prevent
328 // the mode change brightness to be visible.
329 std::vector<display::GammaRampRGBEntry> degamma;
330 std::vector<display::GammaRampRGBEntry> gamma;
331 if (current_color_space_.IsHDR())
332 return CommitGammaCorrection(degamma, gamma);
333
334 // TODO(mcasas) This should be the inverse value of DisplayChangeObservers's
335 // FillDisplayColorSpaces's kHDRLevel, move to a common place.
336 // TODO(b/165822222): adjust this level based on the display brightness.
337 constexpr float kSDRLevel = 0.85;
338 // TODO(mcasas): Retrieve this from the |drm_| HardwareDisplayPlaneManager.
339 constexpr size_t kNumGammaSamples = 64ul;
340 // Only using kSDRLevel of the available values shifts the contrast ratio, we
341 // restore it via a smaller local gamma correction using this exponent.
342 constexpr float kExponent = 1.2;
343 FillPowerFunctionValues(&gamma, kNumGammaSamples, kSDRLevel, kExponent);
344 CommitGammaCorrection(degamma, gamma);
345 }
346
CommitGammaCorrection(const std::vector<display::GammaRampRGBEntry> & degamma_lut,const std::vector<display::GammaRampRGBEntry> & gamma_lut)347 void DrmDisplay::CommitGammaCorrection(
348 const std::vector<display::GammaRampRGBEntry>& degamma_lut,
349 const std::vector<display::GammaRampRGBEntry>& gamma_lut) {
350 if (!drm_->plane_manager()->SetGammaCorrection(crtc_, degamma_lut, gamma_lut))
351 LOG(ERROR) << "Failed to set gamma tables for display: crtc_id = " << crtc_;
352 }
353
354 } // namespace ui
355