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