1package drm
2
3import (
4 "encoding/binary"
5 "slices"
6 "testing"
7 "time"
8 "unsafe"
9
10 "github.com/bnema/neferwl/internal/ports"
11)
12
13func TestStructSizesMatchIoctls(t *testing.T) {
14 for _, tc := range []struct {
15 name string
16 got uintptr
17 req uintptr
18 }{
19 {"card_res", unsafe.Sizeof(cardRes{}), ioctlGetResources},
20 {"get_encoder", unsafe.Sizeof(getEncoder{}), ioctlGetEncoder},
21 {"get_connector", unsafe.Sizeof(getConnector{}), ioctlGetConnector},
22 {"fb_cmd2", unsafe.Sizeof(fbCmd2{}), ioctlAddFB2},
23 {"prime_handle", unsafe.Sizeof(primeHandle{}), ioctlPrimeFDToHandle},
24 {"gem_close", unsafe.Sizeof(gemClose{}), ioctlGemClose},
25 {"obj_get_props", unsafe.Sizeof(objGetProps{}), ioctlObjGetProps},
26 {"get_prop", unsafe.Sizeof(getProp{}), ioctlGetProp},
27 {"mode_crtc", unsafe.Sizeof(modeCrtc{}), ioctlGetCrtc},
28 {"set_client_cap", unsafe.Sizeof(setClientCap{}), ioctlSetClientCap},
29 {"get_plane_res", unsafe.Sizeof(getPlaneRes{}), ioctlGetPlaneRes},
30 {"get_plane", unsafe.Sizeof(getPlane{}), ioctlGetPlane},
31 {"get_blob", unsafe.Sizeof(getBlob{}), ioctlGetPropBlob},
32 {"atomic", unsafe.Sizeof(modeAtomic{}), ioctlAtomic},
33 {"create_blob", unsafe.Sizeof(createBlob{}), ioctlCreateBlob},
34 {"get_cap", unsafe.Sizeof(getCap{}), ioctlGetCap},
35 {"create_dumb", unsafe.Sizeof(createDumb{}), ioctlCreateDumb},
36 {"map_dumb", unsafe.Sizeof(mapDumb{}), ioctlMapDumb},
37 {"destroy_dumb", unsafe.Sizeof(uint32(0)), ioctlDestroyDumb},
38 {"destroy_blob", unsafe.Sizeof(uint32(0)), ioctlDestroyBlob},
39 } {
40 if want := (tc.req >> 16) & 0x3fff; tc.got != want {
41 t.Errorf("%s: size %d, ioctl encodes %d", tc.name, tc.got, want)
42 }
43 }
44 if unsafe.Sizeof(modeInfo{}) != 68 {
45 t.Errorf("modeInfo size %d, want 68", unsafe.Sizeof(modeInfo{}))
46 }
47}
48
49func TestWantPicksMode(t *testing.T) {
50 mode := func(w, h uint16, hz uint32, pref bool) modeInfo {
51 // htotal*vtotal = w*h keeps refreshMilli = clock*1000/(w*h).
52 m := modeInfo{HDisplay: w, VDisplay: h, HTotal: w, VTotal: h, Clock: uint32(uint64(w) * uint64(h) * uint64(hz) / 1000), VRefresh: hz}
53 if pref {
54 m.Type = modeTypePrefered
55 }
56 return m
57 }
58 lg := connector{name: "DP-2", connected: true, modes: []modeInfo{mode(5120, 2160, 165, true), mode(3440, 1440, 100, true), mode(5120, 2160, 100, false), mode(5120, 2160, 60, false)}}
59 off := connector{name: "DP-1", modes: []modeInfo{mode(1920, 1080, 60, true)}}
60 for _, tc := range []struct {
61 name string
62 want Want
63 mode string
64 }{
65 {"first preferred", Want{}, "5120x2160@165.000"},
66 {"closest refresh", Want{Modes: map[string][3]float64{"DP-2": {5120, 2160, 99}}}, "5120x2160@100.000"},
67 {"highest refresh", Want{Modes: map[string][3]float64{"DP-2": {5120, 2160, 0}}}, "5120x2160@165.000"},
68 {"unknown mode falls back to preferred", Want{Modes: map[string][3]float64{"DP-2": {800, 600, 0}}}, "5120x2160@165.000"},
69 } {
70 if m := tc.want.pickMode(lg); m.String() != tc.mode {
71 t.Errorf("%s: got %s", tc.name, m)
72 }
73 }
74 vr := lg
75 vr.nonDesktop = true
76 if (Want{}).usable(off) || !(Want{}).usable(lg) || (Want{Disabled: map[string]bool{"DP-2": true}}).usable(lg) || (Want{}).usable(vr) {
77 t.Fatal("usable: non-desktop connectors must not be modeset")
78 }
79}
80
81func TestRefreshMilliLG(t *testing.T) {
82 // Real 5120x2160@165.058 CVT-RB timings.
83 m := modeInfo{Clock: 1_989_530, HDisplay: 5120, HTotal: 5200, VDisplay: 2160, VTotal: 2318}
84 if got := m.refreshMilli(); got < 165_000 || got > 165_100 {
85 t.Fatalf("refresh %d mHz", got)
86 }
87}
88
89func TestParseEDID(t *testing.T) {
90 d := make([]byte, 128)
91 copy(d, "\x00\xff\xff\xff\xff\xff\xff\x00")
92 d[8], d[9] = 0x1e, 0x6d // GSM
93 copy(d[54:], []byte{0, 0, 0, 0xfc, 0})
94 copy(d[59:], "LG ULTRAGEAR+\n")
95 copy(d[72:], []byte{0, 0, 0, 0xff, 0})
96 copy(d[77:], "SN123\n ")
97 m := parseEDID(d)
98 if m.Model != "LG ULTRAGEAR+" || m.Serial != "SN123" || (m.Make != "GSM" && m.Make != "LG Electronics") {
99 t.Fatalf("%+v", m)
100 }
101}
102
103func TestParseFlips(t *testing.T) {
104 ev := func(typ, crtc uint32, user uint64, sec, usec, seq uint32) []byte {
105 b := make([]byte, 32)
106 le := binary.LittleEndian
107 le.PutUint32(b, typ)
108 le.PutUint32(b[4:], 32)
109 le.PutUint64(b[8:], user)
110 le.PutUint32(b[16:], sec)
111 le.PutUint32(b[20:], usec)
112 le.PutUint32(b[24:], seq)
113 le.PutUint32(b[28:], crtc)
114 return b
115 }
116 buf := append(append(ev(eventFlipDone, 41, userFrame, 3, 250, 7), ev(1, 7, 0, 0, 0, 0)...), ev(eventFlipDone, 42, userState, 0, 1, 8)...)
117 got := parseFlips(nil, buf)
118 want := []flipEvent{{crtc: 41, user: userFrame, when: 3*time.Second + 250*time.Microsecond, seq: 7}, {crtc: 42, user: userState, when: time.Microsecond, seq: 8}}
119 if !slices.Equal(got, want) {
120 t.Fatalf("got %+v, want %+v", got, want)
121 }
122 if got := parseFlips(nil, buf[:20]); len(got) != 0 {
123 t.Fatalf("truncated: got %v", got)
124 }
125 // ReadEvents reuses one slice across reads.
126 reused := parseFlips(nil, buf)
127 if n := testing.AllocsPerRun(100, func() { reused = parseFlips(reused[:0], buf) }); n != 0 || !slices.Equal(reused, want) {
128 t.Fatalf("reused parse: %.1f allocs, got %+v", n, reused)
129 }
130}
131
132func TestParseInFormats(t *testing.T) {
133 le := binary.LittleEndian
134 // 3 formats at 24, 2 modifiers at 40.
135 b := make([]byte, 40+2*24)
136 le.PutUint32(b, 1)
137 le.PutUint32(b[8:], 3)
138 le.PutUint32(b[12:], 24)
139 le.PutUint32(b[16:], 2)
140 le.PutUint32(b[20:], 40)
141 for i, f := range []uint32{fourccXRGB, fourccARGB, 0x3432564e} {
142 le.PutUint32(b[24+i*4:], f)
143 }
144 // linear on formats 0 and 2, a tiled modifier on format 1.
145 le.PutUint64(b[40:], 0b101)
146 le.PutUint64(b[56:], 0)
147 le.PutUint64(b[64:], 0b010)
148 le.PutUint64(b[80:], 0x0200000000000001)
149 got := parseInFormats(b)
150 want := []ports.DMABufFormat{{Format: fourccXRGB}, {Format: 0x3432564e}, {Format: fourccARGB, Modifier: 0x0200000000000001}}
151 if !slices.Equal(got, want) {
152 t.Fatalf("got %+v, want %+v", got, want)
153 }
154 if parseInFormats(b[:30]) != nil {
155 t.Fatal("truncated blob parsed")
156 }
157}