├── .travis.yml ├── Makefile ├── README.md ├── _examples ├── draw │ ├── drawpicture.go │ └── glenda.jpg └── modeset │ ├── modeset-double-buffered.go │ └── modeset.go ├── cap.go ├── cap_test.go ├── codes.go ├── doc.go ├── drm.go ├── drm_main_test.go ├── drm_test.go ├── examples_test.go ├── ioctl ├── ioctl_linux.go └── ioctl_test.go └── mode ├── mode.go └── simple.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7 5 | - 1.8 6 | - tip 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | test: 4 | go test -v ./... 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/NeowayLabs/drm?status.svg)](https://godoc.org/github.com/NeowayLabs/drm) 2 | 3 | # drm 4 | 5 | The _Direct Rendering Manager_ (DRM) is a kernel framework to manage _Graphical Processing Units_ (GPU). 6 | It's a kernel abstractions to GPU drivers and a userspace API 7 | designed to support the needs of complex graphics devices. 8 | 9 | DRM was first implemented on Linux but ported to FreeBSD, NetBSD and Solaris (others?). It's the lower level interface between opengl and the graphics card. With this Go library, theoretically, now it's possible to create a X server or a pure OpenGL library in Go (no bindings). 10 | 11 | ## Rationale 12 | 13 | Enables the creation of a graphics stack in Go, avoiding the overhead of the existing C bindings. 14 | Another possibility is using Go to make GPGPU (like opencl). 15 | 16 | ## Examples 17 | 18 | See the [examples](https://github.com/NeowayLabs/drm/tree/master/_examples) directory. 19 | -------------------------------------------------------------------------------- /_examples/draw/drawpicture.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "os" 7 | "time" 8 | "unsafe" 9 | 10 | "launchpad.net/gommap" 11 | 12 | _ "image/jpeg" 13 | 14 | "github.com/NeowayLabs/drm" 15 | "github.com/NeowayLabs/drm/mode" 16 | ) 17 | 18 | type ( 19 | framebuffer struct { 20 | id uint32 21 | handle uint32 22 | data []byte 23 | fb *mode.FB 24 | size uint64 25 | stride uint32 26 | } 27 | 28 | // msetData just store the pair (mode, fb) and the saved CRTC of the mode. 29 | msetData struct { 30 | mode *mode.Modeset 31 | fb framebuffer 32 | savedCrtc *mode.Crtc 33 | } 34 | ) 35 | 36 | func createFramebuffer(file *os.File, dev *mode.Modeset) (framebuffer, error) { 37 | fb, err := mode.CreateFB(file, dev.Width, dev.Height, 32) 38 | if err != nil { 39 | return framebuffer{}, fmt.Errorf("Failed to create framebuffer: %s", err.Error()) 40 | } 41 | stride := fb.Pitch 42 | size := fb.Size 43 | handle := fb.Handle 44 | 45 | fbID, err := mode.AddFB(file, dev.Width, dev.Height, 24, 32, stride, handle) 46 | if err != nil { 47 | return framebuffer{}, fmt.Errorf("Cannot create dumb buffer: %s", err.Error()) 48 | } 49 | 50 | offset, err := mode.MapDumb(file, handle) 51 | if err != nil { 52 | return framebuffer{}, err 53 | } 54 | 55 | mmap, err := gommap.MapAt(0, uintptr(file.Fd()), int64(offset), int64(size), gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED) 56 | if err != nil { 57 | return framebuffer{}, fmt.Errorf("Failed to mmap framebuffer: %s", err.Error()) 58 | } 59 | for i := uint64(0); i < size; i++ { 60 | mmap[i] = 0 61 | } 62 | framebuf := framebuffer{ 63 | id: fbID, 64 | handle: handle, 65 | data: mmap, 66 | fb: fb, 67 | size: size, 68 | stride: stride, 69 | } 70 | return framebuf, nil 71 | } 72 | 73 | func draw(msets []msetData) { 74 | var off uint32 75 | 76 | reader, err := os.Open("glenda.jpg") 77 | if err != nil { 78 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 79 | return 80 | } 81 | defer reader.Close() 82 | 83 | m, _, err := image.Decode(reader) 84 | if err != nil { 85 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 86 | return 87 | } 88 | bounds := m.Bounds() 89 | 90 | for j := 0; j < len(msets); j++ { 91 | mset := msets[j] 92 | for y := bounds.Min.Y; y < bounds.Max.Y; y++ { 93 | for x := bounds.Min.X; x < bounds.Max.X; x++ { 94 | r, g, b, _ := m.At(x, y).RGBA() 95 | off = (mset.fb.stride * uint32(y)) + (uint32(x) * 4) 96 | val := uint32((uint32(r) << 16) | (uint32(g) << 8) | uint32(b)) 97 | *(*uint32)(unsafe.Pointer(&mset.fb.data[off])) = val 98 | } 99 | } 100 | } 101 | 102 | time.Sleep(10 * time.Second) 103 | } 104 | 105 | func destroyFramebuffer(modeset *mode.SimpleModeset, mset msetData, file *os.File) error { 106 | handle := mset.fb.handle 107 | data := mset.fb.data 108 | fb := mset.fb 109 | 110 | err := gommap.MMap(data).UnsafeUnmap() 111 | if err != nil { 112 | return fmt.Errorf("Failed to munmap memory: %s\n", err.Error()) 113 | } 114 | err = mode.RmFB(file, fb.id) 115 | if err != nil { 116 | return fmt.Errorf("Failed to remove frame buffer: %s\n", err.Error()) 117 | } 118 | 119 | err = mode.DestroyDumb(file, handle) 120 | if err != nil { 121 | return fmt.Errorf("Failed to destroy dumb buffer: %s\n", err.Error()) 122 | } 123 | return modeset.SetCrtc(mset.mode, mset.savedCrtc) 124 | } 125 | 126 | func cleanup(modeset *mode.SimpleModeset, msets []msetData, file *os.File) { 127 | for _, mset := range msets { 128 | destroyFramebuffer(modeset, mset, file) 129 | } 130 | 131 | } 132 | 133 | func main() { 134 | file, err := drm.OpenCard(0) 135 | if err != nil { 136 | fmt.Printf("error: %s", err.Error()) 137 | return 138 | } 139 | defer file.Close() 140 | if !drm.HasDumbBuffer(file) { 141 | fmt.Printf("drm device does not support dumb buffers") 142 | return 143 | } 144 | modeset, err := mode.NewSimpleModeset(file) 145 | if err != nil { 146 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 147 | os.Exit(1) 148 | } 149 | 150 | var msets []msetData 151 | for _, mod := range modeset.Modesets { 152 | framebuf, err := createFramebuffer(file, &mod) 153 | if err != nil { 154 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 155 | cleanup(modeset, msets, file) 156 | return 157 | } 158 | 159 | // save current CRTC of this mode to restore at exit 160 | savedCrtc, err := mode.GetCrtc(file, mod.Crtc) 161 | if err != nil { 162 | fmt.Fprintf(os.Stderr, "error: Cannot get CRTC for connector %d: %s", mod.Conn, err.Error()) 163 | cleanup(modeset, msets, file) 164 | return 165 | } 166 | // change the mode 167 | err = mode.SetCrtc(file, mod.Crtc, framebuf.id, 0, 0, &mod.Conn, 1, &mod.Mode) 168 | if err != nil { 169 | fmt.Fprintf(os.Stderr, "Cannot set CRTC for connector %d: %s", mod.Conn, err.Error()) 170 | cleanup(modeset, msets, file) 171 | return 172 | } 173 | msets = append(msets, msetData{ 174 | mode: &mod, 175 | fb: framebuf, 176 | savedCrtc: savedCrtc, 177 | }) 178 | } 179 | 180 | draw(msets) 181 | cleanup(modeset, msets, file) 182 | } 183 | -------------------------------------------------------------------------------- /_examples/draw/glenda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeowayLabs/drm/4939fc0ad34530f99a9ceb398e22c9e946bdbffa/_examples/draw/glenda.jpg -------------------------------------------------------------------------------- /_examples/modeset/modeset-double-buffered.go: -------------------------------------------------------------------------------- 1 | // Port of modeset.c example to Go 2 | // Source: https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset-double-buffered.c 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "os" 10 | "time" 11 | "unsafe" 12 | 13 | "launchpad.net/gommap" 14 | 15 | "github.com/NeowayLabs/drm" 16 | "github.com/NeowayLabs/drm/mode" 17 | ) 18 | 19 | type ( 20 | framebuffer struct { 21 | id uint32 22 | handle uint32 23 | data []byte 24 | fb *mode.FB 25 | size uint64 26 | stride uint32 27 | } 28 | 29 | msetData struct { 30 | mode *mode.Modeset 31 | fbs [2]framebuffer 32 | frontbuf uint 33 | savedCrtc *mode.Crtc 34 | } 35 | ) 36 | 37 | func createFramebuffer(file *os.File, dev *mode.Modeset) (framebuffer, error) { 38 | fb, err := mode.CreateFB(file, dev.Width, dev.Height, 32) 39 | if err != nil { 40 | return framebuffer{}, fmt.Errorf("Failed to create framebuffer: %s", err.Error()) 41 | } 42 | stride := fb.Pitch 43 | size := fb.Size 44 | handle := fb.Handle 45 | 46 | fbID, err := mode.AddFB(file, dev.Width, dev.Height, 24, 32, stride, handle) 47 | if err != nil { 48 | return framebuffer{}, fmt.Errorf("Cannot create dumb buffer: %s", err.Error()) 49 | } 50 | 51 | offset, err := mode.MapDumb(file, handle) 52 | if err != nil { 53 | return framebuffer{}, err 54 | } 55 | 56 | mmap, err := gommap.MapAt(0, uintptr(file.Fd()), int64(offset), int64(size), gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED) 57 | if err != nil { 58 | return framebuffer{}, fmt.Errorf("Failed to mmap framebuffer: %s", err.Error()) 59 | } 60 | for i := uint64(0); i < size; i++ { 61 | mmap[i] = 0 62 | } 63 | framebuf := framebuffer{ 64 | id: fbID, 65 | handle: handle, 66 | data: mmap, 67 | fb: fb, 68 | size: size, 69 | stride: stride, 70 | } 71 | return framebuf, nil 72 | } 73 | 74 | func draw(file *os.File, msets []msetData) { 75 | var ( 76 | r, g, b uint8 77 | rUp, gUp, bUp = true, true, true 78 | off uint32 79 | ) 80 | 81 | rand.Seed(int64(time.Now().Unix())) 82 | r = uint8(rand.Intn(256)) 83 | g = uint8(rand.Intn(256)) 84 | b = uint8(rand.Intn(256)) 85 | 86 | for i := 0; i < 50; i++ { 87 | r = nextColor(&rUp, r, 20) 88 | g = nextColor(&gUp, g, 10) 89 | b = nextColor(&bUp, b, 5) 90 | 91 | for j := 0; j < len(msets); j++ { 92 | mset := msets[j] 93 | buf := &mset.fbs[mset.frontbuf^1] 94 | for k := uint16(0); k < mset.mode.Height; k++ { 95 | for s := uint16(0); s < mset.mode.Width; s++ { 96 | off = (buf.stride * uint32(k)) + (uint32(s) * 4) 97 | val := uint32((uint32(r) << 16) | (uint32(g) << 8) | uint32(b)) 98 | *(*uint32)(unsafe.Pointer(&buf.data[off])) = val 99 | } 100 | } 101 | 102 | err := mode.SetCrtc(file, mset.mode.Crtc, buf.id, 0, 0, &mset.mode.Conn, 1, &mset.mode.Mode) 103 | if err != nil { 104 | log.Printf("[error] Cannot flip CRTC for connector %d: %s", mset.mode.Conn, err.Error()) 105 | return 106 | } 107 | 108 | mset.frontbuf ^= 1 109 | } 110 | 111 | time.Sleep(150 * time.Millisecond) 112 | } 113 | } 114 | 115 | func nextColor(up *bool, cur uint8, mod int) uint8 { 116 | var next uint8 117 | 118 | if *up { 119 | next = cur + 1 120 | } else { 121 | next = cur - 1 122 | } 123 | next = next * uint8(rand.Intn(mod)) 124 | if (*up && next < cur) || (!*up && next > cur) { 125 | *up = !*up 126 | next = cur 127 | } 128 | return next 129 | } 130 | 131 | func destroyFramebuffer(modeset *mode.SimpleModeset, mset msetData, file *os.File) { 132 | fbs := mset.fbs 133 | 134 | for _, fb := range fbs { 135 | handle := fb.handle 136 | data := fb.data 137 | 138 | err := gommap.MMap(data).UnsafeUnmap() 139 | if err != nil { 140 | fmt.Fprintf(os.Stderr, "Failed to munmap memory: %s\n", err.Error()) 141 | continue 142 | } 143 | err = mode.RmFB(file, fb.id) 144 | if err != nil { 145 | fmt.Fprintf(os.Stderr, "Failed to remove frame buffer: %s\n", err.Error()) 146 | continue 147 | } 148 | 149 | err = mode.DestroyDumb(file, handle) 150 | if err != nil { 151 | fmt.Fprintf(os.Stderr, "Failed to destroy dumb buffer: %s\n", err.Error()) 152 | continue 153 | } 154 | 155 | err = modeset.SetCrtc(mset.mode, mset.savedCrtc) 156 | if err != nil { 157 | fmt.Fprintf(os.Stderr, err.Error()) 158 | continue 159 | } 160 | } 161 | } 162 | 163 | func cleanup(modeset *mode.SimpleModeset, msets []msetData, file *os.File) { 164 | for _, mset := range msets { 165 | destroyFramebuffer(modeset, mset, file) 166 | } 167 | 168 | } 169 | 170 | func main() { 171 | file, err := drm.OpenCard(0) 172 | if err != nil { 173 | fmt.Printf("error: %s", err.Error()) 174 | return 175 | } 176 | defer file.Close() 177 | if !drm.HasDumbBuffer(file) { 178 | fmt.Printf("drm device does not support dumb buffers") 179 | return 180 | } 181 | 182 | modeset, err := mode.NewSimpleModeset(file) 183 | if err != nil { 184 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 185 | os.Exit(1) 186 | } 187 | 188 | var msets []msetData 189 | for _, mod := range modeset.Modesets { 190 | framebuf1, err := createFramebuffer(file, &mod) 191 | if err != nil { 192 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 193 | cleanup(modeset, msets, file) 194 | return 195 | } 196 | 197 | framebuf2, err := createFramebuffer(file, &mod) 198 | if err != nil { 199 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 200 | cleanup(modeset, msets, file) 201 | return 202 | } 203 | 204 | // save current CRTC of this mode to restore at exit 205 | savedCrtc, err := mode.GetCrtc(file, mod.Crtc) 206 | if err != nil { 207 | fmt.Fprintf(os.Stderr, "error: Cannot get CRTC for connector %d: %s", mod.Conn, err.Error()) 208 | cleanup(modeset, msets, file) 209 | return 210 | } 211 | // change the mode using framebuf1 initially 212 | err = mode.SetCrtc(file, mod.Crtc, framebuf1.id, 0, 0, &mod.Conn, 1, &mod.Mode) 213 | if err != nil { 214 | fmt.Fprintf(os.Stderr, "Cannot set CRTC for connector %d: %s", mod.Conn, err.Error()) 215 | cleanup(modeset, msets, file) 216 | return 217 | } 218 | msets = append(msets, msetData{ 219 | frontbuf: 0, 220 | mode: &mod, 221 | fbs: [2]framebuffer{ 222 | framebuf1, framebuf2, 223 | }, 224 | savedCrtc: savedCrtc, 225 | }) 226 | } 227 | 228 | draw(file, msets) 229 | cleanup(modeset, msets, file) 230 | } 231 | -------------------------------------------------------------------------------- /_examples/modeset/modeset.go: -------------------------------------------------------------------------------- 1 | // Port of modeset.c example to Go 2 | // Source: https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset.c 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | "os" 9 | "time" 10 | "unsafe" 11 | 12 | "launchpad.net/gommap" 13 | 14 | "github.com/NeowayLabs/drm" 15 | "github.com/NeowayLabs/drm/mode" 16 | ) 17 | 18 | type ( 19 | framebuffer struct { 20 | id uint32 21 | handle uint32 22 | data []byte 23 | fb *mode.FB 24 | size uint64 25 | stride uint32 26 | } 27 | 28 | // msetData just store the pair (mode, fb) and the saved CRTC of the mode. 29 | msetData struct { 30 | mode *mode.Modeset 31 | fb framebuffer 32 | savedCrtc *mode.Crtc 33 | } 34 | ) 35 | 36 | func createFramebuffer(file *os.File, dev *mode.Modeset) (framebuffer, error) { 37 | fb, err := mode.CreateFB(file, dev.Width, dev.Height, 32) 38 | if err != nil { 39 | return framebuffer{}, fmt.Errorf("Failed to create framebuffer: %s", err.Error()) 40 | } 41 | stride := fb.Pitch 42 | size := fb.Size 43 | handle := fb.Handle 44 | 45 | fbID, err := mode.AddFB(file, dev.Width, dev.Height, 24, 32, stride, handle) 46 | if err != nil { 47 | return framebuffer{}, fmt.Errorf("Cannot create dumb buffer: %s", err.Error()) 48 | } 49 | 50 | offset, err := mode.MapDumb(file, handle) 51 | if err != nil { 52 | return framebuffer{}, err 53 | } 54 | 55 | mmap, err := gommap.MapAt(0, uintptr(file.Fd()), int64(offset), int64(size), gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED) 56 | if err != nil { 57 | return framebuffer{}, fmt.Errorf("Failed to mmap framebuffer: %s", err.Error()) 58 | } 59 | for i := uint64(0); i < size; i++ { 60 | mmap[i] = 0 61 | } 62 | framebuf := framebuffer{ 63 | id: fbID, 64 | handle: handle, 65 | data: mmap, 66 | fb: fb, 67 | size: size, 68 | stride: stride, 69 | } 70 | return framebuf, nil 71 | } 72 | 73 | func draw(msets []msetData) { 74 | var ( 75 | r, g, b uint8 76 | rUp, gUp, bUp = true, true, true 77 | off uint32 78 | ) 79 | 80 | rand.Seed(int64(time.Now().Unix())) 81 | r = uint8(rand.Intn(256)) 82 | g = uint8(rand.Intn(256)) 83 | b = uint8(rand.Intn(256)) 84 | 85 | for i := 0; i < 50; i++ { 86 | r = nextColor(&rUp, r, 20) 87 | g = nextColor(&gUp, g, 10) 88 | b = nextColor(&bUp, b, 5) 89 | 90 | for j := 0; j < len(msets); j++ { 91 | mset := msets[j] 92 | for k := uint16(0); k < mset.mode.Height; k++ { 93 | for s := uint16(0); s < mset.mode.Width; s++ { 94 | off = (mset.fb.stride * uint32(k)) + (uint32(s) * 4) 95 | val := uint32((uint32(r) << 16) | (uint32(g) << 8) | uint32(b)) 96 | *(*uint32)(unsafe.Pointer(&mset.fb.data[off])) = val 97 | } 98 | } 99 | } 100 | 101 | time.Sleep(150 * time.Millisecond) 102 | } 103 | } 104 | 105 | func nextColor(up *bool, cur uint8, mod int) uint8 { 106 | var next uint8 107 | 108 | if *up { 109 | next = cur + 1 110 | } else { 111 | next = cur - 1 112 | } 113 | next = next * uint8(rand.Intn(mod)) 114 | if (*up && next < cur) || (!*up && next > cur) { 115 | *up = !*up 116 | next = cur 117 | } 118 | return next 119 | } 120 | 121 | func destroyFramebuffer(modeset *mode.SimpleModeset, mset msetData, file *os.File) error { 122 | handle := mset.fb.handle 123 | data := mset.fb.data 124 | fb := mset.fb 125 | 126 | err := gommap.MMap(data).UnsafeUnmap() 127 | if err != nil { 128 | return fmt.Errorf("Failed to munmap memory: %s\n", err.Error()) 129 | } 130 | err = mode.RmFB(file, fb.id) 131 | if err != nil { 132 | return fmt.Errorf("Failed to remove frame buffer: %s\n", err.Error()) 133 | } 134 | 135 | err = mode.DestroyDumb(file, handle) 136 | if err != nil { 137 | return fmt.Errorf("Failed to destroy dumb buffer: %s\n", err.Error()) 138 | } 139 | return modeset.SetCrtc(mset.mode, mset.savedCrtc) 140 | } 141 | 142 | func cleanup(modeset *mode.SimpleModeset, msets []msetData, file *os.File) { 143 | for _, mset := range msets { 144 | destroyFramebuffer(modeset, mset, file) 145 | } 146 | 147 | } 148 | 149 | func main() { 150 | file, err := drm.OpenCard(0) 151 | if err != nil { 152 | fmt.Printf("error: %s", err.Error()) 153 | return 154 | } 155 | defer file.Close() 156 | if !drm.HasDumbBuffer(file) { 157 | fmt.Printf("drm device does not support dumb buffers") 158 | return 159 | } 160 | modeset, err := mode.NewSimpleModeset(file) 161 | if err != nil { 162 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 163 | os.Exit(1) 164 | } 165 | 166 | var msets []msetData 167 | for _, mod := range modeset.Modesets { 168 | framebuf, err := createFramebuffer(file, &mod) 169 | if err != nil { 170 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 171 | cleanup(modeset, msets, file) 172 | return 173 | } 174 | 175 | // save current CRTC of this mode to restore at exit 176 | savedCrtc, err := mode.GetCrtc(file, mod.Crtc) 177 | if err != nil { 178 | fmt.Fprintf(os.Stderr, "error: Cannot get CRTC for connector %d: %s", mod.Conn, err.Error()) 179 | cleanup(modeset, msets, file) 180 | return 181 | } 182 | // change the mode 183 | err = mode.SetCrtc(file, mod.Crtc, framebuf.id, 0, 0, &mod.Conn, 1, &mod.Mode) 184 | if err != nil { 185 | fmt.Fprintf(os.Stderr, "Cannot set CRTC for connector %d: %s", mod.Conn, err.Error()) 186 | cleanup(modeset, msets, file) 187 | return 188 | } 189 | msets = append(msets, msetData{ 190 | mode: &mod, 191 | fb: framebuf, 192 | savedCrtc: savedCrtc, 193 | }) 194 | } 195 | 196 | draw(msets) 197 | cleanup(modeset, msets, file) 198 | } 199 | -------------------------------------------------------------------------------- /cap.go: -------------------------------------------------------------------------------- 1 | package drm 2 | 3 | import ( 4 | "os" 5 | "unsafe" 6 | 7 | "github.com/NeowayLabs/drm/ioctl" 8 | ) 9 | 10 | type ( 11 | capability struct { 12 | id uint64 13 | val uint64 14 | } 15 | ) 16 | 17 | const ( 18 | CapDumbBuffer uint64 = iota + 1 19 | CapVBlankHighCRTC 20 | CapDumbPreferredDepth 21 | CapDumbPreferShadow 22 | CapPrime 23 | CapTimestampMonotonic 24 | CapAsyncPageFlip 25 | CapCursorWidth 26 | CapCursorHeight 27 | 28 | CapAddFB2Modifiers = 0x10 29 | ) 30 | 31 | func HasDumbBuffer(file *os.File) bool { 32 | cap, err := GetCap(file, CapDumbBuffer) 33 | if err != nil { 34 | return false 35 | } 36 | return cap != 0 37 | } 38 | 39 | func GetCap(file *os.File, capid uint64) (uint64, error) { 40 | cap := &capability{} 41 | cap.id = capid 42 | err := ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLGetCap), uintptr(unsafe.Pointer(cap))) 43 | if err != nil { 44 | return 0, err 45 | } 46 | return cap.val, nil 47 | } 48 | -------------------------------------------------------------------------------- /cap_test.go: -------------------------------------------------------------------------------- 1 | package drm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/NeowayLabs/drm" 7 | ) 8 | 9 | func TestHasDumbBuffer(t *testing.T) { 10 | file, err := drm.OpenCard(0) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | defer file.Close() 15 | version, err := drm.GetVersion(file) 16 | if err != nil { 17 | t.Error(err) 18 | return 19 | } 20 | if hasDumb := drm.HasDumbBuffer(file); hasDumb != (cardInfo.capabilities[drm.CapDumbBuffer] != 0) { 21 | t.Errorf("Card '%s' should support dumb buffers...Got %v but %d", version.Name, hasDumb, cardInfo.capabilities[drm.CapDumbBuffer]) 22 | return 23 | } 24 | } 25 | 26 | func TestGetCap(t *testing.T) { 27 | file, err := drm.OpenCard(0) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | defer file.Close() 32 | for cap, capval := range cardInfo.capabilities { 33 | ccap, err := drm.GetCap(file, cap) 34 | if err != nil { 35 | t.Error(err) 36 | return 37 | } 38 | if ccap != capval { 39 | t.Errorf("Capability %d differs: %d != %d", cap, ccap, capval) 40 | return 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /codes.go: -------------------------------------------------------------------------------- 1 | package drm 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/NeowayLabs/drm/ioctl" 7 | ) 8 | 9 | const IOCTLBase = 'd' 10 | 11 | var ( 12 | // DRM_IOWR(0x00, struct drm_version) 13 | IOCTLVersion = ioctl.NewCode(ioctl.Read|ioctl.Write, 14 | uint16(unsafe.Sizeof(version{})), IOCTLBase, 0) 15 | 16 | // DRM_IOWR(0x0c, struct drm_get_cap) 17 | IOCTLGetCap = ioctl.NewCode(ioctl.Read|ioctl.Write, 18 | uint16(unsafe.Sizeof(capability{})), IOCTLBase, 0x0c) 19 | ) 20 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package drm provides a library to interact with DRM 2 | // (Direct Rendering Manager) and KMS (Kernel Mode Setting) interfaces. 3 | // DRM is a low level interface for the graphics card (gpu) and this package 4 | // enables the creation of graphics library on top of the kernel drm/kms 5 | // subsystem. 6 | package drm 7 | -------------------------------------------------------------------------------- /drm.go: -------------------------------------------------------------------------------- 1 | package drm 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strconv" 9 | "strings" 10 | "unsafe" 11 | 12 | "github.com/NeowayLabs/drm/ioctl" 13 | ) 14 | 15 | type ( 16 | version struct { 17 | Major int32 18 | Minor int32 19 | Patch int32 20 | namelen int64 21 | name uintptr 22 | datelen int64 23 | date uintptr 24 | desclen int64 25 | desc uintptr 26 | } 27 | 28 | // Version of DRM driver 29 | Version struct { 30 | version 31 | 32 | Major, Minor, Patch int32 33 | Name string // Name of the driver (eg.: i915) 34 | Date string 35 | Desc string 36 | } 37 | ) 38 | 39 | const ( 40 | driPath = "/dev/dri" 41 | ) 42 | 43 | func Available() (Version, error) { 44 | f, err := OpenCard(0) 45 | if err != nil { 46 | // handle backward linux compat? 47 | // check /proc/dri/0 ? 48 | return Version{}, err 49 | } 50 | defer f.Close() 51 | return GetVersion(f) 52 | } 53 | 54 | func OpenCard(n int) (*os.File, error) { 55 | return open(fmt.Sprintf("%s/card%d", driPath, n)) 56 | } 57 | 58 | func OpenControlDev(n int) (*os.File, error) { 59 | return open(fmt.Sprintf("%s/controlD%d", driPath, n)) 60 | } 61 | 62 | func OpenRenderDev(n int) (*os.File, error) { 63 | return open(fmt.Sprintf("%s/renderD%d", driPath, n)) 64 | } 65 | 66 | func open(path string) (*os.File, error) { 67 | return os.OpenFile(path, os.O_RDWR, 0) 68 | } 69 | 70 | func GetVersion(file *os.File) (Version, error) { 71 | var ( 72 | name, date, desc []byte 73 | ) 74 | 75 | version := &version{} 76 | err := ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLVersion), 77 | uintptr(unsafe.Pointer(version))) 78 | if err != nil { 79 | return Version{}, err 80 | } 81 | 82 | if version.namelen > 0 { 83 | name = make([]byte, version.namelen+1) 84 | version.name = uintptr(unsafe.Pointer(&name[0])) 85 | } 86 | 87 | if version.datelen > 0 { 88 | date = make([]byte, version.datelen+1) 89 | version.date = uintptr(unsafe.Pointer(&date[0])) 90 | } 91 | if version.desclen > 0 { 92 | desc = make([]byte, version.desclen+1) 93 | version.desc = uintptr(unsafe.Pointer(&desc[0])) 94 | } 95 | 96 | err = ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLVersion), 97 | uintptr(unsafe.Pointer(version))) 98 | if err != nil { 99 | return Version{}, err 100 | } 101 | 102 | // remove C null byte at end 103 | name = name[:version.namelen] 104 | date = date[:version.datelen] 105 | desc = desc[:version.desclen] 106 | 107 | nozero := func(r rune) bool { 108 | if r == 0 { 109 | return true 110 | } else { 111 | return false 112 | } 113 | } 114 | 115 | v := Version{ 116 | version: *version, 117 | Major: version.Major, 118 | Minor: version.Minor, 119 | Patch: version.Patch, 120 | 121 | Name: string(bytes.TrimFunc(name, nozero)), 122 | Date: string(bytes.TrimFunc(date, nozero)), 123 | Desc: string(bytes.TrimFunc(desc, nozero)), 124 | } 125 | 126 | return v, nil 127 | } 128 | 129 | func ListDevices() []Version { 130 | var devices []Version 131 | files, err := ioutil.ReadDir(driPath) 132 | if err != nil { 133 | return devices 134 | } 135 | 136 | for _, finfo := range files { 137 | name := finfo.Name() 138 | if len(name) <= 4 || 139 | !strings.HasPrefix(name, "card") { 140 | continue 141 | } 142 | 143 | idstr := name[4:] 144 | id, err := strconv.Atoi(idstr) 145 | if err != nil { 146 | continue 147 | } 148 | 149 | devfile, err := OpenCard(id) 150 | if err != nil { 151 | continue 152 | } 153 | dev, err := GetVersion(devfile) 154 | if err != nil { 155 | continue 156 | } 157 | devices = append(devices, dev) 158 | } 159 | 160 | return devices 161 | } 162 | -------------------------------------------------------------------------------- /drm_main_test.go: -------------------------------------------------------------------------------- 1 | package drm_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/NeowayLabs/drm" 9 | ) 10 | 11 | type ( 12 | cardDetail struct { 13 | version drm.Version 14 | capabilities map[uint64]uint64 15 | } 16 | ) 17 | 18 | var ( 19 | card, errCard = drm.Available() 20 | cards = map[string]cardDetail{ 21 | "i915": cardDetail{ 22 | version: drm.Version{ 23 | Major: 1, 24 | Minor: 6, 25 | Patch: 1, 26 | Name: "i915", 27 | Desc: "i915", 28 | Date: "20160425", 29 | }, 30 | capabilities: map[uint64]uint64{ 31 | drm.CapDumbBuffer: 1, 32 | drm.CapVBlankHighCRTC: 1, 33 | drm.CapDumbPreferredDepth: 24, 34 | drm.CapDumbPreferShadow: 1, 35 | drm.CapPrime: 3, 36 | drm.CapTimestampMonotonic: 1, 37 | drm.CapAsyncPageFlip: 0, 38 | drm.CapCursorWidth: 256, 39 | drm.CapCursorHeight: 256, 40 | 41 | drm.CapAddFB2Modifiers: 1, 42 | }, 43 | }, 44 | } 45 | cardInfo cardDetail 46 | ) 47 | 48 | func TestMain(m *testing.M) { 49 | cards[""] = cards["i915"] // i915 bug in 4.8 kernel? 50 | 51 | if errCard != nil { 52 | fmt.Fprintf(os.Stderr, "No graphics card available to test") 53 | os.Exit(1) 54 | } 55 | if _, ok := cards[card.Name]; !ok { 56 | fmt.Fprintf(os.Stderr, "No tests for card %s", card.Name) 57 | os.Exit(1) 58 | } 59 | cardInfo = cards[card.Name] 60 | os.Exit(m.Run()) 61 | } 62 | -------------------------------------------------------------------------------- /drm_test.go: -------------------------------------------------------------------------------- 1 | package drm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/NeowayLabs/drm" 7 | "github.com/NeowayLabs/drm/mode" 8 | ) 9 | 10 | func TestDRIOpen(t *testing.T) { 11 | file, err := drm.OpenCard(0) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | file.Close() 16 | } 17 | 18 | func TestAvailableCard(t *testing.T) { 19 | v, err := drm.Available() 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | if v.Major == 0 && v.Minor == 0 && v.Patch == 0 { 24 | t.Fatalf("failed to get driver version: %#v", v) 25 | } 26 | if v.Major != cardInfo.version.Major && v.Minor != cardInfo.version.Minor && 27 | v.Patch != cardInfo.version.Patch { 28 | t.Logf("Unknow driver version: %d.%d.%d", v.Major, v.Minor, v.Patch) 29 | } 30 | 31 | t.Logf("Driver name: %s", v.Name) 32 | t.Logf("Driver version: %d.%d.%d", v.Major, v.Minor, v.Patch) 33 | t.Logf("Driver date: %s", v.Date) 34 | t.Logf("Driver description: %s", v.Desc) 35 | } 36 | 37 | func TestModeRes(t *testing.T) { 38 | file, err := drm.OpenCard(0) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | defer file.Close() 43 | mres, err := mode.GetResources(file) 44 | if err != nil { 45 | t.Error(err) 46 | return 47 | } 48 | 49 | t.Logf("Number of framebuffers: %d", mres.CountFbs) 50 | t.Logf("Number of CRTCs: %d", mres.CountCrtcs) 51 | t.Logf("Number of connectors: %d", mres.CountConnectors) 52 | t.Logf("Number of encoders: %d", mres.CountEncoders) 53 | t.Logf("Framebuffers ids: %v", mres.Fbs) 54 | t.Logf("CRTC ids: %v", mres.Crtcs) 55 | t.Logf("Connector ids: %v", mres.Connectors) 56 | t.Logf("Encoder ids: %v", mres.Encoders) 57 | } 58 | -------------------------------------------------------------------------------- /examples_test.go: -------------------------------------------------------------------------------- 1 | package drm_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NeowayLabs/drm" 7 | ) 8 | 9 | func ExampleHasDumbBuffer() { 10 | // This example shows how to test if your graphics card 11 | // supports 'dumb buffers' capability. With this capability 12 | // you can create simple memory-mapped buffers without any 13 | // driver-dependent code. 14 | 15 | file, err := drm.OpenCard(0) 16 | if err != nil { 17 | fmt.Printf("error: %s", err.Error()) 18 | return 19 | } 20 | defer file.Close() 21 | if !drm.HasDumbBuffer(file) { 22 | fmt.Printf("drm device does not support dumb buffers") 23 | return 24 | } 25 | fmt.Printf("ok") 26 | 27 | // Output: ok 28 | } 29 | 30 | func ExampleListDevices() { 31 | // Shows how to enumerate the available dri devices 32 | for _, dev := range drm.ListDevices() { 33 | fmt.Printf("Driver name: %s\n", dev.Name) 34 | } 35 | 36 | // Output: Driver name: i915 37 | } 38 | -------------------------------------------------------------------------------- /ioctl/ioctl_linux.go: -------------------------------------------------------------------------------- 1 | package ioctl 2 | 3 | import ( 4 | "fmt" 5 | "syscall" 6 | ) 7 | 8 | // To decode a hex IOCTL code: 9 | // 10 | // Most architectures use this generic format, but check 11 | // include/ARCH/ioctl.h for specifics, e.g. powerpc 12 | // uses 3 bits to encode read/write and 13 bits for size. 13 | // 14 | // bits meaning 15 | // 31-30 00 - no parameters: uses _IO macro 16 | // 10 - read: _IOR 17 | // 01 - write: _IOW 18 | // 11 - read/write: _IOWR 19 | // 20 | // 29-16 size of arguments 21 | // 22 | // 15-8 ascii character supposedly 23 | // unique to each driver 24 | // 25 | // 7-0 function # 26 | // 27 | // So for example 0x82187201 is a read with arg length of 0x218, 28 | // character 'r' function 1. Grepping the source reveals this is: 29 | // 30 | // #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, struct dirent [2]) 31 | // source: https://www.kernel.org/doc/Documentation/ioctl/ioctl-decoding.txt 32 | 33 | type Code struct { 34 | typ uint8 // type of ioctl call (read, write, both or none) 35 | sz uint16 // size of arguments (only 13bits usable) 36 | uniq uint8 // unique ascii character for this device 37 | fn uint8 // function code 38 | } 39 | 40 | const ( 41 | None = uint8(0x0) 42 | Write = uint8(0x1) 43 | Read = uint8(0x2) 44 | ) 45 | 46 | func NewCode(typ uint8, sz uint16, uniq, fn uint8) uint32 { 47 | var code uint32 48 | if typ > Write|Read { 49 | panic(fmt.Errorf("invalid ioctl code value: %d\n", typ)) 50 | } 51 | 52 | if sz > 2<<14 { 53 | panic(fmt.Errorf("invalid ioctl size value: %d\n", sz)) 54 | } 55 | 56 | code = code | (uint32(typ) << 30) 57 | code = code | (uint32(sz) << 16) // sz has 14bits 58 | code = code | (uint32(uniq) << 8) 59 | code = code | uint32(fn) 60 | return code 61 | } 62 | 63 | func Do(fd, cmd, ptr uintptr) error { 64 | _, _, errcode := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) 65 | if errcode != 0 { 66 | return errcode 67 | } 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /ioctl/ioctl_test.go: -------------------------------------------------------------------------------- 1 | package ioctl 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func getbits(n uint32) string { 9 | return strconv.FormatUint(uint64(n), 2) 10 | } 11 | 12 | func TestNewCode(t *testing.T) { 13 | code := NewCode(Read, 0x218, 'r', 1) 14 | expected := uint32(0x82187201) 15 | if code != expected { 16 | t.Errorf("Expected %s but got %s", getbits(expected), 17 | getbits(code)) 18 | return 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mode/mode.go: -------------------------------------------------------------------------------- 1 | package mode 2 | 3 | import ( 4 | "os" 5 | "unsafe" 6 | 7 | "github.com/NeowayLabs/drm" 8 | "github.com/NeowayLabs/drm/ioctl" 9 | ) 10 | 11 | const ( 12 | DisplayInfoLen = 32 13 | ConnectorNameLen = 32 14 | DisplayModeLen = 32 15 | PropNameLen = 32 16 | 17 | Connected = 1 18 | Disconnected = 2 19 | UnknownConnection = 3 20 | ) 21 | 22 | type ( 23 | sysResources struct { 24 | fbIdPtr uint64 25 | crtcIdPtr uint64 26 | connectorIdPtr uint64 27 | encoderIdPtr uint64 28 | CountFbs uint32 29 | CountCrtcs uint32 30 | CountConnectors uint32 31 | CountEncoders uint32 32 | MinWidth, MaxWidth uint32 33 | MinHeight, MaxHeight uint32 34 | } 35 | 36 | sysGetConnector struct { 37 | encodersPtr uint64 38 | modesPtr uint64 39 | propsPtr uint64 40 | propValuesPtr uint64 41 | 42 | countModes uint32 43 | countProps uint32 44 | countEncoders uint32 45 | 46 | encoderID uint32 // current encoder 47 | ID uint32 48 | connectorType uint32 49 | connectorTypeID uint32 50 | 51 | connection uint32 52 | mmWidth, mmHeight uint32 // HxW in millimeters 53 | subpixel uint32 54 | } 55 | 56 | sysGetEncoder struct { 57 | id uint32 58 | typ uint32 59 | 60 | crtcID uint32 61 | 62 | possibleCrtcs uint32 63 | possibleClones uint32 64 | } 65 | 66 | Info struct { 67 | Clock uint32 68 | Hdisplay, HsyncStart, HsyncEnd, Htotal, Hskew uint16 69 | Vdisplay, VsyncStart, VsyncEnd, Vtotal, Vscan uint16 70 | 71 | Vrefresh uint32 72 | 73 | Flags uint32 74 | Type uint32 75 | Name [DisplayModeLen]uint8 76 | } 77 | 78 | Resources struct { 79 | sysResources 80 | 81 | Fbs []uint32 82 | Crtcs []uint32 83 | Connectors []uint32 84 | Encoders []uint32 85 | } 86 | 87 | Connector struct { 88 | sysGetConnector 89 | 90 | ID uint32 91 | EncoderID uint32 92 | Type uint32 93 | TypeID uint32 94 | Connection uint8 95 | Width, Height uint32 96 | Subpixel uint8 97 | 98 | Modes []Info 99 | 100 | Props []uint32 101 | PropValues []uint64 102 | 103 | Encoders []uint32 104 | } 105 | 106 | Encoder struct { 107 | ID uint32 108 | Type uint32 109 | 110 | CrtcID uint32 111 | 112 | PossibleCrtcs uint32 113 | PossibleClones uint32 114 | } 115 | 116 | sysCreateDumb struct { 117 | height, width uint32 118 | bpp uint32 119 | flags uint32 120 | 121 | // returned values 122 | handle uint32 123 | pitch uint32 124 | size uint64 125 | } 126 | 127 | sysMapDumb struct { 128 | handle uint32 // Handle for the object being mapped 129 | pad uint32 130 | 131 | // Fake offset to use for subsequent mmap call 132 | // This is a fixed-size type for 32/64 compatibility. 133 | offset uint64 134 | } 135 | 136 | sysFBCmd struct { 137 | fbID uint32 138 | width, height uint32 139 | pitch uint32 140 | bpp uint32 141 | depth uint32 142 | 143 | /* driver specific handle */ 144 | handle uint32 145 | } 146 | 147 | sysRmFB struct { 148 | handle uint32 149 | } 150 | 151 | sysCrtc struct { 152 | setConnectorsPtr uint64 153 | countConnectors uint32 154 | 155 | id uint32 156 | fbID uint32 // Id of framebuffer 157 | 158 | x, y uint32 // Position on the frameuffer 159 | 160 | gammaSize uint32 161 | modeValid uint32 162 | mode Info 163 | } 164 | 165 | sysDestroyDumb struct { 166 | handle uint32 167 | } 168 | 169 | Crtc struct { 170 | ID uint32 171 | BufferID uint32 // FB id to connect to 0 = disconnect 172 | 173 | X, Y uint32 // Position on the framebuffer 174 | Width, Height uint32 175 | ModeValid int 176 | Mode Info 177 | 178 | GammaSize int // Number of gamma stops 179 | } 180 | 181 | FB struct { 182 | Height, Width, BPP, Flags uint32 183 | Handle uint32 184 | Pitch uint32 185 | Size uint64 186 | } 187 | ) 188 | 189 | var ( 190 | // DRM_IOWR(0xA0, struct drm_mode_card_res) 191 | IOCTLModeResources = ioctl.NewCode(ioctl.Read|ioctl.Write, 192 | uint16(unsafe.Sizeof(sysResources{})), drm.IOCTLBase, 0xA0) 193 | 194 | // DRM_IOWR(0xA1, struct drm_mode_crtc) 195 | IOCTLModeGetCrtc = ioctl.NewCode(ioctl.Read|ioctl.Write, 196 | uint16(unsafe.Sizeof(sysCrtc{})), drm.IOCTLBase, 0xA1) 197 | 198 | // DRM_IOWR(0xA2, struct drm_mode_crtc) 199 | IOCTLModeSetCrtc = ioctl.NewCode(ioctl.Read|ioctl.Write, 200 | uint16(unsafe.Sizeof(sysCrtc{})), drm.IOCTLBase, 0xA2) 201 | 202 | // DRM_IOWR(0xA6, struct drm_mode_get_encoder) 203 | IOCTLModeGetEncoder = ioctl.NewCode(ioctl.Read|ioctl.Write, 204 | uint16(unsafe.Sizeof(sysGetEncoder{})), drm.IOCTLBase, 0xA6) 205 | 206 | // DRM_IOWR(0xA7, struct drm_mode_get_connector) 207 | IOCTLModeGetConnector = ioctl.NewCode(ioctl.Read|ioctl.Write, 208 | uint16(unsafe.Sizeof(sysGetConnector{})), drm.IOCTLBase, 0xA7) 209 | 210 | // DRM_IOWR(0xAE, struct drm_mode_fb_cmd) 211 | IOCTLModeAddFB = ioctl.NewCode(ioctl.Read|ioctl.Write, 212 | uint16(unsafe.Sizeof(sysFBCmd{})), drm.IOCTLBase, 0xAE) 213 | 214 | // DRM_IOWR(0xAF, unsigned int) 215 | IOCTLModeRmFB = ioctl.NewCode(ioctl.Read|ioctl.Write, 216 | uint16(unsafe.Sizeof(uint32(0))), drm.IOCTLBase, 0xAF) 217 | 218 | // DRM_IOWR(0xB2, struct drm_mode_create_dumb) 219 | IOCTLModeCreateDumb = ioctl.NewCode(ioctl.Read|ioctl.Write, 220 | uint16(unsafe.Sizeof(sysCreateDumb{})), drm.IOCTLBase, 0xB2) 221 | 222 | // DRM_IOWR(0xB3, struct drm_mode_map_dumb) 223 | IOCTLModeMapDumb = ioctl.NewCode(ioctl.Read|ioctl.Write, 224 | uint16(unsafe.Sizeof(sysMapDumb{})), drm.IOCTLBase, 0xB3) 225 | 226 | // DRM_IOWR(0xB4, struct drm_mode_destroy_dumb) 227 | IOCTLModeDestroyDumb = ioctl.NewCode(ioctl.Read|ioctl.Write, 228 | uint16(unsafe.Sizeof(sysDestroyDumb{})), drm.IOCTLBase, 0xB4) 229 | ) 230 | 231 | func GetResources(file *os.File) (*Resources, error) { 232 | mres := &sysResources{} 233 | err := ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeResources), 234 | uintptr(unsafe.Pointer(mres))) 235 | if err != nil { 236 | return nil, err 237 | } 238 | 239 | var ( 240 | fbids, crtcids, connectorids, encoderids []uint32 241 | ) 242 | 243 | if mres.CountFbs > 0 { 244 | fbids = make([]uint32, mres.CountFbs) 245 | mres.fbIdPtr = uint64(uintptr(unsafe.Pointer(&fbids[0]))) 246 | } 247 | if mres.CountCrtcs > 0 { 248 | crtcids = make([]uint32, mres.CountCrtcs) 249 | mres.crtcIdPtr = uint64(uintptr(unsafe.Pointer(&crtcids[0]))) 250 | } 251 | if mres.CountEncoders > 0 { 252 | encoderids = make([]uint32, mres.CountEncoders) 253 | mres.encoderIdPtr = uint64(uintptr(unsafe.Pointer(&encoderids[0]))) 254 | } 255 | if mres.CountConnectors > 0 { 256 | connectorids = make([]uint32, mres.CountConnectors) 257 | mres.connectorIdPtr = uint64(uintptr(unsafe.Pointer(&connectorids[0]))) 258 | } 259 | 260 | err = ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeResources), 261 | uintptr(unsafe.Pointer(mres))) 262 | if err != nil { 263 | return nil, err 264 | } 265 | 266 | // TODO(i4k): handle hotplugging in-between the ioctls above 267 | 268 | return &Resources{ 269 | sysResources: *mres, 270 | Fbs: fbids, 271 | Crtcs: crtcids, 272 | Encoders: encoderids, 273 | Connectors: connectorids, 274 | }, nil 275 | } 276 | 277 | func GetConnector(file *os.File, connid uint32) (*Connector, error) { 278 | conn := &sysGetConnector{} 279 | conn.ID = connid 280 | err := ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeGetConnector), 281 | uintptr(unsafe.Pointer(conn))) 282 | if err != nil { 283 | return nil, err 284 | } 285 | 286 | var ( 287 | props, encoders []uint32 288 | propValues []uint64 289 | modes []Info 290 | ) 291 | 292 | if conn.countProps > 0 { 293 | props = make([]uint32, conn.countProps) 294 | conn.propsPtr = uint64(uintptr(unsafe.Pointer(&props[0]))) 295 | 296 | propValues = make([]uint64, conn.countProps) 297 | conn.propValuesPtr = uint64(uintptr(unsafe.Pointer(&propValues[0]))) 298 | } 299 | 300 | if conn.countModes == 0 { 301 | conn.countModes = 1 302 | } 303 | 304 | modes = make([]Info, conn.countModes) 305 | conn.modesPtr = uint64(uintptr(unsafe.Pointer(&modes[0]))) 306 | 307 | if conn.countEncoders > 0 { 308 | encoders = make([]uint32, conn.countEncoders) 309 | conn.encodersPtr = uint64(uintptr(unsafe.Pointer(&encoders[0]))) 310 | } 311 | 312 | err = ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeGetConnector), 313 | uintptr(unsafe.Pointer(conn))) 314 | if err != nil { 315 | return nil, err 316 | } 317 | 318 | ret := &Connector{ 319 | sysGetConnector: *conn, 320 | ID: conn.ID, 321 | EncoderID: conn.encoderID, 322 | Connection: uint8(conn.connection), 323 | Width: conn.mmWidth, 324 | Height: conn.mmHeight, 325 | 326 | // convert subpixel from kernel to userspace */ 327 | Subpixel: uint8(conn.subpixel + 1), 328 | Type: conn.connectorType, 329 | TypeID: conn.connectorTypeID, 330 | } 331 | 332 | ret.Props = make([]uint32, len(props)) 333 | copy(ret.Props, props) 334 | ret.PropValues = make([]uint64, len(propValues)) 335 | copy(ret.PropValues, propValues) 336 | ret.Modes = make([]Info, len(modes)) 337 | copy(ret.Modes, modes) 338 | ret.Encoders = make([]uint32, len(encoders)) 339 | copy(ret.Encoders, encoders) 340 | 341 | return ret, nil 342 | } 343 | 344 | func GetEncoder(file *os.File, id uint32) (*Encoder, error) { 345 | encoder := &sysGetEncoder{} 346 | encoder.id = id 347 | 348 | err := ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeGetEncoder), 349 | uintptr(unsafe.Pointer(encoder))) 350 | if err != nil { 351 | return nil, err 352 | } 353 | 354 | return &Encoder{ 355 | ID: encoder.id, 356 | CrtcID: encoder.crtcID, 357 | Type: encoder.typ, 358 | PossibleCrtcs: encoder.possibleCrtcs, 359 | PossibleClones: encoder.possibleClones, 360 | }, nil 361 | } 362 | 363 | func CreateFB(file *os.File, width, height uint16, bpp uint32) (*FB, error) { 364 | fb := &sysCreateDumb{} 365 | fb.width = uint32(width) 366 | fb.height = uint32(height) 367 | fb.bpp = bpp 368 | err := ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeCreateDumb), 369 | uintptr(unsafe.Pointer(fb))) 370 | if err != nil { 371 | return nil, err 372 | } 373 | return &FB{ 374 | Height: fb.height, 375 | Width: fb.width, 376 | BPP: fb.bpp, 377 | Handle: fb.handle, 378 | Pitch: fb.pitch, 379 | Size: fb.size, 380 | }, nil 381 | } 382 | 383 | func AddFB(file *os.File, width, height uint16, 384 | depth, bpp uint8, pitch, boHandle uint32) (uint32, error) { 385 | f := &sysFBCmd{} 386 | f.width = uint32(width) 387 | f.height = uint32(height) 388 | f.pitch = pitch 389 | f.bpp = uint32(bpp) 390 | f.depth = uint32(depth) 391 | f.handle = boHandle 392 | err := ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeAddFB), 393 | uintptr(unsafe.Pointer(f))) 394 | if err != nil { 395 | return 0, err 396 | } 397 | return f.fbID, nil 398 | } 399 | 400 | func RmFB(file *os.File, bufferid uint32) error { 401 | return ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeRmFB), 402 | uintptr(unsafe.Pointer(&sysRmFB{bufferid}))) 403 | } 404 | 405 | func MapDumb(file *os.File, boHandle uint32) (uint64, error) { 406 | mreq := &sysMapDumb{} 407 | mreq.handle = boHandle 408 | err := ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeMapDumb), 409 | uintptr(unsafe.Pointer(mreq))) 410 | if err != nil { 411 | return 0, err 412 | } 413 | return mreq.offset, nil 414 | } 415 | 416 | func DestroyDumb(file *os.File, handle uint32) error { 417 | return ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeDestroyDumb), 418 | uintptr(unsafe.Pointer(&sysDestroyDumb{handle}))) 419 | } 420 | 421 | func GetCrtc(file *os.File, id uint32) (*Crtc, error) { 422 | crtc := &sysCrtc{} 423 | crtc.id = id 424 | err := ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeGetCrtc), 425 | uintptr(unsafe.Pointer(crtc))) 426 | if err != nil { 427 | return nil, err 428 | } 429 | ret := &Crtc{ 430 | ID: crtc.id, 431 | X: crtc.x, 432 | Y: crtc.y, 433 | ModeValid: int(crtc.modeValid), 434 | BufferID: crtc.fbID, 435 | GammaSize: int(crtc.gammaSize), 436 | } 437 | 438 | ret.Mode = crtc.mode 439 | ret.Width = uint32(crtc.mode.Hdisplay) 440 | ret.Height = uint32(crtc.mode.Vdisplay) 441 | return ret, nil 442 | } 443 | 444 | func SetCrtc(file *os.File, crtcid, bufferid, x, y uint32, connectors *uint32, count int, mode *Info) error { 445 | crtc := &sysCrtc{} 446 | crtc.x = x 447 | crtc.y = y 448 | crtc.id = crtcid 449 | crtc.fbID = bufferid 450 | if connectors != nil { 451 | crtc.setConnectorsPtr = uint64(uintptr(unsafe.Pointer(connectors))) 452 | } 453 | crtc.countConnectors = uint32(count) 454 | if mode != nil { 455 | crtc.mode = *mode 456 | crtc.modeValid = 1 457 | } 458 | return ioctl.Do(uintptr(file.Fd()), uintptr(IOCTLModeSetCrtc), 459 | uintptr(unsafe.Pointer(crtc))) 460 | } 461 | -------------------------------------------------------------------------------- /mode/simple.go: -------------------------------------------------------------------------------- 1 | // Port of modeset.c example to Go 2 | // Source: https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset.c 3 | package mode 4 | 5 | import ( 6 | "fmt" 7 | _ "image/jpeg" 8 | "os" 9 | ) 10 | 11 | type ( 12 | Modeset struct { 13 | Width, Height uint16 14 | 15 | Mode Info 16 | Conn uint32 17 | Crtc uint32 18 | } 19 | 20 | SimpleModeset struct { 21 | Modesets []Modeset 22 | driFile *os.File 23 | } 24 | ) 25 | 26 | func (mset *SimpleModeset) prepare() error { 27 | res, err := GetResources(mset.driFile) 28 | if err != nil { 29 | return fmt.Errorf("Cannot retrieve resources: %s", err.Error()) 30 | } 31 | 32 | for i := 0; i < len(res.Connectors); i++ { 33 | conn, err := GetConnector(mset.driFile, res.Connectors[i]) 34 | if err != nil { 35 | return fmt.Errorf("Cannot retrieve connector: %s", err.Error()) 36 | } 37 | 38 | dev := Modeset{} 39 | dev.Conn = conn.ID 40 | ok, err := mset.setupDev(res, conn, &dev) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | if !ok { 46 | continue 47 | } 48 | 49 | mset.Modesets = append(mset.Modesets, dev) 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func (mset *SimpleModeset) setupDev(res *Resources, conn *Connector, dev *Modeset) (bool, error) { 56 | // check if a monitor is connected 57 | if conn.Connection != Connected { 58 | return false, nil 59 | } 60 | 61 | // check if there is at least one valid mode 62 | if len(conn.Modes) == 0 { 63 | return false, fmt.Errorf("no valid mode for connector %d\n", conn.ID) 64 | } 65 | dev.Mode = conn.Modes[0] 66 | dev.Width = conn.Modes[0].Hdisplay 67 | dev.Height = conn.Modes[0].Vdisplay 68 | 69 | err := mset.findCrtc(res, conn, dev) 70 | if err != nil { 71 | return false, fmt.Errorf("no valid crtc for connector %u: %s", conn.ID, err.Error()) 72 | } 73 | 74 | return true, nil 75 | } 76 | 77 | func (mset *SimpleModeset) findCrtc(res *Resources, conn *Connector, dev *Modeset) error { 78 | var ( 79 | encoder *Encoder 80 | err error 81 | ) 82 | 83 | if conn.EncoderID != 0 { 84 | encoder, err = GetEncoder(mset.driFile, conn.EncoderID) 85 | if err != nil { 86 | return err 87 | } 88 | } 89 | 90 | if encoder != nil { 91 | if encoder.CrtcID != 0 { 92 | crtcid := encoder.CrtcID 93 | found := false 94 | 95 | for i := 0; i < len(mset.Modesets); i++ { 96 | if mset.Modesets[i].Crtc == crtcid { 97 | found = true 98 | break 99 | } 100 | } 101 | 102 | if crtcid >= 0 && !found { 103 | dev.Crtc = crtcid 104 | return nil 105 | } 106 | } 107 | } 108 | 109 | // If the connector is not currently bound to an encoder or if the 110 | // encoder+crtc is already used by another connector (actually unlikely 111 | // but lets be safe), iterate all other available encoders to find a 112 | // matching CRTC. 113 | for i := 0; i < len(conn.Encoders); i++ { 114 | encoder, err := GetEncoder(mset.driFile, conn.Encoders[i]) 115 | if err != nil { 116 | return fmt.Errorf("Cannot retrieve encoder: %s", err.Error()) 117 | } 118 | // iterate all global CRTCs 119 | for j := 0; j < len(res.Crtcs); j++ { 120 | // check whether this CRTC works with the encoder 121 | if (encoder.PossibleCrtcs & (1 << uint(j))) != 0 { 122 | continue 123 | } 124 | 125 | // check that no other device already uses this CRTC 126 | crtcid := res.Crtcs[j] 127 | found := false 128 | for k := 0; k < len(mset.Modesets); k++ { 129 | if mset.Modesets[k].Crtc == crtcid { 130 | found = true 131 | break 132 | } 133 | } 134 | 135 | // we have found a CRTC, so save it and return 136 | if crtcid >= 0 && !found { 137 | dev.Crtc = crtcid 138 | return nil 139 | } 140 | } 141 | } 142 | 143 | return fmt.Errorf("Cannot find a suitable CRTC for connector %d", conn.ID) 144 | } 145 | 146 | func (mset *SimpleModeset) SetCrtc(dev *Modeset, savedCrtc *Crtc) error { 147 | err := SetCrtc(mset.driFile, savedCrtc.ID, 148 | savedCrtc.BufferID, 149 | savedCrtc.X, savedCrtc.Y, 150 | &dev.Conn, 151 | 1, 152 | &savedCrtc.Mode, 153 | ) 154 | if err != nil { 155 | return fmt.Errorf("Failed to restore CRTC: %s\n", err.Error()) 156 | } 157 | 158 | return nil 159 | } 160 | 161 | func NewSimpleModeset(file *os.File) (*SimpleModeset, error) { 162 | var err error 163 | 164 | mset := &SimpleModeset{ 165 | driFile: file, 166 | } 167 | err = mset.prepare() 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | return mset, nil 173 | } 174 | --------------------------------------------------------------------------------