Skip to content

Commit 8054161

Browse files
andrewleechclaude
andcommitted
usb-device-mtp: Add extensive debug logging throughout the MTP driver.
Adds detailed debug print statements to key connection and data handling paths to aid in development and troubleshooting. This includes logging for USB configuration events, data transfers, MTP command processing, and filesystem operations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1a60904 commit 8054161

File tree

1 file changed

+157
-8
lines changed
  • micropython/usb/usb-device-mtp/usb/device

1 file changed

+157
-8
lines changed

micropython/usb/usb-device-mtp/usb/device/mtp.py

Lines changed: 157 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ def __init__(self, storage_path="/", rx_size=_DEFAULT_RX_BUF_SIZE, tx_size=_DEFA
171171

172172
def desc_cfg(self, desc, itf_num, ep_num, strs):
173173
"""Build the USB configuration descriptor for this interface."""
174+
print("[MTP] Building descriptors: itf_num={}, ep_num={}".format(itf_num, ep_num))
174175
# Add the interface descriptor for MTP (PIMA 15740 Still Image)
175176
desc.interface(
176177
itf_num,
@@ -186,6 +187,9 @@ def desc_cfg(self, desc, itf_num, ep_num, strs):
186187
self.ep_in = ep_num | _EP_IN_FLAG
187188
self.ep_intr = (ep_num + 1) | _EP_IN_FLAG
188189

190+
print("[MTP] Endpoints assigned: ep_out=0x{:02x}, ep_in=0x{:02x}, ep_intr=0x{:02x}".format(
191+
self.ep_out, self.ep_in, self.ep_intr))
192+
189193
desc.endpoint(self.ep_out, "bulk", _MAX_PACKET_SIZE, 0)
190194
desc.endpoint(self.ep_in, "bulk", _MAX_PACKET_SIZE, 0)
191195
desc.endpoint(self.ep_intr, "interrupt", 8, 10) # 10ms interval for events
@@ -196,12 +200,15 @@ def num_eps(self):
196200

197201
def on_open(self):
198202
"""Called when the USB host configures the device."""
203+
print("[MTP] Device configured by host")
199204
super().on_open()
200205
# Start transfers for receiving commands and data
201206
self._rx_xfer()
207+
print("[MTP] Interface ready, waiting for MTP commands")
202208

203209
def on_reset(self):
204210
"""Called when the USB device is reset by the host."""
211+
print("[MTP] Device reset by host")
205212
super().on_reset()
206213
# Reset the session state
207214
self._session_open = False
@@ -214,34 +221,62 @@ def on_reset(self):
214221
self._send_object_handle = None
215222
self._send_object_info = None
216223
self._get_object_handle = None
224+
print("[MTP] Session state cleared")
217225

218226
def _rx_xfer(self):
219227
"""Submit a new transfer to receive data from the host."""
220228
if self.is_open() and not self.xfer_pending(self.ep_out) and self._rx.writable():
221-
self.submit_xfer(self.ep_out, self._rx.pend_write(), self._rx_cb)
229+
buf = self._rx.pend_write()
230+
print("[MTP] Submitting OUT transfer, buffer size={}".format(len(buf)))
231+
self.submit_xfer(self.ep_out, buf, self._rx_cb)
232+
else:
233+
if not self.is_open():
234+
print("[MTP] Cannot submit OUT transfer - interface not open")
235+
elif self.xfer_pending(self.ep_out):
236+
print("[MTP] Cannot submit OUT transfer - transfer already pending")
237+
elif not self._rx.writable():
238+
print("[MTP] Cannot submit OUT transfer - RX buffer full ({} bytes)".format(self._rx.readable()))
222239

223240
def _rx_cb(self, ep, res, num_bytes):
224241
"""Callback when data is received from the host."""
242+
print("[MTP] OUT transfer complete: res={}, bytes={}".format(res, num_bytes))
225243
if res == 0:
226244
self._rx.finish_write(num_bytes)
245+
print("[MTP] Scheduling data processing")
227246
schedule(self._process_rx, None)
247+
else:
248+
print("[MTP] OUT transfer failed with error {}".format(res))
228249
self._rx_xfer() # Continue receiving
229250

230251
def _tx_xfer(self):
231252
"""Submit a new transfer to send data to the host."""
232253
if self.is_open() and not self.xfer_pending(self.ep_in) and self._tx.readable():
233-
self.submit_xfer(self.ep_in, self._tx.pend_read(), self._tx_cb)
254+
buf = self._tx.pend_read()
255+
print("[MTP] Submitting IN transfer, data size={}".format(len(buf)))
256+
self.submit_xfer(self.ep_in, buf, self._tx_cb)
257+
else:
258+
if not self.is_open():
259+
print("[MTP] Cannot submit IN transfer - interface not open")
260+
elif self.xfer_pending(self.ep_in):
261+
print("[MTP] Cannot submit IN transfer - transfer already pending")
262+
elif not self._tx.readable():
263+
print("[MTP] Cannot submit IN transfer - no data in TX buffer")
234264

235265
def _tx_cb(self, ep, res, num_bytes):
236266
"""Callback when data has been sent to the host."""
267+
print("[MTP] IN transfer complete: res={}, bytes={}".format(res, num_bytes))
237268
if res == 0:
238269
self._tx.finish_read(num_bytes)
270+
else:
271+
print("[MTP] IN transfer failed with error {}".format(res))
239272
self._tx_xfer() # Send more data if available
240273

241274
def _process_rx(self, _):
242275
"""Process received data from the host."""
243276
# Check if there's enough data for a container header
244-
if self._rx.readable() < _CONTAINER_HEADER_SIZE:
277+
readable = self._rx.readable()
278+
if readable < _CONTAINER_HEADER_SIZE:
279+
print("[MTP] Not enough data for container header ({} bytes)".format(readable))
245280
return
246281

247282
# Peek at the container header without consuming it yet
@@ -253,8 +288,20 @@ def _process_rx(self, _):
253288
code = struct.unpack_from("<H", header, 6)[0]
254289
transaction_id = struct.unpack_from("<I", header, 8)[0]
255290

291+
container_types = {
292+
_MTP_CONTAINER_TYPE_COMMAND: "COMMAND",
293+
_MTP_CONTAINER_TYPE_DATA: "DATA",
294+
_MTP_CONTAINER_TYPE_RESPONSE: "RESPONSE",
295+
_MTP_CONTAINER_TYPE_EVENT: "EVENT"
296+
}
297+
298+
container_type_str = container_types.get(container_type, "UNKNOWN")
299+
print("[MTP] Container header: length={}, type={}, code=0x{:04x}, transaction_id={}".format(
300+
length, container_type_str, code, transaction_id))
301+
256302
# Ensure we have the complete container
257303
if self._rx.readable() < length:
304+
print("[MTP] Waiting for complete container ({}/{} bytes)".format(self._rx.readable(), length))
258305
return
259306

260307
# Now consume the container header
@@ -272,6 +319,25 @@ def _process_rx(self, _):
272319
params.append(param)
273320
self._rx.finish_read(4)
274321

322+
# Map code to operation name for common operations
323+
operation_names = {
324+
_MTP_OPERATION_GET_DEVICE_INFO: "GetDeviceInfo",
325+
_MTP_OPERATION_OPEN_SESSION: "OpenSession",
326+
_MTP_OPERATION_CLOSE_SESSION: "CloseSession",
327+
_MTP_OPERATION_GET_STORAGE_IDS: "GetStorageIDs",
328+
_MTP_OPERATION_GET_STORAGE_INFO: "GetStorageInfo",
329+
_MTP_OPERATION_GET_NUM_OBJECTS: "GetNumObjects",
330+
_MTP_OPERATION_GET_OBJECT_HANDLES: "GetObjectHandles",
331+
_MTP_OPERATION_GET_OBJECT_INFO: "GetObjectInfo",
332+
_MTP_OPERATION_GET_OBJECT: "GetObject",
333+
_MTP_OPERATION_DELETE_OBJECT: "DeleteObject",
334+
_MTP_OPERATION_SEND_OBJECT_INFO: "SendObjectInfo",
335+
_MTP_OPERATION_SEND_OBJECT: "SendObject"
336+
}
337+
338+
op_name = operation_names.get(code, "Unknown")
339+
print("[MTP] Received command: {} (0x{:04x}), params={}".format(op_name, code, params))
340+
275341
# Store operation info for processing
276342
self._current_operation = code
277343
self._current_params = params
@@ -285,11 +351,15 @@ def _process_rx(self, _):
285351
elif container_type == _MTP_CONTAINER_TYPE_DATA:
286352
if not self._current_operation or self._data_phase_complete:
287353
# Unexpected data phase
354+
print("[MTP] Unexpected data phase, no operation in progress")
288355
self._send_response(_MTP_RESPONSE_GENERAL_ERROR)
289356
return
290357

291358
# Process the data phase
292359
data_size = length - _CONTAINER_HEADER_SIZE
360+
print("[MTP] Data phase: size={} bytes for operation 0x{:04x}".format(
361+
data_size, self._current_operation))
362+
293363
if self._rx.readable() >= data_size:
294364
data = bytearray(data_size)
295365
view = memoryview(data)
@@ -304,10 +374,12 @@ def _process_rx(self, _):
304374
position += chunk
305375
remaining -= chunk
306376

377+
print("[MTP] Data phase complete, processing {} bytes".format(data_size))
307378
self._process_data_phase(data)
308379
else:
309380
# Not enough data received
310381
# Skip incomplete data
382+
print("[MTP] Incomplete data received, skipping")
311383
self._rx.finish_read(self._rx.readable())
312384
self._send_response(_MTP_RESPONSE_INCOMPLETE_TRANSFER)
313385

@@ -318,9 +390,28 @@ def _handle_command(self):
318390

319391
# Check if session is open (required for most operations)
320392
if not self._session_open and op != _MTP_OPERATION_OPEN_SESSION and op != _MTP_OPERATION_GET_DEVICE_INFO:
393+
print("[MTP] Rejecting command 0x{:04x} - session not open".format(op))
321394
self._send_response(_MTP_RESPONSE_SESSION_NOT_OPEN)
322395
return
323396

397+
# Map the operation code to a name for better debug messages
398+
operation_names = {
399+
_MTP_OPERATION_GET_DEVICE_INFO: "GetDeviceInfo",
400+
_MTP_OPERATION_OPEN_SESSION: "OpenSession",
401+
_MTP_OPERATION_CLOSE_SESSION: "CloseSession",
402+
_MTP_OPERATION_GET_STORAGE_IDS: "GetStorageIDs",
403+
_MTP_OPERATION_GET_STORAGE_INFO: "GetStorageInfo",
404+
_MTP_OPERATION_GET_NUM_OBJECTS: "GetNumObjects",
405+
_MTP_OPERATION_GET_OBJECT_HANDLES: "GetObjectHandles",
406+
_MTP_OPERATION_GET_OBJECT_INFO: "GetObjectInfo",
407+
_MTP_OPERATION_GET_OBJECT: "GetObject",
408+
_MTP_OPERATION_DELETE_OBJECT: "DeleteObject",
409+
_MTP_OPERATION_SEND_OBJECT_INFO: "SendObjectInfo",
410+
_MTP_OPERATION_SEND_OBJECT: "SendObject"
411+
}
412+
op_name = operation_names.get(op, "Unknown")
413+
print("[MTP] Processing command: {} (0x{:04x})".format(op_name, op))
414+
324415
# Handle operations
325416
if op == _MTP_OPERATION_GET_DEVICE_INFO:
326417
self._cmd_get_device_info()
@@ -348,6 +439,7 @@ def _handle_command(self):
348439
self._cmd_send_object()
349440
else:
350441
# Operation not supported
442+
print("[MTP] Unsupported operation: 0x{:04x}".format(op))
351443
self._send_response(_MTP_RESPONSE_OPERATION_NOT_SUPPORTED)
352444

353445
def _process_data_phase(self, data):
@@ -465,24 +557,34 @@ def _cmd_get_device_info(self):
465557
def _cmd_open_session(self, params):
466558
"""Handle OpenSession command."""
467559
if not params:
560+
print("[MTP] OpenSession: No parameters provided")
468561
self._send_response(_MTP_RESPONSE_INVALID_PARAMETER)
469562
return
470563

471564
session_id = params[0]
565+
print("[MTP] OpenSession: Requested session_id={}".format(session_id))
472566

473567
if session_id == 0:
568+
print("[MTP] OpenSession: Rejecting invalid session ID (0)")
474569
self._send_response(_MTP_RESPONSE_INVALID_PARAMETER)
475570
elif self._session_open:
571+
print("[MTP] OpenSession: Session already open (id={})".format(self._session_id))
476572
self._send_response(_MTP_RESPONSE_SESSION_ALREADY_OPEN)
477573
else:
574+
print("[MTP] OpenSession: Opening new session with id={}".format(session_id))
478575
self._session_open = True
479576
self._session_id = session_id
480-
self._send_response(_MTP_RESPONSE_OK)
577+
481578
# Refresh the object list when opening a session
579+
print("[MTP] OpenSession: Refreshing object list")
482580
self._refresh_object_list()
581+
print("[MTP] OpenSession: Found {} objects".format(len(self._object_handles)))
582+
583+
self._send_response(_MTP_RESPONSE_OK)
483584

484585
def _cmd_close_session(self):
485586
"""Handle CloseSession command."""
587+
print("[MTP] CloseSession: Closing session {}".format(self._session_id))
486588
self._session_open = False
487589
self._session_id = 0
488590
self._send_response(_MTP_RESPONSE_OK)
@@ -955,6 +1057,7 @@ def _process_send_object(self, data):
9551057
def _send_data(self, data, final=True):
9561058
"""Send data phase of an MTP transaction."""
9571059
if not self.is_open():
1060+
print("[MTP] Cannot send data - interface not open")
9581061
return False
9591062

9601063
# Create container header
@@ -967,11 +1070,21 @@ def _send_data(self, data, final=True):
9671070
self._current_operation, # Operation code
9681071
self._transaction_id) # Transaction ID
9691072

1073+
print("[MTP] Sending DATA container: length={}, operation=0x{:04x}, transaction_id={}{}".format(
1074+
total_len, self._current_operation, self._transaction_id,
1075+
", final=True" if final else ""))
1076+
9701077
# Send header
9711078
self._tx.write(container)
9721079

9731080
# Send data
9741081
if data:
1082+
if len(data) > 64:
1083+
print("[MTP] Data payload: {} bytes (first 64 bytes: {})".format(
1084+
len(data), [hex(b) for b in data[:64]]))
1085+
else:
1086+
print("[MTP] Data payload: {} bytes: {}".format(
1087+
len(data), [hex(b) for b in data]))
9751088
self._tx.write(data)
9761089

9771090
# Start transfer
@@ -982,12 +1095,38 @@ def _send_data(self, data, final=True):
9821095
def _send_response(self, response_code, params=None):
9831096
"""Send response phase of an MTP transaction."""
9841097
if not self.is_open():
1098+
print("[MTP] Cannot send response - interface not open")
9851099
return False
1100+
1101+
# Map response code to string for better debug messages
1102+
response_names = {
1103+
_MTP_RESPONSE_OK: "OK",
1104+
_MTP_RESPONSE_GENERAL_ERROR: "GeneralError",
1105+
_MTP_RESPONSE_SESSION_NOT_OPEN: "SessionNotOpen",
1106+
_MTP_RESPONSE_INVALID_TRANSACTION_ID: "InvalidTransactionID",
1107+
_MTP_RESPONSE_OPERATION_NOT_SUPPORTED: "OperationNotSupported",
1108+
_MTP_RESPONSE_PARAMETER_NOT_SUPPORTED: "ParameterNotSupported",
1109+
_MTP_RESPONSE_INCOMPLETE_TRANSFER: "IncompleteTransfer",
1110+
_MTP_RESPONSE_INVALID_STORAGE_ID: "InvalidStorageID",
1111+
_MTP_RESPONSE_INVALID_OBJECT_HANDLE: "InvalidObjectHandle",
1112+
_MTP_RESPONSE_STORE_FULL: "StoreFull",
1113+
_MTP_RESPONSE_STORE_READ_ONLY: "StoreReadOnly",
1114+
_MTP_RESPONSE_PARTIAL_DELETION: "PartialDeletion",
1115+
_MTP_RESPONSE_STORE_NOT_AVAILABLE: "StoreNotAvailable",
1116+
_MTP_RESPONSE_SPECIFICATION_BY_FORMAT_UNSUPPORTED: "SpecificationByFormatUnsupported",
1117+
_MTP_RESPONSE_INVALID_PARENT_OBJECT: "InvalidParentObject",
1118+
_MTP_RESPONSE_INVALID_PARAMETER: "InvalidParameter",
1119+
_MTP_RESPONSE_SESSION_ALREADY_OPEN: "SessionAlreadyOpen"
1120+
}
1121+
response_name = response_names.get(response_code, "Unknown")
9861122

9871123
# Calculate response length
9881124
param_count = len(params) if params else 0
9891125
total_len = _CONTAINER_HEADER_SIZE + param_count * 4
9901126

1127+
print("[MTP] Sending RESPONSE: {} (0x{:04x}), transaction_id={}, params={}".format(
1128+
response_name, response_code, self._transaction_id, params if params else "None"))
1129+
9911130
# Create and fill container header
9921131
container = bytearray(total_len)
9931132
struct.pack_into("<IHHI", container, 0,
@@ -1015,6 +1154,8 @@ def _send_response(self, response_code, params=None):
10151154

10161155
def _refresh_object_list(self):
10171156
"""Scan the filesystem and rebuild the object handle mapping."""
1157+
print("[MTP] Refreshing object list from storage path: {}".format(self._storage_path))
1158+
10181159
# Reset object handles
10191160
self._object_handles = {}
10201161
self._parent_map = {}
@@ -1025,9 +1166,12 @@ def _refresh_object_list(self):
10251166
self._next_object_handle += 1
10261167
self._object_handles[root_handle] = self._storage_path
10271168
self._parent_map[root_handle] = 0 # No parent
1169+
print("[MTP] Added root directory with handle {}".format(root_handle))
10281170

10291171
# Walk the directory tree
10301172
self._scan_directory(self._storage_path, root_handle)
1173+
1174+
print("[MTP] Object scan complete, found {} objects".format(len(self._object_handles)))
10311175

10321176
def _scan_directory(self, path, parent_handle):
10331177
"""Recursively scan a directory and add objects to handle maps."""
@@ -1038,6 +1182,7 @@ def _scan_directory(self, path, parent_handle):
10381182

10391183
# List all entries in this directory
10401184
entries = os.listdir(path)
1185+
print("[MTP] Scanning directory: {}, found {} entries".format(path, len(entries)))
10411186

10421187
for entry in entries:
10431188
full_path = path + entry
@@ -1055,15 +1200,19 @@ def _scan_directory(self, path, parent_handle):
10551200
self._object_handles[handle] = full_path
10561201
self._parent_map[handle] = parent_handle
10571202

1203+
entry_type = "directory" if is_dir else "file"
1204+
print("[MTP] Added {} '{}' with handle {}".format(entry_type, full_path, handle))
1205+
10581206
# Recursively scan subdirectories
10591207
if is_dir:
10601208
self._scan_directory(full_path, handle)
1061-
except:
1209+
except Exception as e:
10621210
# Skip entries that cause errors
1211+
print("[MTP] Error scanning entry '{}': {}".format(full_path, str(e)))
10631212
continue
1064-
except:
1065-
# Ignore errors during directory scan
1066-
pass
1213+
except Exception as e:
1214+
# Log errors during directory scan
1215+
print("[MTP] Error scanning directory '{}': {}".format(path, str(e)))
10671216

10681217
def _get_format_by_path(self, path):
10691218
"""Determine MTP format code based on file extension."""

0 commit comments

Comments
 (0)