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 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 { if err != nil {
return 0, err return 0, err
} }
defer t.free() defer t.free()
if e.Desc.Direction == EndpointDirectionOut {
copy(t.data(), buf)
}
if err := t.submit(); err != nil { if err := t.submit(); err != nil {
return 0, err return 0, err
} }
n, err := t.wait() n, err := t.wait()
if e.Desc.Direction == EndpointDirectionIn {
copy(buf, t.data())
}
if err != nil { if err != nil {
return n, err return n, err
} }

View File

@@ -17,7 +17,7 @@ package gousb
func (e *endpoint) newStream(size, count int, submit bool) (*stream, error) { func (e *endpoint) newStream(size, count int, submit bool) (*stream, error) {
var ts []transferIntf var ts []transferIntf
for i := 0; i < count; i++ { 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 { if err != nil {
for _, t := range ts { for _, t := range ts {
t.free() t.free()

View File

@@ -149,11 +149,11 @@ func (f *fakeLibusb) setAlt(d *libusbDevHandle, intf, alt uint8) error {
return nil 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() f.mu.Lock()
defer f.mu.Unlock() defer f.mu.Unlock()
t := newFakeTransferPointer() t := newFakeTransferPointer()
f.ts[t] = &fakeTransfer{buf: buf, done: done} f.ts[t] = &fakeTransfer{buf: make([]byte, bufLen), done: done}
return t, nil return t, nil
} }
func (f *fakeLibusb) cancel(t *libusbTransfer) error { return errors.New("not implemented") } 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 f.submitted <- ft
return nil return nil
} }
func (f *fakeLibusb) buffer(t *libusbTransfer) []byte { return f.ts[t].buf }
func (f *fakeLibusb) data(t *libusbTransfer) (int, TransferStatus) { func (f *fakeLibusb) data(t *libusbTransfer) (int, TransferStatus) {
f.mu.Lock() f.mu.Lock()
defer f.mu.Unlock() defer f.mu.Unlock()

View File

@@ -27,7 +27,9 @@ import (
#cgo pkg-config: libusb-1.0 #cgo pkg-config: libusb-1.0
#include <libusb.h> #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); int submit(struct libusb_transfer *xfer);
*/ */
import "C" import "C"
@@ -156,9 +158,10 @@ type libusbIntf interface {
setAlt(*libusbDevHandle, uint8, uint8) error setAlt(*libusbDevHandle, uint8, uint8) error
// transfer // 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 cancel(*libusbTransfer) error
submit(*libusbTransfer) error submit(*libusbTransfer) error
buffer(*libusbTransfer) []byte
data(*libusbTransfer) (int, TransferStatus) data(*libusbTransfer) (int, TransferStatus)
free(*libusbTransfer) free(*libusbTransfer)
setIsoPacketLengths(*libusbTransfer, uint32) 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))) 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) { func (libusbImpl) alloc(d *libusbDevHandle, ep *EndpointDesc, timeout time.Duration, isoPackets int, bufLen int, done chan struct{}) (*libusbTransfer, error) {
xfer := C.libusb_alloc_transfer(C.int(isoPackets)) xfer := C.gousb_alloc_transfer_and_buffer(C.int(bufLen), C.int(isoPackets))
if xfer == nil { 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.dev_handle = (*C.libusb_device_handle)(d)
xfer.endpoint = C.uchar(ep.Address) xfer.endpoint = C.uchar(ep.Address)
xfer.timeout = C.uint(timeout / time.Millisecond) xfer.timeout = C.uint(timeout / time.Millisecond)
xfer._type = C.uchar(ep.TransferType) xfer._type = C.uchar(ep.TransferType)
xfer.num_iso_packets = C.int(isoPackets) xfer.num_iso_packets = C.int(isoPackets)
xfer.buffer = (*C.uchar)(unsafe.Pointer(&buf[0]))
xfer.length = C.int(len(buf))
ret := (*libusbTransfer)(xfer) ret := (*libusbTransfer)(xfer)
xferDoneMap.Lock() xferDoneMap.Lock()
xferDoneMap.m[ret] = done xferDoneMap.m[ret] = done
@@ -438,17 +442,29 @@ func (libusbImpl) submit(t *libusbTransfer) error {
return fromErrNo(C.submit((*C.struct_libusb_transfer)(t))) 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) { func (libusbImpl) data(t *libusbTransfer) (int, TransferStatus) {
if TransferType(t._type) == TransferTypeIsochronous { if TransferType(t._type) == TransferTypeIsochronous {
var status TransferStatus 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 n, status
} }
return int(t.actual_length), TransferStatus(t.status) return int(t.actual_length), TransferStatus(t.status)
} }
func (libusbImpl) free(t *libusbTransfer) { 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() xferDoneMap.Lock()
delete(xferDoneMap.m, t) delete(xferDoneMap.m, t)
xferDoneMap.Unlock() xferDoneMap.Unlock()

View File

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

View File

@@ -15,6 +15,7 @@
#include <libusb.h> #include <libusb.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
void print_xfer(struct libusb_transfer *xfer); 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 // compact the data in an isochronous transfer. The contents of individual
// iso packets are shifted left, so that no gaps are left between them. // 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. // 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 i;
int sum = 0; int sum = 0;
unsigned char *in = xfer->buffer; unsigned char *in = xfer->buffer;
@@ -74,3 +75,26 @@ int compact_iso_data(struct libusb_transfer *xfer, unsigned char *status) {
} }
return sum; 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 mu sync.Mutex
// xfer is the allocated libusb_transfer. // xfer is the allocated libusb_transfer.
xfer *libusbTransfer xfer *libusbTransfer
// buf is the buffer allocated for the transfer. Both buf and xfer.buffer // buf is the buffer allocated for the transfer. The underlying memory
// point to the same piece of memory. // is allocated by the C code, both buf and xfer.buffer point to the same
// memory.
buf []byte buf []byte
// done is blocking until the transfer is complete and data and transfer // done is blocking until the transfer is complete and data and transfer
// status are available. // status are available.
@@ -97,6 +98,9 @@ func (t *usbTransfer) free() error {
if t.submitted { if t.submitted {
return errors.New("free() cannot be called on a submitted transfer until wait() returns") return errors.New("free() cannot be called on a submitted transfer until wait() returns")
} }
if t.xfer == nil {
return nil
}
libusb.free(t.xfer) libusb.free(t.xfer)
t.xfer = nil t.xfer = nil
t.buf = nil t.buf = nil
@@ -109,21 +113,21 @@ func (t *usbTransfer) data() []byte {
return t.buf return t.buf
} }
// newUSBTransfer allocates a new transfer structure for communication with a // newUSBTransfer allocates a new transfer structure and a new buffer for
// given device/endpoint, with buf as the underlying transfer buffer. // communication with a given device/endpoint.
func newUSBTransfer(dev *libusbDevHandle, ei *EndpointDesc, buf []byte, timeout time.Duration) (*usbTransfer, error) { func newUSBTransfer(dev *libusbDevHandle, ei *EndpointDesc, bufLen int, timeout time.Duration) (*usbTransfer, error) {
var isoPackets, isoPktSize int var isoPackets, isoPktSize int
if ei.TransferType == TransferTypeIsochronous { if ei.TransferType == TransferTypeIsochronous {
isoPktSize = ei.MaxPacketSize isoPktSize = ei.MaxPacketSize
if len(buf) < isoPktSize { if bufLen < isoPktSize {
isoPktSize = len(buf) isoPktSize = bufLen
} }
isoPackets = len(buf) / isoPktSize isoPackets = bufLen / isoPktSize
debug.Printf("New isochronous transfer - buffer length %d, using %d packets of %d bytes each", len(buf), isoPackets, isoPktSize) debug.Printf("New isochronous transfer - buffer length %d, using %d packets of %d bytes each", bufLen, isoPackets, isoPktSize)
} }
done := make(chan struct{}, 1) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -134,7 +138,7 @@ func newUSBTransfer(dev *libusbDevHandle, ei *EndpointDesc, buf []byte, timeout
t := &usbTransfer{ t := &usbTransfer{
xfer: xfer, xfer: xfer,
buf: buf, buf: libusb.buffer(xfer),
done: done, done: done,
} }
runtime.SetFinalizer(t, func(t *usbTransfer) { runtime.SetFinalizer(t, func(t *usbTransfer) {

View File

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