PORTS Forum

Ports and Interfaces => USB => Topic started by: ulao on September 26, 2020, 10:27:33 pm

Title: linux dies after sending a control transfer.
Post by: ulao on September 26, 2020, 10:27:33 pm
I complied a small little app using libusb, it runs great on windows and also works on linux. However once I send a control transfer on linux, the USB D- line is stays low. Although,  the transfer did make it to the device ( I verified that). I can send another controller transfer so the USB seems to still be alive but all USB activity otherwise is halted.
Also attached the logic capture from saleae that has the USB data in it. I'm guessing its a kernel bug.
The Linux Kernel is Linux 4.19.0-socfpga-r1 armv7l
Title: Re: linux dies after sending a control transfer.
Post by: ulao on September 27, 2020, 11:28:12 am
Here is the exact data.
Not sure what that X is trying to tell me? but it just keeps going on for 6 ms, than dies.
Title: Re: linux dies after sending a control transfer.
Post by: Jan Axelson on September 27, 2020, 08:29:01 pm
Hard to say much without knowing what the device is (commercial product or something under development), what the control transfer is, and whether that control transfer succeeds under Windows. Surely libusb is capable of requesting control transfers.
Title: Re: linux dies after sending a control transfer.
Post by: ulao on September 27, 2020, 09:34:57 pm
It's a device I developed. It has worked fine for years on windows and even under some versions of linux from the best of my knowledge.  So far I can send gets but not sets. It seems the sets do make it to the device but stall the USB completely. I think the answer is in my output capture but it's a bit outside my level of  knowledge.  I also attached a cvs of the logged data but I think its useless in this form. My best guess is some type of kernel bug because I know libusb has worked with other OS's in the past.  It sort of acts like the host didnt receive ack but I know I'm sending that.

The device I'm developing does use V-USB but people have used this drive many times over. It could be my implementation but its been active since 2007. If there was a bug in my code I'd think I'd know by now.

for kicks I ran my application on ubuntu but no commands work I get this for all
.Error sending message: error sending control message: Bad file descriptor
Comes from usbOpenDevice(&dev, VENDOR, NULL, ID, NULL, 1);
this uses usbcalls.c from an open source libusb project.
But I'm pretty sure this is a compilation issue.


Title: Re: linux dies after sending a control transfer.
Post by: Jan Axelson on September 28, 2020, 04:53:33 pm
For Set Report, remember the device has to ACK the data and return a 0-length data packet in the status phase.
Title: Re: linux dies after sending a control transfer.
Post by: ulao on September 28, 2020, 07:12:36 pm
Does this mean my device is sending NAK? And does the data0 packet show I'm sending 516? It should only be 8 bytes.
'
Title: Re: linux dies after sending a control transfer.
Post by: ulao on September 28, 2020, 07:24:38 pm
ok, I see what part of my problem was. I was not supplying the right size and just had it send the 512 max size of he array.  but my device still stalls.  I have it on the scope nice now but it looks good to me, am I missing something in this output?

I'm wondering if this is just a linux thing?

Just to recap, my device starts polling for data (HID gamepad) when the OS ask for it. Once I send a Control Transfer, the data arrives and  the polling stops. I would expect the polling to continue since the OS is asking for it but instead the device requires a reset. 
Title: Re: linux dies after sending a control transfer.
Post by: ulao on September 29, 2020, 06:10:08 pm
So I found my issue but I do not understand why?

This is normally free when the interrupt is ready.

   while (!usbInterruptIsReady())
#define usbInterruptIsReady()   (usbTxLen1 & 0x10)
/* This macro indicates whether the last interrupt message has already been
 * sent. If you set a new interrupt message before the old was sent, the
 * message already buffered will be lost.
 */


Say about ever 8ms (like most OS's and according to USB spec), but once I send a Control Transfers (on linux not windows) this is always true. This explains why only Control Transfers work and my normal interrupts for gamepad data stop.  But why would this not go back to false, does the drive think the CT is still going, is it missing the zero packet?

according to my driver.

#if USB_CFG_IMPLEMENT_FN_WRITE
        if(usbMsgFlags & USB_FLG_USE_USER_RW){
            uchar rval = usbFunctionWrite(data, len);
            if(rval == 0xff){   /* an error occurred */
                usbTxLen = USBPID_STALL;
            }else if(rval != 0){    /* This was the final package */
                usbMsgLen = 0;  /* answer with a zero-sized data packet */ <------------------
            }
        }
#endif

it should have done that as I return a 1 after I receive my data. Not sure what that looks like but I posted my output of the packets above.


Could this maybe be a linux limitation? Maybe it has an issue with claiming the device? I made sure to do a usbCloseDevice when I was done but Linux just is not able to use the device for interrupt transfers  till its reset.

Also I do not understand what library my codes uses? I track it back as for as usb.h

 * Copyright (c) 2000-2003 Johannes Erdfelt <johannes@erdfelt.com>
 * Copyright (c) 2015      Nathan Hjelm <hjelmn@cs.unm.edu>
 all other call are written by Christian Starkjohann and I know who that is but they are just wrappers.
Title: Re: linux dies after sending a control transfer.
Post by: Jan Axelson on October 03, 2020, 10:06:15 am
Yes, the host guarantees that bandwidth will be available for interrupt transfers, and Linux is no exception. But the host will cease communications if a control transfer fails in an unexpected way.

From USB Complete, 5th Ed:

A USB 2.0 device has these responsibilities for transfers on a control endpoint:

Send ACK in response to every Setup packet received without error.

For supported control write requests, send ACK in response to received data in the
Data stage (if present) and return a ZLP in the Status stage.

For supported control read requests, send data in response to IN token packets in
the Data stage and ACK the received ZLP in the Status stage.

For unsupported requests, return STALL in the Data or Status stage.

For all but the Setup stage, one or more NAKs preceding ACK, ZLP, data, or STALL are
acceptable up to the timing limit for the stage.
Title: Re: linux dies after sending a control transfer.
Post by: ulao on October 03, 2020, 10:28:31 am
That does help then as it explains why Linux is halting further communication for my interrupt transfer. However does not explain why Windows cares not.  I do not see any reason to suspect an error in my output above unless maybe that last packet was not Ack'd?
Title: Re: linux dies after sending a control transfer.
Post by: Jan Axelson on October 09, 2020, 10:53:17 am
Timing can be different on different hosts. Years ago I recall problems with devices that were designed to work with Windows but not fully compliant with the specs, and they would fail on other OSes. An OS can also choose to ignore non-compliant behavior, but a missing ACK would be a major issue that any OS would flag.
Title: Re: linux dies after sending a control transfer.
Post by: ulao on October 10, 2020, 07:37:47 pm
Ok turns out to be the call to libusb.The second I get a good retrun from usbOpenDevice Linux cuts off all USB traffic but for the application that called this.  This is what that function does.

Code: [Select]
int usbOpenDevice(usbDevice_t **device, int vendor, char *vendorName, int product, char *productName, int _usesReportIDs)
{
struct usb_bus      *bus;
struct usb_device   *dev;
usb_dev_handle      *handle = NULL;
int                 errorCode = USB_ERROR_NOTFOUND;
static int          didUsbInit = 0;

    if(!didUsbInit){
        usb_init();
        didUsbInit = 1;
    }
    usb_find_busses();
    usb_find_devices();
    for(bus=usb_get_busses(); bus; bus=bus->next){
        for(dev=bus->devices; dev; dev=dev->next){
            if(dev->descriptor.idVendor == vendor && dev->descriptor.idProduct == product){
                char    string[256];
                int     len;
                handle = usb_open(dev); /* we need to open the device in order to query strings */
                if(!handle){
                    errorCode = USB_ERROR_ACCESS;
                    fprintf(stderr, "Warning: cannot open USB device: %s\n", usb_strerror());
                    continue;
                }
                if(vendorName == NULL && productName == NULL){  /* name does not matter */
                    break;
                }
                /* now check whether the names match: */
                len = usbGetStringAscii(handle, dev->descriptor.iManufacturer, 0x0409, string, sizeof(string));
                if(len < 0){
                    errorCode = USB_ERROR_IO;
                    fprintf(stderr, "Warning: cannot query manufacturer for device: %s\n", usb_strerror());
                }else{
                    errorCode = USB_ERROR_NOTFOUND;
                    /* fprintf(stderr, "seen device from vendor ->%s<-\n", string); */
                    if(strcmp(string, vendorName) == 0){
                        len = usbGetStringAscii(handle, dev->descriptor.iProduct, 0x0409, string, sizeof(string));
                        if(len < 0){
                            errorCode = USB_ERROR_IO;
                            fprintf(stderr, "Warning: cannot query product for device: %s\n", usb_strerror());
                        }else{
                            errorCode = USB_ERROR_NOTFOUND;
                            /* fprintf(stderr, "seen product ->%s<-\n", string); */
                            if(strcmp(string, productName) == 0)
                                break;
                        }
                    }
                }
                usb_close(handle);
                handle = NULL;
            }
        }
        if(handle)
            break;
    }
    if(handle != NULL){
        int rval, retries = 3;
        //if(usb_set_configuration(handle, 1)){
            //fprintf(stderr, "Warning: could not set configuration: %s\n", usb_strerror());
        //}
        /* now try to claim the interface and detach the kernel HID driver on
         * linux and other operating systems which support the call.
         */
        while((rval = usb_claim_interface(handle, 0)) != 0 && retries-- > 0){
#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
            if(usb_detach_kernel_driver_np(handle, 0) < 0){
                fprintf(stderr, "Warning: could not detach kernel HID driver: %s\n", usb_strerror());
            }
#endif
        }
#ifndef __APPLE__
        if(rval != 0)
            fprintf(stderr, "Warning: could not claim interface\n");
#endif
/* Continue anyway, even if we could not claim the interface. Control transfers
 * should still work.
 */
        errorCode = 0;
        *device = handle;
        usesReportIDs = _usesReportIDs;
    }
    return errorCode;
}
Calling usbCloseDevice does not release the device either. I must reset it.
I did notice this bit here
Continue anyway, even if we could not claim the interface. Control transfers should still work.
but commenting any of that make the CT's report error device is busy. Makes me yet again wonder if linux is just not as capable as windows.

EDIT: hmm, I'm not alone.
https://libusb-devel.narkive.com/gOkpkDrQ/re-attaching-device-to-kernel-driver-after-usb-detach-kernel-driver-np

and

https://stackoverflow.com/questions/53196265/libusb-send-control-transfer-without-interrupting-stream
Title: Re: linux dies after sending a control transfer.
Post by: Jan Axelson on October 15, 2020, 10:28:29 am
You are using libusb to communicate and at the same time sending HID-class control transfers?
Title: Re: linux dies after sending a control transfer.
Post by: ulao on October 15, 2020, 07:54:37 pm
No, I'm not necessarily using libusb to communicate. I'm not sure what to OS does to do that? If you connect a HID USB gamepad to a linux  box, you will see the event system mount it to js0. You then can use 'Jstest js0' to see the buttons and what the states are. I'm not really sure what jstest uses. Possibly SDL and if so I'm not sure what SDL uses.

SDL's page says these are the dependencies.
sudo apt-get install git cmake build-essential sqlite3 libsqlite3-dev libssl1.0-dev libssl1.0.0 libusb-1.0-0-dev libudev-dev libgtest-dev libbluetooth3 libbluetooth-dev bluez-tools libpulse-dev python3-pip python3-setuptools

So maybe it is using libusb. If so maybe linux is just not capable of sending controller transfers to a controller, as it is useless unless you are polling from it. I mean, that is its only intention...

The same thing is done in windows.  but when the USB drive is attached, it start polling (no need to run anything). In the case of windows I use  control transfers with HIDAPI, I'm not sure what the OS uses to poll the controller I'm guessing HIDUSB or WINUSB depending.
Title: Re: linux dies after sending a control transfer.
Post by: Jan Axelson on October 16, 2020, 11:39:05 am
OK, just wanted to be sure you understood that the host assigns one driver to the HID interface, typically the HID driver, and you can't use two drivers/APIs to access the same device at the same time.
Title: Re: linux dies after sending a control transfer.
Post by: ulao on October 16, 2020, 10:24:15 pm
Quote
OK, just wanted to be sure you understood that the host assigns one driver to the HID interface, typically the HID driver, and you can't use two drivers/APIs to access the same device at the same time.

Well that is not true depending on how you read it. In windows I can use HIDAPI to talk to a device that is also in use by the OS. On top of that I can use HIDAPI to poll interrupts transfers + control transfers + let a another DirectX application game poll for data all at the same time. So if you mean, that you can not have too application usb HIDAPI at the sometime I'd agree. Although one can use HIDAPI and another (HIDUSB?). I can provide proof.



I think I'm learning that in the case of linux its all done with one driver. Sort of a poor design but still not %100 sure I understand this.

Linux has a gamepad 'object' that can use the linux event system. Although to use it with any software you need to use sdl. sdl apparently uses linusb. So using a usb device in any way on linux at all, you are going to need to use libusb and there are no other ways. So you can never use two methods to talk to a device like you can in windows.

Second, I though a USB device was designed to have multiple end points. So you should be able to communicate with ENP0 with libusb and ENP1 with libusb in another instance, no? Or is the limitation the fact only one application can use libusb at a time.

further I was pretty sure you can also talk to a USB device via control transfers at the same time its polling bulk or interrupt date? This comment in the libusb interface I have suggest just that.

/* Continue anyway, even if we could not claim the interface. Control transfers
 * should still work.
 */

Title: Re: linux dies after sending a control transfer.
Post by: ulao on October 16, 2020, 10:38:39 pm
Here is a screen shot of 3 application using the same driver.

1 Native windows, polling the game  controller interrupt transfer.
2 My own app using Control transfers and interrupt transfers via HIDAPI.
3 Dolphin using Direct X using control transfers to set up the PID force feedback engine and polling interrupt transfers for inputs.

When you press buttons or move the stick, all of these windows show movement.
Title: Re: linux dies after sending a control transfer.
Post by: Jan Axelson on October 18, 2020, 12:50:33 pm
The host uses information retrieved during enumeration to assign a low-level driver to a device or interface. Unless an application has exclusive access, multiple applications can access the same device. But they all need to communicate with the driver assigned to the device, for example HID or libusb. Yes, a HID can perform periodic interrupt transfers and HID-class control transfers. 
Title: Re: linux dies after sending a control transfer.
Post by: ulao on October 18, 2020, 07:47:24 pm
Sure, and this is what I figured. My entire point is that Linux seems rather "picky" in this case.

In windows I can run 2 DirectX application at the same time. Do that in Linux (two LibUSB apps) and you fail.  Also LibUSB should have some type of a read only mode or something.  I mean If you want to use a device with something like jstest, there really is no need to take full control of it.  Its just a test app, why take exclusive control.

Though ok, lets settle to agree that you can only run one instance of LibUSB. The what else can I use to communicate with the driver simultaneously? See with linux there is only one option, LibUSB.


Linux should be thinking more open about its devices and one app (maybe SDL) should expose an API. So that many application can use it, or maybe libusb should do this. As it is, its useless accept for 1 case. Expose the device, don't own it.... And I mean that from the OS level. Seems Linux has no way to use a USB  gamepad other then libUSB . So it only seems reasonable that libUSB should allow some type of API once it takes exclusive writes of a device.


I just learned that libusb is not the only option.
 yigityuce / HidApi
seem to show a way to access a device without libusb. I may give that a try.
Title: Re: linux dies after sending a control transfer.
Post by: bpaddock on October 19, 2020, 08:49:59 am
yigityuce / HidApi

That is a five year old fork of HIDAPI.

The libusb people took over HIDAPI and
is keeping it up to date.  Updated three days ago.

They have fixed long standing bugs and addressed issues that
the original author refused to fix, that needed fixed.

Find it here:

https://github.com/libusb/hidapi


While multiple 'things' may be able to access a device at the same time, it is not always a good idea.
I run a HIDAPI based program and my colleague developed for the same device in Delphi.
Running my HIDAPI based program causes his Delphi program to crash if I forget to shut it down.
The device itself doesn't care.