├── .gitignore ├── LICENSE ├── README.md ├── example └── main.go ├── hid.go ├── usb_linux.go ├── usbdef32_linux.go ├── usbdef64_linux.go └── usbdef_linux.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 zserge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hid 2 | Simple HID driver for Go (pure golang, no dependencies, no cgo) 3 | 4 | Currently only Linux (386/amd64) is supported. 5 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "github.com/chzyer/readline" 12 | "github.com/zserge/hid" 13 | ) 14 | 15 | func shell(device hid.Device) { 16 | if err := device.Open(); err != nil { 17 | log.Println("Open error: ", err) 18 | return 19 | } 20 | defer device.Close() 21 | 22 | if report, err := device.HIDReport(); err != nil { 23 | log.Println("HID report error:", err) 24 | return 25 | } else { 26 | log.Println("HID report", hex.EncodeToString(report)) 27 | } 28 | 29 | go func() { 30 | for { 31 | if buf, err := device.Read(-1, 1*time.Second); err == nil { 32 | log.Println("Input report: ", hex.EncodeToString(buf)) 33 | } 34 | } 35 | }() 36 | 37 | commands := map[string]func([]byte){ 38 | "output": func(b []byte) { 39 | if len(b) == 0 { 40 | log.Println("Invalid input: output report data expected") 41 | } else if n, err := device.Write(b, 1*time.Second); err != nil { 42 | log.Println("Output report write failed:", err) 43 | } else { 44 | log.Printf("Output report: written %d bytes\n", n) 45 | } 46 | }, 47 | "set-feature": func(b []byte) { 48 | if len(b) == 0 { 49 | log.Println("Invalid input: feature report data expected") 50 | } else if err := device.SetReport(0, b); err != nil { 51 | log.Println("Feature report write failed:", err) 52 | } else { 53 | log.Printf("Feature report: " + hex.EncodeToString(b) + "\n") 54 | } 55 | }, 56 | "get-feature": func(b []byte) { 57 | if b, err := device.GetReport(0); err != nil { 58 | log.Println("Feature report read failed:", err) 59 | } else { 60 | log.Println("Feature report: " + hex.EncodeToString(b) + "\n") 61 | } 62 | }, 63 | } 64 | 65 | var completer = readline.NewPrefixCompleter( 66 | readline.PcItem("output"), 67 | readline.PcItem("set-feature"), 68 | readline.PcItem("get-feature"), 69 | ) 70 | rl, err := readline.NewEx(&readline.Config{ 71 | Prompt: "> ", 72 | AutoComplete: completer, 73 | }) 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | defer rl.Close() 79 | log.SetOutput(rl.Stderr()) 80 | 81 | out: 82 | for { 83 | line, err := rl.Readline() 84 | if err != nil { 85 | break 86 | } 87 | line = strings.ToLower(line) 88 | for cmd, f := range commands { 89 | if strings.HasPrefix(line, cmd) { 90 | line = strings.TrimSpace(line[len(cmd):]) 91 | raw := []byte{} 92 | if len(line) > 0 { 93 | raw = make([]byte, len(line)/2, len(line)/2) 94 | if _, err := hex.Decode(raw, []byte(line)); err != nil { 95 | log.Println("Invalid input:", err) 96 | log.Println(">>", hex.EncodeToString(raw)) 97 | continue out 98 | } 99 | } 100 | f(raw) 101 | continue out 102 | } 103 | } 104 | } 105 | } 106 | 107 | func main() { 108 | 109 | if len(os.Args) == 2 && (os.Args[1] == "-h" || os.Args[1] == "--help") { 110 | fmt.Println("USAGE:") 111 | fmt.Printf(" %s list USB HID devices\n", os.Args[0]) 112 | fmt.Printf(" %s open USB HID device shell for the given input report size\n", os.Args[0]) 113 | fmt.Printf(" %s -h|--help show this help\n", os.Args[0]) 114 | fmt.Println() 115 | return 116 | } 117 | 118 | // Without arguments - enumerate all HID devices 119 | if len(os.Args) == 1 { 120 | found := false 121 | hid.UsbWalk(func(device hid.Device) { 122 | info := device.Info() 123 | fmt.Printf("%04x:%04x:%04x:%02x\n", info.Vendor, info.Product, info.Revision, info.Interface) 124 | found = true 125 | }) 126 | if !found { 127 | fmt.Println("No USB HID devices found\n") 128 | } 129 | return 130 | } 131 | 132 | hid.UsbWalk(func(device hid.Device) { 133 | info := device.Info() 134 | id := fmt.Sprintf("%04x:%04x:%04x:%02x", info.Vendor, info.Product, info.Revision, info.Interface) 135 | if id != os.Args[1] { 136 | return 137 | } 138 | 139 | shell(device) 140 | }) 141 | } 142 | -------------------------------------------------------------------------------- /hid.go: -------------------------------------------------------------------------------- 1 | package hid 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "time" 7 | ) 8 | 9 | // 10 | // General information about the HID device 11 | // 12 | type Info struct { 13 | Vendor uint16 14 | Product uint16 15 | Revision uint16 16 | 17 | SubClass uint8 18 | Protocol uint8 19 | 20 | Interface uint8 21 | Bus int 22 | Device int 23 | } 24 | 25 | // 26 | // A common HID device interace 27 | // 28 | type Device interface { 29 | Open() error 30 | Close() 31 | Info() Info 32 | HIDReport() ([]byte, error) 33 | SetReport(int, []byte) error 34 | GetReport(int) ([]byte, error) 35 | Read(size int, ms time.Duration) ([]byte, error) 36 | Write(data []byte, ms time.Duration) (int, error) 37 | Ctrl(rtype, req, val, index int, data []byte, t int) (int, error) 38 | } 39 | 40 | // Default Logger setting 41 | var Logger = log.New(ioutil.Discard, "hid", log.LstdFlags) 42 | -------------------------------------------------------------------------------- /usb_linux.go: -------------------------------------------------------------------------------- 1 | package hid 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "regexp" 11 | "strconv" 12 | "syscall" 13 | "time" 14 | "unsafe" 15 | ) 16 | 17 | type usbDevice struct { 18 | info Info 19 | 20 | f *os.File 21 | 22 | epIn int 23 | epOut int 24 | 25 | inputPacketSize uint16 26 | outputPacketSize uint16 27 | 28 | path string 29 | } 30 | 31 | func (hid *usbDevice) Open() (err error) { 32 | if hid.f != nil { 33 | return errors.New("device is already opened") 34 | } 35 | if hid.f, err = os.OpenFile(hid.path, os.O_RDWR, 0644); err != nil { 36 | return 37 | } else { 38 | return hid.claim() 39 | } 40 | } 41 | 42 | func (hid *usbDevice) Close() { 43 | if hid.f != nil { 44 | hid.release() 45 | hid.f.Close() 46 | hid.f = nil 47 | } 48 | } 49 | 50 | func (hid *usbDevice) Info() Info { 51 | return hid.info 52 | } 53 | 54 | func (hid *usbDevice) ioctl(n uint32, arg interface{}) (int, error) { 55 | b := new(bytes.Buffer) 56 | if err := binary.Write(b, binary.LittleEndian, arg); err != nil { 57 | return -1, err 58 | } 59 | r, _, err := syscall.Syscall6(syscall.SYS_IOCTL, 60 | uintptr(hid.f.Fd()), uintptr(n), 61 | uintptr(unsafe.Pointer(&(b.Bytes()[0]))), 0, 0, 0) 62 | return int(r), err 63 | } 64 | 65 | func (hid *usbDevice) claim() error { 66 | ifno := uint32(hid.info.Interface) 67 | if r, errno := hid.ioctl(USBDEVFS_IOCTL, &usbfsIoctl{ 68 | Interface: ifno, 69 | IoctlCode: USBDEVFS_DISCONNECT, 70 | Data: 0, 71 | }); r == -1 { 72 | Logger.Println("driver disconnect failed:", r, errno) 73 | } 74 | 75 | if r, errno := hid.ioctl(USBDEVFS_CLAIM, &ifno); r == -1 { 76 | return errno 77 | } 78 | return nil 79 | } 80 | 81 | func (hid *usbDevice) release() error { 82 | ifno := uint32(hid.info.Interface) 83 | if r, errno := hid.ioctl(USBDEVFS_RELEASE, &ifno); r == -1 { 84 | return errno 85 | } 86 | 87 | if r, errno := hid.ioctl(USBDEVFS_IOCTL, &usbfsIoctl{ 88 | Interface: ifno, 89 | IoctlCode: USBDEVFS_CONNECT, 90 | Data: 0, 91 | }); r == -1 { 92 | Logger.Println("driver connect failed:", r, errno) 93 | } 94 | return nil 95 | } 96 | 97 | func (hid *usbDevice) Ctrl(rtype, req, val, index int, data []byte, t int) (int, error) { 98 | return hid.ctrl(rtype, req, val, index, data, t) 99 | } 100 | 101 | func (hid *usbDevice) ctrl(rtype, req, val, index int, data []byte, t int) (int, error) { 102 | s := usbfsCtrl{ 103 | ReqType: uint8(rtype), 104 | Req: uint8(req), 105 | Value: uint16(val), 106 | Index: uint16(index), 107 | Len: uint16(len(data)), 108 | Timeout: uint32(t), 109 | Data: slicePtr(data), 110 | } 111 | if r, err := hid.ioctl(USBDEVFS_CONTROL, &s); r == -1 { 112 | return -1, err 113 | } else { 114 | return r, nil 115 | } 116 | } 117 | 118 | func (hid *usbDevice) intr(ep int, data []byte, t int) (int, error) { 119 | if r, err := hid.ioctl(USBDEVFS_BULK, &usbfsBulk{ 120 | Endpoint: uint32(ep), 121 | Len: uint32(len(data)), 122 | Timeout: uint32(t), 123 | Data: slicePtr(data), 124 | }); r == -1 { 125 | return -1, err 126 | } else { 127 | return r, nil 128 | } 129 | } 130 | 131 | func (hid *usbDevice) Read(size int, timeout time.Duration) ([]byte, error) { 132 | if size < 0 { 133 | size = int(hid.inputPacketSize) 134 | } 135 | data := make([]byte, size, size) 136 | ms := timeout / (1 * time.Millisecond) 137 | n, err := hid.intr(hid.epIn, data, int(ms)) 138 | if err == nil { 139 | return data[:n], nil 140 | } else { 141 | return nil, err 142 | } 143 | } 144 | 145 | func (hid *usbDevice) Write(data []byte, timeout time.Duration) (int, error) { 146 | if hid.epOut > 0 { 147 | ms := timeout / (1 * time.Millisecond) 148 | return hid.intr(hid.epOut, data, int(ms)) 149 | } else { 150 | return hid.ctrl(0x21, 0x09, 2<<8+0, int(hid.info.Interface), data, len(data)) 151 | } 152 | } 153 | 154 | func (hid *usbDevice) HIDReport() ([]byte, error) { 155 | buf := make([]byte, 256, 256) 156 | // In transfer, recepient interface, GetDescriptor, HidReport type 157 | n, err := hid.ctrl(0x81, 0x06, 0x22<<8+int(hid.info.Interface), 0, buf, 1000) 158 | if err != nil { 159 | return nil, err 160 | } else { 161 | return buf[:n], nil 162 | } 163 | } 164 | 165 | func (hid *usbDevice) GetReport(report int) ([]byte, error) { 166 | buf := make([]byte, 256, 256) 167 | // 10100001, GET_REPORT, type*256+id, intf, len, data 168 | n, err := hid.ctrl(0xa1, 0x01, 3<<8+report, int(hid.info.Interface), buf, 1000) 169 | if err != nil { 170 | return nil, err 171 | } else { 172 | return buf[:n], nil 173 | } 174 | } 175 | 176 | func (hid *usbDevice) SetReport(report int, data []byte) error { 177 | // 00100001, SET_REPORT, type*256+id, intf, len, data 178 | _, err := hid.ctrl(0x21, 0x09, 3<<8+report, int(hid.info.Interface), data, 1000) 179 | return err 180 | } 181 | 182 | // 183 | // Enumeration 184 | // 185 | 186 | func cast(b []byte, to interface{}) error { 187 | r := bytes.NewBuffer(b) 188 | return binary.Read(r, binary.LittleEndian, to) 189 | } 190 | 191 | // typical /dev bus entry: /dev/bus/usb/006/003 192 | var reDevBusDevice = regexp.MustCompile(`/dev/bus/usb/(\d+)/(\d+)`) 193 | 194 | func walker(path string, cb func(Device)) error { 195 | if desc, err := ioutil.ReadFile(path); err != nil { 196 | return err 197 | } else { 198 | r := bytes.NewBuffer(desc) 199 | expected := map[byte]bool{ 200 | UsbDescTypeDevice: true, 201 | } 202 | devDesc := deviceDesc{} 203 | var device *usbDevice 204 | for r.Len() > 0 { 205 | if length, err := r.ReadByte(); err != nil { 206 | return err 207 | } else if err := r.UnreadByte(); err != nil { 208 | return err 209 | } else { 210 | body := make([]byte, length, length) 211 | if n, err := r.Read(body); err != nil { 212 | return err 213 | } else if n != int(length) || length < 2 { 214 | return errors.New("short read") 215 | } else { 216 | if !expected[body[1]] { 217 | continue 218 | } 219 | switch body[1] { 220 | case UsbDescTypeDevice: 221 | expected[UsbDescTypeDevice] = false 222 | expected[UsbDescTypeConfig] = true 223 | if err := cast(body, &devDesc); err != nil { 224 | return err 225 | } 226 | //info := Info{ 227 | //} 228 | case UsbDescTypeConfig: 229 | expected[UsbDescTypeInterface] = true 230 | expected[UsbDescTypeReport] = false 231 | expected[UsbDescTypeEndpoint] = false 232 | // Device left from the previous config 233 | if device != nil { 234 | cb(device) 235 | device = nil 236 | } 237 | case UsbDescTypeInterface: 238 | if device != nil { 239 | cb(device) 240 | device = nil 241 | } 242 | expected[UsbDescTypeEndpoint] = true 243 | expected[UsbDescTypeReport] = true 244 | i := &interfaceDesc{} 245 | if err := cast(body, i); err != nil { 246 | return err 247 | } 248 | if i.InterfaceClass == UsbHidClass { 249 | matches := reDevBusDevice.FindStringSubmatch(path) 250 | bus := 0 251 | dev := 0 252 | if len(matches) >= 3 { 253 | bus, _ = strconv.Atoi(matches[1]) 254 | dev, _ = strconv.Atoi(matches[2]) 255 | } 256 | device = &usbDevice{ 257 | info: Info{ 258 | Vendor: devDesc.Vendor, 259 | Product: devDesc.Product, 260 | Revision: devDesc.Revision, 261 | SubClass: i.InterfaceSubClass, 262 | Protocol: i.InterfaceProtocol, 263 | Interface: i.Number, 264 | Bus: bus, 265 | Device: dev, 266 | }, 267 | path: path, 268 | } 269 | } 270 | case UsbDescTypeEndpoint: 271 | if device != nil { 272 | if device.epIn != 0 && device.epOut != 0 { 273 | cb(device) 274 | device.epIn = 0 275 | device.epOut = 0 276 | } 277 | e := &endpointDesc{} 278 | if err := cast(body, e); err != nil { 279 | return err 280 | } 281 | if e.Address > 0x80 && device.epIn == 0 { 282 | device.epIn = int(e.Address) 283 | device.inputPacketSize = e.MaxPacketSize 284 | } else if e.Address < 0x80 && device.epOut == 0 { 285 | device.epOut = int(e.Address) 286 | device.outputPacketSize = e.MaxPacketSize 287 | } 288 | } 289 | } 290 | } 291 | } 292 | } 293 | if device != nil { 294 | cb(device) 295 | } 296 | } 297 | return nil 298 | } 299 | 300 | func UsbWalk(cb func(Device)) { 301 | filepath.Walk(DevBusUsb, func(f string, fi os.FileInfo, err error) error { 302 | if err != nil || fi.IsDir() { 303 | return nil 304 | } 305 | if err := walker(f, cb); err != nil { 306 | Logger.Println("UsbWalk: ", err) 307 | } 308 | return nil 309 | }) 310 | } 311 | -------------------------------------------------------------------------------- /usbdef32_linux.go: -------------------------------------------------------------------------------- 1 | // +build 386 amd64p32 arm armbe mips mipsle mips64p32 mips64p32le ppc s390 sparc 2 | 3 | package hid 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | const ( 10 | USBDEVFS_IOCTL = 0xc00c5512 11 | USBDEVFS_BULK = 0xc0105502 12 | USBDEVFS_CONTROL = 0xc0105500 13 | ) 14 | 15 | type usbfsIoctl struct { 16 | Interface uint32 17 | IoctlCode uint32 18 | Data uint32 19 | } 20 | 21 | type usbfsCtrl struct { 22 | ReqType uint8 23 | Req uint8 24 | Value uint16 25 | Index uint16 26 | Len uint16 27 | Timeout uint32 28 | Data uint32 29 | } 30 | 31 | type usbfsBulk struct { 32 | Endpoint uint32 33 | Len uint32 34 | Timeout uint32 35 | Data uint32 36 | } 37 | 38 | func slicePtr(b []byte) uint32 { 39 | return uint32(uintptr(unsafe.Pointer(&b[0]))) 40 | } 41 | -------------------------------------------------------------------------------- /usbdef64_linux.go: -------------------------------------------------------------------------------- 1 | // +build amd64 arm64 arm64be ppc64 ppc64le mips64 mips64le s390x sparc64 2 | 3 | package hid 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | const ( 10 | USBDEVFS_IOCTL = 0xc0105512 11 | USBDEVFS_BULK = 0xc0185502 12 | USBDEVFS_CONTROL = 0xc0185500 13 | ) 14 | 15 | type usbfsIoctl struct { 16 | Interface uint32 17 | IoctlCode uint32 18 | Data uint64 19 | } 20 | 21 | type usbfsCtrl struct { 22 | ReqType uint8 23 | Req uint8 24 | Value uint16 25 | Index uint16 26 | Len uint16 27 | Timeout uint32 28 | _ uint32 29 | Data uint64 // FIXME 30 | } 31 | 32 | type usbfsBulk struct { 33 | Endpoint uint32 34 | Len uint32 35 | Timeout uint32 36 | _ uint32 37 | Data uint64 38 | } 39 | 40 | func slicePtr(b []byte) uint64 { 41 | return uint64(uintptr(unsafe.Pointer(&b[0]))) 42 | } 43 | -------------------------------------------------------------------------------- /usbdef_linux.go: -------------------------------------------------------------------------------- 1 | package hid 2 | 3 | const UsbHidClass = 3 4 | 5 | type deviceDesc struct { 6 | Length uint8 7 | DescriptorType uint8 8 | USB uint16 9 | DeviceClass uint8 10 | DeviceSubClass uint8 11 | DeviceProtocol uint8 12 | MaxPacketSize uint8 13 | Vendor uint16 14 | Product uint16 15 | Revision uint16 16 | ManufacturerIndex uint8 17 | ProductIndex uint8 18 | SerialIndex uint8 19 | NumConfigurations uint8 20 | } 21 | 22 | type configDesc struct { 23 | Length uint8 24 | DescriptorType uint8 25 | TotalLength uint16 26 | NumInterfaces uint8 27 | ConfigurationValue uint8 28 | Configuration uint8 29 | Attributes uint8 30 | MaxPower uint8 31 | } 32 | 33 | type interfaceDesc struct { 34 | Length uint8 35 | DescriptorType uint8 36 | Number uint8 37 | AltSetting uint8 38 | NumEndpoints uint8 39 | InterfaceClass uint8 40 | InterfaceSubClass uint8 41 | InterfaceProtocol uint8 42 | InterfaceIndex uint8 43 | } 44 | 45 | type endpointDesc struct { 46 | Length uint8 47 | DescriptorType uint8 48 | Address uint8 49 | Attributes uint8 50 | MaxPacketSize uint16 51 | Interval uint8 52 | } 53 | 54 | type hidReportDesc struct { 55 | Length uint8 56 | DescriptorType uint8 57 | } 58 | 59 | const ( 60 | USBDEVFS_CONNECT = 0x5517 61 | USBDEVFS_DISCONNECT = 0x5516 62 | USBDEVFS_CLAIM = 0x8004550f 63 | USBDEVFS_RELEASE = 0x80045510 64 | ) 65 | 66 | const DevBusUsb = "/dev/bus/usb" 67 | 68 | const ( 69 | UsbDescTypeDevice = 1 70 | UsbDescTypeConfig = 2 71 | UsbDescTypeString = 3 72 | UsbDescTypeInterface = 4 73 | UsbDescTypeEndpoint = 5 74 | UsbDescTypeReport = 33 75 | ) 76 | --------------------------------------------------------------------------------