larskanis/libusb

Rookie Question

fredkelly opened this issue · 0 comments

Hello there,

Firstly, apologies if this is not the appropriate forum for implementation questions. I'm trying to use the library to create a ruby translation of the following C++ library:

void UsbDownloader::run()
{
    bool hasError = true;
#ifndef USB_READ_DUMP
    libusb_device_handle* handle = 0;

    do { // Error loop
        int r;

        // Open USB device
        handle = libusb_open_device_with_vid_pid(ctx, BSM_VID, BSM_PID);
        if (!handle) {
            qCritical() << "Failed to open the device";
            break;
        }
        qDebug() << "USB device opened";

        // Detach kernel driver
        if (libusb_kernel_driver_active(handle, USB_INTERFACE_IN)) {
            qDebug() << "Detaching kernel driver...";
            r = libusb_detach_kernel_driver(handle, USB_INTERFACE_IN);
            if (r < 0) {
                qCritical() << "libusb_detach_kernel_driver error" << r;
                break;
            }
            qDebug() << "Kernel driver detached";
        }

        // Claim interface
        qDebug() << "Claiming interface...";
        r = libusb_claim_interface(handle, USB_INTERFACE_IN);
        if (r < 0) {
            qCritical() << "usb_claim_interface error" << r;
            break;
        }
        qDebug() << "Interface claimed";

        // Prepare to receive data
        qDebug() << "Register for interrupt data";
        libusb_transfer *transfer_receive = libusb_alloc_transfer(0);
        unsigned char buffer_receive[8];
        UsbDownloaderData usb_data;
#ifdef USB_WRITE_DUMP
        usb_data.dump.setFileName(USB_WRITE_DUMP);
        usb_data.dump.open(QIODevice::WriteOnly | QIODevice::Truncate);
#endif
        libusb_fill_interrupt_transfer(transfer_receive, handle, LIBUSB_ENDPOINT_IN | USB_INTERFACE_OUT, buffer_receive, sizeof(buffer_receive), cb_in, &usb_data, 30000);
        r = libusb_submit_transfer(transfer_receive);
        if (r < 0) {
            qCritical() << "libusb_submit_transfer error" << r;
            break;
        }

        // Prepare to send request
        qDebug() << "Send control request";
        libusb_transfer *transfer_send = libusb_alloc_transfer(0);
        unsigned char buffer_send[LIBUSB_CONTROL_SETUP_SIZE + USB_CTRL_DATA_LEN] __attribute__ ((aligned (2)));
        libusb_fill_control_setup(buffer_send, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE, USB_CTRL_REQUEST, USB_CTRL_VALUE, 0, USB_CTRL_DATA_LEN);
        buffer_send[LIBUSB_CONTROL_SETUP_SIZE] = USB_CTRL_DATA_FIRST;
        memset(buffer_send + LIBUSB_CONTROL_SETUP_SIZE + 1, 0, USB_CTRL_DATA_LEN - 1);
        libusb_fill_control_transfer(transfer_send, handle, buffer_send, cb_out, 0, 3000);
        r = libusb_submit_transfer(transfer_send);
        if (r < 0) {
            qCritical() << "libusb_submit_transfer error" << r;
            break;
        }

        // Wait for completion
        while (!usb_data.completed) {
            r = libusb_handle_events_completed(ctx, 0);
            emit progress(100 * usb_data.data.size() / USB_EXPECTED_LEN);
            if (r < 0)
                break;
        }

#ifdef USB_WRITE_DUMP
        if (usb_data.dump.isOpen())
            usb_data.dump.close();
#endif

        // Emit completion signal
        if (usb_data.completed) {
            emit completed(usb_data.data);
            hasError = false;
        }
    } while(false);

    // Close USB device
    if (handle) {
        libusb_release_interface(handle, USB_INTERFACE_IN);
        qDebug() << "Released interface";
        libusb_close(handle);
        handle = 0;
        qDebug() << "Closed USB device";
    }
#else
    do { // Error loop
        QFile usb_data_file(USB_READ_DUMP);
        if (!usb_data_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            qCritical() << "Failed to open" << USB_READ_DUMP;
            break;
        }

        UsbDownloaderData usb_data;
#ifdef USB_WRITE_DUMP
        usb_data.dump.setFileName(USB_WRITE_DUMP);
        usb_data.dump.open(QIODevice::WriteOnly | QIODevice::Truncate);
#endif

        while (!usb_data_file.atEnd()) {
            char buff[20];
            qint64 s = usb_data_file.readLine(buff, 20);
            if (s < 16 || s > 17)
                break;

            if (s == 17 && buff[16] != '\n')
                break;

            libusb_transfer t;
            unsigned char b[8];
            bool ok;
            t.buffer = b;
            t.actual_length = 8;
            t.status = LIBUSB_TRANSFER_ERROR; // ignored as error, to avoid resubmit
            t.user_data = &usb_data;
            for(int i = 0; i < 8; ++i) {
                b[i] = (unsigned char) QString("%1%2").arg(buff[i * 2]).arg(buff[i * 2 + 1]).toUShort(&ok, 16);
                if (!ok)
                    break;
            }
            if (!ok)
                break;

            cb_in(&t);
            emit progress(100 * usb_data.data.size() / USB_EXPECTED_LEN);
        }

#ifdef USB_WRITE_DUMP
        if (usb_data.dump.isOpen())
            usb_data.dump.close();
#endif

        if (usb_data_file.atEnd()) {
            emit completed(usb_data.data);
            hasError = false;
        }

        usb_data_file.close();
    } while(false);
#endif
    // Emit error signal
    if (hasError)
        emit error();
}

I understand that I need to initiate a control_transfer to set up the transfer, and then use interrupt_transfer for the passing of data from my device. My current implementation looks something like this:

usb = LIBUSB::Context.new
@device = usb.devices(idVendor: BSM_VID, idProduct: BSM_PID).first
@device.open do |device|
	# setup interrupt transfer (ready to receive)
	device.claim_interface(0) do |handle|
		handle.control_transfer(
			bmRequestType: 0x21,
			bRequest: USB_CTRL_REQUEST,
			wValue: USB_CTRL_VALUE,
			wIndex: 0x000,
			dataOut: '\x10\x00\x00\x00\x00\x00\x00\x00'
		) do |result|
			puts "control_transfer result: #{result}"
		end

		handle.interrupt_transfer(
			endpoint: @device.endpoints.first,
			dataIn: 8
		)
	end
end

This yields a TRANSFER_STALL in the control_transfer block and raises error TRANSFER_TIMED_OUT (LIBUSB::ERROR_TIMEOUT).

I'm hoping someone might be able to shed some light on how to correctly use the control_transfer and interrupt_transfer methods of LIBUSB::DevHandle in tandem?

Really appreciate the help!