1package drm
2
3import (
4 "runtime"
5 "unsafe"
6
7 "golang.org/x/sys/unix"
8)
9
10// KMS object properties, read by name; atomic commits set them.
11
12const (
13 ioctlObjGetProps = 0xC02064B9 // DRM_IOWR('d', 0xB9, struct drm_mode_obj_get_properties)
14 ioctlGetProp = 0xC04064AA // DRM_IOWR('d', 0xAA, struct drm_mode_get_property)
15
16 objCrtc = 0xcccccccc
17 objConnector = 0xc0c0c0c0
18)
19
20type objGetProps struct {
21 props, values uint64
22 count, objID uint32
23 objType, _ uint32
24}
25
26type propEnum struct {
27 value uint64
28 name [32]byte
29}
30
31type getProp struct {
32 values, enumBlobs uint64
33 propID, flags uint32
34 name [32]byte
35 countValues, countEn uint32
36}
37
38// objProps returns an object's property IDs and values by name.
39func objProps(fd int, obj, typ uint32) (map[string][2]uint64, error) {
40 q := objGetProps{objID: obj, objType: typ}
41 if err := ioctl(fd, ioctlObjGetProps, unsafe.Pointer(&q)); err != nil {
42 return nil, err
43 }
44 out := map[string][2]uint64{}
45 if q.count == 0 {
46 return out, nil
47 }
48 ids := make([]uint32, q.count)
49 vals := make([]uint64, q.count)
50 q.props, q.values = uint64(uintptr(unsafe.Pointer(&ids[0]))), uint64(uintptr(unsafe.Pointer(&vals[0])))
51 if err := ioctl(fd, ioctlObjGetProps, unsafe.Pointer(&q)); err != nil {
52 return nil, err
53 }
54 for i := range min(int(q.count), len(ids)) {
55 p := getProp{propID: ids[i]}
56 if ioctl(fd, ioctlGetProp, unsafe.Pointer(&p)) != nil {
57 continue
58 }
59 out[unix.ByteSliceToString(p.name[:])] = [2]uint64{uint64(ids[i]), vals[i]}
60 }
61 return out, nil
62}
63
64// readContentTypeProp records the connector's content type enum values.
65func readContentTypeProp(fd int, props map[string][2]uint64) (uint32, [5]uint64) {
66 id := uint32(props["content type"][0])
67 var values [5]uint64
68 if id == 0 {
69 return 0, values
70 }
71 p := getProp{propID: id}
72 if ioctl(fd, ioctlGetProp, unsafe.Pointer(&p)) != nil || p.countEn == 0 {
73 return 0, values
74 }
75 enums := make([]propEnum, p.countEn)
76 v := make([]uint64, max(p.countValues, 1))
77 p.enumBlobs = uint64(uintptr(unsafe.Pointer(&enums[0])))
78 p.values = uint64(uintptr(unsafe.Pointer(&v[0])))
79 if ioctl(fd, ioctlGetProp, unsafe.Pointer(&p)) != nil {
80 return 0, values
81 }
82 for _, e := range enums[:min(int(p.countEn), len(enums))] {
83 switch unix.ByteSliceToString(e.name[:]) {
84 case "No Data":
85 values[0] = e.value
86 case "Graphics":
87 values[1] = e.value
88 case "Photo":
89 values[2] = e.value
90 case "Cinema":
91 values[3] = e.value
92 case "Game":
93 values[4] = e.value
94 }
95 }
96 runtime.KeepAlive(enums)
97 runtime.KeepAlive(v)
98 return id, values
99}
100
101// connectorHDRProps retains connector property IDs, enum values and the
102// original max bpc for SDR modesets and restoration.
103type connectorHDRProps struct {
104 Metadata, Colorspace, MaxBPC uint32
105 BT2020Value uint64
106 DefaultValue uint64 // connector Colorspace "Default" enum
107 HasDefault bool
108 MaxBPCValue uint64 // current value for eventual restoration
109}
110
111// readConnectorHDRProps checks property metadata, not only current values.
112func readConnectorHDRProps(fd int, props map[string][2]uint64) connectorHDRProps {
113 var out connectorHDRProps
114 out.Metadata = uint32(props["HDR_OUTPUT_METADATA"][0])
115 if id := uint32(props["Colorspace"][0]); id != 0 {
116 p := getProp{propID: id}
117 if ioctl(fd, ioctlGetProp, unsafe.Pointer(&p)) == nil && p.countEn > 0 {
118 enums := make([]propEnum, p.countEn)
119 p.enumBlobs = uint64(uintptr(unsafe.Pointer(&enums[0])))
120 // The kernel also copies an enum's values when countValues is
121 // set: a nil values pointer makes the whole call fail (EFAULT).
122 values := make([]uint64, max(p.countValues, 1))
123 p.values = uint64(uintptr(unsafe.Pointer(&values[0])))
124 if ioctl(fd, ioctlGetProp, unsafe.Pointer(&p)) == nil {
125 out.Colorspace, out.BT2020Value = bt2020Enum(id, enums[:min(int(p.countEn), len(enums))])
126 for _, e := range enums[:min(int(p.countEn), len(enums))] {
127 if unix.ByteSliceToString(e.name[:]) == "Default" {
128 out.DefaultValue, out.HasDefault = e.value, true
129 }
130 }
131 }
132 runtime.KeepAlive(enums)
133 runtime.KeepAlive(values)
134 }
135 }
136 if id := uint32(props["max bpc"][0]); id != 0 {
137 p := getProp{propID: id}
138 if ioctl(fd, ioctlGetProp, unsafe.Pointer(&p)) == nil && p.countValues >= 2 {
139 values := make([]uint64, p.countValues)
140 p.values = uint64(uintptr(unsafe.Pointer(&values[0])))
141 if ioctl(fd, ioctlGetProp, unsafe.Pointer(&p)) == nil && p.countValues >= 2 && bpcAllows10(values) {
142 out.MaxBPC, out.MaxBPCValue = id, props["max bpc"][1]
143 }
144 runtime.KeepAlive(values)
145 }
146 }
147 return out
148}
149
150func bt2020Enum(id uint32, enums []propEnum) (uint32, uint64) {
151 for _, e := range enums {
152 if unix.ByteSliceToString(e.name[:]) == "BT2020_RGB" {
153 return id, e.value
154 }
155 }
156 return 0, 0
157}
158
159func bpcAllows10(values []uint64) bool {
160 return len(values) >= 2 && values[0] <= 10 && values[1] >= 10
161}
162
163// hdrCapability combines EDID and connector support, retaining the first reason.
164type hdrCapability struct {
165 Capable bool
166 Reason string
167 HDRMetadata
168}
169
170func detectHDR(m Monitor, p connectorHDRProps) hdrCapability {
171 c := hdrCapability{HDRMetadata: m.HDR}
172 switch {
173 case !m.HDR.PQ:
174 c.Reason = "edid: no PQ"
175 case !m.HDR.StaticType1:
176 c.Reason = "edid: no static metadata type 1"
177 case !m.HDR.BT2020RGB:
178 c.Reason = "edid: no BT2020RGB"
179 case p.Metadata == 0:
180 c.Reason = "connector: no HDR_OUTPUT_METADATA"
181 case p.Colorspace == 0:
182 c.Reason = "connector: no BT2020_RGB Colorspace"
183 case p.MaxBPC == 0:
184 c.Reason = "connector: max bpc below 10"
185 case !p.HasDefault:
186 c.Reason = "connector: no Default Colorspace"
187 default:
188 c.Capable = true
189 }
190 return c
191}
192
193// vrrProperty returns the CRTC's VRR_ENABLED property when the connector
194// is VRR capable, else 0.
195func vrrProperty(k kms, conn, crtc uint32) uint32 {
196 cp, err := k.objProps(conn, objConnector)
197 if err != nil || cp["vrr_capable"][1] != 1 {
198 return 0
199 }
200 rp, err := k.objProps(crtc, objCrtc)
201 if err != nil {
202 return 0
203 }
204 return uint32(rp["VRR_ENABLED"][0])
205}