mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-05 16:38:09 +00:00
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 <noreply@openai.com>
This commit is contained in:
co-authored by
gpt-daybreak-blue
parent
a528d5ac1b
commit
495bc201d8
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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('<IHH', 0x61334D50, 0x8000 | len(payload), 0x0109) + payload + b'a3'
|
||||
|
||||
|
||||
def read_exact(port, length, deadline):
|
||||
data = bytearray()
|
||||
while len(data) < length:
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0 or not select.select([port], [], [], remaining)[0]:
|
||||
raise RuntimeError(f'reply timed out after {len(data)}/{length} bytes')
|
||||
data.extend(os.read(port, length - len(data)))
|
||||
return data
|
||||
|
||||
|
||||
def check_pair(port, first, second):
|
||||
# One write can place both commands in one USB packet. Neither response may
|
||||
# depend on a subsequent write waking a command left in the receive buffer.
|
||||
pending = ping(first) + ping(second)
|
||||
while pending:
|
||||
if not select.select([], [port], [], 2)[1]:
|
||||
raise RuntimeError('write timed out')
|
||||
pending = pending[os.write(port, pending):]
|
||||
deadline = time.monotonic() + 2
|
||||
for expected in (first, second):
|
||||
header = read_exact(port, 10, deadline)
|
||||
magic, length, status, _, command = struct.unpack('<IHbbH', header)
|
||||
if (magic, length, status, command) != (0x62334D50, 0x8000 | len(expected), 0, 0x0109):
|
||||
raise RuntimeError(f'unexpected reply header: {header.hex()}')
|
||||
reply = read_exact(port, len(expected) + 2, deadline)
|
||||
if reply != expected + b'b3':
|
||||
raise RuntimeError(f'ping content mismatch: {reply.hex()}')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--port', required=True)
|
||||
parser.add_argument('--rounds', type=int, default=100)
|
||||
args = parser.parse_args()
|
||||
if args.rounds < 1:
|
||||
parser.error('--rounds must be positive')
|
||||
port = os.open(args.port, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
|
||||
saved = termios.tcgetattr(port)
|
||||
try:
|
||||
tty.setraw(port)
|
||||
config = termios.tcgetattr(port)
|
||||
config[2] |= termios.CLOCAL | termios.CREAD
|
||||
config[2] &= ~getattr(termios, 'CRTSCTS', 0)
|
||||
config[4] = config[5] = termios.B115200
|
||||
termios.tcsetattr(port, termios.TCSANOW, config)
|
||||
termios.tcflush(port, termios.TCIOFLUSH)
|
||||
lengths = (0, 1, 53, 54, 55, 117, 118, 119, 512)
|
||||
for iteration in range(args.rounds):
|
||||
for length in lengths:
|
||||
payload = bytes((iteration + i) & 255 for i in range(length))
|
||||
check_pair(port, payload, bytes([iteration & 255]))
|
||||
print(f'PASS: {args.rounds * len(lengths) * 2} pings, including coalesced commands')
|
||||
finally:
|
||||
termios.tcsetattr(port, termios.TCSANOW, saved)
|
||||
os.close(port)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user