1package drm
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "sync"
8
9 "github.com/bnema/purego"
10 "golang.org/x/sys/unix"
11)
12
13// Hotplug reports connector changes through a udev netlink monitor on the
14// drm subsystem (the kernel sends HOTPLUG=1 on the card). libudev is loaded
15// with purego.
16
17var (
18 udevOnce sync.Once
19 udevErr error
20
21 udevNew func() uintptr
22 udevUnref func(uintptr) uintptr
23 monitorNew func(udev uintptr, name string) uintptr
24 monitorUnref func(uintptr) uintptr
25 monitorFilter func(mon uintptr, subsystem string, devtype uintptr) int32
26 monitorEnable func(uintptr) int32
27 monitorFD func(uintptr) int32
28 monitorReceive func(uintptr) uintptr
29 deviceUnref func(uintptr) uintptr
30 devicePropertyVal func(dev uintptr, key string) string
31)
32
33func loadUdev() error {
34 udevOnce.Do(func() {
35 lib, err := purego.Dlopen("libudev.so.1", purego.RTLD_NOW|purego.RTLD_LOCAL)
36 if err != nil {
37 udevErr = fmt.Errorf("open libudev.so.1: %w", err)
38 return
39 }
40 defer func() {
41 if r := recover(); r != nil {
42 udevErr = fmt.Errorf("libudev: %v", r)
43 }
44 }()
45 reg := func(fn any, name string) { purego.RegisterLibFunc(fn, lib, "udev_"+name) }
46 reg(&udevNew, "new")
47 reg(&udevUnref, "unref")
48 reg(&monitorNew, "monitor_new_from_netlink")
49 reg(&monitorUnref, "monitor_unref")
50 reg(&monitorFilter, "monitor_filter_add_match_subsystem_devtype")
51 reg(&monitorEnable, "monitor_enable_receiving")
52 reg(&monitorFD, "monitor_get_fd")
53 reg(&monitorReceive, "monitor_receive_device")
54 reg(&deviceUnref, "device_unref")
55 reg(&devicePropertyVal, "device_get_property_value")
56 })
57 return udevErr
58}
59
60// WatchHotplug sends on changed after every DRM hotplug event until ctx
61// ends. Events that arrive while a send is pending are merged.
62func WatchHotplug(ctx context.Context, changed chan<- struct{}) error {
63 if err := loadUdev(); err != nil {
64 return err
65 }
66 udev := udevNew()
67 if udev == 0 {
68 return errors.New("udev_new failed")
69 }
70 defer udevUnref(udev)
71 mon := monitorNew(udev, "udev")
72 if mon == 0 {
73 return errors.New("udev_monitor_new_from_netlink failed")
74 }
75 defer monitorUnref(mon)
76 if monitorFilter(mon, "drm", 0) < 0 || monitorEnable(mon) < 0 {
77 return errors.New("udev monitor setup failed")
78 }
79 fd := monitorFD(mon)
80 for ctx.Err() == nil {
81 fds := []unix.PollFd{{Fd: fd, Events: unix.POLLIN}}
82 n, err := unix.Poll(fds, 100)
83 if err != nil && !errors.Is(err, unix.EINTR) {
84 return fmt.Errorf("udev poll: %w", err)
85 }
86 if n <= 0 {
87 continue
88 }
89 dev := monitorReceive(mon)
90 if dev == 0 {
91 continue
92 }
93 hot := devicePropertyVal(dev, "HOTPLUG") == "1"
94 deviceUnref(dev)
95 if !hot {
96 continue
97 }
98 select {
99 case changed <- struct{}{}:
100 default:
101 }
102 }
103 return nil
104}