Defective USB Host hanging up your target - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: February 02, 2023, 11:08:48 pm
content: I think this happens via an interrupt flood.In the other direction, I've often seen PCs getting hanged up via some defective USB device being plugged in.Is there any way to guard against this?In my project the USB (CDC & MSC) is implemented entirely under interrupt, so is vulnerable to this kind of thing. One could perhaps detect an excessive IRQ frequency but what then? Block the interrupt for ever (until a power cycle)?

author: ejeffrey
author_rank: Super Contributor
timestamp: February 02, 2023, 11:19:20 pm
content: USB never generates interrupts. "interrupts transfers" are a type of USB communication, but not related to host interrupts.

author: peter-h
author_rank: Super Contributor
timestamp: February 03, 2023, 03:17:21 am
content: The USB implementation in the 32F4 is wholly interrupt-driven.A CDC ISR transfers 64 byte packets. A MSC ISR transfers 512 byte packets.It perhaps could be run using polling, from an RTOS task, but that would be inefficient.

author: bugnate
author_rank: Regular Contributor
timestamp: February 03, 2023, 03:54:19 am
content: You are asking what can be done on the device side in the case of a defective host? IMO the clean thing would be to initiate a soft disconnect on your end (see SDIS bit). On the host side this appears as an unplug. The USB +5v will stay active but there might be implications for power otherwise (but probably not). From there I guess you could either try to re-connect or give up on the host.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 03, 2023, 05:00:10 am
content: Quote from: peter-h on February 02, 2023, 11:08:48 pmI don't think there's a mechanism which would let a host flood your MCU with interrupts.

author: peter-h
author_rank: Super Contributor
timestamp: February 03, 2023, 01:50:10 pm
content: The Host can poll it very fast.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 03, 2023, 02:33:40 pm
content: Quote from: peter-h on February 03, 2023, 01:50:10 pmPoll how? What tokens does it send to you?

author: peter-h
author_rank: Super Contributor
timestamp: February 03, 2023, 04:06:05 pm
content: I don't have a USB analyser so I can't tell you. But I recently saw a case where a PC (win7-64) has a VMWARE VM (winXP) running, and the computer "could not decide" whether the USB device should connect to the base OS or to the VM. The result was something weird, and it hung up the target.However, plenty of cases over the years where e.g. a duff flash stick would make a PC run very slowly.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 03, 2023, 05:19:37 pm
content: Quote from: peter-h on February 03, 2023, 04:06:05 pmI guess if it starts enumerating the device over and over again and your enumeration code is slow but high priority, it could produce a lot of load on the CPU. More likely your code is not robust enough to handle broken enumerations.It's impossible to tell without investigating, and without knowing you cannot really fix it.

author: ejeffrey
author_rank: Super Contributor
timestamp: February 03, 2023, 06:10:02 pm
content: Quote from: peter-h on February 03, 2023, 03:17:21 amSorry, I was responding to the part about "In the other direction, I've often seen PCs getting hanged up via some defective USB device being plugged in" -- this mostly happens due to a buggy driver. A USB device cannot flood the host with interrupts because it can't even create host interrupts. This is the case at least for USB 2.0 and below. I don't know how USB >= 3.0 works for superspeed devices.Anyway, I doubt that a host is sending tokens dramatically faster than the standard says, but it is certainly possible. But without some sort of analysis tool to actually see what is happening everything is just speculation. I will say, the host may be doing something unusual such as rapidly enabling and disabling the device, but the chances that the bug is with your implementation is something like 99%. The USB stacks in mainstream operating systems have been extremely well vetted and tested on literally billions and billions of devices. That doesn't mean there aren't bugs in them -- especially ones that show up in unusual circumstances. But 99% of the time the problem is going to be with your under development device, not the host.

author: peter-h
author_rank: Super Contributor
timestamp: February 03, 2023, 09:09:10 pm
content: Can anyone suggest a way of bypassing this USB OUT function ("OUT" meanstoHost i.e. the opposite of the correct USB terminology) if no USB cable is plugged in?This is a fairly common bit of code from the ST Cube USB CDC (VCP) library, with additions from me to make it thread-safe, and a timeout in case a USB Host (with a USB VCP application running) is not connected.Code:/*** @brief CDC_Transmit_FS* Data to send over USB IN endpoint are sent over CDC interface* through this function. Note that "IN" in USB terminology means* box -> PC.** @param Buf: Buffer of data to be sent* @param Len: Number of data to be sent (in bytes)* ** This function had a call rate limit of about 10kHz, and had a packet length limit* of about 800 bytes - until the flow control mod below. These limits still apply if* flow_control=false.** This function is BLOCKING if flow_control=true. That means that if there is no Host* application receiving the data (e.g. Teraterm) it will block output.* The problem is that if no USB device is connected from startup, the while() loop* would block for ever, hence the timeout.** Flow control is normally disabled for the debugging output functions.** If called before USB thread starts, all output is dumped - for obvious reasons!** Mutexed to enable multiple RTOS tasks to output to port 0.**/#define USB_TX_TIMEOUT 100// 100msuint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len, bool flow_control){if ( g_USB_started && (Len>0) ){uint32_t counter = 0;bool timeout = false;osMutexAcquire(g_CDC_transmit_mutex, osWaitForever);USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassDataCDC;// Wait on USB Host accepting the data.if ( flow_control ){// Loop around until USB Host has picked up the last data// This rarely holds things up - 20us typ. delaywhile (hcdc->TxState != 0)// This gets changed by USB thread, hence the g_USB_started test{osDelay(1);counter++;if (counter>USB_TX_TIMEOUT){timeout=true;break;}}}if (!timeout){// Output the data to USBUSBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);USBD_CDC_TransmitPacket(&hUsbDeviceFS);}osMutexRelease(g_CDC_transmit_mutex);g_comms_act=LED_COM_TC;// indicate data flow on LED 0// If flow control is selected OFF, we do a crude wait to make CDC output work// even with a slow Host. This actually needs to be only a ~100-200us wait, but// is host dependent.if ( !flow_control ){osDelay(2);}}return USBD_OK;}

author: peter-h
author_rank: Super Contributor
timestamp: February 05, 2023, 06:17:01 pm
content: Update, in case someone finds this one day, is herehttps://community.st.com/s/question/0D53W0000272BM1SAM/32f417-can-one-detect-when-a-usb-host-is-connected?t=1675610861832No good solution really. Sensing VBUS is the easy low hanging fruit, detecting if a USB cable is even connected.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 05, 2023, 06:39:20 pm
content: Quote from: peter-h on February 05, 2023, 06:17:01 pmYou're not really interested in whether the cable is connected, but rather whether it is successfully enumerated by the OS. The best way would be to know if any files are open for your device, but without a custom driver this is not really possible.In the USB library, or whatever you use, there must be a variable which indicates the USB state. Otherwise it wouldn't be able to enumerate. At the very least there must a variable which holds the device address given to your device by the host. You can try to use it.

author: peter-h
author_rank: Super Contributor
timestamp: February 05, 2023, 07:56:32 pm
content: I agree, but it is not trivial.Open files are undetectable on a FAT volume. Windoze sees a removable drive as a load of 512 byte blocks and it totally owns them, and uses them as it wishes. I have FatFS providing file-level visibility in this volume to internal applications, but it can't even tell me when windoze has finished writing a file! (There are hacks for this, involving snooping inside USB MSC packets).But it is a fair point in that windoze will see both CDC and MSC profiles in this case and a connection to MSC may be more detectable than a connection to CDC (VCP). In particular I believe windoze does a 1 block read of a removable drive at 1Hz and that would be trivial to detect. But, MacOS or Unix??QuoteI will use it if you can tell me where it isI am using the ST Cube MX library, from ~ 3 years ago. I just don't have the understanding.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 05, 2023, 09:08:52 pm
content: Quote from: peter-h on February 05, 2023, 07:56:32 pmReminds me of "knowing where to tap" story:http://jeffacubed.com/the-boilermaker-story-or-knowing-where-to-tap/If you look at the source code, it must be easy to spot. Most likely they named the state variable using something with "state" in it.

author: ejeffrey
author_rank: Super Contributor
timestamp: February 05, 2023, 10:24:49 pm
content: Quote from: peter-h on February 05, 2023, 07:56:32 pmWait, what does the filesystem driver have to do with a CDC device?

author: peter-h
author_rank: Super Contributor
timestamp: February 05, 2023, 10:46:46 pm
content: One could determine whether a "connection to some kind of USB Host" exists, from the 1Hz removable storage device poll.But yes it won't tell you whether anybody is running a VCP application on the relevant PC COM port.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: February 06, 2023, 10:35:43 am
content: There aretonsof discussions on the subject just a single Google request away.This oneeven mentions some interrupt-related bug in older ST code (among with solutions how to detect the CONFIGURED state and VCP open/close).

author: peter-h
author_rank: Super Contributor
timestamp: February 06, 2023, 11:21:26 am
content: Thanks.There is some weird code there which might be working by luck, like this one which is not checking TxState (can work by luck because I found experimentally that USB can accept data at a very high speed, like 10k packets per second, and the TxState flag goes busy for only 20us at a time)Code:USBD_CDC_SetTxBuffer(hUsbDevice_0, Buf, Len);// Busy wait if USB is busy or exit on success or disconnection happenswhile(1){//Check if USB went offline while retryingif ((hUsbDevice_0->dev_state != USBD_STATE_CONFIGURED)|| (hUsbDevice_0->ep0_state == USBD_EP0_STATUS_IN)){result = USBD_FAIL;break;}// Try sendresult = USBD_CDC_TransmitPacket(hUsbDevice_0);I also wonder why he does a set_buffer, then other checks, and then conditionally does a transmit. Surely those two lines should always go together, and only once you are actually outputting data.The other thing is that they appear to be transmitting from a "foreground code" context, while the USB code all runs under interrupts, so interrupts need to be disabled around the buffer set and data output - like I do belowCode:if ( g_USB_started && (Len>0) && (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9)==1) ){osMutexAcquire(g_CDC_transmit_mutex, osWaitForever);USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassDataCDC;uint32_t timeout=0;bool error=false;// Wait on USB Host accepting the data.// A simple 1 sec timeout prevents a hang.if ( flow_control ){// Loop around until USB Host has picked up the previous datawhile (hcdc->TxState != 0)// This gets changed by USB interrupt{osDelay(2);timeout++;if (timeout>500){hcdc->TxState=0;error=true;break;}}}if (!error){// Output the data to USB.// USB interrupts must be disabled around this.__disable_irq();USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);USBD_CDC_TransmitPacket(&hUsbDeviceFS);__enable_irq();}osMutexRelease(g_CDC_transmit_mutex);g_comms_act=LED_COM_TC;// indicate data flow on LED 0// If flow control is selected OFF, we do a crude wait to make CDC output work// even with a slow Host.// In the usage context (outputting debugs to Teraterm) this actually needs to be only// a ~100-200us wait, but is host dependent. This gives us a 1-2ms delay between// *blocks* (typically whole lines).if ( !flow_control ){asm("nop");// for breakpointsosDelay(2);}}I will look at the other code and see if I can extract some additional "USB active" qualifiers. However so much of this seems to be Host OS dependent.I can see stuff likeCode:USBD_StatusTypeDef USBD_LL_SOF(USBD_HandleTypeDef *pdev){if(pdev->dev_state == USBD_STATE_CONFIGURED){if(pdev->pClass->SOF != NULL){pdev->pClass->SOF(pdev);}}return USBD_OK;}but can't see how to get to pdev from within CDC_Transmit_FS().Somebody on the ST forum wrote that __disable_irq(); cannot be used within an RTOS task but I totally don't get why that should be. Obviously it will stop RTOS task switching. If you just want to stop RTOS task switching you use portENTER_CRITICAL().After more digging I found thatCode:/*** @brief Opens an endpoint of the low level driver.* @param pdev: Device handle* @param ep_addr: Endpoint number* @param ep_type: Endpoint type* @param ep_mps: Endpoint max packet size* @retval USBD status*/USBD_StatusTypeDef USBD_LL_OpenEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_mps){gets called when a USB connection is made, andCode:/*** @brief Closes an endpoint of the low level driver.* @param pdev: Device handle* @param ep_addr: Endpoint number* @retval USBD status*/USBD_StatusTypeDef USBD_LL_CloseEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr){TopLED(false);HAL_StatusTypeDef hal_status = HAL_OK;USBD_StatusTypeDef usb_status = USBD_OK;hal_status = HAL_PCD_EP_Close(pdev->pData, ep_addr);usb_status = USBD_Get_USB_Status(hal_status);return usb_status;}gets called when it is broken, so these two functions could be used to toggle a flag.Another pair of functions which does a similar thing but specifically for CDC isCDC_Init_FS()CDC_DeInit_FS()These, in the original ST code, do a malloc() and free() which nicely crashes your target after a few days as windoze goes to sleep periodically - due to heap fragmentationThis was replaced by a static buffer, which incidentally makes the 2nd function an empty one.I am now using these to toggle a flag. No indication however if there is anapplicationon the VCP COM port.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 06, 2023, 03:05:27 pm
content: Quote from: peter-h on February 06, 2023, 11:21:26 amNone of that is Host OS dependent. You can get an MCU, configure it as a host, connect your device to it, and you will not be able to distinguish this from Windows or Linux. There are some heuristics you can use to differentiate between host OSes. Like Windows will ask for string 0xEE while others won't. But none of this will tell you if there are any apps actually connected to your device.Quote from: peter-h on February 06, 2023, 11:21:26 amUSBD_STATE_CONFIGURED is already a good indicator that your device has been enumerated by the host. Without custom driver on the host PC, you won't get much better than this.SOF is a token which is sent by the USB host at the beginning of every frame (1 ms).

author: peter-h
author_rank: Super Contributor
timestamp: February 06, 2023, 03:17:30 pm
content: QuoteIn that case it looks likeCDC_Init_FS()CDC_DeInit_FS()are already doing the job for me, if I set a boolean to true in the 1st one and to false in the 2nd one.

author: Sauli
author_rank: Contributor
timestamp: February 06, 2023, 03:21:54 pm
content: If an application on the PC is expecting data from your device, you will get IN tokens to the endpoind you are using to send that data. The Cube USB stack may not directly tell you that info, but it is hidden somewhere in the USB peripheral registers. IN endpoint interrupt register is a likely candidate for that. Either it tells you that the peripheral sent a NAK in response to an IN token or that an IN token was received while the transmit FIFO was empty.Or you can just have a timeout on transmit. If the transmit times out, nobody is listening.Generally you have no hope sending anything unless the USB peripheral is in configured state. And by that I mean it is both configured and not suspended. If it is suspended you could try remote wakeup first. That probably won't help though as the PC should not suspend a port that an application is waiting data from. Windows suspends a port if there is no application connected to that port.

author: peter-h
author_rank: Super Contributor
timestamp: February 06, 2023, 04:14:39 pm
content: I've done a 1 sec timeout on the TxState flag.Monitoring a GPIO pin with a scope shows this hardly ever moves and if it does, it is for ~20us only.I don't want to get into the USB registers. That's just too convolutedBTW my box is a client, not a Host (OTG Master or whatever you call it). OTG is in theory supported by the Cube code but was deliberately not implemented because customers have unrealistic expectations regarding what client profiles it will support, and regarding how much power it can provide.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 06, 2023, 04:29:37 pm
content: Quote from: Sauli on February 06, 2023, 03:21:54 pmIf I remember correctly, usbser.sys receives data even if the COM port is not open by any of the apps.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 06, 2023, 04:46:23 pm
content: Colossally simple, and exactly the same as forany other interrupt source: calculate maximum possible theoretical interrupt rate and do your processing in significantly less time than the interrupt period. If you have some operations that require longer time to finish and are triggered with some sort of command, just ignore the new command while things are unfinished.Just limit the speed to USB 1.1. If the peripheral can only generate an interrupt for a whole packet, that is going to be a lot of time between interrupts. If you don't make it, improve your code until you make it. If still unable, start ignoring packets. Generic pattern which works:Code:volatile int working_on_it;isr_peripheral() // high priority{if(working_on_it)return; // ignore packetworking_on_it = 1;NVIC->STIR = PROCESSING_IRQn;}isr_processing() // low priority{do_things_with_packet;working_on_it = 0;}

author: ejeffrey
author_rank: Super Contributor
timestamp: February 06, 2023, 05:28:48 pm
content: Quote from: Siwastaja on February 06, 2023, 04:46:23 pmThis is all true, but since he doesn't have any instrumentation on how many interrupts are being generated, what tokens are being received, or much of anything else, it is premature to guess that the problem has anything to do with interrupts at all. So far we started with "under certain host operations my microcontroller stops responding" to "how can I tell if there is a host process reading from my serial endpoint" with some stuff about mass storage filesystem operations mixed in. The diagnostic mechanism seems to be "pick a random function in my code, post it and ask if there are any problems with it" without explaining the context.

author: peter-h
author_rank: Super Contributor
timestamp: February 06, 2023, 05:35:47 pm
content: I am trying to walk a tight line betweenQuoteand posting too much detail which isguaranteedto produce no responseBut I moved the goalposts from what you describe, to a requirement to be able to just dump the output data if no Host is connected. And that has been solved.I am not posting "random functions".

author: GromBeestje
author_rank: Frequent Contributor
timestamp: February 06, 2023, 06:57:50 pm
content: Rather then overloading with requests could an invalid request cause the firmware to hang/crash?Eg. Windows XP used to request String Index 0xEE to receive MSOS1 descriptors, which could crash firmwares that don't perform bound checking on their USB requester handlers.

author: peter-h
author_rank: Super Contributor
timestamp: February 07, 2023, 09:41:34 am
content: The ST Cube software is dated variously around 2012 so I would expect a presumably well known winXP behaviour to be known by then.

author: GromBeestje
author_rank: Frequent Contributor
timestamp: February 07, 2023, 05:49:20 pm
content: That was merely an example of an occurrence of bad requests by hosts from the past.

author: peter-h
author_rank: Super Contributor
timestamp: February 07, 2023, 06:37:26 pm
content: I did a search of the project for 0xee and MSOS1 and nothing turns up

author: peter-h
author_rank: Super Contributor
timestamp: February 21, 2023, 07:06:47 pm
content: Just an update in case anybody finds this one day.IMHO USB is full of sh*it and the only reason why it works "ok" today is because billion $ commercial interests have spent countless man-years fixing various issues, and with each USB product vendor wasting this time and money in-house independently.I've managed to eliminate some very subtle problems by doing 2 things1) checking for VBUS=1 before sending any data out of USB2) checking a flag which is set/reset by CDC_Init_FS() and CDC_DeInit_FS(). Experts tell me that that is all one can do without having a custom driver.Note also that the ST Cube code uses the heap (malloc and free) in these two functions, so your productwilleventually crash.