From 1aaa100bdbaa462fce20b360bfdfe1dae36f39d6 Mon Sep 17 00:00:00 2001 From: zagrodzki Date: Tue, 29 Aug 2017 12:11:04 +0200 Subject: [PATCH] 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. --- endpoint.go | 8 +++++++- endpoint_stream.go | 2 +- fakelibusb_test.go | 5 +++-- libusb.go | 34 +++++++++++++++++++++++++--------- rawread/main.go | 3 +++ transfer.c | 26 +++++++++++++++++++++++++- transfer.go | 26 +++++++++++++++----------- transfer_test.go | 6 +++--- 8 files changed, 82 insertions(+), 28 deletions(-) diff --git a/endpoint.go b/endpoint.go index f7e074f..5c7c6c6 100644 --- a/endpoint.go +++ b/endpoint.go @@ -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 } diff --git a/endpoint_stream.go b/endpoint_stream.go index 052107b..4f6949c 100644 --- a/endpoint_stream.go +++ b/endpoint_stream.go @@ -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() diff --git a/fakelibusb_test.go b/fakelibusb_test.go index 6e2fb3b..3dd1fb4 100644 --- a/fakelibusb_test.go +++ b/fakelibusb_test.go @@ -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() diff --git a/libusb.go b/libusb.go index 4a6ae37..016b7db 100644 --- a/libusb.go +++ b/libusb.go @@ -27,7 +27,9 @@ import ( #cgo pkg-config: libusb-1.0 #include -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() diff --git a/rawread/main.go b/rawread/main.go index 654a1a6..e1cf4a6 100644 --- a/rawread/main.go +++ b/rawread/main.go @@ -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 { diff --git a/transfer.c b/transfer.c index ebf2080..636ac55 100644 --- a/transfer.c +++ b/transfer.c @@ -15,6 +15,7 @@ #include #include +#include #include 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); +} diff --git a/transfer.go b/transfer.go index 004b65a..78943c2 100644 --- a/transfer.go +++ b/transfer.go @@ -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) { diff --git a/transfer_test.go b/transfer_test.go index 85a6d47..9ce0fcc 100644 --- a/transfer_test.go +++ b/transfer_test.go @@ -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) }