-
Notifications
You must be signed in to change notification settings - Fork 128
/
Copy pathusb.go
327 lines (275 loc) · 11.3 KB
/
usb.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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
// Copyright 2013 Google Inc. All rights reserved.
// Copyright 2016 the gousb Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
Package gousb provides an low-level interface to attached USB devices.
# A Short Tutorial
A Context manages all resources necessary for communicating with USB
devices.
Through the Context users can iterate over available USB devices.
The USB standard defines a mechanism of discovering USB device functionality
through descriptors. After the device is attached and
initialized by the host stack, it's possible to retrieve its descriptor
(the device descriptor). It contains elements such as product and vendor IDs,
bus number and device number (address) on the bus.
In gousb, the Device struct represents a USB device. The Device struct’s Desc
field contains all known information about the device.
Among other information in the device descriptor is a list of configuration
descriptors, accessible through Device.Desc.Configs.
The USB standard allows one physical USB device to switch between different
sets of behaviors, or working modes, by selecting one of the offered configs
(each device has at least one).
This allows the same device to sometimes present itself as e.g. a 3G modem,
and sometimes as a flash drive with the drivers for that 3G modem.
Configs are mutually exclusive, each device
can have only one active config at a time. Switching the active config performs
a light-weight device reset. Each config in the device descriptor has
a unique identification number.
In gousb a device config needs to be selected through Device.Config(num).
It returns a Config struct that represents the device in this particular configuration.
The configuration descriptor is accessible through Config.Desc.
A config descriptor determines the list of available USB interfaces on the device.
Each interface is a virtual device within the physical USB device and its active
config. There can be many interfaces active concurrently. Interfaces are
enumerated sequentially starting from zero.
Additionally, each interface comes with a number of alternate settings for
the interface, which are somewhat similar to device configs, but on the
interface level. Each interface can have only a single alternate setting
active at any time. Alternate settings are enumerated sequentially starting from
zero.
In gousb an interface and its alternate setting can be selected through
Config.Interface(num, altNum). The Interface struct is the representation
of the claimed interface with a particular alternate setting.
The descriptor of the interface is available through Interface.Setting.
An interface with a particular alternate setting defines up to 30 data
endpoints, each identified by a unique address. The endpoint address is a combination
of endpoint number (1..15) and endpoint directionality (IN/OUT).
IN endpoints have addresses 0x81..0x8f, while OUT endpoints 0x01..0x0f.
An endpoint can be considered similar to a UDP/IP port,
except the data transfers are unidirectional.
Endpoints are represented by the Endpoint struct, and all defined endpoints
can be obtained through the Endpoints field of the Interface.Setting.
Each endpoint descriptor (EndpointDesc) defined in the interface's endpoint
map includes information about the type of the endpoint:
- endpoint address
- endpoint number
- direction: IN (device-to-host) or OUT (host-to-device)
- transfer type: USB standard defines a few distinct data transfer types:
--- bulk - high throughput, but no guaranteed bandwidth and no latency guarantees,
--- isochronous - medium throughput, guaranteed bandwidth, some latency guarantees,
--- interrupt - low throughput, high latency guarantees.
The endpoint descriptor determines the type of the transfer that will be used.
- maximum packet size: maximum number of bytes that can be sent or received by the device in a single USB transaction.
and a few other less frequently used pieces of endpoint information.
An IN Endpoint can be opened for reading through Interface.InEndpoint(epNum),
while an OUT Endpoint can be opened for writing through Interface.OutEndpoint(epNum).
An InEndpoint implements the io.Reader interface, an OutEndpoint implements
the io.Writer interface. Both Reads and Writes will accept larger slices
of data than the endpoint's maximum packet size, the transfer will be split
into smaller USB transactions as needed. But using Read/Write size equal
to an integer multiple of maximum packet size helps with improving the transfer
performance.
Apart from 15 possible data endpoints, each USB device also has a control endpoint.
The control endpoint is present regardless of the current device config, claimed
interfaces and their alternate settings. It makes a lot of sense, as the control endpoint is actually used, among others,
to issue commands to switch the active config or select an alternate setting for an interface.
Control commands are also often used to control the behavior of the device. There is no single
standard for control commands though, and many devices implement their custom control command schema.
Control commands can be issued through Device.Control().
# See Also
For more information about USB protocol and handling USB devices,
see the excellent "USB in a nutshell" guide: http://www.beyondlogic.org/usbnutshell/
*/
package gousb
import (
"errors"
"fmt"
"sync"
)
// Context manages all resources related to USB device handling.
type Context struct {
ctx *libusbContext
done chan struct{}
libusb libusbIntf
mu sync.Mutex
devices map[*Device]bool
}
// Debug changes the debug level. Level 0 means no debug, higher levels
// will print out more debugging information.
// TODO(sebek): in the next major release, replace int levels with
// Go-typed constants.
func (c *Context) Debug(level int) {
c.libusb.setDebug(c.ctx, level)
}
func newContextWithImpl(impl libusbIntf) *Context {
c, err := impl.init()
if err != nil {
panic(err)
}
ctx := &Context{
ctx: c,
done: make(chan struct{}),
libusb: impl,
devices: make(map[*Device]bool),
}
go impl.handleEvents(ctx.ctx, ctx.done)
return ctx
}
// NewContext returns a new Context instance with default ContextOptions.
func NewContext() *Context {
return ContextOptions{}.New()
}
// DeviceDiscovery controls USB device discovery.
type DeviceDiscovery int
const (
// EnableDeviceDiscovery means the connected USB devices will be enumerated
// on Context initialization. This enables the use of OpenDevices and
// OpenWithVIDPID. This is the default.
EnableDeviceDiscovery = iota
// DisableDeviceDiscovery means the USB devices are not enumerated and
// OpenDevices will not return any devices.
// Without device discovery, OpenDeviceWithFileDescriptor can be used
// to open devices.
DisableDeviceDiscovery
)
// ContextOptions holds parameters for Context initialization.
type ContextOptions struct {
DeviceDiscovery DeviceDiscovery
}
// New creates a Context, taking into account the optional flags contained in ContextOptions
func (o ContextOptions) New() *Context {
return newContextWithImpl(libusbImpl{
discovery: o.DeviceDiscovery,
})
}
// OpenDevices calls opener with each enumerated device.
// If the opener returns true, the device is opened and a Device is returned if the operation succeeds.
// Every Device returned (whether an error is also returned or not) must be closed.
// If there are any errors enumerating the devices,
// the final one is returned along with any successfully opened devices.
func (c *Context) OpenDevices(opener func(desc *DeviceDesc) bool) ([]*Device, error) {
if c.ctx == nil {
return nil, errors.New("OpenDevices called on a closed or uninitialized Context")
}
list, err := c.libusb.getDevices(c.ctx)
if err != nil {
return nil, err
}
var reterr error
var ret []*Device
for _, dev := range list {
desc, err := c.libusb.getDeviceDesc(dev)
defer c.libusb.dereference(dev)
if err != nil {
reterr = err
continue
}
if !opener(desc) {
continue
}
handle, err := c.libusb.open(dev)
if err != nil {
reterr = err
continue
}
o := &Device{handle: handle, ctx: c, Desc: desc}
ret = append(ret, o)
c.mu.Lock()
c.devices[o] = true
c.mu.Unlock()
}
return ret, reterr
}
// OpenDeviceWithFileDescriptor takes a (Unix) file descriptor of an opened USB
// device and wraps the library around it.
// This is particularly useful when working on Android, where the USB device can be
// opened by the SDK (Java), giving access to the device through the file descriptor
// (https://developer.android.com/reference/android/hardware/usb/UsbDeviceConnection#getFileDescriptor()).
//
// Do note that for this to work the automatic device discovery must be disabled
// at the time when the new Context is created, through the use of
// ContextOptions.DeviceDiscovery.
//
// Example:
//
// ctx := ContextOptions{DeviceDiscovery: DisableDeviceDiscovery}.New()
// device, err := ctx.OpenDeviceWithFileDescriptor(fd)
//
// An error is returned in case the file descriptor is not valid.
func (c *Context) OpenDeviceWithFileDescriptor(fd uintptr) (*Device, error) {
handle, err := c.libusb.wrapSysDevice(c.ctx, fd)
if err != nil {
return nil, err
}
dev := c.libusb.getDevice(handle)
desc, err := c.libusb.getDeviceDesc(dev)
if err != nil {
return nil, fmt.Errorf("device was opened, but getting device descriptor failed: %v", err)
}
o := &Device{handle: handle, ctx: c, Desc: desc}
c.mu.Lock()
c.devices[o] = true
c.mu.Unlock()
return o, nil
}
// OpenDeviceWithVIDPID opens Device from specific VendorId and ProductId.
// If none is found, it returns nil and nil error. If there are multiple devices
// with the same VID/PID, it will return one of them, picked arbitrarily.
// If there were any errors during device list traversal, it is possible
// it will return a non-nil device and non-nil error. A Device.Close() must
// be called to release the device if the returned device wasn't nil.
func (c *Context) OpenDeviceWithVIDPID(vid, pid ID) (*Device, error) {
var found bool
devs, err := c.OpenDevices(func(desc *DeviceDesc) bool {
if found {
return false
}
if desc.Vendor == ID(vid) && desc.Product == ID(pid) {
found = true
return true
}
return false
})
if len(devs) == 0 {
return nil, err
}
return devs[0], nil
}
func (c *Context) closeDev(d *Device) {
c.mu.Lock()
defer c.mu.Unlock()
c.libusb.close(d.handle)
delete(c.devices, d)
}
func (c *Context) checkOpenDevs() error {
c.mu.Lock()
defer c.mu.Unlock()
if l := len(c.devices); l > 0 {
return fmt.Errorf("Context.Close called while %d Devices are still open, Close may be called only after all previously opened devices were successfuly closed", l)
}
return nil
}
// Close releases the Context and all associated resources.
func (c *Context) Close() error {
if c.ctx == nil {
return nil
}
if err := c.checkOpenDevs(); err != nil {
return err
}
c.done <- struct{}{}
err := c.libusb.exit(c.ctx)
c.ctx = nil
return err
}