From 927433ac689fbacceafb9fd287196b3186a2d2b5 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Tue, 5 May 2015 16:23:01 +0300 Subject: [PATCH] initial commit --- example/main.go | 24 ++++ hid.go | 31 ++++++ usb_linux.go | 279 ++++++++++++++++++++++++++++++++++++++++++++++ usbdef32_linux.go | 40 +++++++ usbdef64_linux.go | 42 +++++++ usbdef_linux.go | 75 +++++++++++++ 6 files changed, 491 insertions(+) create mode 100644 example/main.go create mode 100644 hid.go create mode 100644 usb_linux.go create mode 100644 usbdef32_linux.go create mode 100644 usbdef64_linux.go create mode 100644 usbdef_linux.go diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..a83a68a --- /dev/null +++ b/example/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + + "github.com/zserge/hid" +) + +func main() { + hid.UsbWalk(func(device hid.Device) { + log.Printf("%+v\n", device.Info()) + if err := device.Open(); err != nil { + log.Println("Open error: ", err) + return + } + defer device.Close() + + log.Println(device.HIDReport()) + + for i := 0; i < 10; i++ { + log.Println(device.Read(8, 0)) + } + }) +} diff --git a/hid.go b/hid.go new file mode 100644 index 0000000..b2856f0 --- /dev/null +++ b/hid.go @@ -0,0 +1,31 @@ +package hid + +import "time" + +// +// General information about the HID device +// +type Info struct { + Vendor uint16 + Product uint16 + Revision uint16 + + SubClass uint8 + Protocol uint8 + + Interface uint8 +} + +// +// A common HID device interace +// +type Device interface { + Open() error + Close() + Info() Info + HIDReport() ([]byte, error) + SetReport(int, []byte) error + GetReport(int) ([]byte, error) + Read(n int, ms time.Duration) ([]byte, error) + Write(data []byte, ms time.Duration) (int, error) +} diff --git a/usb_linux.go b/usb_linux.go new file mode 100644 index 0000000..ce68915 --- /dev/null +++ b/usb_linux.go @@ -0,0 +1,279 @@ +package hid + +import ( + "bytes" + "encoding/binary" + "errors" + "io/ioutil" + "log" + "os" + "path/filepath" + "syscall" + "time" + "unsafe" +) + +type usbDevice struct { + info Info + + f *os.File + + epIn int + epOut int + + path string +} + +func (hid *usbDevice) Open() (err error) { + if hid.f != nil { + return errors.New("device is already opened") + } + if hid.f, err = os.OpenFile(hid.path, os.O_RDWR, 0644); err != nil { + return + } else { + return hid.claim() + } +} + +func (hid *usbDevice) Close() { + if hid.f != nil { + hid.release() + hid.f.Close() + hid.f = nil + } +} + +func (hid *usbDevice) Info() Info { + return hid.info +} + +func (hid *usbDevice) ioctl(n uint32, arg interface{}) (int, error) { + b := new(bytes.Buffer) + if err := binary.Write(b, binary.LittleEndian, arg); err != nil { + return -1, err + } + r, _, err := syscall.Syscall6(syscall.SYS_IOCTL, + uintptr(hid.f.Fd()), uintptr(n), + uintptr(unsafe.Pointer(&(b.Bytes()[0]))), 0, 0, 0) + return int(r), err +} + +func (hid *usbDevice) claim() error { + ifno := uint32(hid.info.Interface) + if r, errno := hid.ioctl(USBDEVFS_IOCTL, &usbfsIoctl{ + Interface: ifno, + IoctlCode: USBDEVFS_DISCONNECT, + Data: 0, + }); r == -1 { + log.Println("driver disconnect failed:", r, errno) + } + + if r, errno := hid.ioctl(USBDEVFS_CLAIM, &ifno); r == -1 { + return errno + } else { + return nil + } + return nil +} + +func (hid *usbDevice) release() error { + ifno := uint32(hid.info.Interface) + if r, errno := hid.ioctl(USBDEVFS_RELEASE, &ifno); r == -1 { + return errno + } + + if r, errno := hid.ioctl(USBDEVFS_IOCTL, &usbfsIoctl{ + Interface: ifno, + IoctlCode: USBDEVFS_CONNECT, + Data: 0, + }); r == -1 { + log.Println("driver connect failed:", r, errno) + } + return nil +} + +func (hid *usbDevice) ctrl(rtype, req, val, index int, data []byte, t int) (int, error) { + s := usbfsCtrl{ + ReqType: uint8(rtype), + Req: uint8(req), + Value: uint16(val), + Index: uint16(index), + Len: uint16(len(data)), + Timeout: uint32(t), + Data: slicePtr(data), + } + if r, err := hid.ioctl(USBDEVFS_CONTROL, &s); r == -1 { + return -1, err + } else { + return r, nil + } +} + +func (hid *usbDevice) intr(ep int, data []byte, t int) (int, error) { + if r, err := hid.ioctl(USBDEVFS_BULK, &usbfsBulk{ + Endpoint: uint32(ep), + Len: uint32(len(data)), + Timeout: uint32(t), + Data: slicePtr(data), + }); r == -1 { + return -1, err + } else { + return r, nil + } +} + +func (hid *usbDevice) Read(n int, timeout time.Duration) ([]byte, error) { + data := make([]byte, n, n) + ms := timeout / (1 * time.Millisecond) + n, err := hid.intr(hid.epIn, data, int(ms)) + if err == nil { + return data[:n], nil + } else { + return nil, err + } +} + +func (hid *usbDevice) Write(data []byte, timeout time.Duration) (int, error) { + if hid.epOut > 0 { + ms := timeout / (1 * time.Millisecond) + return hid.intr(hid.epOut, data, int(ms)) + } else { + return hid.ctrl(0xa1, 0x09, 2<<8+0, int(hid.info.Interface), data, len(data)) + } +} + +func (hid *usbDevice) HIDReport() ([]byte, error) { + buf := make([]byte, 256, 256) + // In transfer, recepient interface, GetDescriptor, HidReport type + n, err := hid.ctrl(0x81, 0x06, 0x22<<8+int(hid.info.Interface), 0, buf, 1000) + log.Println(n, err) + if err != nil { + return nil, err + } else { + return buf[:n], nil + } +} + +func (hid *usbDevice) GetReport(report int) ([]byte, error) { + buf := make([]byte, 256, 256) + // 10100001, GET_REPORT, type*256+id, intf, len, data + n, err := hid.ctrl(0xa1, 0x01, 3<<8+report, int(hid.info.Interface), buf, 1000) + if err != nil { + return nil, err + } else { + return buf[:n], nil + } +} + +func (hid *usbDevice) SetReport(report int, data []byte) error { + // 00100001, SET_REPORT, type*256+id, intf, len, data + _, err := hid.ctrl(0xa1, 0x09, 3<<8+report, int(hid.info.Interface), data, 1000) + return err +} + +// +// Enumeration +// + +func cast(b []byte, to interface{}) error { + r := bytes.NewBuffer(b) + return binary.Read(r, binary.LittleEndian, to) +} + +func walker(path string, cb func(Device)) error { + if desc, err := ioutil.ReadFile(path); err != nil { + return err + } else { + r := bytes.NewBuffer(desc) + expected := map[byte]bool{ + UsbDescTypeDevice: true, + } + devDesc := deviceDesc{} + var device *usbDevice + for r.Len() > 0 { + if length, err := r.ReadByte(); err != nil { + return err + } else if err := r.UnreadByte(); err != nil { + return err + } else { + body := make([]byte, length, length) + if n, err := r.Read(body); err != nil { + return err + } else if n != int(length) || length < 2 { + return errors.New("short read") + } else { + if !expected[body[1]] { + continue + } + switch body[1] { + case UsbDescTypeDevice: + expected[UsbDescTypeDevice] = false + expected[UsbDescTypeConfig] = true + if err := cast(body, &devDesc); err != nil { + return err + } + //info := Info{ + //} + case UsbDescTypeConfig: + expected[UsbDescTypeInterface] = true + expected[UsbDescTypeReport] = false + expected[UsbDescTypeEndpoint] = false + // Device left from the previous config + if device != nil { + cb(device) + device = nil + } + case UsbDescTypeInterface: + expected[UsbDescTypeEndpoint] = true + expected[UsbDescTypeReport] = true + i := &interfaceDesc{} + if err := cast(body, i); err != nil { + return err + } + if i.InterfaceClass == UsbHidClass { + device = &usbDevice{ + info: Info{ + Vendor: devDesc.Vendor, + Product: devDesc.Product, + Revision: devDesc.Revision, + SubClass: i.InterfaceSubClass, + Protocol: i.InterfaceProtocol, + Interface: i.Number, + }, + path: path, + } + } + case UsbDescTypeEndpoint: + if device != nil { + e := &endpointDesc{} + if err := cast(body, e); err != nil { + return err + } + if e.Address > 0x80 && device.epIn == 0 { + device.epIn = int(e.Address) + } else if e.Address < 0x80 && device.epOut == 0 { + device.epOut = int(e.Address) + } + } + } + } + } + } + if device != nil { + cb(device) + } + } + return nil +} + +func UsbWalk(cb func(Device)) { + filepath.Walk(DevBusUsb, func(f string, fi os.FileInfo, _ error) error { + if fi.IsDir() { + return nil + } + if err := walker(f, cb); err != nil { + log.Println("UsbWalk: ", err) + } + return nil + }) +} diff --git a/usbdef32_linux.go b/usbdef32_linux.go new file mode 100644 index 0000000..9aadbaa --- /dev/null +++ b/usbdef32_linux.go @@ -0,0 +1,40 @@ +// +build !amd64 + +package hid + +import ( + "unsafe" +) + +const ( + USBDEVFS_IOCTL = 0xc00c5512 + USBDEVFS_BULK = 0xc0105502 + USBDEVFS_CONTROL = 0xc0105500 +) + +type usbfsIoctl struct { + Interface uint32 + IoctlCode uint32 + Data uint32 +} + +type usbfsCtrl struct { + ReqType uint8 + Req uint8 + Value uint16 + Index uint16 + Len uint16 + Timeout uint32 + Data uint32 +} + +type usbfsBulk struct { + Endpoint uint32 + Len uint32 + Timeout uint32 + Data uint32 +} + +func slicePtr(b []byte) uint32 { + return uint32(uintptr(unsafe.Pointer(&b[0]))) +} diff --git a/usbdef64_linux.go b/usbdef64_linux.go new file mode 100644 index 0000000..c8265a6 --- /dev/null +++ b/usbdef64_linux.go @@ -0,0 +1,42 @@ +// +build amd64 + +package hid + +import ( + "unsafe" +) + +const ( + USBDEVFS_IOCTL = 0xc0105512 + USBDEVFS_BULK = 0xc0185502 + USBDEVFS_CONTROL = 0xc0185500 +) + +type usbfsIoctl struct { + Interface uint32 + IoctlCode uint32 + Data uint64 +} + +type usbfsCtrl struct { + ReqType uint8 + Req uint8 + Value uint16 + Index uint16 + Len uint16 + Timeout uint32 + _ uint32 + Data uint64 // FIXME +} + +type usbfsBulk struct { + Endpoint uint32 + Len uint32 + Timeout uint32 + _ uint32 + Data uint64 +} + +func slicePtr(b []byte) uint64 { + return uint64(uintptr(unsafe.Pointer(&b[0]))) +} diff --git a/usbdef_linux.go b/usbdef_linux.go new file mode 100644 index 0000000..f015a9a --- /dev/null +++ b/usbdef_linux.go @@ -0,0 +1,75 @@ +package hid + +const UsbHidClass = 3 + +type deviceDesc struct { + Length uint8 + DescriptorType uint8 + USB uint16 + DeviceClass uint8 + DeviceSubClass uint8 + DeviceProtocol uint8 + MaxPacketSize uint8 + Vendor uint16 + Product uint16 + Revision uint16 + ManufacturerIndex uint8 + ProductIndex uint8 + SerialIndex uint8 + NumConfigurations uint8 +} + +type configDesc struct { + Length uint8 + DescriptorType uint8 + TotalLength uint16 + NumInterfaces uint8 + ConfigurationValue uint8 + Configuration uint8 + Attributes uint8 + MaxPower uint8 +} + +type interfaceDesc struct { + Length uint8 + DescriptorType uint8 + Number uint8 + AltSetting uint8 + NumEndpoints uint8 + InterfaceClass uint8 + InterfaceSubClass uint8 + InterfaceProtocol uint8 + InterfaceIndex uint8 +} + +type endpointDesc struct { + Length uint8 + DescriptorType uint8 + Address uint8 + Attributes uint8 + MaxPacketSize uint16 + Interval uint8 +} + +type hidReportDesc struct { + Length uint8 + DescriptorType uint8 +} + +const ( + USBDEVFS_CONNECT = 0x5517 + USBDEVFS_DISCONNECT = 0x5516 + USBDEVFS_CLAIM = 0x8004550f + USBDEVFS_RELEASE = 0x80045510 +) + +const DevBusUsb = "/dev/bus/usb" + +const ( + UsbDescTypeDevice = 1 + UsbDescTypeConfig = 2 + UsbDescTypeString = 3 + UsbDescTypeInterface = 4 + UsbDescTypeEndpoint = 5 + UsbDescTypeReport = 33 +)