Jelajahi Sumber

initial commit

Serge A. Zaitsev 10 tahun lalu
induk
melakukan
adfcbafd00
6 mengubah file dengan 491 tambahan dan 0 penghapusan
  1. 24 0
      example/main.go
  2. 31 0
      hid.go
  3. 279 0
      usb_linux.go
  4. 40 0
      usbdef32_linux.go
  5. 42 0
      usbdef64_linux.go
  6. 75 0
      usbdef_linux.go

+ 24 - 0
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))
+		}
+	})
+}

+ 31 - 0
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)
+}

+ 279 - 0
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
+	})
+}

+ 40 - 0
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])))
+}

+ 42 - 0
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])))
+}

+ 75 - 0
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
+)