From 495bc201d865d089f7bf938068ee15bc1426810d Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Tue, 15 Sep 2026 00:29:08 -0700 Subject: [PATCH] fix(usb): consume pending PM3 and PM5 commands Consume zero-length OUT packets and recognize commands already buffered by usb_read_ng on AT91 and AT32. Reuse each platform's receive cleanup to acknowledge empty packets and rearm reception without changing frame parsing. Cover coalesced commands and endpoint boundaries with the shared hardware regression. Both platforms pass 1,800 pings; PM5 also passes the previously stalling 64-byte command followed by a normal ping. Co-Authored-By: gpt-daybreak-blue --- common_arm/usb/usb_cdc_at32.c | 15 ++++++- common_arm/usb/usb_cdc_at91.c | 20 +++++++-- tools/usb_ping_test.py | 77 +++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 tools/usb_ping_test.py diff --git a/common_arm/usb/usb_cdc_at32.c b/common_arm/usb/usb_cdc_at32.c index 2c6b7d4b8..2edd1aaa6 100644 --- a/common_arm/usb/usb_cdc_at32.c +++ b/common_arm/usb/usb_cdc_at32.c @@ -431,10 +431,23 @@ uint16_t usb_available_length(void) { * @return */ bool usb_poll_validate_length(void) { +#ifndef AS_BOOTROM + // A previous USB packet may already contain the next command. + if (usb_read_ng_has_buffered_data()) { + return true; + } +#endif if (usb_poll() == false) { return false; } - return usb_available_length() > 0; + if (usb_available_length() > 0) { + return true; + } + + // Consume a terminating zero-length packet and rearm the OUT endpoint. + // Otherwise no subsequent command can be received. + usb_vcp_get_rxdata(udev, NULL, 0); + return false; } /** diff --git a/common_arm/usb/usb_cdc_at91.c b/common_arm/usb/usb_cdc_at91.c index 3e80b36a9..f7ed19963 100644 --- a/common_arm/usb/usb_cdc_at91.c +++ b/common_arm/usb/usb_cdc_at91.c @@ -184,6 +184,7 @@ static uint16_t usb_read_ng_data_available(void) { static uint8_t usb_read_ng_data_read(void) { return pUdp->UDP_FDR[AT91C_EP_OUT]; } +#endif // Implemented for read_ng static void usb_read_ng_clear(void) { @@ -196,6 +197,7 @@ static void usb_read_ng_clear(void) { } } +#ifndef AS_BOOTROM // Instance for 'read_ng' apis static const usb_read_ng_config_t g_usb_read_ng_config = { .is_link_ready = usb_read_ng_link_ready, @@ -372,13 +374,25 @@ FORCE_INLINE uint16_t usb_available_length(void) { bug. **/ bool usb_poll_validate_length(void) { +#ifndef AS_BOOTROM + // A previous USB packet may already contain the next command. + if (usb_read_ng_has_buffered_data()) { + return true; + } +#endif // Reuse 'usb_poll()' implemented. if (usb_poll() == false) { return false; } - // Why code this: return (((pUdp->UDP_CSR[AT91C_EP_OUT] & AT91C_UDP_RXBYTECNT) >> 16) > 0); - // For speed? but 'usb_available_length()' is a inline function. - return (usb_available_length() > 0); + if (usb_available_length() > 0) { + return true; + } + + // A transfer whose size is an exact multiple of the endpoint size can end + // with a zero-length packet. Acknowledge it here; otherwise this receive + // bank remains selected forever and data queued in the other bank stalls. + usb_read_ng_clear(); + return false; } /* diff --git a/tools/usb_ping_test.py b/tools/usb_ping_test.py new file mode 100644 index 000000000..bd91adc56 --- /dev/null +++ b/tools/usb_ping_test.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +# SPDX-License-Identifier: GPL-3.0-or-later +"""Test USB packet boundaries and buffered commands on a connected PM3 (POSIX).""" + +import argparse +import os +import select +import struct +import termios +import time +import tty + + +def ping(payload): + return struct.pack('