2025-09-06 1:08 AM
Good day.
According to the description in RM on STM32F103, in Control Transfers:
"A USB device can determine the number and direction of data stages by interpreting the data transferred in the SETUP stage, and is required to STALL the transaction in the case of errors. To do so, at all data stages before the last, the unused direction should be set to STALL, so that, if the host reverses the transfer direction too soon, it gets a STALL as a status stage. While enabling the last data stage, the opposite direction should be set to NAK, so that, if the host reverses the transfer direction (to perform the status stage) immediately, it is kept waiting for the completion of the control operation. If the control operation completes successfully, the software will change NAK to VALID, otherwise to STALL."
At the same time, the USB 2.0 specification, in Chapter 8 on control transactions, says how to handle the special case of a lost ACK in the last phase of IN data:
"8.5.3.3 Error Handling on the Last Data Transaction
If the ACK handshake on an IN transaction is corrupted, the function and the host will temporarily disagree
on whether the transaction was successful. If the transaction is followed by another IN, the toggle retry
mechanism will detect the mismatch and recover from the error. If the ACK was on the last IN of a Data
stage, the toggle retry mechanism cannot be used and an alternative scheme must be used.
The host that successfully received the data of the last IN will send ACK. Later, the host will issue an OUT
token to start the Status stage of the transfer. If the function did not receive the ACK that ended the Data
stage, the function will interpret the start of the Status stage as verification that the host successfully
received the data. Control writes do not have this ambiguity. If an ACK handshake on an OUT gets
corrupted, the host does not advance to the Status stage and retries the last data instead. A detailed analysis
of retry policy is presented in Section 8.6.4."
If you try to do it according to the manual on STM32F103, then in this case, if on the last IN-transfer in the data phase the host sent ACK and immediately started transmitting OUT for the status phase, and the microcontroller for some reason did not see ACK from this IN transfer, then the system will completely block in anticipation, because the OUT endpoint in the device will be in NAK, and the interrupt for the last IN transaction will not arrive (because ACK was not received).
So, the strategy should be different. Instead of NAK for the last transfer for the OUT status phase, you need to set the endpoint to VALID. And thus the last activated transaction will write RX_STAT and TX_STAT to VALID in USB->EP0R at the same time.
It is a pity that in this case ST engineers did not provide the ability to mask interrupts separately for RX and TX events at the endpoints.
That is, now we need to handle two possible situations:
1) the device successfully received ACK, so for the status phase we will get 2 entries into the interrupt procedure;
2) the device lost ACK but entered the interrupt on the completed OUT on the status transaction.
In this case, it is reasonable not to handle (1) case, and when the ACK interrupt occurs, reset CTR_TX and exit the interrupt, leaving STAT_RX in VALID. But how to do this? After all, RM says that these bits, once written to VALID, cannot be changed, because it is not clear when the hardware will complete the transfer (due to the race, we can duplicate the VALID state for the OUT direction of the endpoint).