-
Notifications
You must be signed in to change notification settings - Fork 40
/
raw_enabled.go
254 lines (234 loc) · 8.97 KB
/
raw_enabled.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
// usb - Self contained USB and HID library for Go
// Copyright 2019 The library Authors
//
// This library is free software: you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// The library is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along
// with the library. If not, see <http://www.gnu.org/licenses/>.
//go:build (freebsd && cgo) || (linux && cgo) || (darwin && !ios && cgo) || (windows && cgo)
// +build freebsd,cgo linux,cgo darwin,!ios,cgo windows,cgo
package usb
/*
#include "./libusb/libusb/libusb.h"
// ctx is a global libusb context to interact with devices through.
libusb_context* ctx;
*/
import "C"
import (
"fmt"
"reflect"
"sync"
"unsafe"
)
// enumerateRaw returns a list of all the USB devices attached to the system which
// match the vendor and product id:
// - If the vendor id is set to 0 then any vendor matches.
// - If the product id is set to 0 then any product matches.
// - If the vendor and product id are both 0, all USB devices are returned.
func enumerateRaw(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
// Enumerate the devices, and free all the matching refcounts (we'll reopen any
// explicitly requested).
infos, err := enumerateRawWithRef(vendorID, productID)
for _, info := range infos {
C.libusb_unref_device(info.rawDevice.(*C.libusb_device))
}
// If enumeration failed, don't return anything, otherwise everything
if err != nil {
return nil, err
}
return infos, nil
}
// enumerateRawWithRef is the internal device enumerator that retains 1 reference
// to every matched device so they may selectively be opened on request.
func enumerateRawWithRef(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
// Ensure we have a libusb context to interact through. The enumerate call is
// protexted by a mutex outside, so it's fine to do the below check and init.
if C.ctx == nil {
if err := fromRawErrno(C.libusb_init((**C.libusb_context)(&C.ctx))); err != nil {
return nil, fmt.Errorf("failed to initialize libusb: %v", err)
}
}
// Retrieve all the available USB devices and wrap them in Go
var deviceList **C.libusb_device
count := C.libusb_get_device_list(C.ctx, &deviceList)
if count < 0 {
return nil, rawError(count)
}
defer C.libusb_free_device_list(deviceList, 1)
var devices []*C.libusb_device
*(*reflect.SliceHeader)(unsafe.Pointer(&devices)) = reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(deviceList)),
Len: int(count),
Cap: int(count),
}
//
var infos []DeviceInfo
for devnum, dev := range devices {
// Retrieve the libusb device descriptor and skip non-queried ones
var desc C.struct_libusb_device_descriptor
if err := fromRawErrno(C.libusb_get_device_descriptor(dev, &desc)); err != nil {
return infos, fmt.Errorf("failed to get device %d descriptor: %v", devnum, err)
}
if (vendorID > 0 && uint16(desc.idVendor) != vendorID) || (productID > 0 && uint16(desc.idProduct) != productID) {
continue
}
// Skip HID devices, they are handled directly by OS libraries
if desc.bDeviceClass == C.LIBUSB_CLASS_HID {
continue
}
// Iterate over all the configurations and find raw interfaces
for cfgnum := 0; cfgnum < int(desc.bNumConfigurations); cfgnum++ {
// Retrieve the all the possible USB configurations of the device
var cfg *C.struct_libusb_config_descriptor
if err := fromRawErrno(C.libusb_get_config_descriptor(dev, C.uint8_t(cfgnum), &cfg)); err != nil {
return infos, fmt.Errorf("failed to get device %d config %d: %v", devnum, cfgnum, err)
}
var ifaces []C.struct_libusb_interface
*(*reflect.SliceHeader)(unsafe.Pointer(&ifaces)) = reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(cfg._interface)),
Len: int(cfg.bNumInterfaces),
Cap: int(cfg.bNumInterfaces),
}
// Drill down into each advertised interface
for ifacenum, iface := range ifaces {
if iface.num_altsetting == 0 {
continue
}
var alts []C.struct_libusb_interface_descriptor
*(*reflect.SliceHeader)(unsafe.Pointer(&alts)) = reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(iface.altsetting)),
Len: int(iface.num_altsetting),
Cap: int(iface.num_altsetting),
}
for _, alt := range alts {
// Skip HID interfaces, they are handled directly by OS libraries
if alt.bInterfaceClass == C.LIBUSB_CLASS_HID {
continue
}
// Find the endpoints that can speak libusb interrupts
var ends []C.struct_libusb_endpoint_descriptor
*(*reflect.SliceHeader)(unsafe.Pointer(&ends)) = reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(alt.endpoint)),
Len: int(alt.bNumEndpoints),
Cap: int(alt.bNumEndpoints),
}
var reader, writer *uint8
for _, end := range ends {
// Skip any non-interrupt endpoints
if end.bmAttributes != C.LIBUSB_TRANSFER_TYPE_INTERRUPT {
continue
}
if end.bEndpointAddress&C.LIBUSB_ENDPOINT_IN == C.LIBUSB_ENDPOINT_IN {
reader = new(uint8)
*reader = uint8(end.bEndpointAddress)
} else {
writer = new(uint8)
*writer = uint8(end.bEndpointAddress)
}
}
// If both in and out interrupts are available, match the device
if reader != nil && writer != nil {
// Enumeration matched, bump the device refcount to avoid cleaning it up
C.libusb_ref_device(dev)
port := uint8(C.libusb_get_port_number(dev))
infos = append(infos, DeviceInfo{
Path: fmt.Sprintf("%04x:%04x:%02d", uint16(desc.idVendor), uint16(desc.idProduct), port),
VendorID: uint16(desc.idVendor),
ProductID: uint16(desc.idProduct),
Interface: ifacenum,
rawDevice: dev,
rawPort: &port,
rawReader: reader,
rawWriter: writer,
})
}
}
}
}
}
return infos, nil
}
// openRaw connects to a low level libusb device by its path name.
func openRaw(info DeviceInfo) (*rawDevice, error) {
// Enumerate all the devices matching this particular info
matches, err := enumerateRawWithRef(info.VendorID, info.ProductID)
if err != nil {
// Enumeration failed, make sure any subresults are released
for _, match := range matches {
C.libusb_unref_device(match.rawDevice.(*C.libusb_device))
}
return nil, err
}
// Find the specific endpoint we're interested in
var device *C.libusb_device
for _, match := range matches {
// Keep the matching device reference, release anything else
if device == nil && *match.rawPort == *info.rawPort && match.Interface == info.Interface {
device = match.rawDevice.(*C.libusb_device)
} else {
C.libusb_unref_device(match.rawDevice.(*C.libusb_device))
}
}
if device == nil {
return nil, fmt.Errorf("failed to open device: not found")
}
// Open the mathcing device
info.rawDevice = device
var handle *C.struct_libusb_device_handle
if err := fromRawErrno(C.libusb_open(info.rawDevice.(*C.libusb_device), (**C.struct_libusb_device_handle)(&handle))); err != nil {
return nil, fmt.Errorf("failed to open device: %v", err)
}
if err := fromRawErrno(C.libusb_claim_interface(handle, (C.int)(info.Interface))); err != nil {
C.libusb_close(handle)
return nil, fmt.Errorf("failed to claim interface: %v", err)
}
return &rawDevice{
DeviceInfo: info,
handle: handle,
}, nil
}
// rawDevice is a live low level USB connected device handle.
type rawDevice struct {
DeviceInfo // Embed the infos for easier access
handle *C.struct_libusb_device_handle // Low level USB device to communicate through
lock sync.Mutex
}
// Close releases the raw USB device handle.
func (dev *rawDevice) Close() error {
dev.lock.Lock()
defer dev.lock.Unlock()
if dev.handle != nil {
C.libusb_release_interface(dev.handle, (C.int)(dev.Interface))
C.libusb_close(dev.handle)
dev.handle = nil
}
C.libusb_unref_device(dev.rawDevice.(*C.libusb_device))
return nil
}
// Write sends a binary blob to a low level USB device.
func (dev *rawDevice) Write(b []byte) (int, error) {
dev.lock.Lock()
defer dev.lock.Unlock()
var transferred C.int
if err := fromRawErrno(C.libusb_interrupt_transfer(dev.handle, (C.uchar)(*dev.rawWriter), (*C.uchar)(&b[0]), (C.int)(len(b)), &transferred, (C.uint)(0))); err != nil {
return 0, fmt.Errorf("failed to write to device: %v", err)
}
return int(transferred), nil
}
// Read retrieves a binary blob from a low level USB device.
func (dev *rawDevice) Read(b []byte) (int, error) {
dev.lock.Lock()
defer dev.lock.Unlock()
var transferred C.int
if err := fromRawErrno(C.libusb_interrupt_transfer(dev.handle, (C.uchar)(*dev.rawReader), (*C.uchar)(&b[0]), (C.int)(len(b)), &transferred, (C.uint)(0))); err != nil {
return 0, fmt.Errorf("failed to read from device: %v", err)
}
return int(transferred), nil
}