mirror of
https://github.com/hathach/tinyusb.git
synced 2026-08-18 02:53:35 +00:00
Merge pull request #3790 from hathach/fix/lpc43-hfp-reliability
Fix HFP HIL reliability issue
This commit is contained in:
@ -59,6 +59,30 @@ void SystemInit(void);
|
||||
// Invoked by startup code
|
||||
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
|
||||
unsigned int *pSCB_VTOR = (unsigned int *) 0xE000ED08;
|
||||
|
||||
|
||||
@ -64,9 +64,10 @@
|
||||
* AHB Prescaler = 1
|
||||
* APB1 Prescaler = 1
|
||||
* APB2 Prescaler = 1
|
||||
* MSI Frequency(Hz) = 8000000
|
||||
* PLL_M = 1
|
||||
* PLL_N = 10
|
||||
* MSI Frequency(Hz) = 48000000
|
||||
* LSE Frequency(Hz) = 32768
|
||||
* PLL_M = 6
|
||||
* PLL_N = 20
|
||||
* PLL_Q = 2
|
||||
* PLL_R = 2
|
||||
* VDD(V) = 3.3
|
||||
@ -78,29 +79,35 @@ static inline void board_clock_init(void)
|
||||
{
|
||||
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
|
||||
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
|
||||
RCC_CRSInitTypeDef RCC_CRSInitStruct = {0};
|
||||
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
|
||||
|
||||
/** Configure the main internal regulator output voltage
|
||||
*/
|
||||
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
|
||||
* in the RCC_OscInitTypeDef structure.
|
||||
*/
|
||||
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSI;
|
||||
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
|
||||
RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
|
||||
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
|
||||
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE | RCC_OSCILLATORTYPE_MSI;
|
||||
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
|
||||
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
|
||||
RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
|
||||
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_11;
|
||||
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
|
||||
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
|
||||
RCC_OscInitStruct.PLL.PLLM = 1;
|
||||
RCC_OscInitStruct.PLL.PLLN = 10;
|
||||
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
|
||||
RCC_OscInitStruct.PLL.PLLM = 6;
|
||||
RCC_OscInitStruct.PLL.PLLN = 20;
|
||||
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
|
||||
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
|
||||
|
||||
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
|
||||
*/
|
||||
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);
|
||||
|
||||
/** Enable the SYSCFG APB clock
|
||||
*/
|
||||
__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 */
|
||||
/* Use the same LSE-trimmed MSI source for USB and the CPU PLL. */
|
||||
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USB;
|
||||
PeriphClkInitStruct.UsbClockSelection = RCC_USBCLKSOURCE_HSI48;
|
||||
PeriphClkInitStruct.UsbClockSelection = RCC_USBCLKSOURCE_MSI;
|
||||
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
|
||||
|
||||
/* Select PLL output as UART clock source */
|
||||
|
||||
@ -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",
|
||||
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
|
||||
if (is_complete && xferred_bytes > 0 && !(xferred_bytes & (threshold - 1))) {
|
||||
// Send/queue ZLP if packet is full-sized but transfer is complete.
|
||||
// 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_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr));
|
||||
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.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
|
||||
cb_data.io_container = headered_packet;
|
||||
cb_data.io_container.header->len = sizeof(mtp_container_header_t);
|
||||
|
||||
@ -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)
|
||||
|
||||
// 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 -------------//
|
||||
#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)
|
||||
|
||||
@ -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_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
|
||||
|
||||
@ -71,11 +71,18 @@ enum {
|
||||
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
|
||||
typedef struct
|
||||
{
|
||||
//------------- 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-------------//
|
||||
volatile uint8_t CAPLENGTH; ///< Capability Registers Length
|
||||
|
||||
@ -237,6 +237,10 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
|
||||
usbmode |= USBMODE_CM_DEVICE;
|
||||
dcd_reg->USBMODE = usbmode;
|
||||
|
||||
#ifdef CI_HS_SET_AHB_BURST
|
||||
CI_HS_SET_AHB_BURST(rhport);
|
||||
#endif
|
||||
|
||||
#ifdef CFG_TUD_CI_HS_VBUS_CHARGE
|
||||
dcd_reg->OTGSC = OTGSC_VBUS_CHARGE | OTGSC_OTG_TERMINATION;
|
||||
#else
|
||||
|
||||
@ -82,6 +82,10 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
|
||||
hcd_reg->USBMODE = USBMODE_CM_HOST;
|
||||
#endif
|
||||
|
||||
#ifdef CI_HS_SET_AHB_BURST
|
||||
CI_HS_SET_AHB_BURST(rhport);
|
||||
#endif
|
||||
|
||||
#if !TUH_OPT_HIGH_SPEED
|
||||
hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED;
|
||||
#endif
|
||||
|
||||
@ -1143,7 +1143,12 @@ static void handle_incomplete_iso_in(uint8_t rhport) {
|
||||
xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN);
|
||||
if (xfer->iso_retry > 0) {
|
||||
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};
|
||||
deptsiz.xfer_size = xfer->total_len;
|
||||
deptsiz.packet_count = tu_div_ceil(xfer->total_len, xfer->max_size);
|
||||
|
||||
@ -7,9 +7,8 @@
|
||||
"device": true, "host": false, "dual": false
|
||||
},
|
||||
"flasher": {
|
||||
"name": "jlink",
|
||||
"uid": "774470029",
|
||||
"args": "-device STM32L412KB"
|
||||
"name": "stlink",
|
||||
"uid": "0673FF575051717867034946"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -23,9 +23,10 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
# iperf throughput tests (device/net_lwip_*)
|
||||
# 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
|
||||
import hashlib
|
||||
import ctypes
|
||||
from pymtp import MTP
|
||||
from pymtp import LIBMTP_DeviceEntry, LIBMTP_RawDevice, MTP
|
||||
import string
|
||||
|
||||
# Enumeration wait budget. The first attempt gets ENUM_TIMEOUT; retry attempts get the
|
||||
@ -86,11 +87,11 @@ def enum_timeout() -> int:
|
||||
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
|
||||
body (subprocess, libmtp scan) counts against the budget. Returns the first truthy
|
||||
predicate value, or None on timeout."""
|
||||
deadline = time.monotonic() + enum_timeout()
|
||||
body (subprocess, libmtp scan) counts against the budget. An explicit timeout overrides
|
||||
that budget. Returns the first truthy predicate value, or None on timeout."""
|
||||
deadline = time.monotonic() + (enum_timeout() if timeout is None else timeout)
|
||||
while True:
|
||||
r = predicate()
|
||||
if r:
|
||||
@ -285,23 +286,86 @@ def read_disk_file(uid: str, lun: int, fname: str) -> bytes:
|
||||
return data
|
||||
|
||||
|
||||
def open_mtp_dev(uid):
|
||||
def open_mtp_dev(uid: str):
|
||||
mtp = MTP()
|
||||
last_detail = None
|
||||
deadline = time.monotonic() + 2 * enum_timeout()
|
||||
|
||||
def try_open():
|
||||
# unmount gio/gvfs MTP mount which blocks libmtp from accessing the device
|
||||
subprocess.run(f"gio mount -u mtp://TinyUsb_TinyUsb_Device_{uid}/",
|
||||
shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
for raw in mtp.detect_devices():
|
||||
mtp.device = mtp.mtp.LIBMTP_Open_Raw_Device(ctypes.byref(raw))
|
||||
if mtp.device:
|
||||
sn = mtp.get_serialnumber().decode('utf-8')
|
||||
if sn == uid:
|
||||
return mtp
|
||||
mtp.disconnect()
|
||||
def find_ready_mtp():
|
||||
nonlocal last_detail
|
||||
for marker_name in glob.glob('/dev/libmtp-*'):
|
||||
marker = Path(marker_name)
|
||||
serial = ''
|
||||
try:
|
||||
# libmtp-runtime publishes libmtp-%k only after its synchronous
|
||||
# mtp-probe has accepted the device. Starting from that small, ready-only
|
||||
# set avoids a broad sysfs scan racing unrelated parallel re-enumerations.
|
||||
sysname = marker.name[len('libmtp-'):]
|
||||
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 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):
|
||||
@ -1003,15 +1067,13 @@ def test_device_mtp(board):
|
||||
_null = os.open(os.devnull, os.O_WRONLY)
|
||||
os.dup2(_null, fd)
|
||||
|
||||
mtp = open_mtp_dev(uid)
|
||||
|
||||
# --- AFTER: restore stderr ---
|
||||
os.dup2(_saved, fd)
|
||||
os.close(_null)
|
||||
os.close(_saved)
|
||||
|
||||
if mtp is None or mtp.device is None:
|
||||
assert False, 'MTP device not found'
|
||||
try:
|
||||
mtp = open_mtp_dev(uid)
|
||||
finally:
|
||||
# --- AFTER: restore stderr ---
|
||||
os.dup2(_saved, fd)
|
||||
os.close(_null)
|
||||
os.close(_saved)
|
||||
|
||||
try:
|
||||
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'
|
||||
# test send 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.close()
|
||||
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
|
||||
import select
|
||||
with open(midi_port, 'rb') as f:
|
||||
notes = []
|
||||
midi_fd = os.open(midi_port, os.O_RDONLY | os.O_NONBLOCK)
|
||||
try:
|
||||
data = bytearray()
|
||||
# Read for up to 3 seconds to capture a few notes (286ms interval)
|
||||
end_time = time.monotonic() + 3
|
||||
while time.monotonic() < end_time:
|
||||
ready, _, _ = select.select([f], [], [], 0.5)
|
||||
if ready:
|
||||
data = f.read(64)
|
||||
if data:
|
||||
# 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
|
||||
while (remaining := end_time - time.monotonic()) > 0:
|
||||
ready, _, _ = select.select([midi_fd], [], [], min(0.5, remaining))
|
||||
if not ready:
|
||||
continue
|
||||
try:
|
||||
chunk = os.read(midi_fd, 64)
|
||||
except BlockingIOError:
|
||||
continue
|
||||
if not chunk:
|
||||
break
|
||||
data.extend(chunk)
|
||||
finally:
|
||||
os.close(midi_fd)
|
||||
|
||||
notes = []
|
||||
# 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)}'
|
||||
# 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)]
|
||||
assert sample_count > 1024, f'Not enough samples captured: {sample_count}'
|
||||
|
||||
# The firmware sends a continuous uint16 ramp. Using ALSA hw: capture bypasses
|
||||
# PulseAudio processing, so most adjacent samples should differ by exactly 1.
|
||||
total_diffs = sample_count - 1
|
||||
one_step = 0
|
||||
near_step = 0
|
||||
for i in range(total_diffs):
|
||||
d = (samples[i + 1] - samples[i]) & 0xFFFF
|
||||
if d == 1:
|
||||
one_step += 1
|
||||
if d in (0, 1, 2, 47, 48, 49):
|
||||
near_step += 1
|
||||
# The producer is already running while ALSA activates streaming, so the
|
||||
# initial overwritable software FIFO (at most 224 samples) can transition
|
||||
# between ramp generations. After that startup window, require an exact ramp.
|
||||
startup_samples = 256
|
||||
for i in range(startup_samples, sample_count - 1):
|
||||
expected = (samples[i] + 1) & 0xFFFF
|
||||
assert samples[i + 1] == expected, (
|
||||
f'Audio mismatch at sample {i + 1}: expected {expected}, got {samples[i + 1]}')
|
||||
|
||||
one_ratio = one_step / total_diffs
|
||||
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='')
|
||||
print(f' ALSA {pcm}', end='')
|
||||
|
||||
|
||||
def test_device_hid_generic_inout(board):
|
||||
|
||||
@ -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_Find_Folder.restype = ctypes.POINTER(LIBMTP_Folder)
|
||||
_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.argtypes = [ctypes.POINTER(LIBMTP_RawDevice)]
|
||||
@ -451,16 +453,14 @@ class MTP:
|
||||
|
||||
def debug_stack(self):
|
||||
"""
|
||||
Checks if __DEBUG__ is set, if so, prints and clears the
|
||||
errorstack.
|
||||
Checks if __DEBUG__ is set, and if so prints the error stack.
|
||||
|
||||
@rtype: None
|
||||
@return: None
|
||||
"""
|
||||
|
||||
if __DEBUG__:
|
||||
self.mtp.LIBMTP_Dump_Errorstack()
|
||||
#self.mtp.LIBMTP_Clear_Errorstack()
|
||||
if __DEBUG__ and self.device:
|
||||
self.mtp.LIBMTP_Dump_Errorstack(self.device)
|
||||
|
||||
def detect_devices(self):
|
||||
"""
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
# 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)
|
||||
# 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)
|
||||
# iperf - throughput tests (device/net_lwip_*)
|
||||
hidapi
|
||||
|
||||
Reference in New Issue
Block a user