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
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;

View File

@ -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 */

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",
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);

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)
// 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)

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_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

View File

@ -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

View File

@ -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

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;
#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

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);
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);

View File

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

View File

@ -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):

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_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):
"""

View File

@ -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