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