Allocate libusb buffers in C (#11)

* add alloc/free_transfer_and_buffer. Manages the buffer memory on the C
side.

* switch libusb.go to use the new alloc/free_transfer_and_buffer. Add
a new buffer() call to get access to the allocated buffer as a Go slice.

* Fake USB transfer uses the new alloc/free/buffer interface.

* Switch to the new libusb.alloc signature, where libusb owns the buffer.

* newUSBTransfer now allocates a separate buffer, do a copy on
endpoint.transfer.

* newUSBTransfer will now allocate it's own buffer.

* Enable autodetach in rawread.
This commit is contained in:
zagrodzki
2017-08-29 12:11:04 +02:00
committed by GitHub
parent bc91dd3f2c
commit 1aaa100bdb
8 changed files with 82 additions and 28 deletions

View File

@@ -89,17 +89,23 @@ func (e *endpoint) transfer(buf []byte) (int, error) {
return 0, nil
}
t, err := newUSBTransfer(e.h, &e.Desc, buf, e.Timeout)
t, err := newUSBTransfer(e.h, &e.Desc, len(buf), e.Timeout)
if err != nil {
return 0, err
}
defer t.free()
if e.Desc.Direction == EndpointDirectionOut {
copy(t.data(), buf)
}
if err := t.submit(); err != nil {
return 0, err
}
n, err := t.wait()
if e.Desc.Direction == EndpointDirectionIn {
copy(buf, t.data())
}
if err != nil {
return n, err
}

View File

@@ -17,7 +17,7 @@ package gousb
func (e *endpoint) newStream(size, count int, submit bool) (*stream, error) {
var ts []transferIntf
for i := 0; i < count; i++ {
t, err := newUSBTransfer(e.h, &e.Desc, make([]byte, size), e.Timeout)
t, err := newUSBTransfer(e.h, &e.Desc, size, e.Timeout)
if err != nil {
for _, t := range ts {
t.free()

View File

@@ -149,11 +149,11 @@ func (f *fakeLibusb) setAlt(d *libusbDevHandle, intf, alt uint8) error {
return nil
}
func (f *fakeLibusb) alloc(_ *libusbDevHandle, _ *EndpointDesc, _ time.Duration, _ int, buf []byte, done chan struct{}) (*libusbTransfer, error) {
func (f *fakeLibusb) alloc(_ *libusbDevHandle, _ *EndpointDesc, _ time.Duration, _ int, bufLen int, done chan struct{}) (*libusbTransfer, error) {
f.mu.Lock()
defer f.mu.Unlock()
t := newFakeTransferPointer()
f.ts[t] = &fakeTransfer{buf: buf, done: done}
f.ts[t] = &fakeTransfer{buf: make([]byte, bufLen), done: done}
return t, nil
}
func (f *fakeLibusb) cancel(t *libusbTransfer) error { return errors.New("not implemented") }
@@ -164,6 +164,7 @@ func (f *fakeLibusb) submit(t *libusbTransfer) error {
f.submitted <- ft
return nil
}
func (f *fakeLibusb) buffer(t *libusbTransfer) []byte { return f.ts[t].buf }
func (f *fakeLibusb) data(t *libusbTransfer) (int, TransferStatus) {
f.mu.Lock()
defer f.mu.Unlock()

View File

@@ -27,7 +27,9 @@ import (
#cgo pkg-config: libusb-1.0
#include <libusb.h>
int compact_iso_data(struct libusb_transfer *xfer, unsigned char *status);
int gousb_compact_iso_data(struct libusb_transfer *xfer, unsigned char *status);
struct libusb_transfer *gousb_alloc_transfer_and_buffer(int bufLen, int numIsoPackets);
void gousb_free_transfer_and_buffer(struct libusb_transfer *xfer);
int submit(struct libusb_transfer *xfer);
*/
import "C"
@@ -156,9 +158,10 @@ type libusbIntf interface {
setAlt(*libusbDevHandle, uint8, uint8) error
// transfer
alloc(*libusbDevHandle, *EndpointDesc, time.Duration, int, []byte, chan struct{}) (*libusbTransfer, error)
alloc(*libusbDevHandle, *EndpointDesc, time.Duration, int, int, chan struct{}) (*libusbTransfer, error)
cancel(*libusbTransfer) error
submit(*libusbTransfer) error
buffer(*libusbTransfer) []byte
data(*libusbTransfer) (int, TransferStatus)
free(*libusbTransfer)
setIsoPacketLengths(*libusbTransfer, uint32)
@@ -411,18 +414,19 @@ func (libusbImpl) setAlt(d *libusbDevHandle, iface, setup uint8) error {
return fromErrNo(C.libusb_set_interface_alt_setting((*C.libusb_device_handle)(d), C.int(iface), C.int(setup)))
}
func (libusbImpl) alloc(d *libusbDevHandle, ep *EndpointDesc, timeout time.Duration, isoPackets int, buf []byte, done chan struct{}) (*libusbTransfer, error) {
xfer := C.libusb_alloc_transfer(C.int(isoPackets))
func (libusbImpl) alloc(d *libusbDevHandle, ep *EndpointDesc, timeout time.Duration, isoPackets int, bufLen int, done chan struct{}) (*libusbTransfer, error) {
xfer := C.gousb_alloc_transfer_and_buffer(C.int(bufLen), C.int(isoPackets))
if xfer == nil {
return nil, fmt.Errorf("libusb_alloc_transfer(%d) failed", isoPackets)
return nil, fmt.Errorf("gousb_alloc_transfer_and_buffer(%d, %d) failed", bufLen, isoPackets)
}
if int(xfer.length) != bufLen {
return nil, fmt.Errorf("gousb_alloc_transfer_and_buffer(%d, %d): length = %d, want %d", bufLen, isoPackets, xfer.length, bufLen)
}
xfer.dev_handle = (*C.libusb_device_handle)(d)
xfer.endpoint = C.uchar(ep.Address)
xfer.timeout = C.uint(timeout / time.Millisecond)
xfer._type = C.uchar(ep.TransferType)
xfer.num_iso_packets = C.int(isoPackets)
xfer.buffer = (*C.uchar)(unsafe.Pointer(&buf[0]))
xfer.length = C.int(len(buf))
ret := (*libusbTransfer)(xfer)
xferDoneMap.Lock()
xferDoneMap.m[ret] = done
@@ -438,17 +442,29 @@ func (libusbImpl) submit(t *libusbTransfer) error {
return fromErrNo(C.submit((*C.struct_libusb_transfer)(t)))
}
func (libusbImpl) buffer(t *libusbTransfer) []byte {
// TODO(go1.10?): replace with more user-friendly construct once
// one exists. https://github.com/golang/go/issues/13656
var ret []byte
*(*reflect.SliceHeader)(unsafe.Pointer(&ret)) = reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(t.buffer)),
Len: int(t.length),
Cap: int(t.length),
}
return ret
}
func (libusbImpl) data(t *libusbTransfer) (int, TransferStatus) {
if TransferType(t._type) == TransferTypeIsochronous {
var status TransferStatus
n := int(C.compact_iso_data((*C.struct_libusb_transfer)(t), (*C.uchar)(unsafe.Pointer(&status))))
n := int(C.gousb_compact_iso_data((*C.struct_libusb_transfer)(t), (*C.uchar)(unsafe.Pointer(&status))))
return n, status
}
return int(t.actual_length), TransferStatus(t.status)
}
func (libusbImpl) free(t *libusbTransfer) {
C.libusb_free_transfer((*C.struct_libusb_transfer)(t))
C.gousb_free_transfer_and_buffer((*C.struct_libusb_transfer)(t))
xferDoneMap.Lock()
delete(xferDoneMap.m, t)
xferDoneMap.Unlock()

View File

@@ -140,6 +140,9 @@ func main() {
}
dev := devs[0]
log.Print("Enabling autodetach")
dev.SetAutoDetach(true)
log.Printf("Setting configuration %d...", *config)
cfg, err := dev.Config(*config)
if err != nil {

View File

@@ -15,6 +15,7 @@
#include <libusb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_xfer(struct libusb_transfer *xfer);
@@ -53,7 +54,7 @@ void print_xfer(struct libusb_transfer *xfer) {
// compact the data in an isochronous transfer. The contents of individual
// iso packets are shifted left, so that no gaps are left between them.
// Status is set to the first non-zero status of an iso packet.
int compact_iso_data(struct libusb_transfer *xfer, unsigned char *status) {
int gousb_compact_iso_data(struct libusb_transfer *xfer, unsigned char *status) {
int i;
int sum = 0;
unsigned char *in = xfer->buffer;
@@ -74,3 +75,26 @@ int compact_iso_data(struct libusb_transfer *xfer, unsigned char *status) {
}
return sum;
}
// allocates a libusb transfer and a buffer for packet data.
struct libusb_transfer *gousb_alloc_transfer_and_buffer(int bufLen, int isoPackets) {
struct libusb_transfer *xfer = libusb_alloc_transfer(isoPackets);
if (xfer == NULL) {
return NULL;
}
xfer->buffer = (unsigned char*)malloc(bufLen);
if (xfer->buffer == NULL) {
libusb_free_transfer(xfer);
return NULL;
}
xfer->length = bufLen;
return xfer;
}
// frees a libusb transfer and its buffer. The buffer of the given
// libusb_transfer must have been allocated with alloc_transfer_and_buffer.
void gousb_free_transfer_and_buffer(struct libusb_transfer *xfer) {
free(xfer->buffer);
xfer->length = 0;
libusb_free_transfer(xfer);
}

View File

@@ -26,8 +26,9 @@ type usbTransfer struct {
mu sync.Mutex
// xfer is the allocated libusb_transfer.
xfer *libusbTransfer
// buf is the buffer allocated for the transfer. Both buf and xfer.buffer
// point to the same piece of memory.
// buf is the buffer allocated for the transfer. The underlying memory
// is allocated by the C code, both buf and xfer.buffer point to the same
// memory.
buf []byte
// done is blocking until the transfer is complete and data and transfer
// status are available.
@@ -97,6 +98,9 @@ func (t *usbTransfer) free() error {
if t.submitted {
return errors.New("free() cannot be called on a submitted transfer until wait() returns")
}
if t.xfer == nil {
return nil
}
libusb.free(t.xfer)
t.xfer = nil
t.buf = nil
@@ -109,21 +113,21 @@ func (t *usbTransfer) data() []byte {
return t.buf
}
// newUSBTransfer allocates a new transfer structure for communication with a
// given device/endpoint, with buf as the underlying transfer buffer.
func newUSBTransfer(dev *libusbDevHandle, ei *EndpointDesc, buf []byte, timeout time.Duration) (*usbTransfer, error) {
// newUSBTransfer allocates a new transfer structure and a new buffer for
// communication with a given device/endpoint.
func newUSBTransfer(dev *libusbDevHandle, ei *EndpointDesc, bufLen int, timeout time.Duration) (*usbTransfer, error) {
var isoPackets, isoPktSize int
if ei.TransferType == TransferTypeIsochronous {
isoPktSize = ei.MaxPacketSize
if len(buf) < isoPktSize {
isoPktSize = len(buf)
if bufLen < isoPktSize {
isoPktSize = bufLen
}
isoPackets = len(buf) / isoPktSize
debug.Printf("New isochronous transfer - buffer length %d, using %d packets of %d bytes each", len(buf), isoPackets, isoPktSize)
isoPackets = bufLen / isoPktSize
debug.Printf("New isochronous transfer - buffer length %d, using %d packets of %d bytes each", bufLen, isoPackets, isoPktSize)
}
done := make(chan struct{}, 1)
xfer, err := libusb.alloc(dev, ei, timeout, isoPackets, buf, done)
xfer, err := libusb.alloc(dev, ei, timeout, isoPackets, bufLen, done)
if err != nil {
return nil, err
}
@@ -134,7 +138,7 @@ func newUSBTransfer(dev *libusbDevHandle, ei *EndpointDesc, buf []byte, timeout
t := &usbTransfer{
xfer: xfer,
buf: buf,
buf: libusb.buffer(xfer),
done: done,
}
runtime.SetFinalizer(t, func(t *usbTransfer) {

View File

@@ -57,13 +57,13 @@ func TestNewTransfer(t *testing.T) {
Direction: tc.dir,
TransferType: tc.tt,
MaxPacketSize: tc.maxPkt,
}, make([]byte, tc.buf), tc.timeout)
}, tc.buf, tc.timeout)
if err != nil {
t.Fatalf("newUSBTransfer(): %v", err)
}
defer xfer.free()
if got, want := len(xfer.buf), tc.wantLength; got != want {
if got, want := len(xfer.data()), tc.wantLength; got != want {
t.Errorf("xfer.buf: got %d bytes, want %d", got, want)
}
}
@@ -81,7 +81,7 @@ func TestTransferProtocol(t *testing.T) {
Direction: EndpointDirectionIn,
TransferType: TransferTypeBulk,
MaxPacketSize: 512,
}, make([]byte, 10240), time.Second)
}, 10240, time.Second)
if err != nil {
t.Fatalf("newUSBTransfer: %v", err)
}