Merge pull request #3790 from hathach/fix/lpc43-hfp-reliability

Fix HFP HIL reliability issue
This commit is contained in:
Ha Thach
2026-08-17 19:04:37 +07:00
committed by GitHub
13 changed files with 228 additions and 107 deletions

View File

@ -59,6 +59,30 @@ void SystemInit(void);
// Invoked by startup code // Invoked by startup code
void SystemInit(void) void SystemInit(void)
{ {
#if defined(__ICCARM__) && !defined(DONT_RESET_ON_RESTART)
__disable_irq();
#endif
if (Chip_CREG_OnChipFlashIsPresent()) {
// The boot ROM configures flash for its 96 MHz clock, and debugger core
// resets can preserve it. Use safe timing before switching the M4 to 204 MHz.
Chip_CREG_SetFLASHAccess(FLASHTIM_SAFE_SETTING);
__DSB();
__ISB();
}
#if defined(__ICCARM__) && !defined(DONT_RESET_ON_RESTART)
// A debugger restart resets the M4 core, but can leave LPC43 peripherals and
// pending interrupts active. Match the GCC startup sequence, which the IAR
// startup lacks, before the C runtime can reuse peripheral DMA memory.
LPC_RGU->RESET_CTRL[0] = 0x10DF1000u;
LPC_RGU->RESET_CTRL[1] = 0x01DFF7FFu;
for (uint32_t i = 0; i < 8; i++) {
NVIC->ICPR[i] = UINT32_MAX;
}
__enable_irq();
#endif
#ifdef __USE_LPCOPEN #ifdef __USE_LPCOPEN
unsigned int *pSCB_VTOR = (unsigned int *) 0xE000ED08; unsigned int *pSCB_VTOR = (unsigned int *) 0xE000ED08;

View File

@ -64,9 +64,10 @@
* AHB Prescaler = 1 * AHB Prescaler = 1
* APB1 Prescaler = 1 * APB1 Prescaler = 1
* APB2 Prescaler = 1 * APB2 Prescaler = 1
* MSI Frequency(Hz) = 8000000 * MSI Frequency(Hz) = 48000000
* PLL_M = 1 * LSE Frequency(Hz) = 32768
* PLL_N = 10 * PLL_M = 6
* PLL_N = 20
* PLL_Q = 2 * PLL_Q = 2
* PLL_R = 2 * PLL_R = 2
* VDD(V) = 3.3 * VDD(V) = 3.3
@ -78,29 +79,35 @@ static inline void board_clock_init(void)
{ {
RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_CRSInitTypeDef RCC_CRSInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
/** Configure the main internal regulator output voltage /** Configure the main internal regulator output voltage
*/ */
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/* HAL clock setup reconfigures its tick while MSI is the reset SYSCLK. */
HAL_InitTick((1UL << __NVIC_PRIO_BITS) - 1UL);
/** Initializes the RCC Oscillators according to the specified parameters /** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure. * in the RCC_OscInitTypeDef structure.
*/ */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE | RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.LSEState = RCC_LSE_ON;
RCC_OscInitStruct.HSI48State = RCC_HSI48_ON; RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_11;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
RCC_OscInitStruct.PLL.PLLM = 1; RCC_OscInitStruct.PLL.PLLM = 6;
RCC_OscInitStruct.PLL.PLLN = 10; RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
HAL_RCC_OscConfig(&RCC_OscInitStruct); HAL_RCC_OscConfig(&RCC_OscInitStruct);
/* Stabilize MSI against the on-board 32.768 kHz LSE crystal. */
HAL_RCCEx_EnableMSIPLLMode();
/** Initializes the CPU, AHB and APB buses clocks /** Initializes the CPU, AHB and APB buses clocks
*/ */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
@ -112,24 +119,9 @@ static inline void board_clock_init(void)
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
/** Enable the SYSCFG APB clock /* Use the same LSE-trimmed MSI source for USB and the CPU PLL. */
*/
__HAL_RCC_CRS_CLK_ENABLE();
/** Configures CRS
*/
RCC_CRSInitStruct.Prescaler = RCC_CRS_SYNC_DIV1;
RCC_CRSInitStruct.Source = RCC_CRS_SYNC_SOURCE_USB;
RCC_CRSInitStruct.Polarity = RCC_CRS_SYNC_POLARITY_RISING;
RCC_CRSInitStruct.ReloadValue = __HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000,1000);
RCC_CRSInitStruct.ErrorLimitValue = 34;
RCC_CRSInitStruct.HSI48CalibrationValue = 32;
HAL_RCCEx_CRSConfig(&RCC_CRSInitStruct);
/* Select HSI48 output as USB clock source */
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USB; PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USB;
PeriphClkInitStruct.UsbClockSelection = RCC_USBCLKSOURCE_HSI48; PeriphClkInitStruct.UsbClockSelection = RCC_USBCLKSOURCE_MSI;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
/* Select PLL output as UART clock source */ /* Select PLL output as UART clock source */

View File

@ -437,8 +437,11 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
TU_LOG_DRV(" MTP Data %s CB: xferred_bytes=%lu, xferred_len/total_len=%lu/%lu, is_complete=%d\r\n", TU_LOG_DRV(" MTP Data %s CB: xferred_bytes=%lu, xferred_len/total_len=%lu/%lu, is_complete=%d\r\n",
is_data_in ? "IN" : "OUT", xferred_bytes, p_mtp->xferred_len, p_mtp->total_len, is_complete ? 1 : 0); is_data_in ? "IN" : "OUT", xferred_bytes, p_mtp->xferred_len, p_mtp->total_len, is_complete ? 1 : 0);
// Send/queue ZLP if packet is full-sized but transfer is complete // Send/queue ZLP if packet is full-sized but transfer is complete.
if (is_complete && xferred_bytes > 0 && !(xferred_bytes & (threshold - 1))) { // OUT must deliver this final payload to the application before receiving
// its terminating ZLP below.
const bool need_zlp = is_complete && xferred_bytes > 0 && !(xferred_bytes & (threshold - 1));
if (is_data_in && need_zlp) {
TU_LOG_DRV(" queue ZLP\r\n"); TU_LOG_DRV(" queue ZLP\r\n");
TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr)); TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr));
TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, NULL, 0, false)); TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, NULL, 0, false));
@ -466,9 +469,16 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
cb_data.io_container = headerless_packet; cb_data.io_container = headerless_packet;
cb_data.io_container.payload_bytes = xferred_bytes; cb_data.io_container.payload_bytes = xferred_bytes;
} }
tud_mtp_data_xfer_cb(&cb_data); if (xferred_bytes > 0) {
tud_mtp_data_xfer_cb(&cb_data);
}
if (is_complete) { if (need_zlp) {
TU_LOG_DRV(" queue ZLP\r\n");
TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr));
TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, NULL, 0, false));
return true;
} else if (is_complete) {
// back to header + payload for response // back to header + payload for response
cb_data.io_container = headered_packet; cb_data.io_container = headered_packet;
cb_data.io_container.header->len = sizeof(mtp_container_header_t); cb_data.io_container.header->len = sizeof(mtp_container_header_t);

View File

@ -36,6 +36,9 @@ static const ci_hs_controller_t _ci_controller[] =
#define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base) #define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base)
// NXP recommends AHBBRST = INCR16 (remainder as unspecified-length bursts)
#define CI_HS_SET_AHB_BURST(_p) (CI_HS_REG(_p)->SBUSCFG = SBUSCFG_AHBBRST_INCR16_UNSPEC)
//------------- DCD -------------// //------------- DCD -------------//
#define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) #define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum)
#define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) #define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum)

View File

@ -34,4 +34,9 @@ static const ci_hs_controller_t _ci_controller[] =
#define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) #define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum)
#define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) #define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum)
// USB0 (high-speed) only: NXP recommends AHBBRST = INCR16 (remainder as
// unspecified-length bursts)
#define CI_HS_SET_AHB_BURST(_p) \
do { if ((_p) == 0) { CI_HS_REG(_p)->SBUSCFG = SBUSCFG_AHBBRST_INCR16_UNSPEC; } } while (0)
#endif #endif

View File

@ -71,11 +71,18 @@ enum {
USBMODE_VBUS_POWER_SELECT = TU_BIT(5), // Need to be enabled for LPC18XX/43XX in host mode USBMODE_VBUS_POWER_SELECT = TU_BIT(5), // Need to be enabled for LPC18XX/43XX in host mode
}; };
// SBUSCFG
enum {
SBUSCFG_AHBBRST_INCR16_UNSPEC = 7, // INCR16 burst, remainder as unspecified-length bursts
};
// Device Registers // Device Registers
typedef struct typedef struct
{ {
//------------- ID + HW Parameter Registers-------------// //------------- ID + HW Parameter Registers-------------//
volatile uint32_t TU_RESERVED[64]; ///< For iMX RT10xx, but not used by LPC18XX/LPC43XX volatile uint32_t TU_RESERVED[36]; ///< ID/HW parameter registers, not used by this driver
volatile uint32_t SBUSCFG; ///< System Bus Interface Configuration (not present on every MCU)
volatile uint32_t TU_RESERVED[27];
//------------- Capability Registers-------------// //------------- Capability Registers-------------//
volatile uint8_t CAPLENGTH; ///< Capability Registers Length volatile uint8_t CAPLENGTH; ///< Capability Registers Length

View File

@ -237,6 +237,10 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
usbmode |= USBMODE_CM_DEVICE; usbmode |= USBMODE_CM_DEVICE;
dcd_reg->USBMODE = usbmode; dcd_reg->USBMODE = usbmode;
#ifdef CI_HS_SET_AHB_BURST
CI_HS_SET_AHB_BURST(rhport);
#endif
#ifdef CFG_TUD_CI_HS_VBUS_CHARGE #ifdef CFG_TUD_CI_HS_VBUS_CHARGE
dcd_reg->OTGSC = OTGSC_VBUS_CHARGE | OTGSC_OTG_TERMINATION; dcd_reg->OTGSC = OTGSC_VBUS_CHARGE | OTGSC_OTG_TERMINATION;
#else #else

View File

@ -82,6 +82,10 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
hcd_reg->USBMODE = USBMODE_CM_HOST; hcd_reg->USBMODE = USBMODE_CM_HOST;
#endif #endif
#ifdef CI_HS_SET_AHB_BURST
CI_HS_SET_AHB_BURST(rhport);
#endif
#if !TUH_OPT_HIGH_SPEED #if !TUH_OPT_HIGH_SPEED
hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED; hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED;
#endif #endif

View File

@ -1143,7 +1143,12 @@ static void handle_incomplete_iso_in(uint8_t rhport) {
xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN); xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN);
if (xfer->iso_retry > 0) { if (xfer->iso_retry > 0) {
xfer->iso_retry--; xfer->iso_retry--;
// Restart ISO transfe: re-write TSIZ and CTL // Restart ISO transfer: re-write DMA address, TSIZ, and CTL
#if CFG_TUD_DWC2_DMA_ENABLE
if (dma_device_enabled(dwc2)) {
epin->diepdma = (uintptr_t) xfer->buffer;
}
#endif
dwc2_ep_tsize_t deptsiz = {.value = 0}; dwc2_ep_tsize_t deptsiz = {.value = 0};
deptsiz.xfer_size = xfer->total_len; deptsiz.xfer_size = xfer->total_len;
deptsiz.packet_count = tu_div_ceil(xfer->total_len, xfer->max_size); deptsiz.packet_count = tu_div_ceil(xfer->total_len, xfer->max_size);

View File

@ -7,9 +7,8 @@
"device": true, "host": false, "dual": false "device": true, "host": false, "dual": false
}, },
"flasher": { "flasher": {
"name": "jlink", "name": "stlink",
"uid": "774470029", "uid": "0673FF575051717867034946"
"args": "-device STM32L412KB"
} }
}, },
{ {

View File

@ -23,9 +23,10 @@
# THE SOFTWARE. # THE SOFTWARE.
# Host setup (required: a missing tool fails its test rather than skipping it): # Host setup (required: a missing tool fails its test rather than skipping it):
# - System packages: sudo apt install mtools libmtp9 alsa-utils iperf # - System packages: sudo apt install mtools libmtp9 libmtp-runtime alsa-utils iperf
# mtools read_disk_file (device/cdc_msc, device/msc_dual_lun) # mtools read_disk_file (device/cdc_msc, device/msc_dual_lun)
# libmtp9 pymtp ctypes load (device/mtp); Debian 13 uses libmtp9t64 # libmtp9 pymtp ctypes load (device/mtp); Debian 13 uses libmtp9t64
# libmtp-runtime mtp-probe and the completed-device /dev/libmtp-* marker
# alsa-utils arecord (device/audio_test_freertos) # alsa-utils arecord (device/audio_test_freertos)
# iperf throughput tests (device/net_lwip_*) # iperf throughput tests (device/net_lwip_*)
# openocd unified openocd from https://github.com/hathach/openocd (branch tinyusb) for wch, rp2040/rp2350, analog max32 # openocd unified openocd from https://github.com/hathach/openocd (branch tinyusb) for wch, rp2040/rp2350, analog max32
@ -68,7 +69,7 @@ _mp = multiprocessing.get_context('fork')
Pool, Lock, Semaphore, Manager = _mp.Pool, _mp.Lock, _mp.Semaphore, _mp.Manager Pool, Lock, Semaphore, Manager = _mp.Pool, _mp.Lock, _mp.Semaphore, _mp.Manager
import hashlib import hashlib
import ctypes import ctypes
from pymtp import MTP from pymtp import LIBMTP_DeviceEntry, LIBMTP_RawDevice, MTP
import string import string
# Enumeration wait budget. The first attempt gets ENUM_TIMEOUT; retry attempts get the # Enumeration wait budget. The first attempt gets ENUM_TIMEOUT; retry attempts get the
@ -86,11 +87,11 @@ def enum_timeout() -> int:
return _enum_timeout return _enum_timeout
def wait_until(predicate, step: float = 1.0): def wait_until(predicate, step: float = 1.0, timeout: float | None = None):
"""Poll predicate under the per-attempt enum budget. Deadline-based so a slow predicate """Poll predicate under the per-attempt enum budget. Deadline-based so a slow predicate
body (subprocess, libmtp scan) counts against the budget. Returns the first truthy body (subprocess, libmtp scan) counts against the budget. An explicit timeout overrides
predicate value, or None on timeout.""" that budget. Returns the first truthy predicate value, or None on timeout."""
deadline = time.monotonic() + enum_timeout() deadline = time.monotonic() + (enum_timeout() if timeout is None else timeout)
while True: while True:
r = predicate() r = predicate()
if r: if r:
@ -285,23 +286,86 @@ def read_disk_file(uid: str, lun: int, fname: str) -> bytes:
return data return data
def open_mtp_dev(uid): def open_mtp_dev(uid: str):
mtp = MTP() mtp = MTP()
last_detail = None
deadline = time.monotonic() + 2 * enum_timeout()
def try_open(): def find_ready_mtp():
# unmount gio/gvfs MTP mount which blocks libmtp from accessing the device nonlocal last_detail
subprocess.run(f"gio mount -u mtp://TinyUsb_TinyUsb_Device_{uid}/", for marker_name in glob.glob('/dev/libmtp-*'):
shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) marker = Path(marker_name)
for raw in mtp.detect_devices(): serial = ''
mtp.device = mtp.mtp.LIBMTP_Open_Raw_Device(ctypes.byref(raw)) try:
if mtp.device: # libmtp-runtime publishes libmtp-%k only after its synchronous
sn = mtp.get_serialnumber().decode('utf-8') # mtp-probe has accepted the device. Starting from that small, ready-only
if sn == uid: # set avoids a broad sysfs scan racing unrelated parallel re-enumerations.
return mtp sysname = marker.name[len('libmtp-'):]
mtp.disconnect() dev_path = Path('/sys/bus/usb/devices') / sysname
serial = (dev_path / 'serial').read_text().strip()
if (serial.lower() != uid.lower()
or (dev_path / 'idVendor').read_text().strip() != 'cafe'
or (dev_path / 'idProduct').read_text().strip() != '4017'):
continue
busnum = int((dev_path / 'busnum').read_text())
devnum = int((dev_path / 'devnum').read_text())
usb_node = Path('/dev/bus/usb') / f'{busnum:03d}' / f'{devnum:03d}'
if marker.resolve(strict=True) != usb_node or not os.access(
usb_node, os.R_OK | os.W_OK):
last_detail = f'{marker} did not resolve to an accessible {usb_node}'
continue
return busnum, devnum
except (OSError, ValueError) as e:
# A marker can disappear while another board flashes. Only retain
# diagnostics for this board's marker, not unrelated MTP devices.
if serial.lower() == uid.lower():
last_detail = f'{marker}: {e}'
return None return None
return wait_until(try_open) def remaining() -> float:
return max(0.0, deadline - time.monotonic())
target = wait_until(find_ready_mtp, step=0.05, timeout=remaining())
if target is None:
detail = f': {last_detail}' if last_detail else '; install libmtp-runtime'
raise AssertionError(f'MTP udev device not ready for {uid}{detail}')
# A desktop GVFS session may claim MTP after udev probing. This is a no-op on
# headless runners, but preserves support for rigs where the mount exists.
try:
subprocess.run(['gio', 'mount', '-u', f'mtp://TinyUsb_TinyUsb_Device_{uid}/'],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2)
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
# GIO can race a disconnect/re-enumeration. Resolve the completed marker again
# rather than opening a stale bus/device tuple.
target = wait_until(find_ready_mtp, step=0.05, timeout=remaining())
if target is None:
raise AssertionError(f'MTP udev device disappeared for {uid}')
busnum, devnum = target
# TinyUSB needs no libmtp device quirks. Construct its raw entry directly so
# this test never probes another MTP board that is still being initialized.
entry = LIBMTP_DeviceEntry(None, 0xcafe, None, 0x4017, 0)
raw = LIBMTP_RawDevice(entry, busnum, devnum)
mtp.device = mtp.mtp.LIBMTP_Open_Raw_Device(ctypes.byref(raw))
if not mtp.device:
raise AssertionError(f'libmtp could not open MTP {uid} at {busnum:03d}/{devnum:03d}')
try:
serial_raw = mtp.get_serialnumber()
serial = serial_raw.decode('utf-8') if serial_raw else ''
if serial.lower() != uid.lower():
raise AssertionError(f'MTP serial mismatch at {busnum:03d}/{devnum:03d}: {serial}')
except Exception:
try:
mtp.disconnect()
except Exception:
pass
raise
return mtp
def get_printer_dev(id: str, vendor_str, product_str, ifnum: int): def get_printer_dev(id: str, vendor_str, product_str, ifnum: int):
@ -1003,15 +1067,13 @@ def test_device_mtp(board):
_null = os.open(os.devnull, os.O_WRONLY) _null = os.open(os.devnull, os.O_WRONLY)
os.dup2(_null, fd) os.dup2(_null, fd)
mtp = open_mtp_dev(uid) try:
mtp = open_mtp_dev(uid)
# --- AFTER: restore stderr --- finally:
os.dup2(_saved, fd) # --- AFTER: restore stderr ---
os.close(_null) os.dup2(_saved, fd)
os.close(_saved) os.close(_null)
os.close(_saved)
if mtp is None or mtp.device is None:
assert False, 'MTP device not found'
try: try:
assert b"TinyUSB" == mtp.get_manufacturer(), 'MTP wrong manufacturer' assert b"TinyUSB" == mtp.get_manufacturer(), 'MTP wrong manufacturer'
@ -1037,7 +1099,9 @@ def test_device_mtp(board):
assert f2_md5_expect == hashlib.md5(f2_data).hexdigest(), 'MTP file2 wrong data' assert f2_md5_expect == hashlib.md5(f2_data).hexdigest(), 'MTP file2 wrong data'
# test send file # test send file
with open(f3, "wb") as file: with open(f3, "wb") as file:
f3_data = os.urandom(random.randint(1024, 3*1024)) # 1524-byte payload + 12-byte MTP header = 3 full 512-byte buffers.
# This exercises delivery of the final OUT payload before its ZLP.
f3_data = bytes((i % 251) + 1 for i in range(1524))
file.write(f3_data) file.write(f3_data)
file.close() file.close()
fid = mtp.send_file_from_file(f3, b'file3') fid = mtp.send_file_from_file(f3, b'file3')
@ -1149,26 +1213,37 @@ def test_device_midi_test(board):
# Read MIDI messages and verify note on/off # Read MIDI messages and verify note on/off
import select import select
with open(midi_port, 'rb') as f: midi_fd = os.open(midi_port, os.O_RDONLY | os.O_NONBLOCK)
notes = [] try:
data = bytearray()
# Read for up to 3 seconds to capture a few notes (286ms interval) # Read for up to 3 seconds to capture a few notes (286ms interval)
end_time = time.monotonic() + 3 end_time = time.monotonic() + 3
while time.monotonic() < end_time: while (remaining := end_time - time.monotonic()) > 0:
ready, _, _ = select.select([f], [], [], 0.5) ready, _, _ = select.select([midi_fd], [], [], min(0.5, remaining))
if ready: if not ready:
data = f.read(64) continue
if data: try:
# Parse MIDI bytes: note_on = 0x90, note_off = 0x80 chunk = os.read(midi_fd, 64)
i = 0 except BlockingIOError:
while i + 2 < len(data): continue
status = data[i] if not chunk:
if (status & 0xF0) == 0x90: # Note On break
notes.append(data[i + 1]) data.extend(chunk)
i += 3 finally:
elif (status & 0xF0) == 0x80: # Note Off os.close(midi_fd)
i += 3
else: notes = []
i += 1 # Parse MIDI bytes: note_on = 0x90, note_off = 0x80
i = 0
while i + 2 < len(data):
status = data[i]
if (status & 0xF0) == 0x90: # Note On
notes.append(data[i + 1])
i += 3
elif (status & 0xF0) == 0x80: # Note Off
i += 3
else:
i += 1
assert len(notes) >= 2, f'Expected at least 2 MIDI notes, got {len(notes)}' assert len(notes) >= 2, f'Expected at least 2 MIDI notes, got {len(notes)}'
# Verify notes are from the expected sequence # Verify notes are from the expected sequence
@ -1231,24 +1306,16 @@ def test_device_audio_test_freertos(board):
samples = [int.from_bytes(raw[i:i + 2], 'little', signed=False) for i in range(0, len(raw), 2)] samples = [int.from_bytes(raw[i:i + 2], 'little', signed=False) for i in range(0, len(raw), 2)]
assert sample_count > 1024, f'Not enough samples captured: {sample_count}' assert sample_count > 1024, f'Not enough samples captured: {sample_count}'
# The firmware sends a continuous uint16 ramp. Using ALSA hw: capture bypasses # The producer is already running while ALSA activates streaming, so the
# PulseAudio processing, so most adjacent samples should differ by exactly 1. # initial overwritable software FIFO (at most 224 samples) can transition
total_diffs = sample_count - 1 # between ramp generations. After that startup window, require an exact ramp.
one_step = 0 startup_samples = 256
near_step = 0 for i in range(startup_samples, sample_count - 1):
for i in range(total_diffs): expected = (samples[i] + 1) & 0xFFFF
d = (samples[i + 1] - samples[i]) & 0xFFFF assert samples[i + 1] == expected, (
if d == 1: f'Audio mismatch at sample {i + 1}: expected {expected}, got {samples[i + 1]}')
one_step += 1
if d in (0, 1, 2, 47, 48, 49):
near_step += 1
one_ratio = one_step / total_diffs print(f' ALSA {pcm}', end='')
near_ratio = near_step / total_diffs
assert one_ratio >= 0.85, f'Unexpected audio pattern (strict ratio={one_ratio:.3f})'
assert near_ratio >= 0.98, f'Unexpected audio pattern (relaxed ratio={near_ratio:.3f})'
print(f' ALSA {pcm} strict={one_ratio:.3f} relaxed={near_ratio:.3f}', end='')
def test_device_hid_generic_inout(board): def test_device_hid_generic_inout(board):

View File

@ -420,6 +420,8 @@ _libmtp.LIBMTP_Get_Playlist.restype = ctypes.POINTER(LIBMTP_Playlist)
_libmtp.LIBMTP_Get_Folder_List.restype = ctypes.POINTER(LIBMTP_Folder) _libmtp.LIBMTP_Get_Folder_List.restype = ctypes.POINTER(LIBMTP_Folder)
_libmtp.LIBMTP_Find_Folder.restype = ctypes.POINTER(LIBMTP_Folder) _libmtp.LIBMTP_Find_Folder.restype = ctypes.POINTER(LIBMTP_Folder)
_libmtp.LIBMTP_Get_Errorstack.restype = ctypes.POINTER(LIBMTP_Error) _libmtp.LIBMTP_Get_Errorstack.restype = ctypes.POINTER(LIBMTP_Error)
_libmtp.LIBMTP_Dump_Errorstack.argtypes = [ctypes.POINTER(LIBMTP_MTPDevice)]
_libmtp.LIBMTP_Dump_Errorstack.restype = None
_libmtp.LIBMTP_Open_Raw_Device.restype = ctypes.POINTER(LIBMTP_MTPDevice) _libmtp.LIBMTP_Open_Raw_Device.restype = ctypes.POINTER(LIBMTP_MTPDevice)
_libmtp.LIBMTP_Open_Raw_Device.argtypes = [ctypes.POINTER(LIBMTP_RawDevice)] _libmtp.LIBMTP_Open_Raw_Device.argtypes = [ctypes.POINTER(LIBMTP_RawDevice)]
@ -451,16 +453,14 @@ class MTP:
def debug_stack(self): def debug_stack(self):
""" """
Checks if __DEBUG__ is set, if so, prints and clears the Checks if __DEBUG__ is set, and if so prints the error stack.
errorstack.
@rtype: None @rtype: None
@return: None @return: None
""" """
if __DEBUG__: if __DEBUG__ and self.device:
self.mtp.LIBMTP_Dump_Errorstack() self.mtp.LIBMTP_Dump_Errorstack(self.device)
#self.mtp.LIBMTP_Clear_Errorstack()
def detect_devices(self): def detect_devices(self):
""" """

View File

@ -1,7 +1,8 @@
# System packages (install separately): # System packages (install separately):
# sudo apt install mtools libmtp9 alsa-utils iperf # sudo apt install mtools libmtp9 libmtp-runtime alsa-utils iperf
# mtools - read_disk_file (device/cdc_msc, device/msc_dual_lun) # mtools - read_disk_file (device/cdc_msc, device/msc_dual_lun)
# libmtp9 - pymtp ctypes load (device/mtp); Debian 13 uses libmtp9t64 # libmtp9 - pymtp ctypes load (device/mtp); Debian 13 uses libmtp9t64
# libmtp-runtime - mtp-probe and the completed-device /dev/libmtp-* marker
# alsa-utils - arecord (device/audio_test_freertos) # alsa-utils - arecord (device/audio_test_freertos)
# iperf - throughput tests (device/net_lwip_*) # iperf - throughput tests (device/net_lwip_*)
hidapi hidapi