1package drm
2
3import (
4 "bytes"
5 "errors"
6 "os"
7 "path/filepath"
8 "testing"
9
10 "golang.org/x/sys/unix"
11)
12
13// Real-device check of the dumb buffer layer: no modeset, no atomic commit.
14// It skips when no card node can be opened or makes dumb buffers.
15func TestDumbBuffersOnRealDevice(t *testing.T) {
16 cards, _ := filepath.Glob("/dev/dri/card*")
17 if len(cards) == 0 {
18 t.Skip("no /dev/dri/card* node")
19 }
20 for _, path := range cards {
21 f, err := os.OpenFile(path, os.O_RDWR|unix.O_CLOEXEC, 0)
22 if err != nil {
23 t.Logf("%s: %v", path, err)
24 continue
25 }
26 done := func() bool {
27 defer f.Close()
28 return dumbDeviceRoundTrip(t, path, kmsDevice{fd: int(f.Fd())})
29 }()
30 if done {
31 return
32 }
33 }
34 t.Skip("no card node makes dumb buffers for this user")
35}
36
37// dumbDeviceRoundTrip runs the buffer life cycle on k and reports false
38// when the card cannot do it for a reason unrelated to the code under test
39// (the permission or the driver).
40func dumbDeviceRoundTrip(t *testing.T, path string, k kmsDevice) bool {
41 size := cursorSize(k.fd)
42 var imgs [2]dumbBuffer
43 var mems [2][]byte
44 var fbs [2]uint32
45 cleanup := func() {
46 for i := range imgs {
47 if fbs[i] != 0 {
48 if err := k.rmFB(fbs[i]); err != nil {
49 t.Errorf("rmfb: %v", err)
50 }
51 }
52 if mems[i] != nil {
53 if err := k.unmapDumb(mems[i]); err != nil {
54 t.Errorf("munmap: %v", err)
55 }
56 }
57 if imgs[i].handle != 0 {
58 if err := k.destroyDumb(imgs[i].handle); err != nil {
59 t.Errorf("destroy dumb: %v", err)
60 }
61 }
62 }
63 }
64 defer cleanup()
65 for i := range imgs {
66 b, err := k.createDumb(size, size)
67 if err != nil {
68 if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) || errors.Is(err, unix.ENOTSUP) || errors.Is(err, unix.ENOSYS) || errors.Is(err, unix.EINVAL) {
69 t.Logf("%s: create dumb: %v", path, err)
70 return false
71 }
72 t.Fatalf("%s: create dumb: %v", path, err)
73 }
74 imgs[i] = b
75 if int(b.pitch) < size*4 || b.size < uint64(int(b.pitch)*size) {
76 t.Fatalf("%s: dumb %dx%d pitch %d size %d", path, size, size, b.pitch, b.size)
77 }
78 if mems[i], err = k.mapDumb(b); err != nil {
79 t.Fatalf("%s: map dumb: %v", path, err)
80 }
81 if fbs[i], err = k.addFBHandle(b, size, size, fourccARGB); err != nil {
82 t.Fatalf("%s: add fb2 ARGB8888: %v", path, err)
83 }
84 t.Logf("%s: dumb buffer %d: %dx%d pitch %d size %d fb %d", path, i, size, size, b.pitch, b.size, fbs[i])
85 }
86 // Write a pattern through the shared mapping; each buffer keeps its own.
87 for i, mem := range mems {
88 d := &dumbCursor{k: k, size: size}
89 d.img[i] = dumbImage{pitch: int(imgs[i].pitch), mem: mem}
90 px := bytes.Repeat([]byte{byte(0x10 + i), 0x20, 0x30, 0xff}, 3*2)
91 if err := d.write(i, px, 3, 2, 0); err != nil {
92 t.Fatal(err)
93 }
94 }
95 for i, mem := range mems {
96 pitch := int(imgs[i].pitch)
97 want := []byte{byte(0x10 + i), 0x20, 0x30, 0xff}
98 for y := range 2 {
99 if got := mem[y*pitch : y*pitch+4]; !bytes.Equal(got, want) {
100 t.Fatalf("%s: buffer %d row %d read back %v, want %v", path, i, y, got, want)
101 }
102 if got := mem[y*pitch+3*4]; got != 0 {
103 t.Fatalf("%s: buffer %d row %d not cleared after the image", path, i, y)
104 }
105 }
106 if mem[2*pitch] != 0 || mem[(size-1)*pitch+(size-1)*4] != 0 {
107 t.Fatalf("%s: buffer %d not cleared below the image", path, i)
108 }
109 }
110 return true
111}